diff --git a/openam-core/pom.xml b/openam-core/pom.xml index 059c613b3c..d4c7059010 100755 --- a/openam-core/pom.xml +++ b/openam-core/pom.xml @@ -257,15 +257,8 @@ - org.apache.click - click-nodeps - ${click.version} - - - - org.apache.click - click-extras - ${click.version} + org.freemarker + freemarker diff --git a/openam-core/src/main/java/com/sun/identity/config/DefaultSummary.java b/openam-core/src/main/java/com/sun/identity/config/DefaultSummary.java index 7288a3f45a..6fccbdccc3 100644 --- a/openam-core/src/main/java/com/sun/identity/config/DefaultSummary.java +++ b/openam-core/src/main/java/com/sun/identity/config/DefaultSummary.java @@ -30,7 +30,6 @@ package com.sun.identity.config; -import com.sun.identity.config.util.ProtectedPage; import com.sun.identity.setup.AMSetupServlet; import com.sun.identity.setup.AMSetupUtils; import com.sun.identity.setup.HttpServletRequestWrapper; @@ -39,17 +38,12 @@ import jakarta.servlet.http.HttpServletRequest; import com.sun.identity.shared.Constants; -import org.openidentityplatform.openam.click.control.ActionLink; +import org.openidentityplatform.openam.config.servlet.ConfiguratorAction; +import org.openidentityplatform.openam.config.servlet.ProtectedSetupPage; -public class DefaultSummary extends ProtectedPage { - - public ActionLink createConfig = - new ActionLink("createDefaultConfig", this, "createDefaultConfig"); - - public void onInit() { - super.onInit(); - } - +public class DefaultSummary extends ProtectedSetupPage { + + @ConfiguratorAction public boolean createDefaultConfig() { HttpServletRequest req = getContext().getRequest(); HttpServletRequestWrapper request = @@ -134,7 +128,6 @@ SetupConstants.CONFIG_VAR_BASE_DIR, getBaseDir( debug.error("DefaultSummary.createDefaultConfig()", e); } writeToResponse( ((responseString!=null)?responseString:"") ); - setPath(null); return false; } } diff --git a/openam-core/src/main/java/com/sun/identity/config/Options.java b/openam-core/src/main/java/com/sun/identity/config/Options.java index 8c4a3e63d8..5a6890e8ad 100644 --- a/openam-core/src/main/java/com/sun/identity/config/Options.java +++ b/openam-core/src/main/java/com/sun/identity/config/Options.java @@ -25,53 +25,45 @@ * $Id: Options.java,v 1.7 2009/01/05 23:17:09 veiming Exp $ * * Portions Copyrighted 2011-2015 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems LLC. */ package com.sun.identity.config; -import com.sun.identity.config.util.TemplatedPage; import com.sun.identity.setup.AMSetupServlet; import com.sun.identity.setup.EmbeddedOpenDS; -import org.openidentityplatform.openam.click.control.ActionLink; import org.forgerock.openam.upgrade.VersionUtils; +import org.openidentityplatform.openam.config.servlet.SetupPage; -public class Options extends TemplatedPage { +public class Options extends SetupPage { - public ActionLink createConfigLink = new ActionLink("upgradeLink", this, "upgrade" ); - public ActionLink testUrlLink = new ActionLink("coexistLink", this, "coexist" ); - public ActionLink pushConfigLink = new ActionLink("olderUpgradeLink", this, "olderUpgrade" ); + // Unlike the wizard/summary pages, this has no onSecurityCheck() override: the old Options + // extended TemplatedPage (not ProtectedPage), so it stayed reachable even once OpenAM is + // configured - that's how a completed install reaches the upgrade-options branch below. + // SetupPage's default onSecurityCheck() (always true) already matches that. - protected boolean upgrade = false; - protected boolean upgradeCompleted = false; - protected boolean isOpenDS1x = false; - protected boolean debugOn = false; - - private java.util.Locale configLocale = null; - - protected String getTitle() { - return isNewInstall() ? "configuration.options.title" : "upgrade.title"; - } + @Override + public void onInit() { + super.onInit(); + + boolean upgrade = !isNewInstall(); + addModel("upgrade", Boolean.valueOf(upgrade)); - public void doInit() { - upgrade = !isNewInstall(); - upgradeCompleted = AMSetupServlet.isUpgradeCompleted(); + boolean upgradeCompleted = AMSetupServlet.isUpgradeCompleted(); addModel("upgradeCompleted", Boolean.valueOf(upgradeCompleted)); - addModel( "upgrade", Boolean.valueOf( upgrade ) ); if (upgrade) { - addModel("currentVersion", VersionUtils.getCurrentVersion()); + addModel("currentVersion", VersionUtils.getCurrentVersion()); } - isOpenDS1x = EmbeddedOpenDS.isOpenDSVer1Installed(); + boolean isOpenDS1x = EmbeddedOpenDS.isOpenDSVer1Installed(); addModel("isOpenDS1x", Boolean.valueOf(isOpenDS1x)); if (isOpenDS1x) { addModel("odsdir", AMSetupServlet.getBaseDir()); } - - debugOn = getContext().getRequest().getParameter( "debug" ) != null; - - if (debugOn) { + + if (getContext().getRequest().getParameter("debug") != null) { AMSetupServlet.enableDebug(); } } diff --git a/openam-core/src/main/java/com/sun/identity/config/util/AjaxPage.java b/openam-core/src/main/java/com/sun/identity/config/util/AjaxPage.java deleted file mode 100644 index 76697d76c0..0000000000 --- a/openam-core/src/main/java/com/sun/identity/config/util/AjaxPage.java +++ /dev/null @@ -1,410 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) 2007 Sun Microsystems Inc. All Rights Reserved - * - * The contents of this file are subject to the terms - * of the Common Development and Distribution License - * (the License). You may not use this file except in - * compliance with the License. - * - * You can obtain a copy of the License at - * https://opensso.dev.java.net/public/CDDLv1.0.html or - * opensso/legal/CDDLv1.0.txt - * See the License for the specific language governing - * permission and limitations under the License. - * - * When distributing Covered Code, include this CDDL - * Header Notice in each file and include the License file - * at opensso/legal/CDDLv1.0.txt. - * If applicable, add the following below the CDDL Header, - * with the fields enclosed by brackets [] replaced by - * your own identifying information: - * "Portions Copyrighted [year] [name of copyright owner]" - * - * $Id: AjaxPage.java,v 1.24 2010/01/04 19:15:16 veiming Exp $ - * - * Portions Copyrighted 2011-2016 ForgeRock AS. - * Portions Copyrighted 2025 3A Systems LLC. - */ - -package com.sun.identity.config.util; - -import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*; - -import com.iplanet.am.util.SystemProperties; -import com.sun.identity.config.SessionAttributeNames; -import com.sun.identity.setup.AMSetupServlet; -import com.sun.identity.setup.AMSetupUtils; -import com.sun.identity.setup.SetupConstants; -import com.sun.identity.shared.Constants; -import com.sun.identity.shared.debug.Debug; -import com.sun.identity.shared.locale.Locale; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.GeneralSecurityException; -import java.util.MissingResourceException; -import java.util.ResourceBundle; -import java.util.concurrent.TimeUnit; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.openidentityplatform.openam.click.Page; -import org.openidentityplatform.openam.click.control.ActionLink; -import org.forgerock.openam.ldap.LDAPRequests; -import org.forgerock.opendj.ldap.Connection; -import org.forgerock.opendj.ldap.ConnectionFactory; -import org.forgerock.opendj.ldap.LDAPConnectionFactory; -import org.forgerock.opendj.ldap.LdapException; -import org.forgerock.opendj.ldap.ResultCode; -import org.forgerock.opendj.ldap.SSLContextBuilder; -import org.forgerock.util.Options; -import org.forgerock.util.time.Duration; - -public abstract class AjaxPage extends Page { - - public ActionLink checkPasswordsLink = - new ActionLink("checkPasswords", this, "checkPasswords"); - - public ActionLink validateInputLink = - new ActionLink("validateInput", this, "validateInput" ); - - public ActionLink resetSessionAttributesLink = - new ActionLink("resetSessionAttributes", this, - "resetSessionAttributes"); - - public static final String RESPONSE_TEMPLATE = "{\"valid\":\"${valid}\", \"body\":\"${body}\"}"; - public static final String OLD_RESPONSE_TEMPLATE = "{\"isValid\":${isValid}, \"errorMessage\":\"${errorMessage}\"}"; - - private static int MIN_PASSWORD_SIZE = 8; - private boolean rendering = false; - private String hostName; - protected java.util.Locale configLocale = null; - - // localization properties - protected ResourceBundle rb = null; - protected static final String RB_NAME = "amConfigurator"; - - public String responseString = "true"; - public static Debug debug = Debug.getInstance("amConfigurator"); - - public AjaxPage() { - } - - @Override - public void onInit() { - super.onInit(); - initializeResourceBundle(); - addModel("page", this); - } - - public boolean isRendering() { - return rendering; - } - - protected String toString( String paramName ) { - String value = getContext().getRequest().getParameter( paramName ); - value = ( value != null ? value.trim() : null ); - value = ("".equals(value) ? null : value ); - return value; - } - - protected boolean toBoolean(String paramName) { - String value = toString(paramName); - if (value == null) { - return false; - } - if ((value.equalsIgnoreCase("on")) || - (value.equalsIgnoreCase("true")) || - (value.equalsIgnoreCase("yes")) || - (value.equalsIgnoreCase("checked"))) { - return true; - } else { - return false; - } - } - - protected int toInt( String paramName ) { - int intValue = 0; - String value = toString( paramName ); - if ( value != null ) { - try { - intValue = Integer.parseInt( value ); - } catch ( NumberFormatException e ) {} - } - return intValue; - } - - protected void writeValid() { - writeValid( null ); - } - protected void writeValid( String message ) { - String out = ( message != null ? message : "" ); - writeJsonResponse(true, out); - } - protected void writeInvalid( String message ) { - String out = ( message != null ? message : "" ); - writeJsonResponse( false, out ); - } - - protected void writeJsonResponse(String valid, String responseBody) { - String response = RESPONSE_TEMPLATE; - response = response.replaceFirst("\\$\\{" + "valid" + "\\}", valid); - response = response.replaceFirst("\\$\\{" + "body" + "\\}", responseBody); - writeToResponse(response); - } - protected void writeJsonResponse(boolean valid, String responseBody) { - String response = RESPONSE_TEMPLATE; - response = response.replaceFirst("\\$\\{" + "valid" + "\\}", String.valueOf(valid)); - response = response.replaceFirst("\\$\\{" + "body" + "\\}", responseBody); - writeToResponse(response); - } - - protected Connection getConnection(String host, int port, String bindDN, char[] bindPwd, int timeout, boolean isSSl) - throws GeneralSecurityException, LdapException { - Options ldapOptions = Options.defaultOptions() - .set(CONNECT_TIMEOUT, new Duration((long)timeout, TimeUnit.SECONDS)) - .set(AUTHN_BIND_REQUEST, LDAPRequests.newSimpleBindRequest(bindDN, bindPwd)); - - if (isSSl) { - String defaultProtocolVersion = SystemProperties.get(Constants.LDAP_SERVER_TLS_VERSION, "TLS"); - ldapOptions = ldapOptions.set(SSL_CONTEXT, - new SSLContextBuilder().setProtocol(defaultProtocolVersion).getSSLContext()); - } - - ConnectionFactory factory = new LDAPConnectionFactory(host, port, ldapOptions); - return factory.getConnection(); - } - - protected boolean writeErrorToResponse(ResultCode resultCode) { - String msg = getMessage(resultCode); - if (msg != null) { - writeToResponse(getLocalizedString(msg)); - return true; - } - return false; - } - - private String getMessage(ResultCode resultCode) { - if (ResultCode.CLIENT_SIDE_CONNECT_ERROR.equals(resultCode)) { - return "ldap.connect.error"; - } else if (ResultCode.CLIENT_SIDE_SERVER_DOWN.equals(resultCode)) { - return "ldap.server.down"; - } else if (ResultCode.INVALID_DN_SYNTAX.equals(resultCode)) { - return "ldap.invalid.dn"; - } else if (ResultCode.NO_SUCH_OBJECT.equals(resultCode)) { - return "ldap.nosuch.object"; - } else if (ResultCode.INVALID_CREDENTIALS.equals(resultCode)) { - return "ldap.invalid.credentials"; - } else if (ResultCode.UNWILLING_TO_PERFORM.equals(resultCode)) { - return "ldap.unwilling"; - } else if (ResultCode.INAPPROPRIATE_AUTHENTICATION.equals(resultCode)) { - return "ldap.inappropriate"; - } else if (ResultCode.CONSTRAINT_VIOLATION.equals(resultCode)) { - return "ldap.constraint"; - } else { - return null; - } - } - - protected void writeToResponse( String text ) { - try { - // Note: this writer is obtained from the Apache Click context. Should this be triggered by a JSP this - // may not be in compliance with the JSP spec regarding mandatory use of the buffered JspWriter. - getContext().getResponse().getWriter().write( text ); - this.rendering = true; - } catch ( IOException e ) { - throw new RuntimeException( e ); - } - } - - public void initializeResourceBundle() { - HttpServletRequest req = - (HttpServletRequest)getContext().getRequest(); - HttpServletResponse res = - (HttpServletResponse)getContext().getResponse(); - - setLocale(req); - res.setContentType("text/html; charset=UTF-8"); - } - - private void setLocale (HttpServletRequest request) { - if (request != null) { - String superLocale = request.getParameter("locale"); - if (superLocale != null && superLocale.length() > 0) { - configLocale = Locale.getLocaleObjFromAcceptLangHeader( - superLocale); - } else { - String acceptLangHeader = - (String)request.getHeader("Accept-Language"); - if ((acceptLangHeader != null) && - (acceptLangHeader.length() > 0)) - { - configLocale = Locale.getLocaleObjFromAcceptLangHeader( - acceptLangHeader); - } else { - configLocale = java.util.Locale.getDefault(); - } - } - try { - rb = ResourceBundle.getBundle(RB_NAME, configLocale); - } catch (MissingResourceException mre) { - // do nothing - } - } - } - - public String getQuoteEscapedLocalizedString(String i18nKey) { - String value = getLocalizedString(i18nKey); - return value.replace("'", "\\'"); - } - - public String getLocalizedString(String i18nKey) { - if (rb == null) { - initializeResourceBundle(); - } - - String localizedValue = null; - try { - localizedValue = Locale.getString(rb, i18nKey, debug); - } catch (MissingResourceException mre) { - // do nothing - } - return (localizedValue == null) ? i18nKey : localizedValue; - } - - public String getHostName() { - if (hostName == null) { - hostName = getContext().getRequest().getServerName(); - } - return hostName; - } - - public String getHostName(String serverUrl, String defaultHostName) { - URL url = null; - - try { - url = new URL(serverUrl); - } catch (MalformedURLException mue) { - return defaultHostName; - } - - return url.getHost(); - } - - public int getServerPort(String serverUrl, int defaultPort) { - URL url = null; - - try { - url = new URL(serverUrl); - } catch (MalformedURLException mue) { - return defaultPort; - } - - return url.getPort(); - } - - public String getBaseDir(HttpServletRequest req) { - String basedir = AMSetupServlet.getPresetConfigDir(); - if ((basedir == null) || (basedir.length() == 0)) { - String tmp = System.getProperty("user.home"); - if (File.separatorChar == '\\') { - tmp = tmp.replace('\\', '/'); - } - String uri = req.getRequestURI(); - int idx = uri.indexOf("/", 1); - if (idx != -1) { - uri = uri.substring(0, idx); - } - - basedir = (tmp.endsWith("/")) ? tmp.substring(0, tmp.length()-1) : - tmp; - basedir += uri; - } - - return basedir; - } - - public String getCookieDomain() { - return getHostName(); - } - - public boolean validateInput() { - String key = toString("key"); - String value = toString("value"); - - if (value == null) { - responseString = "missing.required.field"; - } else { - getContext().setSessionAttribute(key, value); - } - - writeToResponse(getLocalizedString(responseString)); - setPath(null); - return false; - } - - public boolean resetSessionAttributes() { - try { - Field[] fields = SessionAttributeNames.class.getDeclaredFields(); - for (int i = 0; i < fields.length; i++) { - try { - getContext().removeSessionAttribute( - (String)fields[i].get(null)); - } catch (IllegalAccessException e) { - //ingore - } - } - } catch (SecurityException e) { - writeToResponse(e.getMessage()); - } - setPath(null); - return false; - } - - public String getAttribute(String attr, String defaultValue) { - String value = (String)getContext().getSessionAttribute(attr); - return (value != null) ? value : defaultValue; - } - - public String getAvailablePort(int portNumber) { - return Integer.toString( - AMSetupUtils.getFirstUnusedPort(getHostName(), portNumber, 1000)); - } - - public boolean checkPasswords() { - String confirm = toString("confirm"); - String password = toString("password"); - String otherPassword = toString("otherPassword"); - String type = toString("type"); - - if (password == null) { - responseString = getLocalizedString("missing.password"); - } else if (password.length() < MIN_PASSWORD_SIZE) { - responseString = getLocalizedString("password.size.invalid"); - } else if (confirm == null) { - responseString = getLocalizedString("missing.confirm.password"); - } else if (confirm.length() < MIN_PASSWORD_SIZE) { - responseString = getLocalizedString("password.size.invalid"); - } else if (!password.equals(confirm)) { - responseString = getLocalizedString("password.dont.match"); - } else if ((otherPassword != null) && (otherPassword.equals(password))) { - responseString = getLocalizedString("agent.admin.password.same"); - } else { - if (type.equals("agent")) { - type = SessionAttributeNames.CONFIG_VAR_AMLDAPUSERPASSWD; - } else { - type = SetupConstants.CONFIG_VAR_ADMIN_PWD; - } - getContext().setSessionAttribute(type, password); - } - - writeToResponse(responseString); - setPath(null); - return false; - } -} diff --git a/openam-core/src/main/java/com/sun/identity/config/util/TemplatedForm.java b/openam-core/src/main/java/com/sun/identity/config/util/TemplatedForm.java deleted file mode 100644 index d9c03bc22c..0000000000 --- a/openam-core/src/main/java/com/sun/identity/config/util/TemplatedForm.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) 2007 Sun Microsystems Inc. All Rights Reserved - * - * The contents of this file are subject to the terms - * of the Common Development and Distribution License - * (the License). You may not use this file except in - * compliance with the License. - * - * You can obtain a copy of the License at - * https://opensso.dev.java.net/public/CDDLv1.0.html or - * opensso/legal/CDDLv1.0.txt - * See the License for the specific language governing - * permission and limitations under the License. - * - * When distributing Covered Code, include this CDDL - * Header Notice in each file and include the License file - * at opensso/legal/CDDLv1.0.txt. - * If applicable, add the following below the CDDL Header, - * with the fields enclosed by brackets [] replaced by - * your own identifying information: - * "Portions Copyrighted [year] [name of copyright owner]" - * - * $Id: TemplatedForm.java,v 1.4 2008/06/25 05:42:41 qcheng Exp $ - * - */ - -/* - * Portions Copyrighted 2011 ForgeRock AS - */ - -package com.sun.identity.config.util; - -import org.apache.click.control.Field; -import org.apache.click.control.Form; - -import java.util.Iterator; - -/** - * @author Jeffrey Bermudez - */ -public class TemplatedForm extends Form { - - public TemplatedForm(String name) { - super(name); - } - - public TemplatedForm() { - super(); - } - - public boolean doProcess() { - // We can overwrite this method to put some specific login here - return true; - } - - public final boolean onProcess() { - super.onProcess(); - - Iterator it = getFieldList().iterator(); - while (it.hasNext()) { - if (!((Field) it.next()).onProcess()) { - return false; - } - } - - return doProcess(); - } - -} diff --git a/openam-core/src/main/java/com/sun/identity/config/util/TemplatedPage.java b/openam-core/src/main/java/com/sun/identity/config/util/TemplatedPage.java deleted file mode 100644 index 2b6669aa5d..0000000000 --- a/openam-core/src/main/java/com/sun/identity/config/util/TemplatedPage.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) 2007 Sun Microsystems Inc. All Rights Reserved - * - * The contents of this file are subject to the terms - * of the Common Development and Distribution License - * (the License). You may not use this file except in - * compliance with the License. - * - * You can obtain a copy of the License at - * https://opensso.dev.java.net/public/CDDLv1.0.html or - * opensso/legal/CDDLv1.0.txt - * See the License for the specific language governing - * permission and limitations under the License. - * - * When distributing Covered Code, include this CDDL - * Header Notice in each file and include the License file - * at opensso/legal/CDDLv1.0.txt. - * If applicable, add the following below the CDDL Header, - * with the fields enclosed by brackets [] replaced by - * your own identifying information: - * "Portions Copyrighted [year] [name of copyright owner]" - * - * $Id: TemplatedPage.java,v 1.10 2009/01/05 23:17:10 veiming Exp $ - * - * Portions Copyrighted 2016 ForgeRock AS. - */ -package com.sun.identity.config.util; - -import static org.forgerock.openam.utils.Time.*; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Iterator; -import java.util.List; - -/** - * - */ -public abstract class TemplatedPage extends AjaxPage { - - public static final String STATUS_MESSAGE_CODES_SESSION_KEY = "statusMessageCodes"; - - public int currentYear = getCalendarInstance().get( Calendar.YEAR ); - - public List statusMessages = new ArrayList(); //of Strings - - public String getTemplate() { - return "assets/templates/main.html"; - } - - /** - * Return the i18n code of the page title. - * @return the i18n code of the page title; - */ - protected abstract String getTitle(); - - - protected List getStatusMessageCodes() { - return (List)getContext().getSessionAttribute( - STATUS_MESSAGE_CODES_SESSION_KEY); - } - - protected void addStatusMessageCode(String statusMessageCode) { - List codes = getStatusMessageCodes(); - if (codes == null) { - codes = new ArrayList(); - } - codes.add(statusMessageCode); - getContext().setSessionAttribute( - STATUS_MESSAGE_CODES_SESSION_KEY, codes); - } - - protected void clearStatusMessageCodes() { - getContext().removeSessionAttribute(STATUS_MESSAGE_CODES_SESSION_KEY); - } - - public void onInit() { - super.onInit(); - addModel("title", getLocalizedString(getTitle())); - List sessionStatusMessages = getStatusMessageCodes(); - if (sessionStatusMessages != null && !sessionStatusMessages.isEmpty()) { - Iterator i = sessionStatusMessages.iterator(); - while ( i.hasNext() ) { - String messageCode = (String)i.next(); - statusMessages.add(getLocalizedString(messageCode) ); - } - clearStatusMessageCodes(); - } - doInit(); - } - - protected void doInit() { - //subclass implementation hook. - } - -} diff --git a/openam-core/src/main/java/com/sun/identity/config/wizard/LDAPStoreWizardPage.java b/openam-core/src/main/java/com/sun/identity/config/wizard/LDAPStoreWizardPage.java index 5536149cb5..26b33e7703 100644 --- a/openam-core/src/main/java/com/sun/identity/config/wizard/LDAPStoreWizardPage.java +++ b/openam-core/src/main/java/com/sun/identity/config/wizard/LDAPStoreWizardPage.java @@ -25,23 +25,22 @@ * $Id: LDAPStoreWizardPage.java,v 1.7 2008/06/25 05:42:42 qcheng Exp $ * * Portions Copyrighted 2011-2014 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems LLC. */ package com.sun.identity.config.wizard; import com.sun.identity.config.pojos.LDAPStore; -import com.sun.identity.config.util.ProtectedPage; -import org.openidentityplatform.openam.click.control.ActionLink; +import org.openidentityplatform.openam.config.servlet.ConfiguratorAction; +import org.openidentityplatform.openam.config.servlet.ProtectedSetupPage; /* * LDAPStoreWizardPage is the base for Step 3. */ -public class LDAPStoreWizardPage extends ProtectedPage { +public class LDAPStoreWizardPage extends ProtectedSetupPage { public LDAPStore store = null; - public ActionLink clearLink = - new ActionLink("clearStore", this, "clearStore"); private String type = "config"; private String typeTitle = "Configuration"; private String storeSessionName = "customConfigStore"; @@ -82,6 +81,7 @@ public void setStoreSessionName( String storeSessionName ) { this.storeSessionName = storeSessionName; } + @Override public void onInit() { addModel("type", getType()); addModel("typeTitle", getTypeTitle()); @@ -91,12 +91,13 @@ public void onInit() { store = ensureConfig(); addModel("store", store); - super.onInit(); + super.onInit(); } + @ConfiguratorAction public boolean clearStore() { getContext().removeSessionAttribute( getStoreSessionName()); - setPath(null); + skipRender(); return false; } diff --git a/openam-core/src/main/java/com/sun/identity/config/wizard/Step1.java b/openam-core/src/main/java/com/sun/identity/config/wizard/Step1.java index 004f4c1635..d809bb30bc 100644 --- a/openam-core/src/main/java/com/sun/identity/config/wizard/Step1.java +++ b/openam-core/src/main/java/com/sun/identity/config/wizard/Step1.java @@ -25,32 +25,30 @@ * $Id: Step1.java,v 1.9 2009/01/05 23:17:10 veiming Exp $ * * Portions Copyrighted 2011-2014 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems LLC. */ package com.sun.identity.config.wizard; import com.sun.identity.config.SessionAttributeNames; -import com.sun.identity.config.util.ProtectedPage; -import org.openidentityplatform.openam.click.control.ActionLink; +import org.openidentityplatform.openam.config.servlet.ConfiguratorAction; +import org.openidentityplatform.openam.config.servlet.ProtectedSetupPage; /** * This is the first step in the advanced configuration flow. * The user will be required to add the default admin password and * the agent passwords. */ -public class Step1 extends ProtectedPage { - - // these two links required for client side validation calls. - public ActionLink validateLink = - new ActionLink("checkAdminPassword", this, "checkAdminPassword" ); - public ActionLink validateAgent = - new ActionLink("checkAgentPassword", this, "checkAgentPassword" ); - +public class Step1 extends ProtectedSetupPage { + + // Not referenced by step1.htm/step1.ftl (the template posts to the base checkPasswords + // handler instead), but kept web-invokable for byte-identical behavior with the old Click page. + @ConfiguratorAction public boolean checkAdminPassword() { String adminPassword = toString("admin"); String adminConfirm = toString("adminConfirm"); - - if (adminPassword == null || adminConfirm == null) { + + if (adminPassword == null || adminConfirm == null) { writeInvalid(getLocalizedString("missing.required.field")); } else { if (adminPassword.length() < 8) { @@ -61,34 +59,32 @@ public boolean checkAdminPassword() { writeValid("OK"); getContext().setSessionAttribute( SessionAttributeNames.CONFIG_VAR_ADMIN_PWD, adminPassword); - } + } } - setPath(null); return false; } - + + @ConfiguratorAction public boolean checkAgentPassword() { String agentPassword = toString("agent"); String agentConfirm = toString("agentConfirm"); - String tmpadmin = (String)getContext().getSessionAttribute( + String tmpadmin = (String) getContext().getSessionAttribute( SessionAttributeNames.CONFIG_VAR_ADMIN_PWD); - - if (agentPassword == null || agentConfirm == null) { + + if (agentPassword == null || agentConfirm == null) { writeInvalid(getLocalizedString("missing.required.field")); } else if (agentPassword.equals(tmpadmin)) { writeInvalid(getLocalizedString("agent.admin.passwords.match")); } else if (agentPassword.length() < 8) { writeInvalid(getLocalizedString("invalid.password.length")); } else if (!agentPassword.equals(agentConfirm)) { - writeInvalid(getLocalizedString("passwords.do.not.match")); + writeInvalid(getLocalizedString("passwords.do.not.match")); } else { writeValid("OK"); getContext().setSessionAttribute( SessionAttributeNames.CONFIG_VAR_AMLDAPUSERPASSWD, - agentPassword); + agentPassword); } - setPath(null); return false; - } + } } - diff --git a/openam-core/src/main/java/com/sun/identity/config/wizard/Step2.java b/openam-core/src/main/java/com/sun/identity/config/wizard/Step2.java index 34cc0ce668..f9753cf5f0 100644 --- a/openam-core/src/main/java/com/sun/identity/config/wizard/Step2.java +++ b/openam-core/src/main/java/com/sun/identity/config/wizard/Step2.java @@ -25,12 +25,12 @@ * $Id: Step2.java,v 1.15 2010/01/04 19:08:36 veiming Exp $ * * Portions Copyrighted 2011-2016 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems LLC. */ package com.sun.identity.config.wizard; import com.sun.identity.config.SessionAttributeNames; -import com.sun.identity.config.util.ProtectedPage; import com.sun.identity.setup.AMSetupServlet; import com.sun.identity.setup.ServicesDefaultValues; import com.sun.identity.setup.SetupConstants; @@ -38,14 +38,11 @@ import java.net.MalformedURLException; import java.net.URL; -import org.openidentityplatform.openam.click.control.ActionLink; +import org.openidentityplatform.openam.config.servlet.ConfiguratorAction; +import org.openidentityplatform.openam.config.servlet.ProtectedSetupPage; import org.forgerock.openam.utils.StringUtils; -public class Step2 extends ProtectedPage { - public ActionLink validateConfigDirLink = - new ActionLink("validateConfigDir", this, "validateConfigDir"); - public ActionLink validateCookieDomainLink = - new ActionLink("validateCookieDomain", this, "validateCookieDomain"); +public class Step2 extends ProtectedSetupPage { @Override public void onInit() { @@ -88,9 +85,9 @@ public void onInit() { if (!hasWritePermission(baseDir)) { String deployURI = getContext().getServletContext().getContextPath(); - add("initialCheck", "" + - getLocalizedString("configuration.wizard.step2.no.write.permission.to.basedir") + + getLocalizedString("configuration.wizard.step2.no.write.permission.to.basedir") + ""); } else if (alreadyHasContent(baseDir)) { String deployURI = getContext().getServletContext().getContextPath(); @@ -101,17 +98,17 @@ public void onInit() { } else { add("initialCheck", ""); } - + super.onInit(); } - + private static boolean alreadyHasContent(String dirName) { - File f = new File(dirName); - + File f = new File(dirName); + if (f.exists() && f.isDirectory()) { return (f.list().length > 0); } - + return false; } @@ -122,10 +119,11 @@ private static boolean hasWritePermission(String dirName) { } return (f == null) ? false : f.isDirectory() && f.canWrite(); } - + + @ConfiguratorAction public boolean validateConfigDir() { String configDir = toString("dir"); - + if (configDir == null) { writeToResponse(getLocalizedString("missing.required.field")); } else if (!hasWritePermission(configDir)) { @@ -139,10 +137,10 @@ public boolean validateConfigDir() { SessionAttributeNames.CONFIG_DIR, configDir); writeToResponse("true"); } - setPath(null); - return false; + return false; } + @ConfiguratorAction public boolean validateCookieDomain() { String serverUrl = toString("serverurl"); String domain = toString("domain"); @@ -155,10 +153,9 @@ public boolean validateCookieDomain() { getContext().setSessionAttribute(SessionAttributeNames.COOKIE_DOMAIN, domain); writeToResponse("true"); } - setPath(null); return false; } - + private boolean mismatchedCookieDomain(String serverUrl, String domain) { if (StringUtils.isNotEmpty(serverUrl) && StringUtils.isNotEmpty(domain)) { try { @@ -173,14 +170,14 @@ private boolean mismatchedCookieDomain(String serverUrl, String domain) { } private String getServerURL() { - String hostname = (String)getContext().getRequest().getServerName(); - int portnum = (int)getContext().getRequest().getServerPort(); - String protocol = (String)getContext().getRequest().getScheme(); + String hostname = getContext().getRequest().getServerName(); + int portnum = getContext().getRequest().getServerPort(); + String protocol = getContext().getRequest().getScheme(); return protocol + "://" + hostname + ":" + portnum; } /** - * used to add the key to the page and to the session so it can + * used to add the key to the page and to the session so it can * be retrieved when the final store is done */ private void add(String key, String value) { diff --git a/openam-core/src/main/java/com/sun/identity/config/wizard/Step3.java b/openam-core/src/main/java/com/sun/identity/config/wizard/Step3.java index e2efd62131..ad14cc8c8a 100644 --- a/openam-core/src/main/java/com/sun/identity/config/wizard/Step3.java +++ b/openam-core/src/main/java/com/sun/identity/config/wizard/Step3.java @@ -42,8 +42,8 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Map; -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.control.ActionLink; +import org.openidentityplatform.openam.config.servlet.ConfiguratorAction; +import org.openidentityplatform.openam.config.servlet.ConfiguratorContext; import org.forgerock.openam.ldap.LDAPUtils; import org.forgerock.opendj.ldap.Connection; import org.forgerock.opendj.ldap.DN; @@ -52,48 +52,28 @@ import org.forgerock.opendj.ldap.ResultCode; /** - * Step 3 is for selecting the embedded or external configuration store + * Step 3 is for selecting the embedded or external configuration store */ public class Step3 extends LDAPStoreWizardPage { public static final String LDAP_STORE_SESSION_KEY = "wizardCustomConfigStore"; - public ActionLink validateSMHostLink = - new ActionLink("validateSMHost", this, "validateSMHost"); - public ActionLink validateRootSuffixLink = - new ActionLink("validateRootSuffix", this, "validateRootSuffix"); - public ActionLink setReplicationLink = - new ActionLink("setReplication", this, "setReplication"); - public ActionLink validateHostNameLink = - new ActionLink("validateHostName", this, "validateHostName"); - public ActionLink validateConfigStoreHost = - new ActionLink("validateConfigStoreHost", this, "validateConfigStoreHost"); - public ActionLink setConfigType = - new ActionLink("setConfigType", this, "setConfigType"); - public ActionLink validateLocalPortLink = - new ActionLink("validateLocalPort", this, "validateLocalPort"); - public ActionLink validateLocalAdminPortLink = - new ActionLink("validateLocalAdminPort", this, "validateLocalAdminPort"); - public ActionLink validateLocalJmxPortLink = - new ActionLink("validateLocalJmxPort", this, "validateLocalJmxPort"); - public ActionLink validateEncKey = - new ActionLink("validateEncKey", this, "validateEncKey"); - private static final String QUOTE = "\""; private static final String SEPARATOR = "\" : \""; private String localRepPort; - + public Step3() { } - + + @Override public void onInit() { String val = getAttribute("rootSuffix", Wizard.defaultRootSuffix); addModel("rootSuffix", val); val = getAttribute("encryptionKey", AMSetupUtils.getRandomString()); addModel("encryptionKey", val); - + val = getAttribute("configStorePort", getAvailablePort(50389)); addModel("configStorePort", val); addModel("localConfigPort", val); @@ -109,7 +89,7 @@ public void onInit() { localRepPort = getAttribute("localRepPort", getAvailablePort(58989)); addModel("localRepPort", localRepPort); - val = getAttribute("existingPort", getAvailablePort(50389)); + val = getAttribute("existingPort", getAvailablePort(50389)); addModel("existingPort", val); val = getAttribute("existingRepPort", getAvailablePort(58990)); @@ -117,7 +97,7 @@ public void onInit() { val = getAttribute("configStoreSSL", "SIMPLE"); addModel("configStoreSSL", val); - + if (val.equals("SSL")) { addModel("selectConfigStoreSSL", "checked=\"checked\""); } else { @@ -126,10 +106,10 @@ public void onInit() { // initialize the data store type being used val = getAttribute( - SetupConstants.CONFIG_VAR_DATA_STORE, + SetupConstants.CONFIG_VAR_DATA_STORE, SetupConstants.SMS_EMBED_DATASTORE); addModel(SetupConstants.CONFIG_VAR_DATA_STORE, val); - + if (val.equals(SetupConstants.SMS_EMBED_DATASTORE)) { addModel("selectEmbedded", "checked=\"checked\""); addModel("selectExternal", ""); @@ -159,8 +139,9 @@ public void onInit() { } super.onInit(); - } + } + @ConfiguratorAction public boolean setConfigType() { String type = toString("type"); if (type.equals("remote")) { @@ -170,21 +151,34 @@ public boolean setConfigType() { getContext().setSessionAttribute( SessionAttributeNames.CONFIG_STORE_HOST, "localhost"); } - getContext().setSessionAttribute( + getContext().setSessionAttribute( SessionAttributeNames.CONFIG_VAR_DATA_STORE, type); + // NOTE: the old Click ActionLink returned `true` here with no setPath(null), which in the + // Click framework would go on to run onGet() + render the full step3 page into this AJAX + // response body. ConfiguratorServlet's actionLink dispatch never renders after invoking a + // handler (see ConfiguratorServlet.invokeAction), regardless of the method's return value. + // The callers (enableRemote/disableRemote in step3.ftl) are fire-and-forget - no callback + // reads the response - and Step3.onInit() has no session side effects beyond what is set + // above, so skipping that render is behaviorally invisible to the wizard. See + // docs/migration/click-to-freemarker/04-implementation-notes.md. + skipRender(); return true; } + @ConfiguratorAction public boolean setReplication() { String type = toString("multi"); if (type.equals("enable")) { type = SetupConstants.DS_EMP_REPL_FLAG_VAL; - } + } getContext().setSessionAttribute( SessionAttributeNames.DS_EMB_REPL_FLAG, type); + // See the NOTE in setConfigType() above - same fire-and-forget, no-render situation. + skipRender(); return true; } + @ConfiguratorAction public boolean validateRootSuffix() { String rootsuffix = toString("rootSuffix"); if ((rootsuffix == null) || (rootsuffix.trim().length() == 0)) { @@ -197,13 +191,13 @@ public boolean validateRootSuffix() { getContext().setSessionAttribute( SessionAttributeNames.CONFIG_STORE_ROOT_SUFFIX, rootsuffix); } - setPath(null); - return false; + return false; } - + + @ConfiguratorAction public boolean validateLocalPort() { String port = toString("port"); - + if (port == null) { writeToResponse(getLocalizedString("missing.required.field")); } else { @@ -243,11 +237,11 @@ public boolean validateLocalPort() { writeToResponse(getLocalizedString("invalid.port.number")); } } - setPath(null); - return false; + return false; } - public boolean validateLocalAdminPort() { + @ConfiguratorAction + public boolean validateLocalAdminPort() { String port = toString("port"); if (port == null) { @@ -289,10 +283,10 @@ public boolean validateLocalAdminPort() { writeToResponse(getLocalizedString("invalid.port.number")); } } - setPath(null); return false; } + @ConfiguratorAction public boolean validateLocalJmxPort() { String port = toString("port"); @@ -335,7 +329,6 @@ public boolean validateLocalJmxPort() { writeToResponse(getLocalizedString("invalid.port.number")); } } - setPath(null); return false; } @@ -343,6 +336,7 @@ public boolean validateLocalJmxPort() { * Returns false always. Length of encryption key * must be at least 10 chars. */ + @ConfiguratorAction public boolean validateEncKey() { String key = toString("encKey"); @@ -357,16 +351,15 @@ public boolean validateEncKey() { writeToResponse("true"); } } - setPath(null); return false; - } + } + @ConfiguratorAction public boolean validateConfigStoreHost() { String host = toString("configStoreHost"); if (host == null) { writeToResponse(getLocalizedString("missing.required.field")); - setPath(null); return false; } else { getContext().setSessionAttribute("configStoreHost", host); @@ -378,7 +371,6 @@ public boolean validateConfigStoreHost() { } catch (UnknownHostException uhe) { writeToResponse(getLocalizedString("contact.host.unknown")); } - setPath(null); return false; } @@ -386,21 +378,22 @@ public boolean validateConfigStoreHost() { * a call is made to the OpenAM url entered in the browser. If * the OpenAM server * exists a Map of data will be returned which contains the - * information about the existing servers data store, including any + * information about the existing servers data store, including any * replication ports if its embedded. * Information to control the UI is returned in a JSON object of the form - * { - * "param1" : "value1", + * { + * "param1" : "value1", * "param2" : "value2" * } * The JS on the browser will interpret the above and make the necessary * changes to prompt the user for any more details required. */ + @ConfiguratorAction public boolean validateHostName() { StringBuffer sb = new StringBuffer(); String hostName = toString("hostName"); - - if (hostName == null) { + + if (hostName == null) { addObject(sb, "code", "100"); addObject(sb, "message", getLocalizedString("missing.required.field")); @@ -409,18 +402,18 @@ public boolean validateHostName() { String admin = "amadmin"; String password = (String)getContext().getSessionAttribute( SessionAttributeNames.CONFIG_VAR_ADMIN_PWD); - - try { + + try { String dsType; Map data = AMSetupUtils.getRemoteServerInfo(hostName, admin, password); - + // data returned from existing OpenAM server - if (data != null && !data.isEmpty()) { + if (data != null && !data.isEmpty()) { addObject(sb, "code", "100"); addObject(sb, "message", getLocalizedString("ok.string")); - + setupDSParams(data); - + String key = (String)data.get("enckey"); getContext().setSessionAttribute( SessionAttributeNames.ENCRYPTION_KEY, key); @@ -428,27 +421,27 @@ public boolean validateHostName() { getContext().setSessionAttribute( SessionAttributeNames.ENCLDAPUSERPASSWD, (String)data.get("ENCLDAPUSERPASSWD")); - + // true for embedded, false for ODSEE - String embedded = + String embedded = (String)data.get(BootstrapData.DS_ISEMBEDDED); - addObject(sb, "embedded", embedded); + addObject(sb, "embedded", embedded); String host = (String)data.get(BootstrapData.DS_HOST); if (embedded.equals("true")) { getContext().setSessionAttribute( SessionAttributeNames.CONFIG_STORE_HOST, getHostName()); addObject(sb, "configStoreHost", getHostName()); - - // set the multi embedded flag + + // set the multi embedded flag getContext().setSessionAttribute( - SessionAttributeNames.CONFIG_VAR_DATA_STORE, - SetupConstants.SMS_EMBED_DATASTORE); - + SessionAttributeNames.CONFIG_VAR_DATA_STORE, + SetupConstants.SMS_EMBED_DATASTORE); + getContext().setSessionAttribute( SessionAttributeNames.DS_EMB_REPL_FLAG, - SetupConstants.DS_EMP_REPL_FLAG_VAL); - + SetupConstants.DS_EMP_REPL_FLAG_VAL); + // get the existing replication ports if any String replAvailable = (String)data.get( BootstrapData.DS_REPLICATIONPORT_AVAILABLE); @@ -475,10 +468,10 @@ public boolean validateHostName() { SessionAttributeNames.CONFIG_STORE_PWD, password); } else { getContext().setSessionAttribute( - SessionAttributeNames.CONFIG_STORE_PORT, + SessionAttributeNames.CONFIG_STORE_PORT, (String)data.get(BootstrapData.DS_PORT)); getContext().setSessionAttribute( - SessionAttributeNames.CONFIG_STORE_HOST, host); + SessionAttributeNames.CONFIG_STORE_HOST, host); addObject(sb, "configStoreHost", host); String dsprot = (String)data.get( @@ -488,7 +481,7 @@ public boolean validateHostName() { getContext().setSessionAttribute( SessionAttributeNames.CONFIG_STORE_SSL, dsSSL); addObject(sb, "configStoreSSL", dsSSL); - + String dspwd = (String)data.get(BootstrapData.DS_PWD); getContext().setSessionAttribute( SessionAttributeNames.CONFIG_STORE_PWD, @@ -509,7 +502,7 @@ public boolean validateHostName() { getContext().setSessionAttribute( SessionAttributeNames.EXISTING_STORE_PORT, ds_existingStorePort); addObject(sb, "existingStorePort", ds_existingStorePort); - + getContext().setSessionAttribute( SessionAttributeNames.EXISTING_HOST, host); @@ -542,12 +535,11 @@ public boolean validateHostName() { addObject(sb, "message", message); } } - sb.append(" }"); + sb.append(" }"); writeToResponse(sb.toString()); - setPath(null); return false; } - + private void addObject(StringBuffer sb, String key, String value) { if (sb.length() < 1) { // add first object @@ -559,16 +551,16 @@ private void addObject(StringBuffer sb, String key, String value) { .append(key) .append(SEPARATOR) .append(value) - .append(QUOTE); + .append(QUOTE); } - + /* * the following value have been pulled from an existing OpenAM - * server which was configured to use an external DS. We need to set the DS + * server which was configured to use an external DS. We need to set the DS * values in the request so they can be used to configure the existing * OpenAM server. */ - private void setupDSParams(Map data) { + private void setupDSParams(Map data) { String tmp = (String)data.get(BootstrapData.DS_BASE_DN); getContext().setSessionAttribute( SessionAttributeNames.CONFIG_STORE_ROOT_SUFFIX, tmp); @@ -576,22 +568,23 @@ private void setupDSParams(Map data) { tmp = (String)data.get(BootstrapData.DS_MGR); getContext().setSessionAttribute( SessionAttributeNames.CONFIG_STORE_LOGIN_ID, tmp); - + tmp = (String)data.get(BootstrapData.DS_PWD); getContext().setSessionAttribute( SessionAttributeNames.CONFIG_STORE_PWD, tmp); - + getContext().setSessionAttribute( - SessionAttributeNames.CONFIG_VAR_DATA_STORE, - SetupConstants.SMS_DS_DATASTORE); + SessionAttributeNames.CONFIG_VAR_DATA_STORE, + SetupConstants.SMS_DS_DATASTORE); } /** * Validate an Existing SM Host for Configuration Backend. * @return */ + @ConfiguratorAction public boolean validateSMHost() { - Context ctx = getContext(); + ConfiguratorContext ctx = getContext(); String strSSL = (String)ctx.getSessionAttribute( SessionAttributeNames.CONFIG_STORE_SSL); boolean ssl = (strSSL != null) && (strSSL.equals("SSL")); @@ -656,7 +649,6 @@ public boolean validateSMHost() { writeToResponse(getLocalizedString("cannot.connect.to.SM.datastore")); } - setPath(null); return false; } -} \ No newline at end of file +} diff --git a/openam-core/src/main/java/com/sun/identity/config/wizard/Step4.java b/openam-core/src/main/java/com/sun/identity/config/wizard/Step4.java index 3d759a3ed2..b17f794de4 100644 --- a/openam-core/src/main/java/com/sun/identity/config/wizard/Step4.java +++ b/openam-core/src/main/java/com/sun/identity/config/wizard/Step4.java @@ -25,12 +25,12 @@ * $Id: Step4.java,v 1.20 2009/10/27 05:31:45 hengming Exp $ * * Portions Copyrighted 2011-2016 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems LLC. */ package com.sun.identity.config.wizard; import com.sun.identity.config.SessionAttributeNames; -import com.sun.identity.config.util.ProtectedPage; import com.sun.identity.setup.SetupConstants; import java.io.IOException; import java.net.Socket; @@ -40,8 +40,9 @@ import javax.naming.directory.Attributes; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.control.ActionLink; +import org.openidentityplatform.openam.config.servlet.ConfiguratorAction; +import org.openidentityplatform.openam.config.servlet.ConfiguratorContext; +import org.openidentityplatform.openam.config.servlet.ProtectedSetupPage; import org.forgerock.openam.ldap.LDAPRequests; import org.forgerock.openam.ldap.LDAPUtils; import org.forgerock.opendj.ldap.Connection; @@ -52,45 +53,18 @@ /** * Step 4 is the input of the remote user data store properties. */ -public class Step4 extends ProtectedPage { +public class Step4 extends ProtectedSetupPage { public static final String LDAP_STORE_SESSION_KEY = "wizardCustomUserStore"; - public ActionLink validateUMHostLink = - new ActionLink("validateUMHost", this, "validateUMHost"); - public ActionLink validateUMDomainNameLink = - new ActionLink("validateUMDomainName", this, - "validateUMDomainName"); - public ActionLink setSSLLink = - new ActionLink("setSSL", this, "setSSL"); - public ActionLink setUMEmbedded = - new ActionLink("setUMEmbedded", this, "setUMEmbedded"); - public ActionLink resetUMEmbedded = - new ActionLink("resetUMEmbedded", this, "resetUMEmbedded"); - public ActionLink setHostLink = - new ActionLink("setHost", this, "setHost"); - public ActionLink setDomainNameLink = - new ActionLink("setDomainName", this, "setDomainName"); - public ActionLink setPortLink = - new ActionLink("setPort", this, "setPort"); - public ActionLink setRootSuffixLink = - new ActionLink("setRootSuffix", this, "setRootSuffix"); - public ActionLink setLoginIDLink = - new ActionLink("setLoginID", this, "setLoginID"); - public ActionLink setPasswordLink = - new ActionLink("setPassword", this, "setPassword"); - public ActionLink setStoreTypeLink = - new ActionLink("setStoreType", this, "setStoreType"); private String responseString = "ok"; private static final String ObjectClassFilter = "(objectclass=*)"; - - public Step4() { - } - + + @Override public void onInit() { super.onInit(); - Context ctx = getContext(); - + ConfiguratorContext ctx = getContext(); + if (ctx.getSessionAttribute(SessionAttributeNames.USER_STORE_HOST) == null) { String val = getAttribute(SetupConstants.CONFIG_VAR_DATA_STORE, @@ -138,7 +112,7 @@ public void onInit() { String val = getAttribute(SetupConstants.USER_STORE_HOST,getHostName()); ctx.setSessionAttribute(SessionAttributeNames.USER_STORE_HOST, val); addModel("userStoreHost", val); - + val = getAttribute(SetupConstants.USER_STORE_SSL, "SIMPLE"); ctx.setSessionAttribute(SessionAttributeNames.USER_STORE_SSL, val); if (val.equals("SSL")) { @@ -156,7 +130,7 @@ public void onInit() { ctx.setSessionAttribute(SessionAttributeNames.USER_STORE_LOGIN_ID, val); addModel("userStoreLoginId", val); - val = getAttribute(SetupConstants.USER_STORE_ROOT_SUFFIX, + val = getAttribute(SetupConstants.USER_STORE_ROOT_SUFFIX, Wizard.defaultRootSuffix); ctx.setSessionAttribute(SessionAttributeNames.USER_STORE_ROOT_SUFFIX, val); @@ -185,6 +159,14 @@ public void onInit() { addModel("selectLDAPv3opends", ""); addModel("selectLDAPv3tivoli", ""); } else if (val.equals("LDAPv3ForODSEE")) { + // NOTE: pre-existing bug ported verbatim from the old Click page (verified against + // source, see docs/migration/click-to-freemarker/04-implementation-notes.md): this + // branch sets selectLDAPv3odsee to "checked" and then immediately overwrites it back + // to "" instead of setting selectLDAPv3opends. Net effect (unchanged from Click): + // neither the ODSEE nor OpenDS radio renders checked when the store type is ODSEE, + // and selectLDAPv3opends is left unset. The template defaults these vars with !"" so + // FreeMarker's strict undefined-variable check doesn't turn this cosmetic bug into a + // 500 error. addModel("selectLDAPv3odsee", "checked=\"checked\""); addModel("selectLDAPv3ad", ""); addModel("selectLDAPv3addc", ""); @@ -217,12 +199,16 @@ public void onInit() { addModel("selectExternalUM", ""); } } - - public boolean setAll() { - setPath(null); + + // Not bound to any Click ActionLink and not called by step4.htm/step4.ftl in the original + // page either - genuinely unreachable before and after this port, so left un-annotated + // (no @ConfiguratorAction) to keep it that way. Kept only because it was pre-existing code. + public boolean setAll() { + skipRender(); return false; } - + + @ConfiguratorAction public boolean setSSL() { String ssl = toString("ssl"); if ((ssl != null) && ssl.length() > 0) { @@ -233,56 +219,58 @@ public boolean setSSL() { SessionAttributeNames.USER_STORE_SSL, "SIMPLE"); } writeToResponse(getLocalizedString(responseString)); - setPath(null); return false; } + @ConfiguratorAction public boolean setDomainName() { String domainname = toString("domainname"); if ((domainname != null) && domainname.length() > 0) { getContext().setSessionAttribute( - SessionAttributeNames.USER_STORE_DOMAINNAME, + SessionAttributeNames.USER_STORE_DOMAINNAME, domainname); getContext().setSessionAttribute( SessionAttributeNames.EXT_DATA_STORE, "true"); } else { - responseString = "missing.domain.name"; + responseString = "missing.domain.name"; } writeToResponse(getLocalizedString(responseString)); - setPath(null); return false; } + @ConfiguratorAction public boolean setHost() { String host = toString("host"); if ((host != null) && host.length() > 0) { getContext().setSessionAttribute( SessionAttributeNames.USER_STORE_HOST, host); } else { - responseString = "missing.host.name"; + responseString = "missing.host.name"; } writeToResponse(getLocalizedString(responseString)); - setPath(null); return false; } + @ConfiguratorAction public boolean setUMEmbedded() { getContext().setSessionAttribute(SessionAttributeNames.EXT_DATA_STORE, "false"); - setPath(null); + skipRender(); return false; } + @ConfiguratorAction public boolean resetUMEmbedded() { getContext().setSessionAttribute(SessionAttributeNames.EXT_DATA_STORE, "true"); - setPath(null); + skipRender(); return false; } - + + @ConfiguratorAction public boolean setPort() { String port = toString("port"); - + if ((port != null) && port.length() > 0) { int intValue = Integer.parseInt(port); if ((intValue > 0) && (intValue < 65535)) { @@ -292,39 +280,39 @@ public boolean setPort() { responseString = "invalid.port.number"; } } else { - responseString = "missing.host.port"; + responseString = "missing.host.port"; } writeToResponse(getLocalizedString(responseString)); - setPath(null); return false; } - + + @ConfiguratorAction public boolean setLoginID() { String dn = toString("dn"); if ((dn != null) && dn.length() > 0) { getContext().setSessionAttribute( SessionAttributeNames.USER_STORE_LOGIN_ID, dn); } else { - responseString = "missing.login.id"; + responseString = "missing.login.id"; } writeToResponse(getLocalizedString(responseString)); - setPath(null); return false; } - + + @ConfiguratorAction public boolean setPassword() { String pwd = toString("password"); if ((pwd != null) && pwd.length() > 0) { getContext().setSessionAttribute( SessionAttributeNames.USER_STORE_LOGIN_PWD, pwd); } else { - responseString = "missing.password"; + responseString = "missing.password"; } writeToResponse(getLocalizedString(responseString)); - setPath(null); return false; } - + + @ConfiguratorAction public boolean setRootSuffix() { String rootsuffix = toString("rootsuffix"); @@ -333,33 +321,33 @@ public boolean setRootSuffix() { getContext().setSessionAttribute( SessionAttributeNames.USER_STORE_ROOT_SUFFIX, rootsuffix); } else { - responseString = "invalid.dn"; + responseString = "invalid.dn"; } } else { - responseString = "missing.root.suffix"; + responseString = "missing.root.suffix"; } writeToResponse(getLocalizedString(responseString)); - setPath(null); return false; } - + + @ConfiguratorAction public boolean setStoreType() { String type = toString("type"); if ((type != null) && type.length() > 0) { getContext().setSessionAttribute( SessionAttributeNames.USER_STORE_TYPE, type); - } + } writeToResponse(responseString); - setPath(null); return false; } - + + @ConfiguratorAction public boolean validateUMHost() { - Context ctx = getContext(); + ConfiguratorContext ctx = getContext(); String strSSL = (String)ctx.getSessionAttribute( SessionAttributeNames.USER_STORE_SSL); boolean ssl = (strSSL != null) && (strSSL.equals("SSL")); - + String host = (String)ctx.getSessionAttribute( SessionAttributeNames.USER_STORE_HOST); String strPort = (String)ctx.getSessionAttribute( @@ -371,7 +359,7 @@ public boolean validateUMHost() { SessionAttributeNames.USER_STORE_ROOT_SUFFIX); String bindPwd = (String)ctx.getSessionAttribute( SessionAttributeNames.USER_STORE_LOGIN_PWD); - + try (Connection conn = getConnection(host, port, bindDN, bindPwd.toCharArray(), 5, ssl)) { //String filter = "cn=" + "\"" + rootSuffix + "\""; // NOT SURE Why "cn" is specified. would never work. String[] attrs = {""}; @@ -381,18 +369,17 @@ public boolean validateUMHost() { ResultCode resultCode = lex.getResult().getResultCode(); if (!writeErrorToResponse(resultCode)) { writeToResponse(getLocalizedString("cannot.connect.to.SM.datastore")); - } + } } catch (Exception e) { writeToResponse(getLocalizedString("cannot.connect.to.SM.datastore")); } - setPath(null); return false; } + @ConfiguratorAction public boolean validateUMDomainName() { - setPath(null); - Context ctx = getContext(); + ConfiguratorContext ctx = getContext(); String strSSL = (String)ctx.getSessionAttribute( SessionAttributeNames.USER_STORE_SSL); boolean ssl = (strSSL != null) && (strSSL.equals("SSL")); @@ -401,7 +388,7 @@ public boolean validateUMDomainName() { SessionAttributeNames.USER_STORE_DOMAINNAME); String rootSuffixAD = dnsDomainToDN(domainName); getContext().setSessionAttribute( - SessionAttributeNames.USER_STORE_ROOT_SUFFIX, + SessionAttributeNames.USER_STORE_ROOT_SUFFIX, rootSuffixAD); String[] hostAndPort = {""}; try { @@ -414,7 +401,7 @@ public boolean validateUMDomainName() { writeToResponse( getLocalizedString("cannot.connect.to.UM.datastore")); return false; - } + } String host = hostAndPort[0]; int port = Integer.parseInt(hostAndPort[1]); @@ -424,7 +411,7 @@ public boolean validateUMDomainName() { SessionAttributeNames.USER_STORE_ROOT_SUFFIX); String bindPwd = (String)ctx.getSessionAttribute( SessionAttributeNames.USER_STORE_LOGIN_PWD); - + try (Connection conn = getConnection(host, port, bindDN, bindPwd.toCharArray(), 3, ssl)) { //String filter = "cn=" + "\"" + rootSuffix + "\""; String[] attrs = {""}; @@ -443,20 +430,20 @@ public boolean validateUMDomainName() { // Method to get hostname and port number with the // provided Domain Name for Active Directory user data store. - private String[] getLdapHostAndPort(String domainName) + private String[] getLdapHostAndPort(String domainName) throws NamingException, IOException { if (!domainName.endsWith(".")) { domainName+='.'; } DirContext ictx = null; // Check if domain name is a valid one. - // The resource record type A is defined in RFC 1035. + // The resource record type A is defined in RFC 1035. try { Hashtable env = new Hashtable(); - env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, + env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory"); ictx = new InitialDirContext(env); - Attributes attributes = + Attributes attributes = ictx.getAttributes(domainName, new String[]{"A"}); Attribute attrib = attributes.get("A"); if (attrib == null) { @@ -474,10 +461,10 @@ private String[] getLdapHostAndPort(String domainName) final String ldapServer = "_ldap._tcp." + domainName; try { // Attempting to resolve ldapServer to SRV record. - // This is a mechanism defined in MSDN, querying + // This is a mechanism defined in MSDN, querying // SRV records for _ldap._tcp.DOMAINNAME. // and get host and port from domain. - Attributes attributes = + Attributes attributes = ictx.getAttributes(ldapServer, new String[]{"SRV"}); Attribute attr = attributes.get("SRV"); if (attr == null) { @@ -485,12 +472,12 @@ private String[] getLdapHostAndPort(String domainName) } String[] srv = attr.get().toString().split(" "); String hostNam = srv[3]; - serverHostName = + serverHostName = hostNam.substring(0, hostNam.length() -1); - if ((serverHostName != null) && + if ((serverHostName != null) && serverHostName.length() > 0) { getContext().setSessionAttribute( - SessionAttributeNames.USER_STORE_HOST, + SessionAttributeNames.USER_STORE_HOST, serverHostName); } serverPortStr = srv[2]; @@ -500,7 +487,7 @@ private String[] getLdapHostAndPort(String domainName) throw e; } - // try to connect to LDAP port to make sure this machine + // try to connect to LDAP port to make sure this machine // has LDAP service int serverPort = Integer.parseInt(serverPortStr); if ((serverPort > 0) && (serverPort < 65535)) { @@ -521,7 +508,7 @@ private String[] getLdapHostAndPort(String domainName) } // Method to convert the domain name to the root suffix. - // eg., Domain Name amqa.test.com is converted to root suffix + // eg., Domain Name amqa.test.com is converted to root suffix // DC=amqa,DC=test,DC=com static String dnsDomainToDN(String domainName) { StringBuilder buf = new StringBuilder(); diff --git a/openam-core/src/main/java/com/sun/identity/config/wizard/Step5.java b/openam-core/src/main/java/com/sun/identity/config/wizard/Step5.java index 49877744ad..bd41bae70a 100644 --- a/openam-core/src/main/java/com/sun/identity/config/wizard/Step5.java +++ b/openam-core/src/main/java/com/sun/identity/config/wizard/Step5.java @@ -25,6 +25,7 @@ * $Id: Step5.java,v 1.9 2009/01/05 23:17:10 veiming Exp $ * * Portions Copyrighted 2011-2016 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems LLC. */ package com.sun.identity.config.wizard; @@ -32,10 +33,10 @@ import java.net.MalformedURLException; import java.net.URL; -import org.openidentityplatform.openam.click.control.ActionLink; +import org.openidentityplatform.openam.config.servlet.ConfiguratorAction; +import org.openidentityplatform.openam.config.servlet.ProtectedSetupPage; import com.sun.identity.config.SessionAttributeNames; -import com.sun.identity.config.util.ProtectedPage; /** * Wizard Step # 5: Site Name, URL and Session HA Failover indicator. @@ -45,18 +46,9 @@ * as this information will be replicated by the underlying store. * */ -public class Step5 extends ProtectedPage { - - public ActionLink clearLink = new ActionLink( - "clear", this, "clear"); - public ActionLink validateLink = new ActionLink( - "validateURL", this, "validateURL"); - public ActionLink validateSiteLink = new ActionLink( - "validateSite", this, "validateSite"); - - public Step5() { - } +public class Step5 extends ProtectedSetupPage { + @Override public void onInit() { String host = (String) getContext().getSessionAttribute( SessionAttributeNames.LB_SITE_NAME); @@ -77,10 +69,11 @@ public void onInit() { * * @return boolean indicator to view. */ + @ConfiguratorAction public boolean clear() { getContext().removeSessionAttribute(SessionAttributeNames.LB_SITE_NAME); getContext().removeSessionAttribute(SessionAttributeNames.LB_PRIMARY_URL); - setPath(null); + skipRender(); return false; } @@ -91,6 +84,7 @@ public boolean clear() { * port = primary loadURL * Just a little confusing! */ + @ConfiguratorAction public boolean validateSite() { boolean returnVal = false; String siteName = toString("host"); @@ -102,7 +96,6 @@ public boolean validateSite() { SessionAttributeNames.LB_SITE_NAME, siteName); writeValid("ok.label"); } - setPath(null); return returnVal; } @@ -113,6 +106,7 @@ public boolean validateSite() { * port = primary loadURL * Just a little confusing! */ + @ConfiguratorAction public boolean validateURL() { boolean returnVal = false; String primaryURL = toString("port"); @@ -139,7 +133,6 @@ public boolean validateURL() { returnVal = true; } } - setPath(null); return returnVal; } diff --git a/openam-core/src/main/java/com/sun/identity/config/wizard/Step6.java b/openam-core/src/main/java/com/sun/identity/config/wizard/Step6.java index 012cd0b07c..21fb2c462e 100644 --- a/openam-core/src/main/java/com/sun/identity/config/wizard/Step6.java +++ b/openam-core/src/main/java/com/sun/identity/config/wizard/Step6.java @@ -25,63 +25,63 @@ * $Id: Step6.java,v 1.13 2009/01/05 23:17:10 veiming Exp $ * * Portions Copyrighted 2011-2014 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems LLC. */ package com.sun.identity.config.wizard; import com.sun.identity.config.SessionAttributeNames; -import com.sun.identity.config.util.ProtectedPage; -import org.openidentityplatform.openam.click.control.ActionLink; +import org.openidentityplatform.openam.config.servlet.ConfiguratorAction; +import org.openidentityplatform.openam.config.servlet.ProtectedSetupPage; /** * This is the first step in the advanced configuration flow. * The user will be required to add the default admin password and * the agent passwords. */ -public class Step6 extends ProtectedPage { - - // this links required for client side validation calls - public ActionLink validateAgent = - new ActionLink("checkAgentPassword", this, "checkAgentPassword" ); - - public void onInit() { +public class Step6 extends ProtectedSetupPage { + + @Override + public void onInit() { String agentPwd = (String)getContext().getSessionAttribute( SessionAttributeNames.CONFIG_VAR_AMLDAPUSERPASSWD); if (agentPwd != null) { addModel("agentPassword",agentPwd); } - + String confirmPwd = (String)getContext().getSessionAttribute( SessionAttributeNames.CONFIG_VAR_AMLDAPUSERPASSWD_CONFIRM); if (confirmPwd != null) { addModel("agentConfirm", confirmPwd); } - + super.onInit(); } - + + // Not referenced by step6.htm/step6.ftl (the template posts to the base checkPasswords + // handler instead, same as Step1), but kept web-invokable for byte-identical behavior with + // the old Click page - see docs/migration/click-to-freemarker/04-implementation-notes.md. + @ConfiguratorAction public boolean checkAgentPassword() { String agentPassword = toString("agent"); String agentConfirm = toString("agentConfirm"); String tmpadmin = (String)getContext().getSessionAttribute( SessionAttributeNames.CONFIG_VAR_ADMIN_PWD); - - if (agentPassword == null || agentConfirm == null) { + + if (agentPassword == null || agentConfirm == null) { writeInvalid(getLocalizedString("missing.required.field")); } else if (agentPassword.equals(tmpadmin)) { writeInvalid(getLocalizedString("agent.admin.passwords.match")); } else if (agentPassword.length() < 8) { writeInvalid(getLocalizedString("invalid.password.length")); } else if (!agentPassword.equals(agentConfirm)) { - writeInvalid(getLocalizedString("passwords.do.not.match")); + writeInvalid(getLocalizedString("passwords.do.not.match")); } else { writeValid("OK"); getContext().setSessionAttribute( SessionAttributeNames.CONFIG_VAR_AMLDAPUSERPASSWD, agentPassword); } - setPath(null); return false; - } + } } - diff --git a/openam-core/src/main/java/com/sun/identity/config/wizard/Step7.java b/openam-core/src/main/java/com/sun/identity/config/wizard/Step7.java index 1dd7506dd5..998cf77c41 100644 --- a/openam-core/src/main/java/com/sun/identity/config/wizard/Step7.java +++ b/openam-core/src/main/java/com/sun/identity/config/wizard/Step7.java @@ -25,27 +25,25 @@ * $Id: Step7.java,v 1.15 2009/10/27 05:31:45 hengming Exp $ * * Portions Copyrighted 2010-2016 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems LLC. */ package com.sun.identity.config.wizard; -import org.openidentityplatform.openam.click.Context; - import com.sun.identity.config.SessionAttributeNames; -import com.sun.identity.config.util.ProtectedPage; import com.sun.identity.setup.SetupConstants; +import org.openidentityplatform.openam.config.servlet.ConfiguratorContext; +import org.openidentityplatform.openam.config.servlet.ProtectedSetupPage; /** * This is the summary page for the values entered during the configuration * process. No actual work is done here except setting the page elements. */ -public class Step7 extends ProtectedPage { - - private static final String DISABLED = "Disabled"; - private static final String ENABLED = "Enabled"; +public class Step7 extends ProtectedSetupPage { + @Override public void onInit() { - Context ctx = getContext(); + ConfiguratorContext ctx = getContext(); String tmp = getAttribute( SetupConstants.CONFIG_VAR_DATA_STORE, SetupConstants.SMS_EMBED_DATASTORE); @@ -141,7 +139,6 @@ public void onInit() { (String) ctx.getSessionAttribute( SessionAttributeNames.LB_PRIMARY_URL)); - // Initialize our Parent Object. super.onInit(); } diff --git a/openam-core/src/main/java/com/sun/identity/config/wizard/Wizard.java b/openam-core/src/main/java/com/sun/identity/config/wizard/Wizard.java index 24e87770d0..e43145907d 100644 --- a/openam-core/src/main/java/com/sun/identity/config/wizard/Wizard.java +++ b/openam-core/src/main/java/com/sun/identity/config/wizard/Wizard.java @@ -25,7 +25,7 @@ * $Id: Wizard.java,v 1.27 2009/01/17 02:05:35 kevinserwin Exp $ * * Portions Copyrighted 2010-2016 ForgeRock AS. - * Portions Copyrighted 2025 3A Systems LLC. + * Portions Copyrighted 2025-2026 3A Systems LLC. */ package com.sun.identity.config.wizard; @@ -35,10 +35,10 @@ import jakarta.servlet.http.HttpServletRequest; -import org.openidentityplatform.openam.click.control.ActionLink; +import org.openidentityplatform.openam.config.servlet.ConfiguratorAction; +import org.openidentityplatform.openam.config.servlet.ProtectedSetupPage; import com.sun.identity.config.SessionAttributeNames; -import com.sun.identity.config.util.ProtectedPage; import com.sun.identity.setup.AMSetupServlet; import com.sun.identity.setup.AMSetupUtils; import com.sun.identity.setup.ConfiguratorException; @@ -47,37 +47,42 @@ import com.sun.identity.setup.SetupConstants; import com.sun.identity.shared.Constants; -public class Wizard extends ProtectedPage implements Constants { +public class Wizard extends ProtectedSetupPage implements Constants { public int startingTab = 1; - public ActionLink createConfigLink = - new ActionLink("createConfig", this, "createConfig" ); - public ActionLink testUrlLink = - new ActionLink("testNewInstanceUrl", this, "testNewInstanceUrl" ); - public ActionLink pushConfigLink = - new ActionLink("pushConfig", this, "pushConfig" ); - private String cookieDomain = null; - private String hostName = getHostName(); + private String hostName; private String dataStore = SetupConstants.SMS_EMBED_DATASTORE; - + public static String defaultUserName = "cn=Directory Manager"; public static String defaultPassword = ""; public static String defaultRootSuffix = DEFAULT_ROOT_SUFFIX; - public String defaultPort = Integer.toString( - AMSetupUtils.getFirstUnusedPort(hostName, 50389, 1000)); - public String defaultAdminPort = Integer.toString( - AMSetupUtils.getFirstUnusedPort(hostName, 4444, 1000)); - public String defaultJmxPort = Integer.toString( - AMSetupUtils.getFirstUnusedPort(hostName, 1689, 1000)); - + public String defaultPort; + public String defaultAdminPort; + public String defaultJmxPort; + + @Override + public void onInit() { + super.onInit(); + // The old Click Wizard computed these in eager field initializers, which ran fine there + // because Click binds Context.getThreadLocalContext() before constructing the page. + // ConfiguratorContext is only attached after construction here (see ConfiguratorServlet), + // so the same "recompute on every request" behavior moves to onInit(), which runs + // unconditionally before both render and action dispatch. + hostName = getHostName(); + defaultPort = Integer.toString(AMSetupUtils.getFirstUnusedPort(hostName, 50389, 1000)); + defaultAdminPort = Integer.toString(AMSetupUtils.getFirstUnusedPort(hostName, 4444, 1000)); + defaultJmxPort = Integer.toString(AMSetupUtils.getFirstUnusedPort(hostName, 1689, 1000)); + } + /** - * This is the 'execute' operation for the entire wizard. This method - * aggregates all data submitted across the wizard pages here in one lump + * This is the 'execute' operation for the entire wizard. This method + * aggregates all data submitted across the wizard pages here in one lump * and hands it off to the back-end for processing. */ + @ConfiguratorAction public boolean createConfig() { HttpServletRequest req = getContext().getRequest(); @@ -302,10 +307,8 @@ public boolean createConfig() { } catch (ConfiguratorException cfe) { writeToResponse(cfe.getMessage()); } - - setPath(null); + return false; } - } diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionEventDispatcher.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionEventDispatcher.java deleted file mode 100644 index 6081167fdc..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionEventDispatcher.java +++ /dev/null @@ -1,620 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedHashSet; - -import java.util.List; -import java.util.Set; - -import org.openidentityplatform.openam.click.ajax.AjaxBehavior; -import org.openidentityplatform.openam.click.service.ConfigService; -import org.openidentityplatform.openam.click.service.LogService; -import org.apache.click.util.HtmlStringBuffer; -import org.apache.commons.lang.ClassUtils; -import org.apache.commons.lang.Validate; - -/** - * Provides a control ActionListener and AjaxBehavior dispatcher. The - * ClickServlet will dispatch registered ActionListeners and AjaxBehaviors after - * page controls have been processed. - * - *

Example Usage

- * The following example shows how to register an ActionListener with a custom - * Control: - * - *
- * public class MyControl extends AbstractControl {
- *     ...
- *
- *     public boolean onProcess() {
- *         bindRequestValue();
- *
- *         if (isClicked()) {
- *             // Dispatch an action listener event for invocation after
- *             // control processing has finished
- *             dispatchActionEvent();
- *         }
- *
- *         return true;
- *     }
- * } 
- * - * When the link is clicked it invokes the method - * {@link org.openidentityplatform.openam.click.control.AbstractControl#dispatchActionEvent()}. - * This method registers the Control's action listener with the - * ActionEventDispatcher. The ClickServlet will subsequently invoke the registered - * {@link ActionListener#onAction(Control)} method after all the Page controls - * onProcess() method have been invoked. - */ -public class ActionEventDispatcher { - - // Constants -------------------------------------------------------------- - - /** The thread local dispatcher holder. */ - private static final ThreadLocal THREAD_LOCAL_DISPATCHER_STACK - = new ThreadLocal<>(); - - // Variables -------------------------------------------------------------- - - /** The list of registered event sources. */ - List eventSourceList; - - /** The list of registered event listeners. */ - List eventListenerList; - - /** The set of Controls with attached AjaxBehaviors. */ - Set ajaxBehaviorSourceSet; - - /** - * The {@link org.apache.click.ActionResult} to render. This action result is - * returned from the target Behavior. - */ - ActionResult actionResult; - - /** The application log service. */ - LogService logger; - - // Constructors ----------------------------------------------------------- - - /** - * Construct the ActionEventDispatcher with the given ConfigService. - * - * @param configService the click application configuration service - */ - public ActionEventDispatcher(ConfigService configService) { - this.logger = configService.getLogService(); - } - - // Public Methods --------------------------------------------------------- - - /** - * Register the event source and event ActionListener to be fired by the - * ClickServlet once all the controls have been processed. - * - * @param source the action event source - * @param listener the event action listener - */ - public static void dispatchActionEvent(Control source, ActionListener listener) { - Validate.notNull(source, "Null source parameter"); - Validate.notNull(listener, "Null listener parameter"); - - ActionEventDispatcher instance = getThreadLocalDispatcher(); - instance.registerActionEvent(source, listener); - } - - /** - * Register the source control which AjaxBehaviors should be fired by the - * ClickServlet. - * - * @param source the source control which behaviors should be fired - */ - public static void dispatchAjaxBehaviors(Control source) { - Validate.notNull(source, "Null source parameter"); - - ActionEventDispatcher instance = getThreadLocalDispatcher(); - instance.registerAjaxBehaviorSource(source); - } - - /** - * Return the thread local ActionEventDispatcher instance. - * - * @return the thread local ActionEventDispatcher instance. - * @throws RuntimeException if an ActionEventDispatcher is not available on - * the thread - */ - public static ActionEventDispatcher getThreadLocalDispatcher() { - return getDispatcherStack().peek(); - } - - /** - * Returns true if an ActionEventDispatcher instance is available on the - * current thread, false otherwise. - *

- * Unlike {@link #getThreadLocalDispatcher()} this method can safely be used - * and will not throw an exception if an ActionEventDispatcher is not - * available on the current thread. - * - * @return true if an ActionEventDispatcher instance is available on the - * current thread, false otherwise - */ - public static boolean hasThreadLocalDispatcher() { - DispatcherStack dispatcherStack = THREAD_LOCAL_DISPATCHER_STACK.get(); - if (dispatcherStack == null) { - return false; - } - return !dispatcherStack.isEmpty(); - } - - /** - * Fire all the registered action events after the Page Controls have been - * processed and return true if the page should continue processing. - * - * @param context the request context - * - * @return true if the page should continue processing, false otherwise - */ - public boolean fireActionEvents(Context context) { - - if (!hasActionEvents()) { - return true; - } - - return fireActionEvents(context, getEventSourceList(), getEventListenerList()); - } - - /** - * Fire all the registered AjaxBehaviors and return true if the page should - * continue processing, false otherwise. - * - * @see #fireAjaxBehaviors(Context, java.util.Set) - * - * @param context the request context - * - * @return true if the page should continue processing, false otherwise - */ - public boolean fireAjaxBehaviors(Context context) { - - if (!hasAjaxBehaviorSourceSet()) { - return true; - } - - return fireAjaxBehaviors(context, getAjaxBehaviorSourceSet()); - } - - // Protected Methods ------------------------------------------------------ - - /** - * Allow the dispatcher to handle the error that occurred. - * - * @param throwable the error which occurred during processing - */ - protected void errorOccurred(Throwable throwable) { - // Clear the control listeners and behaviors from the dispatcher - clear(); - } - - /** - * Fire the actions for the given listener list and event source list which - * return true if the page should continue processing. - *

- * This method can be overridden if you need to customize the way events - * are fired. - * - * @param context the request context - * @param eventSourceList the list of source controls - * @param eventListenerList the list of listeners to fire - * - * @return true if the page should continue processing or false otherwise - */ - protected boolean fireActionEvents(Context context, - List eventSourceList, List eventListenerList) { - - boolean continueProcessing = true; - - for (int i = 0, size = eventSourceList.size(); i < size; i++) { - Control source = eventSourceList.remove(0); - ActionListener listener = eventListenerList.remove(0); - - if (!fireActionEvent(context, source, listener)) { - continueProcessing = false; - } - } - - return continueProcessing; - } - - /** - * Fire the action for the given listener and event source which - * return true if the page should continue processing. - *

- * This method can be overridden if you need to customize the way events - * are fired. - * - * @param context the request context - * @param source the source control - * @param listener the listener to fire - * - * @return true if the page should continue processing, false otherwise - */ - protected boolean fireActionEvent(Context context, Control source, - ActionListener listener) { - return listener.onAction(source); - } - - /** - * Fire the AjaxBehaviors for the given control set and return true if the page - * should continue processing, false otherwise. - *

- * This method can be overridden if you need to customize the way - * AjaxBehaviors are fired. - * - * @see #fireAjaxBehaviors(Context, Control) - * - * @param context the request context - * @param ajaxBbehaviorSourceSet the set of controls with attached AjaxBehaviors - * - * @return true if the page should continue processing, false otherwise - */ - protected boolean fireAjaxBehaviors(Context context, Set ajaxBbehaviorSourceSet) { - - boolean continueProcessing = true; - - for (Iterator it = ajaxBbehaviorSourceSet.iterator(); it.hasNext();) { - Control source = it.next(); - - // Pop the first entry in the set - it.remove(); - - if (!fireAjaxBehaviors(context, source)) { - continueProcessing = false; - } - } - - return continueProcessing; - } - - /** - * Fire the AjaxBehaviors for the given control and return true if the - * page should continue processing, false otherwise. AjaxBehaviors will - * only fire if their {@link org.openidentityplatform.openam.click.ajax.AjaxBehavior#isAjaxTarget(Context) isAjaxTarget()} - * method returns true. - *

- * This method can be overridden if you need to customize the way - * AjaxBehaviors are fired. - * - * @param context the request context - * @param source the control which attached behaviors should be fired - * - * @return true if the page should continue processing, false otherwise - */ - protected boolean fireAjaxBehaviors(Context context, Control source) { - - boolean continueProcessing = true; - - if (logger.isTraceEnabled()) { - String sourceClassName = ClassUtils.getShortClassName(source.getClass()); - HtmlStringBuffer buffer = new HtmlStringBuffer(); - buffer.append(" processing AjaxBehaviors for control: '"); - buffer.append(source.getName()).append("' "); - buffer.append(sourceClassName); - logger.trace(buffer.toString()); - } - - for (Behavior behavior : source.getBehaviors()) { - - if (behavior instanceof AjaxBehavior) { - AjaxBehavior ajaxBehavior = (AjaxBehavior) behavior; - - boolean isAjaxTarget = ajaxBehavior.isAjaxTarget(context); - - if (logger.isTraceEnabled()) { - String behaviorClassName = ClassUtils.getShortClassName( - ajaxBehavior.getClass()); - HtmlStringBuffer buffer = new HtmlStringBuffer(); - buffer.append(" invoked: "); - buffer.append(behaviorClassName); - buffer.append(".isAjaxTarget() : "); - buffer.append(isAjaxTarget); - logger.trace(buffer.toString()); - } - - if (isAjaxTarget) { - - // The first non-null ActionResult returned will be rendered, other - // ActionResult instances are ignored - ActionResult behaviorActionResult = - ajaxBehavior.onAction(source); - if (actionResult == null && behaviorActionResult != null) { - actionResult = behaviorActionResult; - } - - if (logger.isTraceEnabled()) { - String behaviorClassName = ClassUtils.getShortClassName( - ajaxBehavior.getClass()); - String actionResultClassName = null; - - if (behaviorActionResult != null) { - actionResultClassName = ClassUtils.getShortClassName( - behaviorActionResult.getClass()); - } - - HtmlStringBuffer buffer = new HtmlStringBuffer(); - buffer.append(" invoked: "); - buffer.append(behaviorClassName); - buffer.append(".onAction() : "); - buffer.append(actionResultClassName); - - if (actionResult == behaviorActionResult - && behaviorActionResult != null) { - buffer.append(" (ActionResult will be rendered)"); - } else { - if (behaviorActionResult == null) { - buffer.append(" (ActionResult is null and will be ignored)"); - } else { - buffer.append(" (ActionResult will be ignored since another AjaxBehavior already retuned a non-null ActionResult)"); - } - } - - logger.trace(buffer.toString()); - } - - continueProcessing = false; - break; - } - } - } - - if (logger.isTraceEnabled()) { - - // continueProcessing is true if no AjaxBehavior was the target - // of the request - if (continueProcessing) { - HtmlStringBuffer buffer = new HtmlStringBuffer(); - String sourceClassName = ClassUtils.getShortClassName( - source.getClass()); - buffer.append(" *no* target AjaxBehavior found for '"); - buffer.append(source.getName()).append("' "); - buffer.append(sourceClassName); - buffer.append(" - invoking AjaxBehavior.isAjaxTarget() returned false for all AjaxBehaviors"); - logger.trace(buffer.toString()); - } - } - - // Ajax requests stops further processing - return continueProcessing; - } - - // Package Private Methods ------------------------------------------------ - - /** - * Register the event source and event ActionListener. - * - * @param source the action event source - * @param listener the event action listener - */ - void registerActionEvent(Control source, ActionListener listener) { - Validate.notNull(source, "Null source parameter"); - Validate.notNull(listener, "Null listener parameter"); - - getEventSourceList().add(source); - getEventListenerList().add(listener); - } - - /** - * Register the AjaxBehavior source control. - * - * @param source the AjaxBehavior source control - */ - void registerAjaxBehaviorSource(Control source) { - Validate.notNull(source, "Null source parameter"); - - getAjaxBehaviorSourceSet().add(source); - } - - /** - * Checks if any Action Events have been registered. - * - * @return true if the dispatcher has any Action Events registered - */ - boolean hasActionEvents() { - if (eventListenerList == null || eventListenerList.isEmpty()) { - return false; - } - return true; - } - - /** - * Return the list of event listeners. - * - * @return list of event listeners - */ - List getEventListenerList() { - if (eventListenerList == null) { - eventListenerList = new ArrayList(); - } - return eventListenerList; - } - - /** - * Return the list of event sources. - * - * @return list of event sources - */ - List getEventSourceList() { - if (eventSourceList == null) { - eventSourceList = new ArrayList(); - } - return eventSourceList; - } - - /** - * Clear the events and behaviors. - */ - void clear() { - if (hasActionEvents()) { - getEventSourceList().clear(); - getEventListenerList().clear(); - } - - if (hasAjaxBehaviorSourceSet()) { - getAjaxBehaviorSourceSet().clear(); - } - } - - /** - * Return the Behavior's action result or null if no behavior was dispatched. - * - * @return the Behavior's action result or null if no behavior was dispatched - */ - ActionResult getActionResult() { - return actionResult; - } - - /** - * Return true if a control with AjaxBehaviors was registered, false otherwise. - * - * @return true if a control with AjaxBehaviors was registered, false otherwise. - */ - boolean hasAjaxBehaviorSourceSet() { - if (ajaxBehaviorSourceSet == null || ajaxBehaviorSourceSet.isEmpty()) { - return false; - } - return true; - } - - /** - * Return the set of controls with attached AjaxBehaviors. - * - * @return set of control with attached AjaxBehaviors - */ - Set getAjaxBehaviorSourceSet() { - if (ajaxBehaviorSourceSet == null) { - ajaxBehaviorSourceSet = new LinkedHashSet(); - } - return ajaxBehaviorSourceSet; - } - - /** - * Adds the specified ActionEventDispatcher on top of the dispatcher stack. - * - * @param actionEventDispatcher the ActionEventDispatcher to add - */ - static void pushThreadLocalDispatcher(ActionEventDispatcher actionEventDispatcher) { - getDispatcherStack().push(actionEventDispatcher); - } - - /** - * Remove and return the actionEventDispatcher instance on top of the - * dispatcher stack. - * - * @return the actionEventDispatcher instance on top of the dispatcher stack - */ - static ActionEventDispatcher popThreadLocalDispatcher() { - DispatcherStack dispatcherStack = getDispatcherStack(); - ActionEventDispatcher actionEventDispatcher = dispatcherStack.pop(); - - if (dispatcherStack.isEmpty()) { - THREAD_LOCAL_DISPATCHER_STACK.set(null); - } - - return actionEventDispatcher; - } - - /** - * Return the stack data structure where ActionEventDispatchers are stored. - * - * @return stack data structure where ActionEventDispatcher are stored - */ - static ActionEventDispatcher.DispatcherStack getDispatcherStack() { - DispatcherStack dispatcherStack = THREAD_LOCAL_DISPATCHER_STACK.get(); - - if (dispatcherStack == null) { - dispatcherStack = new ActionEventDispatcher.DispatcherStack(2); - THREAD_LOCAL_DISPATCHER_STACK.set(dispatcherStack); - } - - return dispatcherStack; - } - - // Inner Classes ---------------------------------------------------------- - - /** - * Provides an unsynchronized Stack. - */ - static class DispatcherStack extends ArrayList { - - /** Serialization version indicator. */ - private static final long serialVersionUID = 1L; - - /** - * Create a new DispatcherStack with the given initial capacity. - * - * @param initialCapacity specify initial capacity of this stack - */ - private DispatcherStack(int initialCapacity) { - super(initialCapacity); - } - - /** - * Pushes the ActionEventDispatcher onto the top of this stack. - * - * @param actionEventDispatcher the ActionEventDispatcher to push onto this stack - * @return the ActionEventDispatcher pushed on this stack - */ - private ActionEventDispatcher push(ActionEventDispatcher actionEventDispatcher) { - add(actionEventDispatcher); - - return actionEventDispatcher; - } - - /** - * Removes and return the ActionEventDispatcher at the top of this stack. - * - * @return the ActionEventDispatcher at the top of this stack - */ - private ActionEventDispatcher pop() { - ActionEventDispatcher actionEventDispatcher = peek(); - - remove(size() - 1); - - return actionEventDispatcher; - } - - /** - * Looks at the ActionEventDispatcher at the top of this stack without - * removing it. - * - * @return the ActionEventDispatcher at the top of this stack - */ - private ActionEventDispatcher peek() { - int length = size(); - - if (length == 0) { - String msg = "No ActionEventDispatcher available on ThreadLocal Dispatcher Stack"; - throw new RuntimeException(msg); - } - - return get(length - 1); - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionListener.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionListener.java deleted file mode 100644 index a7ca79b482..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionListener.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -import java.io.Serializable; - -/** - * Provides a listener interface for receiving control action events. - * The usage model is similar to the java.awt.event.ActionListener - * interface. - *

- * The class that is interested in processing an action event - * implements this interface, and the object created with that class is - * registered with a control, using the control's setActionListener - * method. When the action event occurs, that object's onAction method - * is invoked. - * - *

Listener Example

- * - * An ActionListener example is provided below: - * - *
- * public MyPage extends Page {
- *
- *    public ActionLink link = new ActionLink();
- *
- *    public MyPage() {
- *
- *       link.setActionListener(new ActionListener() {
- *           public boolean onAction(Control source) {
- *               return onLinkClick();
- *           }
- *        });
- *    }
- *
- *    public boolean onLinkClick() {
- *       ..
- *       return true;
- *    }
- * }
- * 
- */ -public interface ActionListener extends Serializable { - - /** - * Return true if the control and page processing should continue, or false - * otherwise. - * - * @param source the source of the action event - * @return true if control and page processing should continue or false - * otherwise. - */ - public boolean onAction(Control source); - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionResult.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionResult.java deleted file mode 100644 index 60ac4b9c70..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionResult.java +++ /dev/null @@ -1,580 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Reader; -import java.io.StringReader; -import java.io.Writer; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import jakarta.servlet.http.HttpServletResponse; - -import org.openidentityplatform.openam.click.util.ClickUtils; - -/** - * Provides an ActionResult that is returned by Page Actions and AjaxBehaviors. - * ActionResults are often used to return a partial response to the browser - * instead of the full page content. - *

- * An ActionResult can consist of a String (HTML, JSON, XML, plain text) or a byte - * array (jpg, gif, png, pdf or excel documents). The ActionResult {@link #contentType} - * must be set appropriately in order for the browser to recognize the action result. - *

- * ActionResults are returned by {@link org.apache.click.ajax.AjaxBehavior Ajax Behaviors} - * and Page Action methods. - * - *

Ajax Behavior

- * - * Ajax requests are handled by adding an {@link org.apache.click.ajax.AjaxBehavior Ajax Behavior} - * to a control. The AjaxBehavior {@link org.apache.click.ajax.AjaxBehavior#onAction(org.apache.click.Control) onAction} - * method will handle the request and return a ActionResult instance that contains - * the response, thus bypassing the rendering of the Page template. For example: - * - *
- * private ActionLink link = new ActionLink("link");
- *
- * public void onInit() {
- *     addControl(link);
- *
- *     link.addBehavior(new AjaxBehavior() {
- *
- *         // The onAction method must return a ActionResult
- *         public ActionResult onAction(Control source) {
- *             // Create a new action result containing an HTML snippet and HTML content type
- *             ActionResult actionResult = new ActionResult("<span>Hello World</span>", ActionResult.HTML);
- *             return actionResult;
- *         }
- *     });
- * } 
- * - *

Page Action

- * - * A Page Action is a method on a Page that can be invoked directly - * from the browser. The Page Action method returns an ActionResult instance that - * is rendered to the browser, thus bypassing the rendering of the Page template. - * - *
- * private ActionLink link = new ActionLink("link");
- *
- * public void onInit() {
- *     link.addControl(link);
- *
- *     // A "pageAction" is set as a parameter on the link. The "pageAction"
- *     // value is set to the Page method: "renderHelloWorld"
- *     link.setParameter(PAGE_ACTION, "renderHelloWorld");
- * }
- *
- * /**
- *  * This is a "pageAction" method that will render an HTML response.
- *  *
- *  * Note the signature of the pageAction: a public, no-argument method
- *  * returning a ActionResult instance.
- *  */
- * public ActionResult renderHelloWorld() {
- *     ActionResult actionResult = new ActionResult("<span>Hello World</span>", ActionResult.HTML);
- *     return actionResult;
- * } 
- * - *

Content Type

- * - * The {@link #contentType} of the ActionResult must be set to the appropriate type - * in order for the client to recognize the response. ActionResult provides constants - * for some of the common content types, including: {@link #XML text/xml}, - * {@link #HTML text/html}, {@link #JSON application/json}, {@link #TEXT text/plain}. - *

- * For example: - *

- * ActionResult actionResult = new ActionResult("alert('hello world');", ActionResult.JAVASCRIPT);
- *
- * ...
- *
- * // content type can also be set through the setContentType method
- * actionResult.setContentType(ActionResult.JAVASCRIPT);
- *
- * ...
- * 
- * - * More content types can be retrieved through {@link org.apache.click.util.ClickUtils#getMimeType(java.lang.String)}: - *
- * // lookup content type for PNG
- * String contentType = ClickUtils.getMimeType("png");
- * actionResult.setContentType(contentType); 
- */ -public class ActionResult { - - // Constants -------------------------------------------------------------- - - /** The plain text content type constant: text/plain. */ - public static final String TEXT = "text/plain"; - - /** The html content type constant: text/html. */ - public static final String HTML = "text/html"; - - /** The The xhtml content type constant: application/xhtml+xml. */ - public static final String XHTML = "application/xhtml+xml"; - - /** The json content type constant: text/json. */ - public static final String JSON = "application/json"; - - /** The javascript content type constant: text/javascript. */ - public static final String JAVASCRIPT = "text/javascript"; - - /** The xml content type constant: text/xml. */ - public static final String XML = "text/xml"; - - /** The ActionResult writer buffer size. */ - private static final int WRITER_BUFFER_SIZE = 256; // For text, set a small response size - - /** The ActionResult output buffer size. */ - private static final int OUTPUT_BUFFER_SIZE = 4 * 1024; // For binary, set a a large response size - - // Variables -------------------------------------------------------------- - - /** The content to render. */ - private String content; - - /** The content as a byte array. */ - private byte[] bytes; - - /** The servlet response reader. */ - private Reader reader; - - /** The servlet response input stream. */ - private InputStream inputStream; - - /** The response content type. */ - private String contentType; - - /** The response character encoding. */ - private String characterEncoding; - - /** Indicates whether the ActionResult should be cached by browser. */ - private boolean cacheActionResult = false; - - /** The path of the actionResult template to render. */ - private String template; - - /** The model for the ActionResult {@link #template}. */ - private Map model; - - // Constructors ----------------------------------------------------------- - - /** - * Construct the ActionResult for the given template and model. - *

- * When the ActionResult is rendered the template and model will be merged and - * the result will be streamed back to the client. - *

- * For example: - *

-     * public class MyPage extends Page {
-     *     public void onInit() {
-     *
-     *         Behavior behavior = new DefaultAjaxBehavior() {
-     *
-     *             public ActionResult onAction() {
-     *
-     *                 Map model = new HashMap();
-     *                 model.put("id", "link");
-     *
-     *                 // Note: we set XML as the content type
-     *                 ActionResult actionResult = new ActionResult("/js/actionResult.xml", model, ActionResult.XML);
-     *
-     *                 return actionResult;
-     *             }
-     *         }
-     *     }
-     * } 
- * - * @param template the template to render and stream back to the client - * @param model the template data model - * @param contentType the response content type - */ - public ActionResult(String template, Map model, String contentType) { - this.template = template; - this.model = model; - this.contentType = contentType; - } - - /** - * Construct the ActionResult for the given reader and content type. - * - * @param reader the reader which characters must be streamed back to the - * client - * @param contentType the response content type - */ - public ActionResult(Reader reader, String contentType) { - this.reader = reader; - this.contentType = contentType; - } - - /** - * Construct the ActionResult for the given inputStream and content type. - * - * @param inputStream the input stream to stream back to the client - * @param contentType the response content type - */ - public ActionResult(InputStream inputStream, String contentType) { - this.inputStream = inputStream; - this.contentType = contentType; - } - - /** - * Construct the ActionResult for the given String content and content type. - * - * @param content the String content to stream back to the client - * @param contentType the response content type - */ - public ActionResult(String content, String contentType) { - this.content = content; - this.contentType = contentType; - } - - /** - * Construct the ActionResult for the given byte array and content type. - * - * @param bytes the byte array to stream back to the client - * @param contentType the response content type - */ - public ActionResult(byte[] bytes, String contentType) { - this.bytes = bytes; - this.contentType = contentType; - } - - /** - * Construct the ActionResult for the given content. The - * {@link jakarta.servlet.http.HttpServletResponse#setContentType(java.lang.String) response content type} - * will default to {@link #TEXT}, unless overridden. - * - * @param content the content to stream back to the client - */ - public ActionResult(String content) { - this.content = content; - } - - /** - * Construct a new empty ActionResult. The - * {@link jakarta.servlet.http.HttpServletResponse#setContentType(java.lang.String) response content type} - * will default to {@link #TEXT}, unless overridden. - */ - public ActionResult() { - } - - // Public Methods --------------------------------------------------------- - - /** - * Set whether the action result should be cached by the client browser or - * not. - *

- * If false, Click will set the following headers to prevent browsers - * from caching the result: - *

-     * response.setHeader("Pragma", "no-cache");
-     * response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
-     * response.setDateHeader("Expires", new Date(1L).getTime()); 
- * - * @param cacheActionResult indicates whether the action result should be cached - * by the client browser or not - */ - public void setCacheActionResult(boolean cacheActionResult) { - this.cacheActionResult = cacheActionResult; - } - - /** - * Return true if the action result should be cached by the client browser, - * defaults to false. It is highly unlikely that you would turn action result - * caching on. - * - * @return true if the action result should be cached by the client browser, - * false otherwise - */ - public boolean isCacheActionRestul() { - return cacheActionResult; - } - - /** - * Return the action result character encoding. If no character encoding is specified - * the request character encoding will be used. - * - * @return the action result character encoding - */ - public String getCharacterEncoding() { - return characterEncoding; - } - - /** - * Set the action result character encoding. If no character encoding is set the - * request character encoding will be used. - * - * @param characterEncoding the action result character encoding - */ - public void setCharacterEncoding(String characterEncoding) { - this.characterEncoding = characterEncoding; - } - - /** - * Set the action result response content type. If no content type is set it will - * default to {@value #TEXT}. - * - * @param contentType the action result response content type - */ - public void setContentType(String contentType) { - this.contentType = contentType; - } - - /** - * Return the action result content type, default is {@value #TEXT}. - * - * @return the response content type - */ - public String getContentType() { - if (contentType == null) { - contentType = TEXT; - } - return contentType; - } - - /** - * Set the content String to stream back to the client. - * - * @param content the content String to stream back to the client - */ - public void setContent(String content) { - this.content = content; - } - - /** - * Return the content String to stream back to the client. - * - * @return the content String to stream back to the client - */ - public String getContent() { - return content; - } - - /** - * Set the byte array to stream back to the client. - * - * @param bytes the byte array to stream back to the client - */ - public void setBytes(byte[] bytes, String contentType) { - this.bytes = bytes; - this.contentType = contentType; - } - - /** - * Return the byte array to stream back to the client. - * - * @return the byte array to stream back to the client - */ - public byte[] getBytes() { - return bytes; - } - - /** - * Set the content to stream back to the client. - * - * @param inputStream the inputStream to stream back to the client - */ - public void setInputStream(InputStream inputStream) { - this.inputStream = inputStream; - } - - /** - * Return the inputStream to stream back to the client. - * - * @return the inputStream to stream back to the client - */ - public InputStream getInputStream() { - return inputStream; - } - - /** - * Set the reader which characters are streamed back to the client. - * - * @param reader the reader which characters are streamed back to the client. - */ - public void setReader(Reader reader) { - this.reader = reader; - } - - /** - * Return the reader which characters are streamed back to the client. - * - * @return the reader which characters are streamed back to the client. - */ - public Reader getReader() { - return reader; - } - - /** - * Return the data model for the ActionResult {@link #template}. - * - * @return the data model for the ActionResult template - */ - public Map getModel() { - if (model == null) { - model = new HashMap(); - } - return model; - } - - /** - * Set the model of the ActionResult template to render. - *

- * If the {@link #template} property is set, the template and {@link #model} - * will be merged and the result will be streamed back to the client. - * - * @param model the model of the template to render - */ - public void setModel(Map model) { - this.model = model; - } - - /** - * Return the template to render for this ActionResult. - * - * @return the template to render for this ActionResult - */ - public String getTemplate() { - return template; - } - - /** - * Set the template to render for this ActionResult. - * - * @param template the template to render for this ActionResult - */ - public void setTemplate(String template) { - this.template = template; - } - - /** - * Render the ActionResult to the client. - * - * @param context the request context - */ - public final void render(Context context) { - prepare(context); - renderActionResult(context); - } - - // Protected Methods ------------------------------------------------------ - - /** - * Render the ActionResult to the client. This method can be overridden - * by subclasses if custom rendering or direct access to the - * HttpServletResponse is required. - * - * @param context the request context - */ - protected void renderActionResult(Context context) { - - HttpServletResponse response = context.getResponse(); - - Reader localReader = getReader(); - InputStream localInputStream = getInputStream(); - - try { - String localContent = getContent(); - byte[] localBytes = getBytes(); - - String localTemplate = getTemplate(); - if (localTemplate != null) { - Map templateModel = getModel(); - if (templateModel == null) { - templateModel = new HashMap(); - } - String result = context.renderTemplate(localTemplate, templateModel); - localReader = new StringReader(result); - - } else if (localContent != null) { - localReader = new StringReader(localContent); - } else if (localBytes != null) { - localInputStream = new ByteArrayInputStream(localBytes); - } - - if (localReader != null) { - Writer writer = response.getWriter(); - char[] buffer = new char[WRITER_BUFFER_SIZE]; - int len = 0; - while (-1 != (len = localReader.read(buffer))) { - writer.write(buffer, 0, len); - } - writer.flush(); - writer.close(); - - } else if (localInputStream != null) { - byte[] buffer = new byte[OUTPUT_BUFFER_SIZE]; - int len = 0; - OutputStream outputStream = response.getOutputStream(); - while (-1 != (len = localInputStream.read(buffer))) { - outputStream.write(buffer, 0, len); - } - outputStream.flush(); - outputStream.close(); - } - - } catch (Exception e) { - throw new RuntimeException(e); - - } finally { - ClickUtils.close(localInputStream); - ClickUtils.close(localReader); - } - } - - // Private Methods -------------------------------------------------------- - - /** - * Prepare the ActionResult for rendering. - * - * @param context the request context - */ - private void prepare(Context context) { - HttpServletResponse response = context.getResponse(); - - if (!isCacheActionRestul()) { - // Set headers to disable cache - response.setHeader("Pragma", "no-cache"); - response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0"); - response.setDateHeader("Expires", new Date(1L).getTime()); - } - - String localContentType = getContentType(); - - if (getCharacterEncoding() == null) { - - // Fallback to request character encoding - if (context.getRequest().getCharacterEncoding() != null) { - response.setContentType(localContentType + "; charset=" - + context.getRequest().getCharacterEncoding()); - } else { - response.setContentType(localContentType); - } - - } else { - response.setContentType(localContentType + "; charset=" + getCharacterEncoding()); - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/Behavior.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/Behavior.java deleted file mode 100644 index 58ac8527e0..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/Behavior.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -/** - * Behaviors provide a mechanism for changing how Controls behave at runtime. - * Behaviors are added to a Control and provides interceptor methods to decorate - * and enhance the source Control. - *

- * Behaviors provide interceptor methods for specific Control life cycle events. - * These interceptor methods can be implemented to further process and decorate - * the control or its children. - *

- * The following interceptor methods are defined: - * - *

    - *
  • preResponse - occurs before the control markup is written to the response
  • - *
  • preRenderHeadElements - occurs after preResponse but before the control - * {@link Control#getHeadElements() HEAD elements} are written to the response
  • - *
  • preDestroy - occurs before the Control {@link Control#onDestroy() onDestroy} - * event handler.
  • - *
- * - * These interceptor methods allow the Behavior to decorate a control, - * for example: - * - *
    - *
  • add or remove Control HEAD elements such as JavaScript and CSS dependencies - * and setup scripts
  • - *
  • add or remove Control attributes such as "class", "style" etc.
  • - *
- */ -public interface Behavior { - - /** - * This event occurs before the markup is written to the HttpServletResponse. - * - * @param source the control the behavior is registered with - */ - public void preResponse(Control source); - - /** - * This event occurs after {@link #preResponse(Control)}, - * but before the Control's {@link Control#getHeadElements()} is called. - * - * @param source the control the behavior is registered with - */ - public void preRenderHeadElements(Control source); - - /** - * This event occurs before the Control {@link Control#onDestroy() onDestroy} - * event handler. This event allows the behavior to cleanup or store Control - * state in the Session. - * - * @param source the control the behavior is registered with - */ - public void preDestroy(Control source); -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/ClickRequestWrapper.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/ClickRequestWrapper.java deleted file mode 100644 index 482c832c77..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/ClickRequestWrapper.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletRequestWrapper; - -import org.openidentityplatform.openam.click.service.FileUploadService; -import org.openidentityplatform.openam.click.util.ClickUtils; - -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.FileUploadException; - -/** - * Provides a custom HttpServletRequest class for shielding users from - * multipart request parameters. Thus calling request.getParameter(String) - * will still work properly. - */ -class ClickRequestWrapper extends HttpServletRequestWrapper { - - /** - * The FileItem objects for "multipart" POST requests. - */ - private final Map fileItemMap; - - /** The request is a multi-part file upload POST request. */ - private final boolean isMultipartRequest; - - /** The map of "multipart" request parameter values. */ - private final Map multipartParameterMap; - - /** The wrapped servlet request. */ - private final HttpServletRequest request; - - // Constructors ----------------------------------------------------------- - - /** - * @see HttpServletRequestWrapper(HttpServletRequest) - */ - ClickRequestWrapper(final HttpServletRequest request, - final FileUploadService fileUploadService) { - super(request); - - this.isMultipartRequest = ClickUtils.isMultipartRequest(request); - this.request = request; - - if (isMultipartRequest) { - - Map requestParams = new HashMap(); - Map fileItems = new HashMap(); - - try { - List itemsList = new ArrayList(); - - try { - - itemsList = fileUploadService.parseRequest(request); - - } catch (FileUploadException fue) { - request.setAttribute(FileUploadService.UPLOAD_EXCEPTION, fue); - } - - for (FileItem fileItem : itemsList) { - String name = fileItem.getFieldName(); - String value = null; - - // Form fields are placed in the request parameter map, - // while file uploads are placed in the file item map. - if (fileItem.isFormField()) { - - if (request.getCharacterEncoding() == null) { - value = fileItem.getString(); - - } else { - try { - value = fileItem.getString(request.getCharacterEncoding()); - - } catch (UnsupportedEncodingException ex) { - throw new RuntimeException(ex); - } - } - - // Add the form field value to the parameters. - addToMapAsString(requestParams, name, value); - - } else { - // Add the file item to the list of file items. - addToMapAsFileItem(fileItems, name, fileItem); - } - } - - } catch (Throwable t) { - - // Don't throw error here as it will break Context creation. - // Instead add the error as a request attribute. - request.setAttribute(Context.CONTEXT_FATAL_ERROR, t); - - } finally { - fileItemMap = Collections.unmodifiableMap(fileItems); - multipartParameterMap = Collections.unmodifiableMap(requestParams); - } - - } else { - fileItemMap = Collections.emptyMap(); - multipartParameterMap = Collections.emptyMap(); - } - } - - // Public Methods --------------------------------------------------------- - - /** - * Returns a map of FileItem arrays keyed on request parameter - * name for "multipart" POST requests (file uploads). Thus each map entry - * will consist of one or more FileItem objects. - * - * @return map of FileItem arrays keyed on request parameter name - * for "multipart" POST requests - */ - public Map getFileItemMap() { - return fileItemMap; - } - - /** - * @see jakarta.servlet.ServletRequest#getParameter(String) - */ - @Override - public String getParameter(String name) { - if (isMultipartRequest) { - Object value = getMultipartParameterMap().get(name); - - if (value instanceof String) { - return (String) value; - } - - if (value instanceof String[]) { - String[] array = (String[]) value; - if (array.length >= 1) { - return array[0]; - } else { - return null; - } - } - - return (value == null ? null : value.toString()); - - } else { - return request.getParameter(name); - } - } - - /** - * @see jakarta.servlet.ServletRequest#getParameterNames() - */ - @Override - @SuppressWarnings("unchecked") - public Enumeration getParameterNames() { - if (isMultipartRequest) { - return Collections.enumeration(getMultipartParameterMap().keySet()); - - } else { - return request.getParameterNames(); - } - } - - /** - * @see jakarta.servlet.ServletRequest#getParameterValues(String) - */ - @Override - public String[] getParameterValues(String name) { - if (isMultipartRequest) { - Object values = getMultipartParameterMap().get(name); - if (values instanceof String) { - return new String[] { values.toString() }; - } - if (values instanceof String[]) { - return (String[]) values; - } else { - return null; - } - - } else { - return request.getParameterValues(name); - } - } - - /** - * @see jakarta.servlet.ServletRequest#getParameterMap() - */ - @Override - @SuppressWarnings("unchecked") - public Map getParameterMap() { - if (isMultipartRequest) { - return getMultipartParameterMap(); - } else { - return request.getParameterMap(); - } - } - - // Package Private Methods ------------------------------------------------ - - /** - * Return the map of "multipart" request parameter map. - * - * @return the "multipart" request parameter map - */ - @SuppressWarnings("unchecked") - Map getMultipartParameterMap() { - if (request.getAttribute(ClickServlet.MOCK_MODE_ENABLED) == null) { - return multipartParameterMap; - } else { - // In mock mode return the request parameter map. This ensures - // calling request.setParameter(x,y) works for both normal and - // multipart requests. - return request.getParameterMap(); - } - } - - // Private Methods -------------------------------------------------------- - - /** - * Stores the specified value in a FileItem array in the map, under the - * specified name. Thus two values stored under the same name will be - * stored in the same array. - * - * @param map the map to add the specified name and value to - * @param name the name of the map key - * @param value the value to add to the FileItem array - */ - private void addToMapAsFileItem(Map map, String name, FileItem value) { - FileItem[] oldValues = map.get(name); - FileItem[] newValues = null; - if (oldValues == null) { - newValues = new FileItem[] {value}; - } else { - newValues = new FileItem[oldValues.length + 1]; - System.arraycopy(oldValues, 0, newValues, 0, oldValues.length); - newValues[oldValues.length] = value; - } - map.put(name, newValues); - } - - /** - * Stores the specified value in an String array in the map, under the - * specified name. Thus two values stored under the same name will be - * stored in the same array. - * - * @param map the map to add the specified name and value to - * @param name the name of the map key - * @param value the value to add to the string array - */ - private void addToMapAsString(Map map, String name, String value) { - String[] oldValues = map.get(name); - String[] newValues = null; - if (oldValues == null) { - newValues = new String[] {value}; - } else { - newValues = new String[oldValues.length + 1]; - System.arraycopy(oldValues, 0, newValues, 0, oldValues.length); - newValues[oldValues.length] = value; - } - map.put(name, newValues); - } - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/ClickServlet.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/ClickServlet.java deleted file mode 100644 index e8ff6b2fe8..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/ClickServlet.java +++ /dev/null @@ -1,2305 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.Writer; -import java.lang.reflect.Field; -import java.util.Collections; -import java.util.Date; -import java.util.Enumeration; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import jakarta.servlet.RequestDispatcher; -import jakarta.servlet.ServletContext; -import jakarta.servlet.ServletException; -import jakarta.servlet.UnavailableException; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; - -import ognl.DefaultMemberAccess; -import ognl.MemberAccess; -import ognl.Ognl; -import ognl.OgnlException; -import ognl.TypeConverter; - -import org.openidentityplatform.openam.click.service.ConfigService; -import org.openidentityplatform.openam.click.service.LogService; -import org.openidentityplatform.openam.click.service.ResourceService; -import org.apache.click.service.TemplateException; -import org.openidentityplatform.openam.click.service.XmlConfigService; -import org.openidentityplatform.openam.click.service.ConfigService.AutoBinding; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.ErrorPage; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.openidentityplatform.openam.click.util.PageImports; -import org.apache.click.util.PropertyUtils; -import org.apache.click.util.RequestTypeConverter; -import org.apache.commons.lang.ClassUtils; -import org.apache.commons.lang.StringUtils; - -/** - * Provides the Click application HttpServlet. - *

- * Generally developers will simply configure the ClickServlet and - * will not use it directly in their code. For a Click web application to - * function the ClickServlet must be configured in the web - * application's /WEB-INF/web.xml file. A simple web application which - * maps all *.htm requests to a ClickServlet is provided below. - * - *

- * <web-app>
- *    <servlet>
- *       <servlet-name>click-servlet</servlet-name>
- *       <servlet-class>org.apache.click.ClickServlet</servlet-class>
- *       <load-on-startup>0</load-on-startup>
- *    </servlet>
- *    <servlet-mapping>
- *       <servlet-name>click-servlet</servlet-name>
- *       <url-pattern>*.htm</url-pattern>
- *    </servlet-mapping>
- * </web-app> 
- * - * By default the ClickServlet will attempt to load an application - * configuration file using the path:   /WEB-INF/click.xml - * - *

Servlet Mapping

- * By convention all Click page templates should have a .htm extension, and - * the ClickServlet should be mapped to process all *.htm URL requests. With - * this convention you have all the static HTML pages use a .html extension - * and they will not be processed as Click pages. - * - *

Load On Startup

- * Note you should always set load-on-startup element to be 0 so the - * servlet is initialized when the server is started. This will prevent any - * delay for the first client which uses the application. - *

- * The ClickServlet performs as much work as possible at startup to - * improve performance later on. The Click start up and caching strategy is - * configured with the Click application mode in the "click.xml" file. - * See the User Guide for information on how to configure the application mode. - * - *

ConfigService

- * - * A single application {@link ConfigService} instance is created by the ClickServlet at - * startup. Once the ConfigService has been initialized it is stored in the - * ServletContext using the key {@value org.apache.click.service.ConfigService#CONTEXT_NAME}. - */ -public class ClickServlet extends HttpServlet { - - // -------------------------------------------------------------- Constants - - private static final long serialVersionUID = 1L; - - /** - * The mock page reference request attribute: key:   - * mock_page_reference. - *

- * This attribute stores the each Page instance as a request attribute. - *

- * Note: a page is only stored as a request attribute - * if the {@link #MOCK_MODE_ENABLED} attribute is set. - */ - static final String MOCK_PAGE_REFERENCE = "mock_page_reference"; - - /** - * The mock mode request attribute: key:   - * mock_mode_enabled. - *

- * If this attribute is set (the value does not matter) certain features - * will be enabled which is needed for running Click in a mock environment. - */ - static final String MOCK_MODE_ENABLED = "mock_mode_enabled"; - - /** - * The click application configuration service classname init parameter name: - *   "config-service-class". - */ - protected final static String CONFIG_SERVICE_CLASS = "config-service-class"; - - /** - * The custom TypeConverter classname as an init parameter name: - * &nbps; "type-converter-class". - */ - protected final static String TYPE_CONVERTER_CLASS = "type-converter-class"; - - /** - * The forwarded request marker attribute:   "click-forward". - */ - protected final static String CLICK_FORWARD = "click-forward"; - - /** - * The Page to forward to request attribute:   "click-page". - */ - protected final static String FORWARD_PAGE = "forward-page"; - - // ----------------------------------------------------- Instance Variables - - /** The click application configuration service. */ - protected ConfigService configService; - - /** The application log service. */ - protected LogService logger; - - /** The OGNL member access handler. */ - protected MemberAccess memberAccess; - - /** The application resource service. */ - protected ResourceService resourceService; - - /** The request parameters OGNL type converter. */ - protected TypeConverter typeConverter; - - /** The thread local page listeners. */ - private static final ThreadLocal> - THREAD_LOCAL_INTERCEPTORS = new ThreadLocal<>(); - - // --------------------------------------------------------- Public Methods - - /** - * Initialize the Click servlet and the Velocity runtime. - * - * @see jakarta.servlet.GenericServlet#init() - * - * @throws ServletException if the application configuration service could - * not be initialized - */ - @Override - public void init() throws ServletException { - - try { - - // Create and initialize the application config service - configService = createConfigService(getServletContext()); - initConfigService(getServletContext()); - logger = configService.getLogService(); - - if (logger.isInfoEnabled()) { - logger.info("Click " + ClickUtils.getClickVersion() - + " initialized in " + configService.getApplicationMode() - + " mode"); - } - - resourceService = configService.getResourceService(); - - } catch (Throwable e) { - // In mock mode this exception can occur if click.xml is not - // available. - if (getServletContext().getAttribute(MOCK_MODE_ENABLED) != null) { - return; - } - - e.printStackTrace(); - - String msg = "error while initializing Click servlet; throwing " - + "jakarta.servlet.UnavailableException"; - - log(msg, e); - - throw new UnavailableException(e.toString()); - } - } - - /** - * @see jakarta.servlet.GenericServlet#destroy() - */ - @Override - public void destroy() { - - try { - - // Destroy the application config service - destroyConfigService(getServletContext()); - - } catch (Throwable e) { - // In mock mode this exception can occur if click.xml is not - // available. - if (getServletContext().getAttribute(MOCK_MODE_ENABLED) != null) { - return; - } - - e.printStackTrace(); - - String msg = "error while destroying Click servlet, throwing " - + "jakarta.servlet.UnavailableException"; - - log(msg, e); - - } finally { - // Dereference the application config service - configService = null; - } - - super.destroy(); - } - - // ------------------------------------------------------ Protected Methods - - /** - * Handle HTTP GET requests. This method will delegate the request to - * {@link #handleRequest(HttpServletRequest, HttpServletResponse, boolean)}. - * - * @see HttpServlet#doGet(HttpServletRequest, HttpServletResponse) - * - * @param request the servlet request - * @param response the servlet response - * @throws ServletException if click app has not been initialized - * @throws IOException if an I/O error occurs - */ - @Override - protected void doGet(HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException { - - handleRequest(request, response, false); - } - - /** - * Handle HTTP POST requests. This method will delegate the request to - * {@link #handleRequest(HttpServletRequest, HttpServletResponse, boolean)}. - * - * @see HttpServlet#doPost(HttpServletRequest, HttpServletResponse) - * - * @param request the servlet request - * @param response the servlet response - * @throws ServletException if click app has not been initialized - * @throws IOException if an I/O error occurs - */ - @Override - protected void doPost(HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException { - - handleRequest(request, response, true); - } - - /** - * Handle the given servlet request and render the results to the - * servlet response. - *

- * If an exception occurs within this method the exception will be delegated - * to: - *

- * {@link #handleException(HttpServletRequest, HttpServletResponse, boolean, Throwable, Class)} - * - * @param request the servlet request to process - * @param response the servlet response to render the results to - * @param isPost determines whether the request is a POST - * @throws IOException if resource request could not be served - */ - protected void handleRequest(HttpServletRequest request, - HttpServletResponse response, boolean isPost) throws IOException { - - // Handle requests for click resources, i.e. CSS, JS and image files - if (resourceService.isResourceRequest(request)) { - resourceService.renderResource(request, response); - return; - } - - long startTime = System.currentTimeMillis(); - - if (logger.isDebugEnabled()) { - HtmlStringBuffer buffer = new HtmlStringBuffer(200); - buffer.append(request.getMethod()); - buffer.append(" "); - buffer.append(request.getRequestURL()); - logger.debug(buffer); - } - - // Handle click page requests - Page page = null; - try { - - ActionEventDispatcher eventDispatcher = createActionEventDispatcher(); - // Bind ActionEventDispatcher to current thread - ActionEventDispatcher.pushThreadLocalDispatcher(eventDispatcher); - - ControlRegistry controlRegistry = createControlRegistry(); - // Bind ControlRegistry to current thread - ControlRegistry.pushThreadLocalRegistry(controlRegistry); - - Context context = createContext(request, response, isPost); - // Bind context to current thread - Context.pushThreadLocalContext(context); - - // Check for fatal error that occurred while creating Context - Throwable error = (Throwable) request.getAttribute(Context.CONTEXT_FATAL_ERROR); - - if (error != null) { - // Process exception through Click's exception handler. - if (error instanceof Exception) { - throw (Exception) error; - } - // Errors are not handled by Click, let the server handle it - if (error instanceof Error) { - throw (Error) error; - } else { - // Throwables are not handled by Click, let the server handle it - throw new RuntimeException(error); - } - } - - page = createPage(context); - - // If no page created, then an PageInterceptor has aborted processing - if (page == null) { - return; - } - - if (page.isStateful()) { - synchronized (page) { - processPage(page); - processPageOnDestroy(page, startTime); - // Mark page as already destroyed for finally block - page = null; - } - - } else { - processPage(page); - } - - } catch (Exception e) { - Class pageClass = - configService.getPageClass(ClickUtils.getResourcePath(request)); - - handleException(request, response, isPost, e, pageClass); - - } catch (ExceptionInInitializerError eiie) { - Throwable cause = eiie.getException(); - cause = (cause != null) ? cause : eiie; - - Class pageClass = - configService.getPageClass(ClickUtils.getResourcePath(request)); - - handleException(request, response, isPost, cause, pageClass); - - } finally { - - try { - if (page != null) { - if (page.isStateful()) { - synchronized (page) { - processPageOnDestroy(page, startTime); - } - - } else { - processPageOnDestroy(page, startTime); - } - } - - for (PageInterceptor interceptor : getThreadLocalInterceptors()) { - interceptor.postDestroy(page); - } - - setThreadLocalInterceptors(null); - - } finally { - // Only clear the context when running in normal mode. - if (request.getAttribute(MOCK_MODE_ENABLED) == null) { - Context.popThreadLocalContext(); - } - ControlRegistry.popThreadLocalRegistry(); - ActionEventDispatcher.popThreadLocalDispatcher(); - } - } - } - - /** - * Provides the application exception handler. The application exception - * will be delegated to the configured error page. The default error page is - * {@link ErrorPage} and the page template is "click/error.htm"

- * Applications which wish to provide their own customized error handling - * must subclass ErrorPage and specify their page in the - * "/WEB-INF/click.xml" application configuration file. For example: - * - *

-     *  <page path="click/error.htm" classname="com.mycorp.util.ErrorPage"/>
-     * 
- * - * If the ErrorPage throws an exception, it will be logged as an error and - * then be rethrown nested inside a RuntimeException. - * - * @param request the servlet request with the associated error - * @param response the servlet response - * @param isPost boolean flag denoting the request method is "POST" - * @param exception the error causing exception - * @param pageClass the page class with the error - */ - protected void handleException(HttpServletRequest request, - HttpServletResponse response, boolean isPost, Throwable exception, - Class pageClass) { - - if (isAjaxRequest(request)) { - handleAjaxException(request, response, isPost, exception, pageClass); - // Exit after handling ajax exception - return; - } - - if (exception instanceof TemplateException) { - TemplateException te = (TemplateException) exception; - if (!te.isParseError()) { - logger.error("handleException: ", exception); - } - - } else { - logger.error("handleException: ", exception); - } - - ErrorPage finalizeRef = null; - try { - final ErrorPage errorPage = createErrorPage(pageClass, exception); - - finalizeRef = errorPage; - - errorPage.setError(exception); - if (errorPage.getFormat() == null) { - errorPage.setFormat(configService.createFormat()); - } - errorPage.setHeaders(configService.getPageHeaders(ConfigService.ERROR_PATH)); - errorPage.setMode(configService.getApplicationMode()); - errorPage.setPageClass(pageClass); - errorPage.setPath(ConfigService.ERROR_PATH); - - processPageFields(errorPage, new ClickServlet.FieldCallback() { - public void processField(String fieldName, Object fieldValue) { - if (fieldValue instanceof Control) { - Control control = (Control) fieldValue; - if (control.getName() == null) { - control.setName(fieldName); - } - - if (!errorPage.getModel().containsKey(control.getName())) { - errorPage.addControl(control); - } - } - } - }); - - if (errorPage.isStateful()) { - synchronized (errorPage) { - processPage(errorPage); - processPageOnDestroy(errorPage, 0); - // Mark page as already destroyed for finally block - finalizeRef = null; - } - - } else { - processPage(errorPage); - } - - } catch (Exception ex) { - String message = - "handleError: " + ex.getClass().getName() - + " thrown while handling " + exception.getClass().getName() - + ". Now throwing RuntimeException."; - - logger.error(message, ex); - - throw new RuntimeException(ex); - - } finally { - if (finalizeRef != null) { - if (finalizeRef.isStateful()) { - synchronized (finalizeRef) { - processPageOnDestroy(finalizeRef, 0); - } - - } else { - processPageOnDestroy(finalizeRef, 0); - } - } - } - } - - /** - * Process the given page invoking its "on" event callback methods - * and directing the response. - *

- * This method does not invoke the "onDestroy()" callback method. - * - * @see #processPageEvents(Page, Context) - * - * @param page the Page to process - * @throws Exception if an error occurs - */ - @SuppressWarnings("deprecation") - protected void processPage(Page page) throws Exception { - - final Context context = page.getContext(); - - PageImports pageImports = createPageImports(page); - page.setPageImports(pageImports); - - if (context.isAjaxRequest()) { - processAjaxPageEvents(page, context); - } else { - processPageEvents(page, context); - } - } - - /** - * Process the given page events, invoking the "on" event callback methods - * and directing the response. - *

- * This method does not invoke the "onDestroy()" callback method. - * - * @param page the Page which events to process - * @param context the request context - * @throws Exception if an error occurs - */ - protected void processPageEvents(Page page, Context context) throws Exception { - - ActionEventDispatcher eventDispatcher = ActionEventDispatcher.getThreadLocalDispatcher(); - ControlRegistry controlRegistry = ControlRegistry.getThreadLocalRegistry(); - - boolean errorOccurred = page instanceof ErrorPage; - // Support direct access of click-error.htm - if (errorOccurred) { - ErrorPage errorPage = (ErrorPage) page; - errorPage.setMode(configService.getApplicationMode()); - - // Notify the eventDispatcher and controlRegistry of the error - eventDispatcher.errorOccurred(errorPage.getError()); - controlRegistry.errorOccurred(errorPage.getError()); - } - - boolean continueProcessing = performOnSecurityCheck(page, context); - - ActionResult actionResult = null; - if (continueProcessing && !errorOccurred) { - // Handle page method - String pageAction = context.getRequestParameter(Page.PAGE_ACTION); - if (pageAction != null) { - // Returned actionResult could be null - actionResult = performPageAction(page, pageAction, context); - continueProcessing = false; - } - } - - if (continueProcessing) { - performOnInit(page, context); - - continueProcessing = performOnProcess(page, context, eventDispatcher); - - if (continueProcessing) { - performOnPostOrGet(page, context, context.isPost()); - - performOnRender(page, context); - } - } - - controlRegistry.processPreResponse(context); - controlRegistry.processPreRenderHeadElements(context); - performRender(page, context, actionResult); - } - - /** - * Perform the onSecurityCheck event callback for the specified page, - * returning true if processing should continue, false otherwise. - * - * @param page the page to perform the security check on - * @param context the request context - * @return true if processing should continue, false otherwise - */ - protected boolean performOnSecurityCheck(Page page, Context context) { - boolean continueProcessing = page.onSecurityCheck(); - - if (logger.isTraceEnabled()) { - logger.trace(" invoked: " - + ClassUtils.getShortClassName(page.getClass()) - + ".onSecurityCheck() : " + continueProcessing); - } - return continueProcessing; - } - - /** - * Perform the page action for the given page and return the action result. - * - * @param page the page which action to perform - * @param pageAction the name of the page action - * @param context the request context - * @return the page action ActionResult instance - */ - protected ActionResult performPageAction(Page page, String pageAction, Context context) { - ActionResult actionResult = ClickUtils.invokeAction(page, pageAction); - - if (logger.isTraceEnabled()) { - HtmlStringBuffer buffer = new HtmlStringBuffer(); - String pageClassName = ClassUtils.getShortClassName(page.getClass()); - buffer.append(" invoked: "); - buffer.append(pageClassName); - buffer.append(".").append(pageAction).append("() : "); - if (actionResult == null) { - buffer.append("null (*no* ActionResult was returned by PageAction)"); - } else { - buffer.append(ClassUtils.getShortClassName(actionResult.getClass())); - } - logger.trace(buffer.toString()); - } - return actionResult; - } - - /** - * Perform the onInit event callback for the specified page. - * - * @param page the page to initialize - * @param context the request context - */ - protected void performOnInit(Page page, Context context) { - page.onInit(); - - if (logger.isTraceEnabled()) { - logger.trace(" invoked: " - + ClassUtils.getShortClassName(page.getClass()) + ".onInit()"); - } - - if (page.hasControls()) { - List controls = page.getControls(); - - for (int i = 0, size = controls.size(); i < size; i++) { - Control control = controls.get(i); - control.onInit(); - - if (logger.isTraceEnabled()) { - String controlClassName = control.getClass().getName(); - controlClassName = controlClassName.substring( - controlClassName.lastIndexOf('.') + 1); - String msg = " invoked: '" + control.getName() + "' " - + controlClassName + ".onInit()"; - logger.trace(msg); - } - } - } - } - - /** - * Perform onProcess event callback for the specified page, returning true - * if processing should continue, false otherwise. - * - * @param page the page to process - * @param context the request context - * @param eventDispatcher the action event dispatcher - * @return true if processing should continue, false otherwise - */ - protected boolean performOnProcess(Page page, Context context, - ActionEventDispatcher eventDispatcher) { - - boolean continueProcessing = true; - - // Make sure don't process a forwarded request - if (page.hasControls() && !context.isForward()) { - List controls = page.getControls(); - - for (int i = 0, size = controls.size(); i < size; i++) { - Control control = controls.get(i); - - int initialListenerCount = 0; - if (logger.isTraceEnabled()) { - initialListenerCount = eventDispatcher.getEventSourceList().size(); - } - - boolean onProcessResult = control.onProcess(); - if (!onProcessResult) { - continueProcessing = false; - } - - if (logger.isTraceEnabled()) { - String controlClassName = ClassUtils.getShortClassName(control.getClass()); - - String msg = " invoked: '" + control.getName() + "' " - + controlClassName + ".onProcess() : " + onProcessResult; - logger.trace(msg); - - if (initialListenerCount != eventDispatcher.getEventSourceList().size()) { - logger.trace(" listener was registered while processing control"); - } - } - } - - if (continueProcessing) { - // Fire registered action events - continueProcessing = eventDispatcher.fireActionEvents(context); - - if (logger.isTraceEnabled()) { - String msg = " invoked: Control listeners : " - + continueProcessing; - logger.trace(msg); - } - } - } - - return continueProcessing; - } - - /** - * Perform onPost or onGet event callback for the specified page. - * - * @param page the page for which the onGet or onPost is performed - * @param context the request context - * @param isPost specifies whether the request is a post or a get - */ - protected void performOnPostOrGet(Page page, Context context, boolean isPost) { - if (isPost) { - page.onPost(); - - if (logger.isTraceEnabled()) { - logger.trace(" invoked: " - + ClassUtils.getShortClassName(page.getClass()) + ".onPost()"); - } - - } else { - page.onGet(); - - if (logger.isTraceEnabled()) { - logger.trace(" invoked: " - + ClassUtils.getShortClassName(page.getClass()) + ".onGet()"); - } - } - } - - /** - * Perform onRender event callback for the specified page. - * - * @param page page to render - * @param context the request context - */ - protected void performOnRender(Page page, Context context) { - page.onRender(); - - if (logger.isTraceEnabled()) { - logger.trace(" invoked: " - + ClassUtils.getShortClassName(page.getClass()) + ".onRender()"); - } - - if (page.hasControls()) { - List controls = page.getControls(); - - for (int i = 0, size = controls.size(); i < size; i++) { - Control control = controls.get(i); - control.onRender(); - - if (logger.isTraceEnabled()) { - String controlClassName = control.getClass().getName(); - controlClassName = controlClassName.substring(controlClassName. - lastIndexOf('.') + 1); - String msg = " invoked: '" + control.getName() + "' " - + controlClassName + ".onRender()"; - logger.trace(msg); - } - } - } - } - - /** - * Performs rendering of the specified page. - * - * @param page page to render - * @param context the request context - * @throws java.lang.Exception if error occurs - */ - protected void performRender(Page page, Context context) throws Exception { - performRender(page, context, null); - } - - /** - * Performs rendering of the specified page. - * - * @param page page to render - * @param context the request context - * @param actionResult the action result - * @throws java.lang.Exception if error occurs - */ - protected void performRender(Page page, Context context, ActionResult actionResult) - throws Exception { - - // Process page interceptors, and abort rendering if specified - for (PageInterceptor interceptor : getThreadLocalInterceptors()) { - if (!interceptor.preResponse(page)) { - return; - } - } - - final HttpServletRequest request = context.getRequest(); - final HttpServletResponse response = context.getResponse(); - - if (StringUtils.isNotBlank(page.getRedirect())) { - String url = page.getRedirect(); - - url = response.encodeRedirectURL(url); - - if (logger.isTraceEnabled()) { - logger.debug(" redirect: " + url); - - } else if (logger.isDebugEnabled()) { - logger.debug("redirect: " + url); - } - - response.sendRedirect(url); - - } else if (StringUtils.isNotBlank(page.getForward())) { - // Indicates the request is forwarded - request.setAttribute(CLICK_FORWARD, CLICK_FORWARD); - - if (logger.isTraceEnabled()) { - logger.debug(" forward: " + page.getForward()); - - } else if (logger.isDebugEnabled()) { - logger.debug("forward: " + page.getForward()); - } - - if (page.getForward().endsWith(".jsp")) { - renderJSP(page); - - } else { - RequestDispatcher dispatcher = - request.getRequestDispatcher(page.getForward()); - - dispatcher.forward(request, response); - } - - } else if (actionResult != null) { - renderActionResult(actionResult, page, context); - - } else if (page.getPath() != null) { - // Render template unless the request was a page action. This check - // guards against the scenario where the page action returns null - // instead of a action result - if (context.getRequestParameter(Page.PAGE_ACTION) == null) { - String pagePath = page.getPath(); - - // Check if request is a JSP page - if (pagePath.endsWith(".jsp") || configService.isJspPage(pagePath)) { - // CLK-141. Set pagePath as the forward value. - page.setForward(StringUtils.replace(pagePath, ".htm", ".jsp")); - - // Indicates the request is forwarded - request.setAttribute(CLICK_FORWARD, CLICK_FORWARD); - renderJSP(page); - - } else { - renderTemplate(page); - } - } - - } else { - if (logger.isTraceEnabled()) { - logger.debug(" path not defined for " + page.getClass().getName()); - - } else if (logger.isDebugEnabled()) { - logger.debug("path not defined for " + page.getClass().getName()); - } - } - } - - /** - * Render the Velocity template defined by the page's path. - *

- * This method creates a Velocity Context using the Page's model Map and - * then merges the template with the Context writing the result to the - * HTTP servlet response. - *

- * This method was adapted from org.apache.velocity.servlet.VelocityServlet. - * - * @param page the page template to merge - * @throws Exception if an error occurs - */ - protected void renderTemplate(Page page) throws Exception { - - long startTime = System.currentTimeMillis(); - - final Map model = createTemplateModel(page); - - Context context = page.getContext(); - HttpServletResponse response = context.getResponse(); - - response.setContentType(page.getContentType()); - - Writer writer = getWriter(response); - - if (page.hasHeaders()) { - setPageResponseHeaders(response, page.getHeaders()); - } - - configService.getTemplateService().renderTemplate(page, model, writer); - - if (!configService.isProductionMode()) { - HtmlStringBuffer buffer = new HtmlStringBuffer(50); - if (logger.isTraceEnabled()) { - buffer.append(" "); - } - buffer.append("renderTemplate: "); - if (!page.getTemplate().equals(page.getPath())) { - buffer.append(page.getPath()); - buffer.append(","); - } - buffer.append(page.getTemplate()); - buffer.append(" - "); - buffer.append(System.currentTimeMillis() - startTime); - buffer.append(" ms"); - logger.info(buffer); - } - } - - /** - * Render the given page as a JSP to the response. - * - * @param page the page to render - * @throws Exception if an error occurs rendering the JSP - */ - protected void renderJSP(Page page) throws Exception { - - long startTime = System.currentTimeMillis(); - - HttpServletRequest request = page.getContext().getRequest(); - - HttpServletResponse response = page.getContext().getResponse(); - - setRequestAttributes(page); - - RequestDispatcher dispatcher = null; - - String forward = page.getForward(); - - // As "getTemplate" returns the page.getPath() by default, which is *.htm - // we need to change to *.jsp in order to compare to the page.getForward() - String jspTemplate = StringUtils.replace(page.getTemplate(), ".htm", ".jsp"); - - if (forward.equals(jspTemplate)) { - dispatcher = request.getRequestDispatcher(forward); - - } else { - dispatcher = request.getRequestDispatcher(page.getTemplate()); - } - - dispatcher.forward(request, response); - - if (!configService.isProductionMode()) { - HtmlStringBuffer buffer = new HtmlStringBuffer(50); - buffer.append("renderJSP: "); - if (!page.getTemplate().equals(page.getForward())) { - buffer.append(page.getTemplate()); - buffer.append(","); - } - buffer.append(page.getForward()); - buffer.append(" - "); - buffer.append(System.currentTimeMillis() - startTime); - buffer.append(" ms"); - logger.info(buffer); - } - } - - /** - * Render the given ActionResult. If the action result is null, nothing is - * rendered. - * - * @param actionResult the action result to render - * @param page the requested page - * @param context the request context - */ - protected void renderActionResult(ActionResult actionResult, Page page, Context context) { - if (actionResult == null) { - return; - } - - long startTime = System.currentTimeMillis(); - - actionResult.render(context); - - if (!configService.isProductionMode()) { - HtmlStringBuffer buffer = new HtmlStringBuffer(50); - if (logger.isTraceEnabled()) { - buffer.append(" "); - } - - buffer.append("renderActionResult ("); - buffer.append(actionResult.getContentType()); - buffer.append(")"); - String template = actionResult.getTemplate(); - if (template != null) { - buffer.append(": "); - buffer.append(template); - } - buffer.append(" - "); - buffer.append(System.currentTimeMillis() - startTime); - buffer.append(" ms"); - logger.info(buffer); - } - } - - /** - * Return a new Page instance for the given request context. This method will - * invoke {@link #initPage(String, Class, HttpServletRequest)} to create - * the Page instance and then set the properties on the page. - * - * @param context the page request context - * @return a new Page instance for the given request, or null if an - * PageInterceptor has aborted page creation - */ - protected Page createPage(Context context) { - - HttpServletRequest request = context.getRequest(); - - // Log request parameters - if (logger.isTraceEnabled()) { - logger.trace(" is Ajax request: " + context.isAjaxRequest()); - logRequestParameters(request); - } - - String path = context.getResourcePath(); - - if (request.getAttribute(FORWARD_PAGE) != null) { - Page forwardPage = (Page) request.getAttribute(FORWARD_PAGE); - - if (forwardPage.getFormat() == null) { - forwardPage.setFormat(configService.createFormat()); - } - - request.removeAttribute(FORWARD_PAGE); - - return forwardPage; - } - - Class pageClass = configService.getPageClass(path); - - if (pageClass == null) { - pageClass = configService.getNotFoundPageClass(); - path = ConfigService.NOT_FOUND_PATH; - } - // Set thread local app page listeners - List interceptors = configService.getPageInterceptors(); - setThreadLocalInterceptors(interceptors); - - for (PageInterceptor listener : interceptors) { - if (!listener.preCreate(pageClass, context)) { - return null; - } - } - - final Page page = initPage(path, pageClass, request); - - if (page.getFormat() == null) { - page.setFormat(configService.createFormat()); - } - - for (PageInterceptor listener : interceptors) { - if (!listener.postCreate(page)) { - return null; - } - } - - return page; - } - - /** - * Process the given pages controls onDestroy methods, reset the pages - * navigation state and process the pages onDestroy method. - * - * @param page the page to process - * @param startTime the start time to log if greater than 0 and not in - * production mode - */ - @SuppressWarnings("deprecation") - protected void processPageOnDestroy(Page page, long startTime) { - Context context = page.getContext(); - if (page.hasControls()) { - - // notify callbacks of destroy event - // TODO check that exceptions don't unnecessarily trigger preDestroy - ControlRegistry.getThreadLocalRegistry().processPreDestroy(context); - - List controls = page.getControls(); - - for (int i = 0, size = controls.size(); i < size; i++) { - try { - Control control = controls.get(i); - control.onDestroy(); - - if (logger.isTraceEnabled()) { - String controlClassName = control.getClass().getName(); - controlClassName = controlClassName.substring(controlClassName.lastIndexOf('.') + 1); - String msg = " invoked: '" + control.getName() - + "' " + controlClassName + ".onDestroy()"; - logger.trace(msg); - } - } catch (Throwable error) { - logger.error(error.toString(), error); - } - } - } - - // Reset the page navigation state - try { - // Reset the path - String path = context.getResourcePath(); - page.setPath(path); - - // Reset the forward - if (configService.isJspPage(path)) { - page.setForward(StringUtils.replace(path, ".htm", ".jsp")); - } else { - page.setForward((String) null); - } - - // Reset the redirect - page.setRedirect((String) null); - - } catch (Throwable error) { - logger.error(error.toString(), error); - } - - try { - page.onDestroy(); - - if (page.isStateful()) { - context.setSessionAttribute(page.getClass().getName(), page); - } else { - context.removeSessionAttribute(page.getClass().getName()); - } - - if (logger.isTraceEnabled()) { - String shortClassName = page.getClass().getName(); - shortClassName = - shortClassName.substring(shortClassName.lastIndexOf('.') + 1); - logger.trace(" invoked: " + shortClassName + ".onDestroy()"); - } - - if (!configService.isProductionMode() && startTime > 0) { - logger.info("handleRequest: " + page.getPath() + " - " - + (System.currentTimeMillis() - startTime) - + " ms"); - } - - } catch (Throwable error) { - logger.error(error.toString(), error); - - } finally { - // Nullify PageImports - page.setPageImports(null); - } - } - - /** - * Initialize a new page instance using - * {@link #newPageInstance(String, Class, HttpServletRequest)} method and - * setting format, headers and the forward if a JSP. - *

- * This method will also automatically register any public Page controls - * in the page's model. When the page is created any public visible - * page Control variables will be automatically added to the page using - * the method {@link Page#addControl(Control)} method. If the controls name - * is not defined it is set to the member variables name before it is added - * to the page. - *

- * This feature saves you from having to manually add the controls yourself. - * If you don't want the controls automatically added, simply declare them - * as non public variables. - *

- * An example auto control registration is provided below. In this example - * the Table control is automatically added to the model using the name - * "table", and the ActionLink controls are added using the names - * "editDetailsLink" and "viewDetailsLink". - * - *

-     * public class OrderDetailsPage extends Page {
-     *
-     *     public Table table = new Table();
-     *     public ActionLink editDetailsLink = new ActionLink();
-     *     publicActionLink viewDetailsLink = new ActionLink();
-     *
-     *     public OrderDetailsPage() {
-     *         ..
-     *     }
-     * } 
- * - * @param path the page path - * @param pageClass the page class - * @param request the page request - * @return initialized page - */ - protected Page initPage(String path, Class pageClass, - HttpServletRequest request) { - - try { - Page newPage = null; - - // Look up the page in the users session. - HttpSession session = request.getSession(false); - if (session != null) { - newPage = (Page) session.getAttribute(pageClass.getName()); - } - - if (newPage == null) { - newPage = newPageInstance(path, pageClass, request); - - if (logger.isTraceEnabled()) { - String shortClassName = pageClass.getName(); - shortClassName = - shortClassName.substring(shortClassName.lastIndexOf('.') + 1); - logger.trace(" invoked: " + shortClassName + ".<>"); - } - } - - activatePageInstance(newPage); - - Map defaultHeaders = configService.getPageHeaders(path); - if (newPage.hasHeaders()) { - - // Don't override existing headers - Map pageHeaders = newPage.getHeaders(); - for (Map.Entry entry : defaultHeaders.entrySet()) { - if (!pageHeaders.containsKey(entry.getKey())) { - pageHeaders.put(entry.getKey(), entry.getValue()); - } - } - - } else { - newPage.getHeaders().putAll(defaultHeaders); - } - - newPage.setPath(path); - - // Bind to final variable to enable callback processing - final Page page = newPage; - - if (configService.getAutoBindingMode() != AutoBinding.NONE) { - - processPageFields(newPage, new ClickServlet.FieldCallback() { - public void processField(String fieldName, Object fieldValue) { - if (fieldValue instanceof Control) { - Control control = (Control) fieldValue; - if (control.getName() == null) { - control.setName(fieldName); - } - - if (!page.getModel().containsKey(control.getName())) { - page.addControl(control); - } - } - } - }); - - processPageRequestParams(page); - } - - // In mock mode add the Page instance as a request attribute. - if (request.getAttribute(MOCK_MODE_ENABLED) != null) { - request.setAttribute(MOCK_PAGE_REFERENCE, page); - } - - return newPage; - - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * Process the page binding any request parameters to any public Page - * fields with the same name which are "primitive" types. These types - * include string, numbers and booleans. - *

- * Type conversion is performed using the TypeConverter - * returned by the {@link #getTypeConverter()} method. - * - * @param page the page whose fields are to be processed - * @throws OgnlException if an error occurs - */ - protected void processPageRequestParams(Page page) throws OgnlException { - - if (configService.getPageFields(page.getClass()).isEmpty()) { - return; - } - - Map ognlContext = null; - - boolean customConverter = - ! getTypeConverter().getClass().equals(RequestTypeConverter.class); - - HttpServletRequest request = page.getContext().getRequest(); - - for (Enumeration e = request.getParameterNames(); e.hasMoreElements();) { - String name = e.nextElement().toString(); - String value = request.getParameter(name); - - if (StringUtils.isNotBlank(value)) { - - Field field = configService.getPageField(page.getClass(), name); - - if (field != null) { - Class type = field.getType(); - - if (customConverter - || (type.isPrimitive() - || String.class.isAssignableFrom(type) - || Number.class.isAssignableFrom(type) - || Boolean.class.isAssignableFrom(type))) { - - if (ognlContext == null) { - ognlContext = Ognl.createDefaultContext( - page, null, getTypeConverter(), getMemberAccess()); - } - - PropertyUtils.setValueOgnl(page, name, value, ognlContext); - - if (logger.isTraceEnabled()) { - logger.trace(" auto bound variable: " + name + "=" + value); - } - } - } - } - } - } - - /** - * Return a new Page instance for the given page path, class and request. - *

- * The default implementation of this method simply creates new page - * instances: - *

-     * protected Page newPageInstance(String path, Class pageClass,
-     *     HttpServletRequest request) throws Exception {
-     *
-     *     return (Page) pageClass.newInstance();
-     * } 
- * - * This method is designed to be overridden by applications providing their - * own page creation patterns. - *

- * A typical example of this would be with Inversion of Control (IoC) - * frameworks such as Spring or HiveMind. For example a Spring application - * could override this method and use a ApplicationContext to instantiate - * new Page objects: - *

-     * protected Page newPageInstance(String path, Class pageClass,
-     *     HttpServletRequest request) throws Exception {
-     *
-     *     String beanName = path.substring(0, path.indexOf("."));
-     *
-     *     if (applicationContext.containsBean(beanName)) {
-     *         Page page = (Page) applicationContext.getBean(beanName);
-     *
-     *     } else {
-     *         page = (Page) pageClass.newInstance();
-     *     }
-     *
-     *     return page;
-     * } 
- * - * @param path the request page path - * @param pageClass the page Class the request is mapped to - * @param request the page request - * @return a new Page object - * @throws Exception if an error occurs creating the Page - */ - protected Page newPageInstance(String path, Class pageClass, - HttpServletRequest request) throws Exception { - - return pageClass.newInstance(); - } - - /** - * Provides an extension point for ClickServlet sub classes to activate - * stateful page which may have been deserialized. - *

- * This method does nothing and is designed for extension. - * - * @param page the page instance to activate - */ - protected void activatePageInstance(Page page) { - } - - /** - * Return a new VelocityContext for the given pages model and Context. - *

- * The following values automatically added to the VelocityContext: - *

    - *
  • any public Page fields using the fields name
  • - *
  • context - the Servlet context path, e.g. /mycorp
  • - *
  • format - the {@link org.apache.click.util.Format} object for formatting - * the display of objects
  • - *
  • imports - the {@link org.apache.click.util.PageImports} object
  • - *
  • messages - the page messages bundle
  • - *
  • path - the page of the page template to render
  • - *
  • request - the pages servlet request
  • - *
  • response - the pages servlet request
  • - *
  • session - the {@link org.apache.click.util.SessionMap} adaptor for the - * users HttpSession
  • - *
- * - * @see org.openidentityplatform.openam.click.util.ClickUtils#createTemplateModel(Page, Context) - * - * @param page the page to create a VelocityContext for - * @return a new VelocityContext - */ - @SuppressWarnings("deprecation") - protected Map createTemplateModel(final Page page) { - - if (configService.getAutoBindingMode() != AutoBinding.NONE) { - - processPageFields(page, new ClickServlet.FieldCallback() { - public void processField(String fieldName, Object fieldValue) { - if (fieldValue instanceof Control == false) { - page.addModel(fieldName, fieldValue); - - } else { - // Add any controls not already added to model - Control control = (Control) fieldValue; - if (!page.getModel().containsKey(control.getName())) { - page.addControl(control); - } - } - } - }); - } - - final Context context = page.getContext(); - final Map model = ClickUtils.createTemplateModel(page, context); - - PageImports pageImports = page.getPageImports(); - pageImports.populateTemplateModel(model); - - return model; - } - - /** - * Set the HTTP headers in the servlet response. The Page response headers - * are defined in {@link Page#getHeaders()}. - * - * @param response the response to set the headers in - * @param headers the map of HTTP headers to set in the response - */ - protected void setPageResponseHeaders(HttpServletResponse response, - Map headers) { - - for (Map.Entry entry : headers.entrySet()) { - String name = entry.getKey(); - Object value = entry.getValue(); - - if (value instanceof String) { - String strValue = (String) value; - if (!strValue.equalsIgnoreCase("Content-Encoding")) { - response.setHeader(name, strValue); - } - - } else if (value instanceof Date) { - long time = ((Date) value).getTime(); - response.setDateHeader(name, time); - - } else if (value instanceof Integer) { - int intValue = (Integer) value; - response.setIntHeader(name, intValue); - - } else if (value != null) { - throw new IllegalStateException("Invalid Page header value type: " - + value.getClass() + ". Header value must of type String, Date or Integer."); - } - } - } - - /** - * Set the page model, context, format, messages and path as request - * attributes to support JSP rendering. These request attributes include: - *
    - *
  • any public Page fields using the fields name
  • - *
  • context - the Servlet context path, e.g. /mycorp
  • - *
  • format - the {@link org.apache.click.util.Format} object for - * formatting the display of objects
  • - *
  • forward - the page forward path, if defined
  • - *
  • imports - the {@link org.apache.click.util.PageImports} object
  • - *
  • messages - the page messages bundle
  • - *
  • path - the page of the page template to render
  • - *
- * - * @param page the page to set the request attributes on - */ - @SuppressWarnings("deprecation") - protected void setRequestAttributes(final Page page) { - final HttpServletRequest request = page.getContext().getRequest(); - - processPageFields(page, new ClickServlet.FieldCallback() { - public void processField(String fieldName, Object fieldValue) { - if (fieldValue instanceof Control == false) { - request.setAttribute(fieldName, fieldValue); - } else { - // Add any controls not already added to model - Control control = (Control) fieldValue; - if (!page.getModel().containsKey(control.getName())) { - page.addControl(control); - } - } - } - }); - - Map model = page.getModel(); - for (Map.Entry entry : model.entrySet()) { - String name = entry.getKey(); - Object value = entry.getValue(); - - request.setAttribute(name, value); - } - - request.setAttribute("context", request.getContextPath()); - if (model.containsKey("context")) { - String msg = page.getClass().getName() + " on " + page.getPath() - + " model contains an object keyed with reserved " - + "name \"context\". The request attribute " - + "has been replaced with the request " - + "context path"; - logger.warn(msg); - } - - request.setAttribute("format", page.getFormat()); - if (model.containsKey("format")) { - String msg = page.getClass().getName() + " on " + page.getPath() - + " model contains an object keyed with reserved " - + "name \"format\". The request attribute " - + "has been replaced with the format object"; - logger.warn(msg); - } - - request.setAttribute("forward", page.getForward()); - if (model.containsKey("forward")) { - String msg = page.getClass().getName() + " on " + page.getPath() - + " model contains an object keyed with reserved " - + "name \"forward\". The request attribute " - + "has been replaced with the page path"; - logger.warn(msg); - } - - request.setAttribute("path", page.getPath()); - if (model.containsKey("path")) { - String msg = page.getClass().getName() + " on " + page.getPath() - + " model contains an object keyed with reserved " - + "name \"path\". The request attribute " - + "has been replaced with the page path"; - logger.warn(msg); - } - - request.setAttribute("messages", page.getMessages()); - if (model.containsKey("messages")) { - String msg = page.getClass().getName() + " on " + page.getPath() - + " model contains an object keyed with reserved " - + "name \"messages\". The request attribute " - + "has been replaced with the page messages"; - logger.warn(msg); - } - - PageImports pageImports = page.getPageImports(); - pageImports.populateRequest(request, model); - } - - /** - * Return the request parameters OGNL TypeConverter. This method - * performs a lazy load of the TypeConverter object, using the classname - * defined in the Servlet init parameter type-converter-class, - * if this parameter is not defined this method will return a - * {@link RequestTypeConverter} instance. - * - * @return the request parameters OGNL TypeConverter - * @throws RuntimeException if the TypeConverter instance could not be created - */ - @SuppressWarnings("unchecked") - protected TypeConverter getTypeConverter() throws RuntimeException { - if (typeConverter == null) { - Class converter = RequestTypeConverter.class; - - try { - String classname = getInitParameter(TYPE_CONVERTER_CLASS); - if (StringUtils.isNotBlank(classname)) { - converter = ClickUtils.classForName(classname); - } - - typeConverter = converter.newInstance(); - - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - return typeConverter; - } - - /** - * Creates and returns a new Context instance for this path, class and - * request. - *

- * The default implementation of this method simply creates a new Context - * instance. - *

- * Subclasses can override this method to provide a custom Context. - * - * @param request the page request - * @param response the page response - * @param isPost true if this is a post request, false otherwise - * @return a Context instance - */ - protected Context createContext(HttpServletRequest request, - HttpServletResponse response, boolean isPost) { - - Context context = new Context(getServletContext(), - getServletConfig(), - request, - response, - isPost, - this); - return context; - } - - /** - * Creates and returns a new ActionEventDispatcher instance. - * - * @return the new ActionEventDispatcher instance - */ - protected ActionEventDispatcher createActionEventDispatcher() { - return new ActionEventDispatcher(configService); - } - - /** - * Creates and returns a new ControlRegistry instance. - * - * @return the new ControlRegistry instance - */ - protected ControlRegistry createControlRegistry() { - return new ControlRegistry(configService); - } - - /** - * Creates and returns a new ErrorPage instance. - *

- * This method creates the custom page as specified in click.xml, - * otherwise the default ErrorPage instance. - *

- * Subclasses can override this method to provide custom ErrorPages tailored - * for specific exceptions. - *

- * Note you can safely use {@link Context} in this - * method. - * - * @param pageClass the page class with the error - * @param exception the error causing exception - * @return a new ErrorPage instance - */ - protected ErrorPage createErrorPage(Class pageClass, Throwable exception) { - try { - return (ErrorPage) configService.getErrorPageClass().newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * Return the application configuration service instance. - * - * @return the application configuration service instance - */ - protected ConfigService getConfigService() { - return configService; - } - - /** - * Return a new Page instance for the given path. The path must start with - * a "/". - * - * @param path the path which maps to a Page class - * @param request the Page request - * @return a new Page object - * @throws IllegalArgumentException if the Page is not found - */ - @SuppressWarnings("unchecked") - protected T createPage(String path, HttpServletRequest request) { - Class pageClass = getConfigService().getPageClass(path); - - if (pageClass == null) { - String msg = "No Page class configured for path: " + path; - throw new IllegalArgumentException(msg); - } - - return (T) initPage(path, pageClass, request); - } - - /** - * Return a new Page instance for the page Class. - * - * @param pageClass the class of the Page to create - * @param request the Page request - * @return a new Page object - * @throws IllegalArgumentException if the Page Class is not configured - * with a unique path - */ - @SuppressWarnings("unchecked") - protected T createPage(Class pageClass, HttpServletRequest request) { - String path = getConfigService().getPagePath(pageClass); - - if (path == null) { - String msg = - "No path configured for Page class: " + pageClass.getName(); - throw new IllegalArgumentException(msg); - } - - return (T) initPage(path, pageClass, request); - } - - /** - * Creates and returns a new PageImports instance for the specified page. - * - * @param page the page to create a new PageImports instance for - * @return the new PageImports instance - */ - protected PageImports createPageImports(Page page) { - return new PageImports(page); - } - - // TODO refactor Page events into its a separate Livecycle class. This will - // take some of the responsibility off ClickServlet and remove code duplication - - /** - * Process the given page events, invoking the "on" event callback methods - * and directing the response. - * - * @param page the page which events to process - * @param context the request context - * @throws Exception if an error occurs - */ - protected void processAjaxPageEvents(Page page, Context context) throws Exception { - - ActionEventDispatcher eventDispatcher = ActionEventDispatcher.getThreadLocalDispatcher(); - - ControlRegistry controlRegistry = ControlRegistry.getThreadLocalRegistry(); - - // TODO Ajax requests shouldn't reach this code path since errors - // are rendered directly - if (page instanceof ErrorPage) { - ErrorPage errorPage = (ErrorPage) page; - errorPage.setMode(configService.getApplicationMode()); - - // Notify the dispatcher and registry of the error - eventDispatcher.errorOccurred(errorPage.getError()); - controlRegistry.errorOccurred(errorPage.getError()); - } - - boolean continueProcessing = performOnSecurityCheck(page, context); - - ActionResult actionResult = null; - if (continueProcessing) { - - // Handle page method - String pageAction = context.getRequestParameter(Page.PAGE_ACTION); - if (pageAction != null) { - continueProcessing = false; - - // Returned action result could be null - actionResult = performPageAction(page, pageAction, context); - - controlRegistry.processPreResponse(context); - controlRegistry.processPreRenderHeadElements(context); - - renderActionResult(actionResult, page, context); - } - } - - if (continueProcessing) { - performOnInit(page, context); - - // TODO: Ajax doesn't support forward. Is it still necessary to - // check isForward? - if (controlRegistry.hasAjaxTargetControls() && !context.isForward()) { - - // Perform onProcess for regsitered Ajax target controls - processAjaxTargetControls(context, eventDispatcher, controlRegistry); - - // Fire AjaxBehaviors registered during the onProcess event - // The target AjaxBehavior will set the eventDispatcher action - // result instance to render - eventDispatcher.fireAjaxBehaviors(context); - - // Ensure we execute the beforeResponse and beforeGetHeadElements - // for Ajax requests - controlRegistry.processPreResponse(context); - controlRegistry.processPreRenderHeadElements(context); - - actionResult = eventDispatcher.getActionResult(); - - // Render the actionResult - renderActionResult(actionResult, page, context); - - } else { - - // If no Ajax target controls have been registered fallback to - // the old behavior of processing and rendering the page template - if (logger.isTraceEnabled()) { - String msg = " *no* Ajax target controls have been registered." - + " Will process the page as a normal non Ajax request."; - logger.trace(msg); - } - - continueProcessing = performOnProcess(page, context, eventDispatcher); - - if (continueProcessing) { - performOnPostOrGet(page, context, context.isPost()); - - performOnRender(page, context); - } - - // If Ajax request does not target a valid page, return a 404 - // repsonse status, allowing JavaScript to display a proper message - if (ConfigService.NOT_FOUND_PATH.equals(page.getPath())) { - HttpServletResponse response = context.getResponse(); - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - return; - } - - controlRegistry.processPreResponse(context); - controlRegistry.processPreRenderHeadElements(context); - performRender(page, context); - } - } else { - // If security check fails for an Ajax request, Click returns without - // any rendering. It is up to the user to render an ActionResult - // in the onSecurityCheck event - // Note: this code path is also followed if a pageAction is invoked - } - } - - /** - * Process all Ajax target controls and return true if the page should continue - * processing, false otherwise. - * - * @param context the request context - * @param eventDispatcher the event dispatcher - * @param controlRegistry the control registry - * @return true if the page should continue processing, false otherwise - */ - protected boolean processAjaxTargetControls(Context context, - ActionEventDispatcher eventDispatcher, ControlRegistry controlRegistry) { - - boolean continueProcessing = true; - - // Resolve the Ajax target control for this request - Control ajaxTarget = resolveAjaxTargetControl(context, controlRegistry); - - if (ajaxTarget != null) { - - // Process the control - if (!ajaxTarget.onProcess()) { - continueProcessing = false; - } - - // Log a trace if no behavior was registered after processing the control - if (logger.isTraceEnabled()) { - - HtmlStringBuffer buffer = new HtmlStringBuffer(); - String controlClassName = ClassUtils.getShortClassName(ajaxTarget.getClass()); - buffer.append(" invoked: '"); - buffer.append(ajaxTarget.getName()); - buffer.append("' ").append(controlClassName); - buffer.append(".onProcess() : ").append(continueProcessing); - logger.trace(buffer.toString()); - - if (!eventDispatcher.hasAjaxBehaviorSourceSet()) { - logger.trace(" *no* AjaxBehavior was registered while processing the control"); - } - } - } - - return continueProcessing; - } - - /** - * Provides an Ajax exception handler. Exceptions are wrapped inside a - * div element and streamed back to the browser. The response status - * is set to an {@link jakarta.servlet.http.HttpServletResponse#SC_INTERNAL_SERVER_ERROR HTTP 500 error} - * which allows the JavaScript that initiated the Ajax request to handle - * the error as appropriate. - *

- * If Click is running in development modes the exception stackTrace - * will be rendered, in production modes an error message is - * rendered. - *

- * Below is an example error response: - * - *

-     * <div id='errorReport' class='errorReport'>
-     * The application encountered an unexpected error.
-     * </div>
-     * 
- * - * @param request the servlet request - * @param response the servlet response - * @param isPost determines whether the request is a POST - * @param exception the error causing exception - * @param pageClass the page class with the error - */ - protected void handleAjaxException(HttpServletRequest request, - HttpServletResponse response, boolean isPost, Throwable exception, - Class pageClass) { - - // If an exception occurs during an Ajax request, stream - // the exception instead of creating an ErrorPage - try { - - PrintWriter writer = null; - - try { - writer = getPrintWriter(response); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - - // TODO: use an ErrorReport instance instead? - writer.write("
\n"); - - if (configService.isProductionMode() || configService.isProfileMode()) { - writer.write("The application encountered an unexpected error."); - } else { - exception.printStackTrace(writer); - } - - writer.write("\n
"); - } finally { - if (writer != null) { - writer.flush(); - } - } - } catch (Throwable error) { - logger.error(error.getMessage(), error); - throw new RuntimeException(error); - } - logger.error("handleException: ", exception); - } - - // ------------------------------------------------ Package Private Methods - - /** - * Return the OGNL MemberAccess. This method performs a lazy load - * of the MemberAccess object, using a {@link DefaultMemberAccess} instance. - * - * @return the OGNL MemberAccess - */ - MemberAccess getMemberAccess() { - if (memberAccess == null) { - memberAccess = new DefaultMemberAccess(true); - } - return memberAccess; - } - - /** - * Create a Click application ConfigService instance. - * - * @param servletContext the Servlet Context - * @return a new application ConfigService instance - * @throws Exception if an initialization error occurs - */ - @SuppressWarnings("unchecked") - ConfigService createConfigService(ServletContext servletContext) - throws Exception { - - Class serviceClass = XmlConfigService.class; - - String classname = servletContext.getInitParameter(CONFIG_SERVICE_CLASS); - if (StringUtils.isNotBlank(classname)) { - serviceClass = ClickUtils.classForName(classname); - } - - return serviceClass.newInstance(); - } - - /** - * Initialize the Click application ConfigService instance and bind - * it as a ServletContext attribute using the key - * "org.apache.click.service.ConfigService". - *

- * This method will use the configuration service class specified by the - * {@link #CONFIG_SERVICE_CLASS} parameter, otherwise it will create a - * {@link org.apache.click.service.XmlConfigService} instance. - * - * @param servletContext the servlet context to retrieve the - * {@link #CONFIG_SERVICE_CLASS} from - * @throws RuntimeException if the configuration service cannot be - * initialized - */ - void initConfigService(ServletContext servletContext) { - - if (configService != null) { - try { - - // Note this order is very important as components need to lookup - // the configService out of the ServletContext while the service - // is being initialized. - servletContext.setAttribute(ConfigService.CONTEXT_NAME, configService); - - // Initialize the ConfigService instance - configService.onInit(servletContext); - - } catch (Exception e) { - - if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } else { - throw new RuntimeException(e); - } - } - } - } - - /** - * Destroy the application configuration service instance and remove - * it from the ServletContext attribute. - * - * @param servletContext the servlet context - * @throws RuntimeException if the configuration service cannot be - * destroyed - */ - void destroyConfigService(ServletContext servletContext) { - - if (configService != null) { - - try { - configService.onDestroy(); - - } catch (Exception e) { - - if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } else { - throw new RuntimeException(e); - } - } finally { - servletContext.setAttribute(ConfigService.CONTEXT_NAME, null); - } - } - } - - /** - * Process all the Pages public fields using the given callback. - * - * @param page the page to obtain the fields from - * @param callback the fields iterator callback - */ - void processPageFields(Page page, ClickServlet.FieldCallback callback) { - - Field[] fields = configService.getPageFieldArray(page.getClass()); - - if (fields != null) { - for (int i = 0; i < fields.length; i++) { - Field field = fields[i]; - - try { - Object fieldValue = field.get(page); - - if (fieldValue != null) { - callback.processField(field.getName(), fieldValue); - } - - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - } - - List getThreadLocalInterceptors() { - List listeners = - THREAD_LOCAL_INTERCEPTORS.get(); - - if (listeners != null) { - return listeners; - } else { - return Collections.emptyList(); - } - } - - void setThreadLocalInterceptors(List listeners) { - THREAD_LOCAL_INTERCEPTORS.set(listeners); - } - - /** - * Retrieve a writer instance for the given context. - * - * @param response the servlet response - * @return a writer instance - * @throws IOException if an input or output exception occurred - */ - Writer getWriter(HttpServletResponse response) throws IOException { - try { - - return response.getWriter(); - - } catch (IllegalStateException ignore) { - // If writer cannot be retrieved fallback to OutputStream. CLK-644 - return new OutputStreamWriter(response.getOutputStream(), - response.getCharacterEncoding()); - } - } - - /** - * Return a PrintWriter instance for the given response. - * - * @param response the servlet response - * @return a PrintWriter instance - */ - PrintWriter getPrintWriter(HttpServletResponse response) throws IOException { - Writer writer = getWriter(response); - if (writer instanceof PrintWriter) { - return (PrintWriter) writer; - } - return new PrintWriter(writer); - } - - /** - * Return true if this is an ajax request, false otherwise. - * - * @param request the servlet request - * @return true if this is an ajax request, false otherwise - */ - boolean isAjaxRequest(HttpServletRequest request) { - boolean isAjaxRequest = false; - if (Context.hasThreadLocalContext()) { - Context context = Context.getThreadLocalContext(); - if (context.isAjaxRequest()) { - isAjaxRequest = true; - } - } else { - isAjaxRequest = ClickUtils.isAjaxRequest(request); - } - return isAjaxRequest; - } - - // ---------------------------------------------------------- Inner Classes - - /** - * Field iterator callback. - */ - static interface FieldCallback { - - /** - * Callback method invoked for each field. - * - * @param fieldName the field name - * @param fieldValue the field value - */ - public void processField(String fieldName, Object fieldValue); - - } - - // Private methods -------------------------------------------------------- - - /** - * Resolve and return the Ajax target control for this request or null if no - * Ajax target was found. - * - * @param context the request context - * @param controlRegistry the control registry - * @return the target Ajax target control or null if no Ajax target was found - */ - private Control resolveAjaxTargetControl(Context context, ControlRegistry controlRegistry) { - - Control ajaxTarget = null; - - if (logger.isTraceEnabled()) { - logger.trace(" the following controls have been registered as potential Ajax targets:"); - if (controlRegistry.hasAjaxTargetControls()) { - for (Control control : controlRegistry.getAjaxTargetControls()) { - HtmlStringBuffer buffer = new HtmlStringBuffer(); - String controlClassName = ClassUtils.getShortClassName(control.getClass()); - buffer.append(" ").append(controlClassName); - buffer.append(": name='").append(control.getName()).append("'"); - logger.trace(buffer.toString()); - } - } else { - logger.trace(" *no* control has been registered"); - } - } - - for (Control control : controlRegistry.getAjaxTargetControls()) { - - if (control.isAjaxTarget(context)) { - ajaxTarget = control; - // The first matching control will be processed. Multiple matching - // controls are not supported - break; - } - } - - if (logger.isTraceEnabled()) { - if (ajaxTarget == null) { - String msg = " *no* target control was found for the Ajax request"; - logger.trace(msg); - - } else { - HtmlStringBuffer buffer = new HtmlStringBuffer(); - buffer.append(" invoked: '"); - buffer.append(ajaxTarget.getName()).append("' "); - String className = ClassUtils.getShortClassName(ajaxTarget.getClass()); - buffer.append(className); - buffer.append(".isAjaxTarget() : true (Ajax target control found)"); - logger.trace(buffer.toString()); - } - } - - return ajaxTarget; - } - - /** - * Log the request parameter names and values. - * - * @param request the HTTP servlet request - */ - private void logRequestParameters(HttpServletRequest request) { - - Map requestParams = new TreeMap(); - - Enumeration e = request.getParameterNames(); - while (e.hasMoreElements()) { - String name = e.nextElement().toString(); - String[] values = request.getParameterValues(name); - requestParams.put(name, values); - } - - for (Map.Entry entry : requestParams.entrySet()) { - String name = entry.getKey(); - String[] values = entry.getValue(); - - HtmlStringBuffer buffer = new HtmlStringBuffer(40); - buffer.append(" request param: " + name + "="); - - if (values == null) { - // ignore - } else if (values.length == 1) { - - buffer.append(ClickUtils.limitLength(values[0], 40)); - } else { - - for (int i = 0; i < values.length; i++) { - if (i == 0) { - buffer.append('['); - } else { - buffer.append(", "); - } - buffer.append(ClickUtils.limitLength(values[i], 40)); - } - buffer.append("]"); - } - - logger.trace(buffer.toString()); - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/Context.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/Context.java deleted file mode 100644 index ac6ddbe9b4..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/Context.java +++ /dev/null @@ -1,973 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -import java.io.StringWriter; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Locale; -import java.util.Map; - -import jakarta.servlet.ServletConfig; -import jakarta.servlet.ServletContext; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletRequestWrapper; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; - -import org.openidentityplatform.openam.click.service.FileUploadService; -import org.openidentityplatform.openam.click.service.LogService; -import org.openidentityplatform.openam.click.service.MessagesMapService; -import org.openidentityplatform.openam.click.service.TemplateService; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.apache.click.util.FlashAttribute; -import org.apache.commons.fileupload.FileItem; - -/** - * Provides the HTTP request context information for pages and controls. - * A new Context object is created for each Page request. The request Context - * object can be obtained from the thread local variable via the - * {@link org.openidentityplatform.openam.click.Context#getThreadLocalContext()} method. - */ -public class Context { - - // -------------------------------------------------------------- Constants - - /** The user's session Locale key:   locale. */ - public static final String LOCALE = "locale"; - - /** - * The attribute key used for storing any error that occurred while - * Context is created. - */ - static final String CONTEXT_FATAL_ERROR = "_context_fatal_error"; - - /** The thread local context stack. */ - private static final ThreadLocal THREAD_LOCAL_CONTEXT_STACK = new ThreadLocal<>(); - - // ----------------------------------------------------- Instance Variables - - /** The servlet context. */ - protected final ServletContext context; - - /** The servlet config. */ - protected final ServletConfig config; - - /** The HTTP method is POST flag. */ - protected final boolean isPost; - - /** The servlet response. */ - protected final HttpServletResponse response; - - /** The click services interface. */ - final ClickServlet clickServlet; - - /** The servlet request. */ - final HttpServletRequest request; - - // ----------------------------------------------------------- Constructors - - /** - * Create a new request context. - * - * @param context the servlet context - * @param config the servlet config - * @param request the servlet request - * @param response the servlet response - * @param isPost the servlet request is a POST - * @param clickServlet the click servlet instance - */ - public Context(ServletContext context, ServletConfig config, - HttpServletRequest request, HttpServletResponse response, - boolean isPost, ClickServlet clickServlet) { - - this.clickServlet = clickServlet; - this.context = context; - this.config = config; - this.isPost = isPost; - - Context.ContextStack contextStack = getContextStack(); - - if (contextStack.size() == 0) { - - // CLK-312. Apply request.setCharacterEncoding before wrapping - // request in ClickRequestWrapper - String charset = clickServlet.getConfigService().getCharset(); - if (charset != null) { - - try { - request.setCharacterEncoding(charset); - - } catch (UnsupportedEncodingException ex) { - String msg = - "The character encoding " + charset + " is invalid."; - LogService logService = - clickServlet.getConfigService().getLogService(); - logService.warn(msg, ex); - } - } - - FileUploadService fus = - clickServlet.getConfigService().getFileUploadService(); - - this.request = new ClickRequestWrapper(request, fus); - - } else { - this.request = request; - } - this.response = response; - } - - /** - * Package private constructor to support Mock objects. - * - * @param request the servlet request - * @param response the servlet response - */ - Context(HttpServletRequest request, HttpServletResponse response) { - // This method should be removed once the deprecated MockContext - // constructor is removed - this.request = request; - this.response = response; - this.clickServlet = null; - this.context = null; - this.config = null; - this.isPost = "POST".equalsIgnoreCase(request.getMethod()); - } - - // --------------------------------------------------------- Public Methods - - /** - * Return the thread local request context instance. - * - * @return the thread local request context instance. - * @throws RuntimeException if a Context is not available on the thread. - */ - public static Context getThreadLocalContext() { - return getContextStack().peek(); - } - - /** - * Returns true if a Context instance is available on the current thread, - * false otherwise. Unlike {@link #getThreadLocalContext()} this method - * can safely be used and will not throw an exception if Context is not - * available on the current thread. - *

- * This method is very useful inside a {@link Control} constructor which - * might need access to the Context. As Controls could potentially be - * instantiated during Click startup (in order to deploy their resources), - * this check can be used to determine whether Context is available or not. - *

- * For example: - * - *

-     * public MyControl extends AbstractControl {
-     *     public MyControl(String name) {
-     *         if (Context.hasThreadLocalContext()) {
-     *             // Context is available, meaning a user initiated a web
-     *             // request
-     *             Context context = getContext();
-     *             String state = (String) context.getSessionAttribute(name);
-     *             setValue(state);
-     *         } else {
-     *             // No Context is available, meaning this is most probably
-     *             // the application startup and deployment phase.
-     *         }
-     *     }
-     * }
-     * 
- * - * @return true if a Context instance is available on the current thread, - * false otherwise - */ - public static boolean hasThreadLocalContext() { - Context.ContextStack contextStack = THREAD_LOCAL_CONTEXT_STACK.get(); - if (contextStack == null) { - return false; - } - return !contextStack.isEmpty(); - } - - /** - * Returns the servlet request. - * - * @return HttpServletRequest - */ - public HttpServletRequest getRequest() { - return request; - } - - /** - * Returns the servlet response. - * - * @return HttpServletResponse - */ - public HttpServletResponse getResponse() { - return response; - } - - /** - * Returns the servlet config. - * - * @return ServletConfig - */ - public ServletConfig getServletConfig() { - return config; - } - - /** - * Returns the servlet context. - * - * @return ServletContext - */ - public ServletContext getServletContext() { - return context; - } - - /** - * Return the user's HttpSession, creating one if necessary. - * - * @return the user's HttpSession, creating one if necessary. - */ - public HttpSession getSession() { - return request.getSession(); - } - - /** - * Return the page resource path from the request. For example: - *
-     * http://www.mycorp.com/banking/secure/login.htm  ->  /secure/login.htm 
- * - * @return the page resource path from the request - */ - public String getResourcePath() { - return ClickUtils.getResourcePath(request); - } - - /** - * Return true if the request has been forwarded. A forwarded request - * will contain a {@link ClickServlet#CLICK_FORWARD} request attribute. - * - * @return true if the request has been forwarded - */ - public boolean isForward() { - return (request.getAttribute(ClickServlet.CLICK_FORWARD) != null); - } - - /** - * Return true if the HTTP request method is "POST". - * - * @return true if the HTTP request method is "POST" - */ - public boolean isPost() { - return isPost; - } - - /** - * Return true if the HTTP request method is "GET". - * - * @return true if the HTTP request method is "GET" - */ - public boolean isGet() { - return !isPost && getRequest().getMethod().equalsIgnoreCase("GET"); - } - - /** - * Return true is this is an Ajax request, false otherwise. - *

- * An Ajax request is identified by the presence of the request header - * or request parameter: "X-Requested-With". - * "X-Requested-With" is the de-facto standard identifier used by - * Ajax libraries. - *

- * Note: incoming requests that contains a request parameter - * "X-Requested-With" will result in this method returning true, even - * though the request itself was not initiated through a XmlHttpRequest - * object. This allows one to programmatically enable Ajax requests. A common - * use case for this feature is when uploading files through an IFrame element. - * By specifying "X-Requested-With" as a request parameter the IFrame - * request will be handled like a normal Ajax request. - * - * @return true if this is an Ajax request, false otherwise - */ - public boolean isAjaxRequest() { - return ClickUtils.isAjaxRequest(getRequest()); - } - - /** - * Return true if the request contains the named attribute. - * - * @param name the name of the request attribute - * @return true if the request contains the named attribute - */ - public boolean hasRequestAttribute(String name) { - return (getRequestAttribute(name) != null); - } - - /** - * Return the named request attribute, or null if not defined. - * - * @param name the name of the request attribute - * @return the named request attribute, or null if not defined - */ - public Object getRequestAttribute(String name) { - return request.getAttribute(name); - } - - /** - * This method will set the named object in the HTTP request. - * - * @param name the storage name for the object in the request - * @param value the object to store in the request - */ - public void setRequestAttribute(String name, Object value) { - request.setAttribute(name, value); - } - - /** - * Return true if the request contains the named parameter. - * - * @param name the name of the request parameter - * @return true if the request contains the named parameter - */ - public boolean hasRequestParameter(String name) { - if (name == null) { - throw new IllegalArgumentException("hasRequestParameter was called" - + " with null name argument. This is often caused when a" - + " Control binds to a request parameter, but its name was not" - + " set."); - } - return (getRequestParameter(name) != null); - } - - /** - * Return the named request parameter. This method supports - * "multipart/form-data" POST requests and should be used in - * preference to the HttpServletRequest method - * getParameter(). - * - * @see org.apache.click.control.Form#onProcess() - * @see #isMultipartRequest() - * @see #getFileItemMap() - * - * @param name the name of the request parameter - * @return the value of the request parameter. - */ - public String getRequestParameter(String name) { - if (name == null) { - throw new IllegalArgumentException("getRequestParameter was called" - + " with null name argument. This is often caused when a" - + " Control binds to a request parameter, but its name was not" - + " set."); - } - return request.getParameter(name); - } - - /** - * Returns an array of String objects containing all of the values the given - * request parameter has, or null if the parameter does not exist. - * - * @param name a String containing the name of the parameter whose - * value is requested - * @return an array of String objects containing the parameter's values - */ - public String[] getRequestParameterValues(String name) { - if (name == null) { - throw new IllegalArgumentException("getRequestParameter was called" - + " with null name argument. This is often caused when a" - + " Control binds to a request parameter, but its name was not" - + " set."); - } - return request.getParameterValues(name); - } - - /** - * Return the named session attribute, or null if not defined. - *

- * If the session is not defined this method will return null, and a - * session will not be created. - *

- * This method supports {@link FlashAttribute} which when accessed are then - * removed from the session. - * - * @param name the name of the session attribute - * @return the named session attribute, or null if not defined - */ - public Object getSessionAttribute(String name) { - if (hasSession()) { - Object object = getSession().getAttribute(name); - - if (object instanceof FlashAttribute) { - FlashAttribute flashObject = (FlashAttribute) object; - object = flashObject.getValue(); - removeSessionAttribute(name); - } - - return object; - } else { - return null; - } - } - - /** - * This method will set the named object in the HttpSession. - *

- * This method will create a session if one does not already exist. - * - * @param name the storage name for the object in the session - * @param value the object to store in the session - */ - public void setSessionAttribute(String name, Object value) { - getSession().setAttribute(name, value); - } - - /** - * Remove the named attribute from the session. If the session does not - * exist or the name is null, this method does nothing. - * - * @param name of the attribute to remove from the session - */ - public void removeSessionAttribute(String name) { - if (hasSession() && name != null) { - getSession().removeAttribute(name); - } - } - - /** - * Return true if there is a session and it contains the named attribute. - * - * @param name the name of the attribute - * @return true if the session contains the named attribute - */ - public boolean hasSessionAttribute(String name) { - return (getSessionAttribute(name) != null); - } - - /** - * Return true if a HttpSession exists, or false otherwise. - * - * @return true if a HttpSession exists, or false otherwise - */ - public boolean hasSession() { - return (request.getSession(false) != null); - } - - /** - * This method will set the named object as a flash HttpSession object. - *

- * The flash object will exist in the session until it is accessed once, - * and then removed. Flash objects are typically used to display a message - * once. - * - * @param name the storage name for the object in the session - * @param value the object to store in the session - */ - public void setFlashAttribute(String name, Object value) { - getSession().setAttribute(name, new FlashAttribute(value)); - } - - /** - * Return the cookie for the given name or null if not found. - * - * @param name the name of the cookie - * @return the cookie for the given name or null if not found - */ - public Cookie getCookie(String name) { - return ClickUtils.getCookie(getRequest(), name); - } - - /** - * Return the cookie value for the given name or null if not found. - * - * @param name the name of the cookie - * @return the cookie value for the given name or null if not found - */ - public String getCookieValue(String name) { - return ClickUtils.getCookieValue(getRequest(), name); - } - - /** - * Sets the given cookie value in the servlet response with the path "/". - *

- * @see ClickUtils#setCookie(HttpServletRequest, HttpServletResponse, String, String, int, String) - * - * @param name the cookie name - * @param value the cookie value - * @param maxAge the maximum age of the cookie in seconds. A negative - * value will expire the cookie at the end of the session, while 0 will delete - * the cookie. - * @return the Cookie object created and set in the response - */ - public Cookie setCookie(String name, String value, int maxAge) { - return ClickUtils.setCookie(getRequest(), - getResponse(), - name, - value, - maxAge, - "/"); - } - - /** - * Invalidate the specified cookie and delete it from the response object. - * Deletes only cookies mapped against the root "/" path. - * - * @see ClickUtils#invalidateCookie(HttpServletRequest, HttpServletResponse, String) - * - * @param name the name of the cookie you want to delete. - */ - public void invalidateCookie(String name) { - ClickUtils.invalidateCookie(getRequest(), getResponse(), name); - } - - /** - * Return a new Page instance for the given path. - *

- * This method can be used to create a target page for the - * {@link org.apache.click.Page#setForward(org.apache.click.Page)}, for example: - * - *

-     * UserEdit userEdit = (UserEdit) getContext().createPage("/user-edit.htm");
-     * userEdit.setUser(user);
-     *
-     * setForward(userEdit); 
- * - * The given page path must start with a '/'. - * - * @param path the Page path as configured in the click.xml file - * @return a new Page object - * @throws IllegalArgumentException if the Page is not found - */ - @SuppressWarnings("unchecked") - public T createPage(String path) { - if (path == null || path.length() == 0) { - throw new IllegalArgumentException("page path cannot be null or empty"); - } - - if (path.charAt(0) != '/') { - throw new IllegalArgumentException("page path must start with a '/'"); - } - return (T) clickServlet.createPage(path, request); - } - - /** - * Return a new Page instance for the given class. - *

- * This method can be used to create a target page for the - * {@link org.apache.click.Page#setForward(org.apache.click.Page)}, for example: - * - *

-     * UserEdit userEdit = (UserEdit) getContext().createPage(UserEdit.class);
-     * userEdit.setUser(user);
-     *
-     * setForward(userEdit); 
- * - * @param pageClass the Page class as configured in the click.xml file - * @return a new Page object - * @throws IllegalArgumentException if the Page is not found, or is not - * configured with a unique path - */ - public T createPage(Class pageClass) { - return clickServlet.createPage(pageClass, request); - } - - /** - * Return the path for the given page Class. - * - * @param pageClass the class of the Page to lookup the path for - * @return the path for the given page Class - * @throws IllegalArgumentException if the Page Class is not configured - * with a unique path - */ - public String getPagePath(Class pageClass) { - return clickServlet.getConfigService().getPagePath(pageClass); - } - - /** - * Return the page Class for the given path. - * - * @param path the page path - * @return the page class for the given path - * @throws IllegalArgumentException if the Page Class for the path is not - * found - */ - public Class getPageClass(String path) { - return clickServlet.getConfigService().getPageClass(path); - } - - /** - * Return the Click application mode value:   - * ["production", "profile", "development", "debug", "trace"]. - * - * @return the application mode value - */ - public String getApplicationMode() { - return clickServlet.getConfigService().getApplicationMode(); - } - - /** - * Return the Click application charset or ISO-8859-1 if not is defined. - *

- * The charset is defined in click.xml through the charset attribute - * on the click-app element. - * - *

-     * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-     * <click-app charset="UTF-8">
-     *    ..
-     * </click-app> 
- * - * @return the application charset or ISO-8859-1 if not defined - */ - public String getCharset() { - String charset = clickServlet.getConfigService().getCharset(); - if (charset == null) { - charset = "ISO-8859-1"; - } - return charset; - } - - /** - * Return a new messages map for the given baseClass (a page or control) - * and the given global resource bundle name. - * - * @param baseClass the target class - * @param globalResource the global resource bundle name - * @return a new messages map with the messages for the target. - */ - public Map createMessagesMap(Class baseClass, String globalResource) { - MessagesMapService messagesMapService = - clickServlet.getConfigService().getMessagesMapService(); - - return messagesMapService.createMessagesMap(baseClass, globalResource, getLocale()); - } - - /** - * Returns a map of FileItem arrays keyed on request parameter - * name for "multipart" POST requests (file uploads). Thus each map entry - * will consist of one or more FileItem objects. - * - * @return map of FileItem arrays keyed on request parameter name - * for "multipart" POST requests - */ - public Map getFileItemMap() { - return findClickRequestWrapper(request).getFileItemMap(); - } - - /** - * Returns the value of a request parameter as a FileItem, for - * "multipart" POST requests (file uploads), or null if the parameter - * is not found. - *

- * If there were multivalued parameters in the request (ie two or more - * file upload fields with the same name), the first fileItem - * in the array is returned. - * - * @param name the name of the parameter of the fileItem to retrieve - * - * @return the fileItem for the specified name - */ - public FileItem getFileItem(String name) { - Object value = findClickRequestWrapper(request).getFileItemMap().get(name); - - if (value != null) { - if (value instanceof FileItem[]) { - FileItem[] array = (FileItem[]) value; - if (array.length >= 1) { - return array[0]; - } - } else if (value instanceof FileItem) { - return (FileItem) value; - } - } - return null; - } - - /** - * Return the users Locale. - *

- * If the users Locale is stored in their session this will be returned. - * Else if the click-app configuration defines a default Locale this - * value will be returned, otherwise the request's Locale will be returned. - *

- * To override the default request Locale set the users Locale using the - * {@link #setLocale(Locale)} method. - *

- * Pages and Controls obtain the users Locale using this method. - * - * @return the users Locale in the session, or if null the request Locale - */ - public Locale getLocale() { - Locale locale = (Locale) getSessionAttribute(LOCALE); - - if (locale == null) { - - if (clickServlet.getConfigService().getLocale() != null) { - locale = clickServlet.getConfigService().getLocale(); - - } else { - locale = getRequest().getLocale(); - } - } - - return locale; - } - - /** - * This method stores the given Locale in the users session. If the given - * Locale is null, the "locale" attribute will be removed from the session. - *

- * The Locale object is stored in the session using the {@link #LOCALE} - * key. - * - * @param locale the Locale to store in the users session using the key - * "locale" - */ - public void setLocale(Locale locale) { - if (locale == null && hasSession()) { - getSession().removeAttribute(LOCALE); - } else { - setSessionAttribute(LOCALE, locale); - } - } - - /** - * Return true if the request is a multi-part content type POST request. - * - * @return true if the request is a multi-part content type POST request - */ - public boolean isMultipartRequest() { - return ClickUtils.isMultipartRequest(request); - } - - /** - * Return a rendered Velocity template and model for the given - * class and model data. - *

- * This method will merge the class .htm Velocity template and - * model data using the applications Velocity Engine. - *

- * An example of the class template resolution is provided below: - *

-     * // Full class name
-     * com.mycorp.control.CustomTextField
-     *
-     * // Template path name
-     * /com/mycorp/control/CustomTextField.htm 
- * - * Example method usage: - *
-     * public String toString() {
-     *     Map model = getModel();
-     *     return getContext().renderTemplate(getClass(), model);
-     * } 
- * - * @param templateClass the class to resolve the template for - * @param model the model data to merge with the template - * @return rendered Velocity template merged with the model data - * @throws RuntimeException if an error occurs - */ - @SuppressWarnings("unchecked") - public String renderTemplate(Class templateClass, Map model) { - - if (templateClass == null) { - String msg = "Null templateClass parameter"; - throw new IllegalArgumentException(msg); - } - - String templatePath = templateClass.getName(); - templatePath = '/' + templatePath.replace('.', '/') + ".htm"; - - return renderTemplate(templatePath, model); - } - - /** - * Return a rendered Velocity template and model data. - *

- * Example method usage: - *

-     * public String toString() {
-     *     Map model = getModel();
-     *     return getContext().renderTemplate("/custom-table.htm", model);
-     * } 
- * - * @param templatePath the path of the Velocity template to render - * @param model the model data to merge with the template - * @return rendered Velocity template merged with the model data - * @throws RuntimeException if an error occurs - */ - public String renderTemplate(String templatePath, Map model) { - - if (templatePath == null) { - String msg = "Null templatePath parameter"; - throw new IllegalArgumentException(msg); - } - - if (model == null) { - String msg = "Null model parameter"; - throw new IllegalArgumentException(msg); - } - - StringWriter stringWriter = new StringWriter(1024); - - TemplateService templateService = - clickServlet.getConfigService().getTemplateService(); - - try { - templateService.renderTemplate(templatePath, model, stringWriter); - - } catch (Exception e) { - String msg = "Error occurred rendering template: " - + templatePath + "\n"; - clickServlet.getConfigService().getLogService().error(msg, e); - - throw new RuntimeException(e); - } - - return stringWriter.toString(); - } - - // ------------------------------------------------ Package Private Methods - - /** - * Return the stack data structure where Context's are stored. - * - * @return stack data structure where Context's are stored - */ - static Context.ContextStack getContextStack() { - Context.ContextStack contextStack = THREAD_LOCAL_CONTEXT_STACK.get(); - - if (contextStack == null) { - contextStack = new Context.ContextStack(2); - THREAD_LOCAL_CONTEXT_STACK.set(contextStack); - } - - return contextStack; - } - - /** - * Adds the specified Context on top of the Context stack. - * - * @param context a context instance - */ - static void pushThreadLocalContext(Context context) { - getContextStack().push(context); - } - - /** - * Remove and return the context instance on top of the context stack. - * - * @return the context instance on top of the context stack - */ - static Context popThreadLocalContext() { - Context.ContextStack contextStack = getContextStack(); - Context context = contextStack.pop(); - - if (contextStack.isEmpty()) { - THREAD_LOCAL_CONTEXT_STACK.remove(); - } - - return context; - } - - /** - * Find and return the ClickRequestWrapper that is wrapped by the specified - * request. - * - * @param request the servlet request that wraps the ClickRequestWrapper - * @return the wrapped servlet request - */ - static ClickRequestWrapper findClickRequestWrapper(ServletRequest request) { - - while (!(request instanceof ClickRequestWrapper) - && request instanceof HttpServletRequestWrapper - && request != null) { - request = ((HttpServletRequestWrapper) request).getRequest(); - } - - if (request instanceof ClickRequestWrapper) { - return (ClickRequestWrapper) request; - } else { - throw new IllegalStateException("Click request is not present"); - } - } - - // -------------------------------------------------- Inner Classes - - /** - * Provides an unsynchronized Context Stack. - */ - static final class ContextStack extends ArrayList { - - /** Serialization version indicator. */ - private static final long serialVersionUID = 1L; - - /** - * Create a new ContextStack with the given initial capacity. - * - * @param initialCapacity specify initial capacity of this stack - */ - private ContextStack(int initialCapacity) { - super(initialCapacity); - } - - /** - * Pushes the Context onto the top of this stack. - * - * @param context the Context to push onto this stack - * @return the Context pushed on this stack - */ - private Context push(Context context) { - add(context); - - return context; - } - - /** - * Removes and return the Context at the top of this stack. - * - * @return the Context at the top of this stack - */ - private Context pop() { - Context context = peek(); - - remove(size() - 1); - - return context; - } - - /** - * Looks at the Context at the top of this stack without removing it. - * - * @return the Context at the top of this stack - */ - private Context peek() { - int length = size(); - - if (length == 0) { - String msg = "No Context available on ThreadLocal Context Stack"; - throw new RuntimeException(msg); - } - - return get(length - 1); - } - } - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/Control.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/Control.java deleted file mode 100644 index 96d0c9d548..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/Control.java +++ /dev/null @@ -1,496 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -import java.io.Serializable; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import jakarta.servlet.ServletContext; - -import org.openidentityplatform.openam.click.element.Element; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -/** - * Provides the interface for Page controls. Controls are also referred to - * as components or widgets. - *

- * When a Page request event is processed Controls may perform server side event - * processing through their {@link #onProcess()} method. Controls are generally - * rendered in a Page by calling their toString() method. - *

- * The Control execution sequence is illustrated below: - *

- * - * - *

HTML HEAD Elements

- * - * Control HTML HEAD elements can be included in the Page by overriding the - * {@link #getHeadElements()} method. - *

- * Below is an example of a custom TextField control specifying that the - * custom.js file should be included in the HTML HEADer: - * - *

- * public class CustomField extends TextField {
- *
- *     public List getHeadElements() {
- *         if(headElements == null) {
- *             // If headElements is null, create default headElements
- *             headElements = super.getHeadElements();
- *
- *             // Add a new JavaScript Import Element for the "/custom.js" script
- *             headElements.add(new JsImport("/click/custom.js"));
- *         }
- *         return headElements;
- *     }
- *
- *     ..
- * } 
- * - * - *

Deploying Resources

- * - * The Click framework uses the Velocity Tools WebappResourceLoader for loading templates. - * This avoids issues associate with using the Velocity ClasspathResourceLoader and - * FileResourceLoader on J2EE application servers. - * To make preconfigured resources (templates, JavaScript, stylesheets, etc.) - * available to web applications Click automatically deploys configured classpath - * resources to the /click directory at startup - * (existing files will not be overwritten). - *

- * Click supports two ways of deploying pre-configured resources. The recommended - * deployment strategy (which also the simplest) relies on packaging resources - * into a special folder of the JAR, called 'META-INF/resources'. At - * startup time Click will scan this folder for resources and deploy them to the - * web application. This deployment strategy is the same approach taken by the - * Servlet 3.0 specification. Please see the section - * Deploying Custom Resources - * for more details. - *

- * An alternative approach to deploying static resources on startup is provided - * by the Control interface through the {@link #onDeploy(ServletContext)} method. - *

- * Continuing our example, the CustomField control deploys its - * custom.js file to the /click directory: - * - *

- * public class CustomField extends TextField {
- *     ..
- *
- *     public void onDeploy(ServletContext servletContext) {
- *         ClickUtils.deployFile
- *             (servletContext, "/com/mycorp/control/custom.js", "click");
- *     }
- * } 
- * - * Controls using the onDeploy() method must be registered in the - * application WEB-INF/click.xml for them to be invoked. - * For example: - * - *
- * <click-app>
- *   <pages package="com.mycorp.page" automapping="true"/>
- *
- *   <controls>
- *     <control classname="com.mycorp.control.CustomField"/>
- *   </controls>
- * </click-app> 
- * - * When the Click application starts up it will deploy any control elements - * defined in the following files in sequential order: - *
    - *
  • /click-controls.xml - *
  • /extras-controls.xml - *
  • WEB-INF/click.xml - *
- * - * Please note {@link org.apache.click.control.AbstractControl} provides - * a default implementation of the Control interface to make it easier for - * developers to create their own controls. - * - * @see org.apache.click.util.PageImports - */ -public interface Control extends Serializable { - - /** - * The global control messages bundle name:   click-control. - */ - public static final String CONTROL_MESSAGES = "click-control"; - - /** - * Return the Page request Context of the Control. - * - * @deprecated getContext() is now obsolete on the Control interface, - * but will still be available on AbstractControl: - * {@link org.apache.click.control.AbstractControl#getContext()} - * - * @return the Page request Context - */ - public Context getContext(); - - /** - * Return the list of HEAD {@link org.apache.click.element.Element elements} - * to be included in the page. Example HEAD elements include - * {@link org.apache.click.element.JsImport JsImport}, - * {@link org.apache.click.element.JsScript JsScript}, - * {@link org.apache.click.element.CssImport CssImport} and - * {@link org.apache.click.element.CssStyle CssStyle}. - *

- * Controls can contribute their own list of HEAD elements by implementing - * this method. - *

- * The recommended approach when implementing this method is to use - * lazy loading to ensure the HEAD elements are only added - * once and when needed. For example: - * - *

-     * public MyControl extends AbstractControl {
-     *
-     *     public List getHeadElements() {
-     *         // Use lazy loading to ensure the JS is only added the
-     *         // first time this method is called.
-     *         if (headElements == null) {
-     *             // Get the head elements from the super implementation
-     *             headElements = super.getHeadElements();
-     *
-     *             // Include the control's external JavaScript resource
-     *             JsImport jsImport = new JsImport("/mycorp/mycontrol/mycontrol.js");
-     *             headElements.add(jsImport);
-     *
-     *             // Include the control's external Css resource
-     *             CssImport cssImport = new CssImport("/mycorp/mycontrol/mycontrol.css");
-     *             headElements.add(cssImport);
-     *         }
-     *         return headElements;
-     *     }
-     * } 
- * - * Alternatively one can add the HEAD elements in the Control's constructor: - * - *
-     * public MyControl extends AbstractControl {
-     *
-     *     public MyControl() {
-     *
-     *         JsImport jsImport = new JsImport("/mycorp/mycontrol/mycontrol.js");
-     *         getHeadElements().add(jsImport);
-     *
-     *         CssImport cssImport = new CssImport("/mycorp/mycontrol/mycontrol.css");
-     *         getHeadHeaders().add(cssImport);
-     *     }
-     * } 
- * - * One can also add HEAD elements from event handler methods such as - * {@link #onInit()}, {@link #onProcess()}, {@link #onRender()} - * etc. - *

- * The order in which JS and CSS files are included will be preserved in the - * page. - *

- * Note: this method must never return null. If no HEAD elements - * are available this method must return an empty {@link java.util.List}. - *

- * Also note: a common problem when overriding getHeadElements in - * subclasses is forgetting to call super.getHeadElements. Consider - * carefully whether you should call super.getHeadElements or not. - * - * @return the list of HEAD elements to be included in the page - */ - public List getHeadElements(); - - /** - * Return HTML element identifier attribute "id" value. - * - * {@link org.apache.click.control.AbstractControl#getId()} - * - * @return HTML element identifier attribute "id" value - */ - public String getId(); - - /** - * Set the controls event listener. - *

- * The method signature of the listener is:

    - *
  • must have a valid Java method name
  • - *
  • takes no arguments
  • - *
  • returns a boolean value
  • - *
- *

- * An example event listener method would be: - * - *

-     * public boolean onClick() {
-     *     System.out.println("onClick called");
-     *     return true;
-     * } 
- * - * @param listener the listener object with the named method to invoke - * @param method the name of the method to invoke - * - * @deprecated this method is now obsolete on the Control interface, but - * will still be available on AbstractControl: - * {@link org.apache.click.control.AbstractControl#setListener(java.lang.Object, java.lang.String)} - */ - public void setListener(Object listener, String method); - - /** - * Return the localized messages Map of the Control. - * - * @return the localized messages Map of the Control - */ - public Map getMessages(); - - /** - * Return the name of the Control. Each control name must be unique in the - * containing Page model or the containing Form. - * - * @return the name of the control - */ - public String getName(); - - /** - * Set the name of the Control. Each control name must be unique in the - * containing Page model or the parent container. - *

- * Please note: changing the name of a Control after it has been - * added to its parent container is undefined. Thus it is best not - * to change the name of a Control once its been set. - * - * @param name of the control - * @throws IllegalArgumentException if the name is null - */ - public void setName(String name); - - /** - * Return the parent of the Control. - * - * @return the parent of the Control - */ - public Object getParent(); - - /** - * Set the parent of the Control. - * - * @param parent the parent of the Control - */ - public void setParent(Object parent); - - /** - * The on deploy event handler, which provides classes the - * opportunity to deploy static resources when the Click application is - * initialized. - *

- * For example: - *

-     * public void onDeploy(ServletContext servletContext) throws IOException {
-     *     ClickUtils.deployFile
-     *         (servletContext, "/com/mycorp/control/custom.js", "click");
-     * } 
- * Please note: a common problem when overriding onDeploy in - * subclasses is forgetting to call super.onDeploy. Consider - * carefully whether you should call super.onDeploy or not. - *

- * Click also supports an alternative deployment strategy which relies on - * packaging resource (stylesheets, JavaScript, images etc.) following a - * specific convention. See the section - * Deploying Custom Resources - * for further details. - * - * @param servletContext the servlet context - */ - public void onDeploy(ServletContext servletContext); - - /** - * The on initialize event handler. Each control will be initialized - * before its {@link #onProcess()} method is called. - *

- * {@link org.apache.click.control.Container} implementations should recursively - * invoke the onInit method on each of their child controls ensuring that - * all controls receive this event. - *

- * Please note: a common problem when overriding onInit in - * subclasses is forgetting to call super.onInit(). Consider - * carefully whether you should call super.onInit() or not, - * especially for {@link org.apache.click.control.Container}s which by default - * call onInit on all their child controls as well. - */ - public void onInit(); - - /** - * The on process event handler. Each control will be processed when the - * Page is requested. - *

- * ClickServlet will process all Page controls in the order they were added - * to the Page. - *

- * {@link org.apache.click.control.Container} implementations should recursively - * invoke the onProcess method on each of their child controls ensuring that - * all controls receive this event. However when a control onProcess method - * return false, no other controls onProcess method should be invoked. - *

- * When a control is processed it should return true if the Page should - * continue event processing, or false if no other controls should be - * processed and the {@link org.apache.click.Page#onGet()} or {@link Page#onPost()} methods - * should not be invoked. - *

- * Please note: a common problem when overriding onProcess in - * subclasses is forgetting to call super.onProcess(). Consider - * carefully whether you should call super.onProcess() or not, - * especially for {@link org.apache.click.control.Container}s which by default - * call onProcess on all their child controls as well. - * - * @return true to continue Page event processing or false otherwise - */ - public boolean onProcess(); - - /** - * The on render event handler. This event handler is invoked prior to the - * control being rendered, and is useful for providing pre rendering logic. - *

- * The on render method is typically used to populate tables performing some - * database intensive operation. By putting the intensive operations in the - * on render method they will not be performed if the user navigates away - * to a different page. - *

- * {@link org.apache.click.control.Container} implementations should recursively - * invoke the onRender method on each of their child controls ensuring that - * all controls receive this event. - *

- * Please note: a common problem when overriding onRender in - * subclasses is forgetting to call super.onRender(). Consider - * carefully whether you should call super.onRender() or not, - * especially for {@link org.apache.click.control.Container}s which by default - * call onRender on all their child controls as well. - */ - public void onRender(); - - /** - * The on destroy request event handler. Control classes should use this - * method to add any resource clean up code. - *

- * This method is guaranteed to be called before the Page object reference - * goes out of scope and is available for garbage collection. - *

- * {@link org.apache.click.control.Container} implementations should recursively - * invoke the onDestroy method on each of their child controls ensuring that - * all controls receive this event. - *

- * Please note: a common problem when overriding onDestroy in - * subclasses is forgetting to call super.onDestroy(). Consider - * carefully whether you should call super.onDestroy() or not, - * especially for {@link org.apache.click.control.Container}s which by default - * call onDestroy on all their child controls as well. - */ - public void onDestroy(); - - /** - * Render the control's HTML representation to the specified buffer. The - * control's {@link java.lang.Object#toString()} method should delegate the - * rendering to the render method for improved performance. - *

- * An example implementation: - *

-     * public class Border extends AbstractContainer {
-     *
-     *     public String toString() {
-     *         int estimatedSizeOfControl = 100;
-     *         HtmlStringBuffer buffer = new HtmlStringBuffer(estimatedSizeOfControl);
-     *         render(buffer);
-     *         return buffer.toString();
-     *     }
-     *
-     *     /**
-     *      * @see Control#render(HtmlStringBuffer)
-     *      */
-     *     public void render(HtmlStringBuffer buffer) {
-     *         buffer.elementStart("div");
-     *         buffer.appendAttribute("name", getName());
-     *         buffer.closeTag();
-     *         buffer.append(getField());
-     *         buffer.elementEnd("div");
-     *     }
-     * }
-     * 
- * - * @param buffer the specified buffer to render the control's output to - */ - public void render(HtmlStringBuffer buffer); - - /** - * Returns true if this control has any - * Behaviors registered, false otherwise. - * - * @return true if this control has any - * Behaviors registered, false otherwise - */ - public boolean hasBehaviors(); - - /** - * Returns the list of behaviors for this control. - * - * @return the list with this control behaviors. - */ - public Set getBehaviors(); - - /** - * Returns true if this control is an Ajax target, false - * otherwise. - *

- * In order for a Control to be considered as an Ajax target it must be - * registered through {@link org.apache.click.ControlRegistry#registerAjaxTarget(org.apache.click.Control) ControlRegistry.registerAjaxTarget}. - *

- * When the Click handles an Ajax request it iterates the Controls - * registered with the {@link org.apache.click.ControlRegistry ControlRegistry} - * and checks if one of them is the Ajax target by calling - * {@link #isAjaxTarget(org.openidentityplatform.openam.click.Context) isAjaxTarget}. If isAjaxTarget - * returns true, Click will process that Control's {@link #getBehaviors() behaviors}. - *

- * Please note: there can only be one target control, so the first - * Control that is identified as the Ajax target will be processed, the other - * controls will be skipped. - *

- * The most common way to check whether a Control is the Ajax target is to - * check if its {@link #getId ID} is available as a request parameter: - * - *

-     * public MyControl extends AbstractControl {
-     *
-     *     ...
-     *
-     *     public boolean isAjaxTarget(Context context) {
-     *         return context.hasRequestParameter(getId());
-     *     }
-     * } 
- * - * Not every scenario can be covered through an ID attribute though. For example - * if an ActionLink is rendered multiple times on the same page, it cannot have an - * ID attribute, as that would lead to duplicate IDs, which isn't allowed by - * the HTML specification. Control implementations has to cater for how the - * control will be targeted. In the case of ActionLink it might check against - * its id, and if that isn't available check against its name. - * - * @param context the request context - * @return true if this control is an Ajax target, false - * otherwise - */ - public boolean isAjaxTarget(Context context); -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/ControlRegistry.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/ControlRegistry.java deleted file mode 100644 index ecc271bb5e..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/ControlRegistry.java +++ /dev/null @@ -1,525 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import org.openidentityplatform.openam.click.service.ConfigService; -import org.openidentityplatform.openam.click.service.LogService; -import org.apache.commons.lang.Validate; - -/** - * Provides a centralized registry where Controls can be registered and interact - * with the Click runtime. - *

- * The primary use of the ControlRegistry is for Controls to register themselves - * as potential targets of Ajax requests - * (If a control is an Ajax request target, it's onProcess() - * method is invoked while other controls are not processed). - *

- * Registering controls as Ajax targets serves a dual purpose. In addition to - * being potential Ajax targets, these controls will have all their Behaviors - * processed by the Click runtime. - *

- * Thus the ControlRegistry provides the Click runtime with easy access to Controls - * that want to be processed for Ajax requests. It also provides quick access - * to Controls that have Behaviors, and particularly AjaxBehaviors that want to - * handle and respond to Ajax requests. - * - *

Register Control as an Ajax Target

- * Below is an example of a Control registering itself as an Ajax target: - * - *
- * public class AbstractControl implements Control {
- *
- *     public void addBehavior(Behavior behavior) {
- *         getBehaviors().add(behavior);
- *         // Adding a behavior also registers the Control as an Ajax target
- *         ControlRegistry.registerAjaxTarget(this);
- *     }
- * } 
- * - *

Register Interceptor

- * Below is an example of a Container registering a Behavior in order to intercept - * and decorate its child controls: - * - *
- * public class MyContainer extends AbstractContainer {
- *
- *     public void onInit() {
- *         Behavior controlInterceptor = getInterceptor();
- *         ControlRegistry.registerInterceptor(this, controlInterceptor);
- *     }
- *
- *     private Behavior getInterceptor() {
- *         Behavior controlInterceptor = new Behavior() {
- *
- *             // This method is invoked before the controls are rendered to the client
- *             public void preResponse(Control source) {
- *                 // Here we can add a CSS class attribute to each child control
- *                 addCssClassToChildControls();
- *             }
- *
- *             // This method is invoked before the HEAD elements are retrieved for each Control
- *             public void preRenderHeadElements(Control source) {
- *             }
- *
- *             // This method is invoked before the Control onDestroy event
- *             public void preDestroy(Control source) {
- *             }
- *         };
- *         return controlInterceptor;
- *     }
- * } 
- */ -public class ControlRegistry { - - // Constants -------------------------------------------------------------- - - /** The thread local registry holder. */ - private static final ThreadLocal THREAD_LOCAL_REGISTRY_STACK = - new ThreadLocal<>(); - - // Variables -------------------------------------------------------------- - - /** The set of Ajax target controls. */ - Set ajaxTargetControls; - - /** The list of registered interceptors. */ - List interceptors; - - /** The application log service. */ - LogService logger; - - // Constructors ----------------------------------------------------------- - - /** - * Construct the ControlRegistry with the given ConfigService. - * - * @param configService the click application configuration service - */ - public ControlRegistry(ConfigService configService) { - this.logger = configService.getLogService(); - } - - // Public Methods --------------------------------------------------------- - - /** - * Return the thread local ControlRegistry instance. - * - * @return the thread local ControlRegistry instance. - * @throws RuntimeException if a ControlRegistry is not available on the - * thread - */ - public static ControlRegistry getThreadLocalRegistry() { - return getRegistryStack().peek(); - } - - /** - * Returns true if a ControlRegistry instance is available on the current - * thread, false otherwise. - *

- * Unlike {@link #getThreadLocalRegistry()} this method can safely be used - * and will not throw an exception if a ControlRegistry is not available on - * the current thread. - * - * @return true if an ControlRegistry instance is available on the - * current thread, false otherwise - */ - public static boolean hasThreadLocalRegistry() { - ControlRegistry.RegistryStack registryStack = THREAD_LOCAL_REGISTRY_STACK.get(); - if (registryStack == null) { - return false; - } - return !registryStack.isEmpty(); - } - - /** - * Register the control to be processed by the Click runtime if the control - * is the Ajax target. A control is an Ajax target if the - * {@link Control#isAjaxTarget(Context)} method returns true. - * Once a target control is identified, Click invokes its - * {@link Control#onProcess()} method. - *

- * This method serves a dual purpose as all controls registered here - * will also have their Behaviors (if any) processed. Processing - * {@link Behavior Behaviors} - * means their interceptor methods will be invoked during the request - * life cycle, passing the control as the argument. - * - * @param control the control to register as an Ajax target - */ - public static void registerAjaxTarget(Control control) { - if (control == null) { - throw new IllegalArgumentException("control cannot be null"); - } - - ControlRegistry instance = getThreadLocalRegistry(); - instance.internalRegisterAjaxTarget(control); - } - - /** - * Register a control event interceptor for the given Control and Behavior. - * The control will be passed as the source control to the Behavior - * interceptor methods: - * {@link org.apache.click.Behavior#preRenderHeadElements(org.apache.click.Control) preRenderHeadElements(Control)}, - * {@link org.apache.click.Behavior#preResponse(org.apache.click.Control) preResponse(Control)} and - * {@link org.apache.click.Behavior#preDestroy(org.apache.click.Control) preDestroy(Control)}. - * - * @param control the interceptor source control - * @param controlInterceptor the control interceptor to register - */ - public static void registerInterceptor(Control control, Behavior controlInterceptor) { - if (control == null) { - throw new IllegalArgumentException("control cannot be null"); - } - if (controlInterceptor == null) { - throw new IllegalArgumentException("control interceptor cannot be null"); - } - - ControlRegistry instance = getThreadLocalRegistry(); - instance.internalRegisterInterceptor(control, controlInterceptor); - } - - // Protected Methods ------------------------------------------------------ - - /** - * Allow the registry to handle the error that occurred. - * - * @param throwable the error which occurred during processing - */ - protected void errorOccurred(Throwable throwable) { - clear(); - } - - // Package Private Methods ------------------------------------------------ - - /** - * Remove all interceptors and ajax target controls from this registry. - */ - void clear() { - if (hasInterceptors()) { - getInterceptors().clear(); - } - - if (hasAjaxTargetControls()) { - getAjaxTargetControls().clear(); - } - } - - /** - * Register the AJAX target control. - * - * @param control the AJAX target control - */ - void internalRegisterAjaxTarget(Control control) { - Validate.notNull(control, "Null control parameter"); - getAjaxTargetControls().add(control); - } - - /** - * Register the source control and associated interceptor. - * - * @param source the interceptor source control - * @param controlInterceptor the control interceptor to register - */ - void internalRegisterInterceptor(Control source, Behavior controlInterceptor) { - Validate.notNull(source, "Null source parameter"); - Validate.notNull(controlInterceptor, "Null interceptor parameter"); - - ControlRegistry.InterceptorHolder interceptorHolder = new ControlRegistry.InterceptorHolder(source, controlInterceptor); - - // Guard against adding duplicate interceptors - List localInterceptors = getInterceptors(); - if (!localInterceptors.contains(interceptorHolder)) { - localInterceptors.add(interceptorHolder); - } - } - - void processPreResponse(Context context) { - if (hasAjaxTargetControls()) { - for (Control control : getAjaxTargetControls()) { - for (Behavior behavior : control.getBehaviors()) { - behavior.preResponse(control); - } - } - } - - if (hasInterceptors()) { - for (ControlRegistry.InterceptorHolder interceptorHolder : getInterceptors()) { - Behavior interceptor = interceptorHolder.getInterceptor(); - Control control = interceptorHolder.getControl(); - interceptor.preResponse(control); - } - } - } - - void processPreRenderHeadElements(Context context) { - if (hasAjaxTargetControls()) { - for (Control control : getAjaxTargetControls()) { - for (Behavior behavior : control.getBehaviors()) { - behavior.preRenderHeadElements(control); - } - } - } - - if (hasInterceptors()) { - for (ControlRegistry.InterceptorHolder interceptorHolder : getInterceptors()) { - Behavior interceptor = interceptorHolder.getInterceptor(); - Control control = interceptorHolder.getControl(); - interceptor.preRenderHeadElements(control); - } - } - } - - void processPreDestroy(Context context) { - if (hasAjaxTargetControls()) { - for (Control control : getAjaxTargetControls()) { - for (Behavior behavior : control.getBehaviors()) { - behavior.preDestroy(control); - } - } - } - - if (hasInterceptors()) { - for (ControlRegistry.InterceptorHolder interceptorHolder : getInterceptors()) { - Behavior interceptor = interceptorHolder.getInterceptor(); - Control control = interceptorHolder.getControl(); - interceptor.preDestroy(control); - } - } - } - - /** - * Checks if any AJAX target control have been registered. - */ - boolean hasAjaxTargetControls() { - if (ajaxTargetControls == null || ajaxTargetControls.isEmpty()) { - return false; - } - return true; - } - - /** - * Return the set of potential Ajax target controls. - * - * @return the set of potential Ajax target controls - */ - Set getAjaxTargetControls() { - if (ajaxTargetControls == null) { - ajaxTargetControls = new LinkedHashSet(); - } - return ajaxTargetControls; - } - - /** - * Checks if any control interceptors have been registered. - */ - boolean hasInterceptors() { - if (interceptors == null || interceptors.isEmpty()) { - return false; - } - return true; - } - - /** - * Return the set of registered control interceptors. - * - * @return set of registered interceptors - */ - List getInterceptors() { - if (interceptors == null) { - interceptors = new ArrayList<>(); - } - return interceptors; - } - - /** - * Adds the specified ControlRegistry on top of the registry stack. - * - * @param controlRegistry the ControlRegistry to add - */ - static void pushThreadLocalRegistry(ControlRegistry controlRegistry) { - getRegistryStack().push(controlRegistry); - } - - /** - * Remove and return the controlRegistry instance on top of the - * registry stack. - * - * @return the controlRegistry instance on top of the registry stack - */ - static ControlRegistry popThreadLocalRegistry() { - ControlRegistry.RegistryStack registryStack = getRegistryStack(); - ControlRegistry controlRegistry = registryStack.pop(); - - if (registryStack.isEmpty()) { - THREAD_LOCAL_REGISTRY_STACK.set(null); - } - - return controlRegistry; - } - - static ControlRegistry.RegistryStack getRegistryStack() { - ControlRegistry.RegistryStack registryStack = THREAD_LOCAL_REGISTRY_STACK.get(); - - if (registryStack == null) { - registryStack = new ControlRegistry.RegistryStack(2); - THREAD_LOCAL_REGISTRY_STACK.set(registryStack); - } - - return registryStack; - } - - /** - * Provides an unsynchronized Stack. - */ - static class RegistryStack extends ArrayList { - - /** Serialization version indicator. */ - private static final long serialVersionUID = 1L; - - /** - * Create a new RegistryStack with the given initial capacity. - * - * @param initialCapacity specify initial capacity of this stack - */ - private RegistryStack(int initialCapacity) { - super(initialCapacity); - } - - /** - * Pushes the ControlRegistry onto the top of this stack. - * - * @param controlRegistry the ControlRegistry to push onto this stack - * @return the ControlRegistry pushed on this stack - */ - private ControlRegistry push(ControlRegistry controlRegistry) { - add(controlRegistry); - - return controlRegistry; - } - - /** - * Removes and return the ControlRegistry at the top of this stack. - * - * @return the ControlRegistry at the top of this stack - */ - private ControlRegistry pop() { - ControlRegistry controlRegistry = peek(); - - remove(size() - 1); - - return controlRegistry; - } - - /** - * Looks at the ControlRegistry at the top of this stack without - * removing it. - * - * @return the ControlRegistry at the top of this stack - */ - private ControlRegistry peek() { - int length = size(); - - if (length == 0) { - String msg = "No ControlRegistry available on ThreadLocal Registry Stack"; - throw new RuntimeException(msg); - } - - return get(length - 1); - } - } - - static class InterceptorHolder { - - private Behavior interceptor; - - private Control control; - - public InterceptorHolder(Control control, Behavior interceptor) { - this.control = control; - this.interceptor = interceptor; - } - - public Behavior getInterceptor() { - return interceptor; - } - - public void setInterceptor(Behavior interceptor) { - this.interceptor = interceptor; - } - - public Control getControl() { - return control; - } - - public void setControl(Control control) { - this.control = control; - } - - /** - * @see Object#equals(java.lang.Object) - * - * @param o the reference object with which to compare - * @return true if this object equals the given object - */ - @Override - public boolean equals(Object o) { - - //1. Use the == operator to check if the argument is a reference to this object. - if (o == this) { - return true; - } - - //2. Use the instanceof operator to check if the argument is of the correct type. - if (!(o instanceof ControlRegistry.InterceptorHolder)) { - return false; - } - - //3. Cast the argument to the correct type. - ControlRegistry.InterceptorHolder that = (ControlRegistry.InterceptorHolder) o; - - boolean equals = this.control == null ? that.control == null : this.control.equals(that.control); - if (!equals) { - return false; - } - - return this.interceptor == null ? that.interceptor == null : this.interceptor.equals(that.interceptor); - } - - /** - * @see java.lang.Object#hashCode() - * - * @return the InterceptorHolder hashCode - */ - @Override - public int hashCode() { - int result = 17; - result = 37 * result + (control == null ? 0 : control.hashCode()); - result = 37 * result + (interceptor == null ? 0 : interceptor.hashCode()); - return result; - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/Page.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/Page.java deleted file mode 100644 index aa65de9e1d..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/Page.java +++ /dev/null @@ -1,1362 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -import java.io.Serializable; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import org.openidentityplatform.openam.click.element.Element; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.apache.click.util.Format; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.openidentityplatform.openam.click.util.PageImports; -import org.apache.commons.lang.StringUtils; - -/** - * Provides the Page request event handler class. - *

- * The Page class plays a central role in Click applications defining how the - * application's pages are processed and rendered. All application pages - * must extend the base Page class, and provide a no arguments constructor. - * - *

Page Execution Sequence

- * - * The default Page execution path for a GET request is: - *
    - *
  1. - * no-args constructor invoked to create a new Page instance. - * At this point no dependencies have been injected into the Page, and any - * request information is not available. You should put any "static" - * page initialization code, which doesn't depend upon request information, - * in the constructor. This will enable subclasses to have this code - * automatically initialized when they are created. - *
  2. - *
  3. - * {@link #format} property is set - *
  4. - *
  5. - * {@link #headers} property is set - *
  6. - *
  7. - * {@link #path} property is set - *
  8. - *
  9. - * {@link #onSecurityCheck()} method called to check whether the page should - * be processed. This method should return true if the Page should continue - * to be processed, or false otherwise. - *
  10. - *
  11. - * {@link #onInit()} method called to complete the initialization of the page - * after all the dependencies have been set. This is where you should put - * any "dynamic" page initialization code which depends upon the request or any - * other dependencies. - *

    - * Form and field controls must be fully initialized by the time this method - * has completed. - *

  12. - *
  13. - * ClickServlet processes all the page {@link #controls} - * calling their {@link org.apache.click.Control#onProcess()} method. If any of these - * controls return false, continued control and page processing will be aborted. - *
  14. - *
  15. - * {@link #onGet()} method called for any additional GET related processing. - *

    - * Form and field controls should NOT be created or initialized at this - * point as the control processing stage has already been completed. - *

  16. - *
  17. - * {@link #onRender()} method called for any pre-render processing. This - * method is often use to perform database queries to load information for - * rendering tables. - *

    - * Form and field controls should NOT be created or initialized at this - * point as the control processing stage has already been completed. - *

  18. - *
  19. - * ClickServlet renders the page merging the {@link #model} with the - * Velocity template defined by the {@link #getTemplate()} property. - *
  20. - *
  21. - * {@link #onDestroy()} method called to clean up any resources. This method - * is guaranteed to be called, even if an exception occurs. You can use - * this method to close resources like database connections or Hibernate - * sessions. - *
  22. - *
- * - * For POST requests the default execution path is identical, except the - * {@link #onPost()} method is called instead of {@link #onGet()}. The POST - * request page execution sequence is illustrated below: - *

- * - * - *

- * A good way to see the page event execution order is to view the log when - * the application mode is set to trace: - * - *

- * [Click] [debug] GET http://localhost:8080/quickstart/home.htm
- * [Click] [trace]    invoked: HomePage.<<init>>
- * [Click] [trace]    invoked: HomePage.onSecurityCheck() : true
- * [Click] [trace]    invoked: HomePage.onInit()
- * [Click] [trace]    invoked: HomePage.onGet()
- * [Click] [trace]    invoked: HomePage.onRender()
- * [Click] [info ]    renderTemplate: /home.htm - 6 ms
- * [Click] [trace]    invoked: HomePage.onDestroy()
- * [Click] [info ] handleRequest:  /home.htm - 24 ms  
- * - *

Rendering Pages

- * - * When a Velocity template is rendered the ClickServlet uses Pages: - *
    - *
  • {@link #getTemplate()} to find the Velocity template.
  • - *
  • {@link #model} to populate the Velocity Context
  • - *
  • {@link #format} to add to the Velocity Context
  • - *
  • {@link #getContentType()} to set as the HttpServletResponse content type
  • - *
  • {@link #headers} to set as the HttpServletResponse headers
  • - *
- * - * These Page properties are also used when rendering JSP pages. - */ -public class Page implements Serializable { - - private static final long serialVersionUID = 1L; - - /** - * The global page messages bundle name:   click-page. - */ - public static final String PAGE_MESSAGES = "click-page"; - - /** - * The Page action request parameter:   "pageAction". - */ - public final static String PAGE_ACTION = "pageAction"; - - // Instance Variables ----------------------------------------------------- - - /** The list of page controls. */ - protected List controls; - - /** - * The list of page HTML HEAD elements including: Javascript imports, - * Css imports, inline Javascript and inline Css. - */ - protected List headElements; - - /** The Velocity template formatter object. */ - protected Format format; - - /** The forward path. */ - protected String forward; - - /** The HTTP response headers. */ - protected Map headers; - - /** The map of localized page resource messages. **/ - protected transient Map messages; - - /** - * The page model. For Velocity templates the model is used to populate the - * Velocity context. For JSP pages the model values are set as named - * request attributes. - */ - protected Map model = new HashMap(); - - /** The Page header imports. */ - protected transient PageImports pageImports; - - /** The path of the page template to render. */ - protected String path; - - /** The redirect path. */ - protected String redirect; - - /** - * The page is stateful and should be saved to the users HttpSession - * between requests, default value is false. - * - * @deprecated stateful pages are not supported anymore, use stateful - * Controls instead - */ - protected boolean stateful; - - /** The path of the page border template to render.*/ - protected String template; - - /** - * Indicates whether Control head elements should be included in the - * page template, default value is true. - */ - protected boolean includeControlHeadElements = true; - - // Event Handlers --------------------------------------------------------- - - /** - * The on Security Check event handler. This event handler is invoked after - * the pages constructor has been called and all the page properties have - * been set. - *

- * Security check provides the Page an opportunity to check the users - * security credentials before processing the Page. - *

- * If security check returns true the Page is processed as - * normal. If the method returns then no other event handlers are invoked - * (except onDestroy() and no page controls are processed. - *

- * If the method returns false, the forward or redirect property should be - * set to send the request to another Page. - *

- * By default this method returns true, subclass may override this method - * to provide their security authorization/authentication mechanism. - * - * @return true by default, subclasses may override this method - */ - public boolean onSecurityCheck() { - return true; - } - - /** - * The on Initialization event handler. This event handler is invoked after - * the {@link #onInit()} method has been called. - *

- * Subclasses should place any initialization code which has dependencies - * on the context or other properties in this method. Generally light - * weight initialization code should be placed in the Pages constructor. - *

- * Time consuming operations such as fetching the results of a database - * query should not be placed in this method. These operations should be - * performed in the {@link #onRender()}, {@link #onGet()} or - * {@link #onPost()} methods so that other event handlers may take - * alternative execution paths without performing these expensive operations. - *

- * Please Note however the qualifier for the previous statement is - * that all form and field controls must be fully initialized before they - * are processed, which is after the onInit() method has - * completed. After this point their onProcess() methods will be - * invoked by the ClickServlet. - *

- * Select controls in particular must have their option list values populated - * before the form is processed otherwise field validations cannot be performed. - *

- * For initializing page controls the best practice is to place all the - * control creation code in the pages constructor, and only place any - * initialization code in the onInit() method which has an external - * dependency to the context or some other object. By following this practice - * it is easy to see what code is "design time" initialization code and what - * is "runtime initialization code". - *

- * When subclassing pages which also use the onInit() method is - * is critical you call the super.onInit() method first, for - * example: - *

-     * public void onInit() {
-     *     super.onInit();
-     *
-     *     // Initialization code
-     *     ..
-     * } 
- */ - public void onInit() { - } - - /** - * The on Get request event handler. This event handler is invoked if the - * HTTP request method is "GET". - *

- * The event handler is invoked after {@link #onSecurityCheck()} has been - * called and all the Page {@link #controls} have been processed. If either - * the security check or one of the controls cancels continued event - * processing the onGet() method will not be invoked. - * - *

Important Note

- * - * Form and field controls should NOT be created - * or initialized at this point as the control processing stage has already - * been completed. Select option list values should also be populated - * before the control processing stage is performed so that they can - * validate the submitted values. - */ - public void onGet() { - } - - /** - * The on Post request event handler. This event handler is invoked if the - * HTTP request method is "POST". - *

- * The event handler is invoked after {@link #onSecurityCheck()} has been - * called and all the Page {@link #controls} have been processed. If either - * the security check or one of the controls cancels continued event - * processing the onPost() method will not be invoked. - * - *

Important Note

- * - * Form and field controls should NOT be created - * or initialized at this point as the control processing stage has already - * been completed. Select option list values should also be populated - * before the control processing stage is performed so that they can - * validate the submitted values. - */ - public void onPost() { - } - - /** - * The on render event handler. This event handler is invoked prior to the - * page being rendered. - *

- * This method will not be invoked if either the security check or one of - * the controls cancels continued event processing. - *

- * The on render method is typically used to populate tables performing some - * database intensive operation. By putting the intensive operations in the - * on render method they will not be performed if the user navigates away - * to a different page. - *

- * If you have code which you are using in both the onGet() and - * onPost() methods, use the onRender() method instead. - * - *

Important Note

- * - * Form and field controls should NOT be created - * or initialized at this point as the control processing stage has already - * been completed. Select option list values should also be populated - * before the control processing stage is performed so that they can - * validate the submitted values. - */ - public void onRender() { - } - - /** - * The on Destroy request event handler. Subclasses may override this method - * to add any resource clean up code. - *

- * This method is guaranteed to be called before the Page object reference - * goes out of scope and is available for garbage collection. - */ - public void onDestroy() { - } - - // Public Methods --------------------------------------------------------- - - /** - * Add the control to the page. The control will be added to the page model - * using the control name as the key. The Controls parent property will - * also be set to the page instance. - *

- * Please note: if the page contains a control with the same name as - * the given control, that control will be replaced by the given control. - * If a control has no name defined it cannot be replaced. - * - * @param control the control to add to the page - * @throws IllegalArgumentException if the control is null or if the name - * of the control is not defined - */ - public void addControl(Control control) { - if (control == null) { - throw new IllegalArgumentException("Null control parameter"); - } - if (StringUtils.isBlank(control.getName())) { - throw new IllegalArgumentException("Control name not defined: " - + control.getClass()); - } - - // Check if page already contains a named value - Object currentValue = getModel().get(control.getName()); - if (currentValue != null && currentValue instanceof Control) { - Control currentControl = (Control) currentValue; - replaceControl(currentControl, control); - return; - } - - // Note: set parent first as setParent might veto further processing - control.setParent(this); - - getControls().add(control); - addModel(control.getName(), control); - } - - /** - * Remove the control from the page. The control will be removed from the - * pages model and the control parent property will be set to null. - * - * @param control the control to remove - * @throws IllegalArgumentException if the control is null, or if the name - * of the control is not defined - */ - public void removeControl(Control control) { - if (control == null) { - throw new IllegalArgumentException("Null control parameter"); - } - if (StringUtils.isBlank(control.getName())) { - throw new IllegalArgumentException("Control name not defined"); - } - - getControls().remove(control); - getModel().remove(control.getName()); - - control.setParent(null); - } - - /** - * Return the list of page Controls. - * - * @return the list of page Controls - */ - public List getControls() { - if (controls == null) { - controls = new ArrayList(); - } - return controls; - } - - /** - * Return true if the page has any controls defined. - * - * @return true if the page has any controls defined - */ - public boolean hasControls() { - return (controls != null) && !controls.isEmpty(); - } - - /** - * Return the request context of the page. - * - * @return the request context of the page - */ - public Context getContext() { - return Context.getThreadLocalContext(); - } - - /** - * Return the HTTP response content type. By default this method returns - * "text/html". - *

- * If the request specifies a character encoding via - * If {@link jakarta.servlet.ServletRequest#getCharacterEncoding()} - * then this method will return "text/html; charset=encoding". - *

- * The ClickServlet uses the pages content type for setting the - * HttpServletResponse content type. - * - * @return the HTTP response content type - */ - public String getContentType() { - String charset = getContext().getRequest().getCharacterEncoding(); - - if (charset == null) { - return "text/html"; - - } else { - return "text/html; charset=" + charset; - } - } - - /** - * Return the Velocity template formatter object. - *

- * The ClickServlet adds the format object to the Velocity context using - * the key "format" so that it can be used in the page template. - * - * @return the Velocity template formatter object - */ - public Format getFormat() { - return format; - } - - /** - * Set the Velocity template formatter object. - * - * @param value the Velocity template formatter object. - */ - public void setFormat(Format value) { - format = value; - } - - /** - * Return the path to forward the request to. - *

- * If the {@link #forward} property is not null it will be used to forward - * the request to in preference to rendering the template defined by the - * {@link #path} property. The request is forwarded using the - * RequestDispatcher. - *

- * See also {@link #getPath()}, {@link #getRedirect()} - * - * @return the path to forward the request to - */ - public String getForward() { - return forward; - } - - /** - * Set the path to forward the request to. - *

- * If the {@link #forward} property is not null it will be used to forward - * the request to in preference to rendering the template defined by the - * {@link #path} property. The request is forwarded using the Servlet - * RequestDispatcher. - *

- * If forward paths start with a "/" - * character the forward path is - * relative to web applications root context, otherwise the path is - * relative to the requests current location. - *

- * For example given a web application deployed to context mycorp - * with the pages: - *

-     *  /index.htm
-     *  /customer/search.htm
-     *  /customer/details.htm
-     *  /customer/management/add-customer.htm 
- * - * To forward to the customer search.htm page from - * the web app root you could set forward as - * setForward("/customer/search.htm") - * or setForward("customer/search.htm"). - *

- * If a user was currently viewing the add-customer.htm - * to forward to customer details.htm you could - * set forward as - * setForward("/customer/details.htm") - * or setForward("../details.htm"). - *

- * See also {@link #setPath(String)}, {@link #setRedirect(String)} - * - * @param value the path to forward the request to - */ - public void setForward(String value) { - forward = value; - } - - /** - * The Page instance to forward the request to. The given Page object - * must have a valid {@link #path} defined, as the {@link #path} specifies - * the location to forward to. - * - * @see #setForward(java.lang.String) - * - * @param page the Page object to forward the request to. - */ - public void setForward(Page page) { - if (page == null) { - throw new IllegalArgumentException("Null page parameter"); - } - if (page.getPath() == null) { - throw new IllegalArgumentException("Page has no path defined"); - } - setForward(page.getPath()); - getContext().setRequestAttribute(ClickServlet.FORWARD_PAGE, page); - } - - /** - * Set the request to forward to the given page class. - * - * @see #setForward(java.lang.String) - * - * @param pageClass the class of the Page to forward the request to - * @throws IllegalArgumentException if the Page Class is not configured - * with a unique path - */ - public void setForward(Class pageClass) { - String target = getContext().getPagePath(pageClass); - - // If page class maps to a jsp, convert to htm which allows ClickServlet - // to process the redirect - if (target != null && target.endsWith(".jsp")) { - target = StringUtils.replaceOnce(target, ".jsp", ".htm"); - } - setForward(target); - } - - /** - * Return the map of HTTP header to be set in the HttpServletResponse. - * - * @return the map of HTTP header to be set in the HttpServletResponse - */ - public Map getHeaders() { - if (headers == null) { - headers = new HashMap(); - } - return headers; - } - - /** - * Return true if the page has headers, false otherwise. - * - * @return true if the page has headers, false otherwise - */ - public boolean hasHeaders() { - return headers != null && !headers.isEmpty(); - } - - /** - * Set the named header with the given value. Value can be either a String, - * Date or Integer. - * - * @param name the name of the header - * @param value the value of the header, either a String, Date or Integer - */ - public void setHeader(String name, Object value) { - if (name == null) { - throw new IllegalArgumentException("Null header name parameter"); - } - - getHeaders().put(name, value); - } - - /** - * Set the map of HTTP header to be set in the HttpServletResponse. - * - * @param value the map of HTTP header to be set in the HttpServletResponse - */ - public void setHeaders(Map value) { - headers = value; - } - - /** - * @deprecated use the new {@link #getHeadElements()} instead - * - * @return the HTML includes statements for the control stylesheet and - * JavaScript files - */ - public final String getHtmlImports() { - throw new UnsupportedOperationException("Use getHeadElements instead"); - } - - /** - * Return the list of HEAD {@link org.apache.click.element.Element elements} - * to be included in the page. Example HEAD elements include - * {@link org.apache.click.element.JsImport JsImport}, - * {@link org.apache.click.element.JsScript JsScript}, - * {@link org.apache.click.element.CssImport CssImport} and - * {@link org.apache.click.element.CssStyle CssStyle}. - *

- * Pages can contribute their own list of HEAD elements by overriding - * this method. - *

- * The recommended approach when overriding this method is to use - * lazy loading to ensure the HEAD elements are only added - * once and when needed. For example: - * - *

-     * public MyPage extends Page {
-     *
-     *     public List getHeadElements() {
-     *         // Use lazy loading to ensure the JS is only added the
-     *         // first time this method is called.
-     *         if (headElements == null) {
-     *             // Get the head elements from the super implementation
-     *             headElements = super.getHeadElements();
-     *
-     *             // Include the page's external Javascript resource
-     *             JsImport jsImport = new JsImport("/mycorp/js/mypage.js");
-     *             headElements.add(jsImport);
-     *
-     *             // Include the page's external Css resource
-     *             CssImport cssImport = new CssImport("/mycorp/js/mypage.css");
-     *             headElements.add(cssImport);
-     *         }
-     *         return headElements;
-     *     }
-     * } 
- * - * Alternatively one can add the HEAD elements in the Page constructor: - * - *
-     * public MyPage extends Page {
-     *
-     *     public MyPage() {
-     *         JsImport jsImport = new JsImport("/mycorp/js/mypage.js");
-     *         getHeadElements().add(jsImport);
-     *
-     *         CssImport cssImport = new CssImport("/mycorp/js/mypage.css");
-     *         getHeadElements().add(cssImport);
-     *     }
-     * } 
- * - * One can also add HEAD elements from event handler methods such as - * {@link #onInit()}, {@link #onGet()}, {@link #onPost()}, {@link #onRender()} - * etc. - *

- * The order in which JS and CSS files are included will be preserved in the - * page. - *

- * Note: this method must never return null. If no HEAD elements - * are available this method must return an empty {@link java.util.List}. - *

- * Also note: a common problem when overriding getHeadElements in - * subclasses is forgetting to call super.getHeadElements. Consider - * carefully whether you should call super.getHeadElements or not. - * - * @return the list of HEAD elements to be included in the page - */ - public List getHeadElements() { - if (headElements == null) { - headElements = new ArrayList(2); - } - return headElements; - } - - /** - * Return the localized Page resource message for the given resource - * name or null if not found. The resource message returned will use the - * Locale obtained from the Context. - *

- * Pages can define text properties files to store localized messages. These - * properties files must be stored on the Page class path with a name - * matching the class name. For example: - *

- * The page class: - *

-     *  package com.mycorp.pages;
-     *
-     *  public class Login extends Page {
-     *     .. 
- * - * The page class property filenames and their path: - *
-     *  /com/mycorp/pages/Login.properties
-     *  /com/mycorp/pages/Login_en.properties
-     *  /com/mycorp/pages/Login_fr.properties 
- * - * Page messages can also be defined in the optional global messages - * bundle: - * - *
-     *  /click-page.properties 
- * - * To define global page messages simply add click-page.properties - * file to your application's class path. Message defined in this properties - * file will be available to all of your application pages. - *

- * Note messages in your page class properties file will override any - * messages in the global click-page.properties file. - *

- * Page messages can be accessed directly in the page template using - * the $messages reference. For examples: - * - *

-     * $messages.title 
- * - * Please see the {@link org.apache.click.util.MessagesMap} adaptor for more - * details. - * - * @param name resource name of the message - * @return the named localized message for the page or null if no message - * was found - */ - public String getMessage(String name) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - return getMessages().get(name); - } - - /** - * Return the formatted page message for the given resource name and - * message format arguments or null if no message was found. The resource - * message returned will use the Locale obtained from the Context. - *

- * {@link #getMessage(java.lang.String)} is invoked to retrieve the message - * for the specified name. - * - * @param name resource name of the message - * @param args the message arguments to format - * @return the named localized message for the page or null if no message - * was found - */ - public String getMessage(String name, Object... args) { - String value = getMessage(name); - - return MessageFormat.format(value, args); - } - - /** - * Return a Map of localized messages for the Page. The messages returned - * will use the Locale obtained from the Context. - * - * @see #getMessage(String) - * - * @return a Map of localized messages for the Page - * @throws IllegalStateException if the context for the Page has not be set - */ - public Map getMessages() { - if (messages == null) { - if (getContext() != null) { - messages = getContext().createMessagesMap(getClass(), PAGE_MESSAGES); - - } else { - String msg = "Context not set cannot initialize messages"; - throw new IllegalStateException(msg); - } - } - return messages; - } - - /** - * Add the named object value to the Pages model map. - *

- * Please note: if the Page contains an object with a matching name, - * that object will be replaced by the given value. - * - * @param name the key name of the object to add - * @param value the object to add - * @throws IllegalArgumentException if the name or value parameters are - * null - */ - public void addModel(String name, Object value) { - if (name == null) { - String msg = "Cannot add null parameter name to " - + getClass().getName() + " model"; - throw new IllegalArgumentException(msg); - } - if (value == null) { - String msg = "Cannot add null " + name + " parameter " - + "to " + getClass().getName() + " model"; - throw new IllegalArgumentException(msg); - } - - getModel().put(name, value); - } - - /** - * Return the Page's model map. The model is used populate the - * Velocity Context with is merged with the page template before rendering. - * - * @return the Page's model map - */ - public Map getModel() { - return model; - } - - /** - * Return the Page header imports. - *

- * PageImports are used define the CSS and JavaScript imports and blocks - * to be included in the page template. - *

- * The PageImports object will be included in the Page template when the - * following methods are invoked: - *

    - *
  • {@link ClickServlet#createTemplateModel(Page)} - for template pages
  • - *
  • {@link ClickServlet#setRequestAttributes(Page)} - for JSP pages
  • - *
- *

- * If you need to tailor the page imports rendered, override this method - * and modify the PageImports object returned. - *

- * If you need to create a custom PageImports, override the method - * {@link ClickServlet#createPageImports(org.openidentityplatform.openam.click.Page)} - * - * @deprecated use the new {@link #getHeadElements()} instead - * - * @return the Page header imports - */ - public PageImports getPageImports() { - return pageImports; - } - - /** - * Set the Page header imports. - *

- * PageImports are used define the CSS and JavaScript imports and blocks - * to be included in the page template. - *

- * The PageImports references will be included in the Page model when the - * following methods are invoked: - *

    - *
  • {@link ClickServlet#createTemplateModel(Page)} - for template pages
  • - *
  • {@link ClickServlet#setRequestAttributes(Page)} - for JSP pages
  • - *
- *

- * If you need to tailor the page imports rendered, override the - * {@link #getPageImports()} method and modify the PageImports object - * returned. - *

- * If you need to create a custom PageImports, override the method - * {@link ClickServlet#createPageImports(org.openidentityplatform.openam.click.Page)} - * - * @deprecated use the new {@link #getHeadElements()} instead - * - * @param pageImports the new pageImports instance to set - */ - public void setPageImports(PageImports pageImports) { - this.pageImports = pageImports; - } - - /** - * Return the path of the Template or JSP to render. - *

- * If this method returns null, Click will not perform any rendering. - * This is useful when you want to stream or write directly to the - * HttpServletResponse. - *

- * See also {@link #getForward()}, {@link #getRedirect()} - * - * @return the path of the Template or JSP to render - */ - public String getPath() { - return path; - } - - /** - * Set the path of the Template or JSP to render. - *

- * By default Click will set the path to the requested page url. Meaning - * if the url /edit-customer.htm is requested, path will be set - * to /edit-customer.htm. - *

- * Here is an example if you want to change the path to a different Template: - *

- *

-     * public void onGet() {
-     *     setPath("/some-other-template.htm");
-     * }
- * And here is an example if you want to change the path to a different JSP. - *
-     * public void onGet() {
-     *     setPath("/some-other-jsp.jsp");
-     * }
- *

- * If path is set to null, Click will not perform any rendering. - * This is useful when you want to stream or write directly to the - * HttpServletResponse. - *

- * See also {@link #setForward(String)}, {@link #setRedirect(String)} - * - * @param value the path of the Template or JSP to render - */ - public void setPath(String value) { - path = value; - } - - /** - * Return the path to redirect the request to. - *

- * If the {@link #redirect} property is not null it will be used to redirect - * the request in preference to {@link #forward} or {@link #path} properties. - * The request is redirected to using the HttpServletResponse.setRedirect() - * method. - *

- * See also {@link #getForward()}, {@link #getPath()} - * - * @return the path to redirect the request to - */ - public String getRedirect() { - return redirect; - } - - /** - * Return true if the page is stateful and should be saved in the users - * HttpSession between requests, default value is false. - * - * @deprecated stateful pages are not supported anymore, use stateful - * Controls instead - * - * @return true if the page is stateful and should be saved in the users - * session - */ - public boolean isStateful() { - return stateful; - } - - /** - * Set whether the page is stateful and should be saved in the users - * HttpSession between requests. - *

- * Click will synchronize on the page instance. This ensures that if - * multiple requests arrive from the same user for the page, only one - * request can access the page at a time. - *

- * Stateful pages are stored in the HttpSession using the key - * page.getClass().getName(). - *

- * It is worth noting that Click checks a Page's stateful property after - * each request. Thus it becomes possible to enable a stateful Page for a - * number of requests and then setting it to false again at which - * point Click will remove the Page from the HttpSession, freeing up memory - * for the server. - * - * @deprecated stateful pages are not supported anymore, use stateful - * Controls instead - * - * @param stateful the flag indicating whether the page should be saved - * between user requests - */ - public void setStateful(boolean stateful) { - this.stateful = stateful; - if (isStateful()) { - getContext().getSession(); - } - } - - /** - * Return true if the Control head elements should be included in the page - * template, false otherwise. Default value is true. - * - * @see #setIncludeControlHeadElements(boolean) - * - * @return true if the Control head elements should be included in the page - * template, false otherwise - */ - public boolean isIncludeControlHeadElements() { - return includeControlHeadElements; - } - - /** - * Set whether the Control head elements should be included in the page - * template. - *

- * By setting this value to false, Click won't include Control's - * {@link #getHeadElements() head elements}, however the Page head elements - * will still be included. - *

- * This allows one to create a single JavaScript and CSS resource file for - * the entire Page which increases performance, since the browser only has - * to load one resource, instead of multiple resources. - *

- * Below is an example: - * - *

-     * public class HomePage extends Page {
-     *
-     *     private Form form = new Form("form");
-     *
-     *     public HomePage() {
-     *         // Indicate that Controls should not import their head elements
-     *         setIncludeControlHeadElements(false);
-     *
-     *         form.add(new EmailField("email");
-     *         addControl(form);
-     *     }
-     *
-     *     // Include the Page JavaScript and CSS resources
-     *     public List getHeadElements() {
-     *         if (headElements == null) {
-     *             headElements = super.getHeadElements();
-     *
-     *             // Include the Page CSS resource. This resource should combine
-     *             // all the CSS necessary for the page
-     *             headElements.add(new CssImport("/assets/css/home-page.css"));
-     *
-     *             // Include the Page JavaScript resource. This resource should
-     *             // combine all the JavaScript necessary for the page
-     *             headElements.add(new JsImport("/assets/js/home-page.js"));
-     *         }
-     *         return headElements;
-     *     }
-     * } 
- * - * @param includeControlHeadElements flag indicating whether Control - * head elements should be included in the page - */ - public void setIncludeControlHeadElements(boolean includeControlHeadElements) { - this.includeControlHeadElements = includeControlHeadElements; - } - - /** - * Set the location to redirect the request to. - *

- * If the {@link #redirect} property is not null it will be used to redirect - * the request in preference to the {@link #forward} and {@link #path} - * properties. The request is redirected using the HttpServletResponse.setRedirect() - * method. - *

- * If the redirect location begins with a "/" - * character the redirect location will be prefixed with the web applications - * context path. Note if the given location is already prefixed - * with the context path, Click won't add it a second time. - *

- * For example if an application is deployed to the context - * "mycorp" calling - * setRedirect("/customer/details.htm") - * will redirect the request to: - * "/mycorp/customer/details.htm" - *

- * If the redirect location does not begin with a "/" - * character the redirect location will be used as specified. Thus if the - * location is http://somehost.com/myapp/customer.jsp, - * Click will redirect to that location. - *

- * JSP note: when redirecting to a JSP template keep in mind that the - * JSP template won't be processed by Click, as ClickServlet is mapped to - * *.htm. Instead JSP templates are processed by the Servlet - * container JSP engine. - *

- * So if you have a situation where a Page Class - * (Customer.class) is mapped to the JSP - * ("/customer.jsp") and you want to redirect to - * Customer.class, you could either redirect to - * ("/customer.htm") or - * use the alternative redirect utility {@link #setRedirect(java.lang.Class)}. - *

- * Please note that Click will url encode the location by invoking - * response.encodeRedirectURL(location) before redirecting. - *

- * See also {@link #setRedirect(java.lang.String, java.util.Map)}, - * {@link #setForward(String)}, {@link #setPath(String)} - * - * @param location the path to redirect the request to - */ - public void setRedirect(String location) { - setRedirect(location, null); - } - - /** - * Set the request to redirect to the give page class. - * - * @see #setRedirect(java.lang.String) - * - * @param pageClass the class of the Page to redirect the request to - * @throws IllegalArgumentException if the Page Class is not configured - * with a unique path - */ - public void setRedirect(Class pageClass) { - setRedirect(pageClass, null); - } - - /** - * Set the request to redirect to the given location and append - * the map of request parameters to the location URL. - *

- * The map keys will be used as the request parameter names and the map - * values will be used as the request parameter values. For example: - * - *

-     * public boolean onSave() {
-     *     // Specify redirect parameters
-     *     Map parameters = new HashMap();
-     *     parameters.put("customerId", getCustomerId());
-     *
-     *     // Set redirect to customer.htm page
-     *     setRedirect("/customer.htm", parameters);
-     *
-     *     return false;
-     * } 
- * - * To render multiple parameter values for the same parameter name, specify - * the values as a String[] array. For example: - * - *
-     * public boolean onSave() {
-     *
-     *     // Specify an array of customer IDs
-     *     String[] ids = {"123", "456", "789"};
-     *
-     *     // Specify redirect parameters
-     *     Map parameters = new HashMap();
-     *     parameters.put("customerIds", ids);
-     *
-     *     // Set redirect to customer.htm page
-     *     setRedirect("/customer.htm", parameters);
-     *
-     *     return false;
-     * } 
- * - * @see #setRedirect(java.lang.String) - * - * @param location the path to redirect the request to - * @param params the map of request parameter name and value pairs - */ - public void setRedirect(String location, Map params) { - Context context = getContext(); - if (StringUtils.isNotBlank(location)) { - if (location.charAt(0) == '/') { - String contextPath = context.getRequest().getContextPath(); - - // Guard against adding duplicate context path - if (!location.startsWith(contextPath + '/')) { - location = contextPath + location; - } - } - } - - if (params != null && !params.isEmpty()) { - HtmlStringBuffer buffer = new HtmlStringBuffer(); - - Iterator> i = params.entrySet().iterator(); - while (i.hasNext()) { - Map.Entry entry = i.next(); - String paramName = entry.getKey(); - Object paramValue = entry.getValue(); - - // Check for multivalued parameter - if (paramValue instanceof String[]) { - String[] paramValues = (String[]) paramValue; - for (int j = 0; j < paramValues.length; j++) { - buffer.append(paramName); - buffer.append("="); - buffer.append(ClickUtils.encodeUrl(paramValues[j], context)); - if (j < paramValues.length - 1) { - buffer.append("&"); - } - } - } else { - if (paramValue != null) { - buffer.append(paramName); - buffer.append("="); - buffer.append(ClickUtils.encodeUrl(paramValue, context)); - } - } - if (i.hasNext()) { - buffer.append("&"); - } - } - - if (buffer.length() > 0) { - if (location.contains("?")) { - location += "&" + buffer.toString(); - } else { - location += "?" + buffer.toString(); - } - } - } - - redirect = location; - } - - /** - * Set the request to redirect to the given page class and and append - * the map of request parameters to the page URL. - *

- * The map keys will be used as the request parameter names and the map - * values will be used as the request parameter values. - * - * @see #setRedirect(java.lang.String, java.util.Map) - * @see #setRedirect(java.lang.String) - * - * @param pageClass the class of the Page to redirect the request to - * @param params the map of request parameter name and value pairs - * @throws IllegalArgumentException if the Page Class is not configured - * with a unique path - */ - public void setRedirect(Class pageClass, - Map params) { - - String target = getContext().getPagePath(pageClass); - - // If page class maps to a jsp, convert to htm which allows ClickServlet - // to process the redirect - if (target != null && target.endsWith(".jsp")) { - target = StringUtils.replaceOnce(target, ".jsp", ".htm"); - } - - setRedirect(target, params); - } - - /** - * Return the path of the page border template to render, by default this - * method returns {@link #getPath()}. - *

- * Pages can override this method to return an alternative border page - * template. This is very useful when implementing an standardized look and - * feel for a web site. The example below provides a BorderedPage base Page - * which other site templated Pages should extend. - * - *

-     * public class BorderedPage extends Page {
-     *     public String getTemplate() {
-     *         return "border.htm";
-     *     }
-     * } 
- * - * The BorderedPage returns the page border template "border.htm": - * - *
-     * <html>
-     *   <head>
-     *     <title> $title </title>
-     *     <link rel="stylesheet" type="text/css" href="style.css" title="Style"/>
-     *   </head>
-     *   <body>
-     *
-     *     <h1> $title </h1>
-     *     <hr/>
-     *
-     *     #parse( $path )
-     *
-     *   </body>
-     * </html> 
- * - * Other pages insert their content into this template, via their - * {@link #path} property using the Velocity - * #parse - * directive. Note the $path value is automatically - * added to the VelocityContext by the ClickServlet. - * - * @return the path of the page template to render, by default returns - * {@link #getPath()} - */ - public String getTemplate() { - return template == null ? getPath() : template; - } - - /** - * Set the page border template path. - *

- * Note: if this value is not set, {@link #getTemplate()} will default - * to {@link #getPath()}. - * - * @param template the border template path - */ - public void setTemplate(String template) { - this.template = template; - } - - // Private methods -------------------------------------------------------- - - /** - * Replace the current control with the new control. - * - * @param currentControl the control currently contained in the page - * @param newControl the control to replace the current control container in - * the page - * - * @throws IllegalStateException if the currentControl is not contained in - * the page - */ - private void replaceControl(Control currentControl, Control newControl) { - - // Current control and new control are referencing the same object - // so we exit early - if (currentControl == newControl) { - return; - } - - int controlIndex = getControls().indexOf(currentControl); - if (controlIndex == -1) { - throw new IllegalStateException("Cannot replace the given control" - + " because it is not present in the page"); - } - - // Note: set parent first since setParent might veto further processing - newControl.setParent(this); - currentControl.setParent(null); - - // Set control to current control index - getControls().set(controlIndex, newControl); - - addModel(newControl.getName(), newControl); - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/PageInterceptor.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/PageInterceptor.java deleted file mode 100644 index 42b4829629..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/PageInterceptor.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -/** - * Provides a Page life cycle interceptor. Classes implementing this interface - * can be used to listen for key page life cycle events and abort further page - * processing if required. - *

- * PageInterceptors can be used for many different purposes including: - *

    - *
  • enforcing application wide page security policies
  • - *
  • injecting dependencies into page objects
  • - *
  • logging and profiling page performance
  • - *
- * - * A Click application can define multiple page interceptors that are invoked in - * the order in which they are returned by the ConfigService. - * - *

Scope

- * - * Page interceptors can be defined with a request level scope, whereby a new - * page interceptor will be created with each page request providing a thread - * safe programming model. - *

- * Please note, as new interceptor instances are created with each request, care - * should be taken to ensure that these objects are light weight and do not - * introduce a performance bottleneck into your application. - *

- * Alternatively, page interceptors can be defined with application level scope - * whereby a single instance is created for the application and is used for - * all requests. - *

- * Note application scope interceptors are more efficient that request scope - * interceptors, but you are responsible for ensuring that they are thread safe - * and support reentrant method invocations as multiple page requests are - * processed at the same time. - * - *

Configuration

- * - * Application PageInterceptors are configured in the click.xml - * configuration file. PageInterceptors must support construction using a - * no-args public constructor. - *

- * Page interceptors can have multiple properties configured with their XML - * definition which are set after the constructor has been called. Properties - * are set using OGNL via {@link org.apache.click.util.PropertyUtils}. - *

- * An example configuration is provided below: - * - *

- * <page-interceptor classname="com.mycorp.PageSecurityInterceptor" scope="application">
- *     <property name="notAuthenticatedPath" value="/not-authenticated.htm"/>
- *     <property name="notAuthorizedPath" value="/not-authorized.htm"/>
- * </page-interceptor> 
- * - * The default scope for page interceptors is "request", but this can be configured - * as "application" as is done in the example configuration above. - * - *

Example

- * - *
- * public class SecurityInterceptor implements PageInterceptor {
- *
- *    // The request not authenticated redirect path.
- *    private String notAuthenticatedPath;
- *
- *    // The request not authorized redirect path.
- *    private String notAuthorizedPath;
- *
- *    // Public Methods ---------------------------------------------------------
- *
- *    public boolean preCreate(Class pageClass, Context context) {
- *
- *       // If authentication required, then ensure user is authenticated
- *       Authentication authentication = pageClass.getAnnotation(Authentication.class);
- *
- *       // TODO: user context check.
- *
- *       if (authentication != null && authentication.required()) {
- *          sendRedirect(getNotAuthenticatedPath(), context);
- *          return false;
- *       }
- *
- *       // If authorization permission defined, then ensure user is authorized to access the page
- *       Authorization authorization = pageClass.getAnnotation(Authorization.class);
- *       if (authorization != null) {
- *          if (!UserContext.getThreadUserContext().hasPermission(authorization.permission())) {
- *             sendRedirect(getNotAuthorizedPath(), context);
- *             return false;
- *          }
- *       }
- *
- *       return true;
- *    }
- *
- *    public boolean postCreate(Page page) {
- *       return true;
- *    }
- *
- *    public boolean preResponse(Page page) {
- *       return true;
- *    }
- *
- *    public void postDestroy(Page page) {
- *    }
- *
- *    public String getNotAuthenticatedPath() {
- *       return notAuthenticatedPath;
- *    }
- *
- *    public void setNotAuthenticatedPath(String notAuthenticatedPath) {
- *       this.notAuthenticatedPath = notAuthenticatedPath;
- *    }
- *
- *    public String getNotAuthorizedPath() {
- *       return notAuthorizedPath;
- *    }
- *
- *    public void setNotAuthorizedPath(String notAuthorizedPath) {
- *       this.notAuthorizedPath = notAuthorizedPath;
- *    }
- *
- *    // Protected Methods ------------------------------------------------------
- *
- *    protected void sendRedirect(String location, Context context) {
- *       if (StringUtils.isNotBlank(location)) {
- *          if (location.charAt(0) == '/') {
- *             String contextPath = context.getRequest().getContextPath();
- *
- *             // Guard against adding duplicate context path
- *             if (!location.startsWith(contextPath + '/')) {
- *                location = contextPath + location;
- *             }
- *          }
- *       }
- *
- *       location = context.getResponse().encodeRedirectURL(location);
- *
- *       try {
- *          context.getResponse().sendRedirect(location);
- *
- *       } catch (IOException ioe) {
- *          throw new RuntimeException(ioe);
- *       }
- *   }
- * } 
- * - *
- * // Page class authentication annotation
- * @Retention(RetentionPolicy.RUNTIME)
- * public @interface Authentication {
- *    boolean required() default true;
- * } 
- * - *
- * // Page class authorization annotation
- * @Retention(RetentionPolicy.RUNTIME)
- * public @interface Authorization {
- *    String permission();
- * }
- * 
- */ -public interface PageInterceptor { - - /** - * Provides a before page object creation interceptor method, which is passed - * the class of the page to be instantiated and the page request context. - * If this method returns true then the normal page processing is performed, - * otherwise if this method returns false the page instance is never created - * and the request is considered to have been handled. - * - * @param pageClass the class of the page to be instantiated - * @param context the page request context - * @return true to continue normal page processing or false whereby the - * request is considered to be handled - */ - public boolean preCreate(Class pageClass, Context context); - - /** - * Provides a post page object creation interceptor method, which is passed - * the instance of the newly created page. This interceptor method is called - * before the page {@link Page#onSecurityCheck()} method is invoked. - *

- * If this method returns true then the normal page processing is performed, - * otherwise if this method returns false the request is considered to have - * been handled. - *

- * Please note the page {@link Page#onDestroy()} method will still be invoked. - * - * @param page the newly instantiated page instance - * @return true to continue normal page processing or false whereby the - * request is considered to be handled - */ - public boolean postCreate(Page page); - - /** - * Provides a page interceptor before response method. This method is invoked - * prior to the page redirect, forward or rendering phase. - *

- * If this method returns true then the normal page processing is performed, - * otherwise if this method returns false request is considered to have been - * handled. - *

- * Please note the page {@link Page#onDestroy()} method will still be invoked. - * - * @param page the newly instantiated page instance - * @return true to continue normal page processing or false whereby the - * request is considered to be handled - */ - public boolean preResponse(Page page); - - /** - * Provides a post page destroy interceptor method. This interceptor method - * is called immediately after the page {@link Page#onDestroy()} method is - * invoked. - * - * @param page the page object which has just been destroyed - */ - public void postDestroy(Page page); - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/ajax/AjaxBehavior.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/ajax/AjaxBehavior.java deleted file mode 100644 index fac9f69b4e..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/ajax/AjaxBehavior.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.ajax; - - -import org.openidentityplatform.openam.click.ActionResult; -import org.openidentityplatform.openam.click.Behavior; -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.Control; - -/** - * AjaxBehavior extends the basic Behavior functionality to allow Controls to - * handle and process incoming Ajax requests. - *

- * To handle an Ajax request, AjaxBehavior exposes the listener method: - * {@link #onAction(org.openidentityplatform.openam.click.Control) onAction}. - * The onAction method returns an ActionResult that is rendered back - * to the browser. - *

- * Before Click invokes the onAction method it checks whether the request - * is targeted at the AjaxBehavior by invoking the method - * {@link #isAjaxTarget(org.openidentityplatform.openam.click.Context) Behavior.isAjaxTarget()}. - * Click will only invoke onAction if isAjaxTarget returns true. - */ -public interface AjaxBehavior extends Behavior { - - /** - * This method can be implemented to handle and respond to an Ajax request. - * For example: - * - *

-     * public void onInit() {
-     *     ActionLink link = new ActionLink("link");
-     *     link.addBehaior(new DefaultAjaxBehavior() {
-     *
-     *         public ActionResult onAction(Control source) {
-     *             ActionResult result = new ActionResult("<h1>Hello world</h1>", ActionResult.HTML);
-     *             return result;
-     *         }
-     *     });
-     * } 
- * - * @param source the control the behavior is attached to - * @return the action result instance - */ - public ActionResult onAction(Control source); - - /** - * Return true if the behavior is the request target, false otherwise. - *

- * This method is queried by Click to determine if the behavior's - * {@link #onAction(org.openidentityplatform.openam.click.Control)} method should be called in - * response to a request. - *

- * By exposing this method through the Behavior interface it provides - * implementers with fine grained control over whether the Behavior's - * {@link #onAction(org.openidentityplatform.openam.click.Control)} method should be invoked or not. - *

- * Below is an example implementation: - * - *

-     * public CustomBehavior implements Behavior {
-     *
-     *     private String eventType;
-     *
-     *     public CustomBehavior(String eventType) {
-     *         // The event type of the behavior
-     *         super(eventType);
-     *     }
-     *
-     *     public boolean isAjaxTarget(Context context) {
-     *         // Retrieve the eventType parameter from the incoming request
-     *         String eventType = context.getRequestParameter("type");
-     *
-     *         // Check if this Behavior's eventType matches the request
-     *         // "type" parameter
-     *         return StringUtils.equalsIgnoreCase(this.eventType, eventType);
-     *     }
-     *
-     *     public ActionResult onAction(Control source) {
-     *         // If isAjaxTarget returned true, the onAction method will be
-     *         // invoked
-     *         ...
-     *     }
-     * } 
- * - * @param context the request context - * @return true if the behavior is the request target, false otherwise - */ - public boolean isAjaxTarget(Context context); -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractContainer.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractContainer.java deleted file mode 100644 index fa553be154..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractContainer.java +++ /dev/null @@ -1,409 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Control; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.ContainerUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Provides a default implementation of the {@link org.openidentityplatform.openam.click.control.Container} interface - * to make it easier for developers to create their own containers. - *

- * Subclasses can override {@link #getTag()} to return a specific HTML element. - *

- * The following example shows how to create an HTML div element: - * - *

- * public class Div extends AbstractContainer {
- *
- *     public String getTag() {
- *         // Return the HTML tag
- *         return "div";
- *     }
- * } 
- */ -public abstract class AbstractContainer extends AbstractControl implements - Container { - - // Constants -------------------------------------------------------------- - - private static final long serialVersionUID = 1L; - - // Instance Variables ----------------------------------------------------- - - /** The list of controls. */ - protected List controls; - - /** The map of controls keyed by field name. */ - protected Map controlMap; - - // Constructors ----------------------------------------------------------- - - /** - * Create a container with no name defined. - */ - public AbstractContainer() { - } - - /** - * Create a container with the given name. - * - * @param name the container name - */ - public AbstractContainer(String name) { - super(name); - } - - // Public Methods --------------------------------------------------------- - - /** - * @see org.openidentityplatform.openam.click.control.Container#add(Control). - *

- * Please note: if the container contains a control with the same name - * as the given control, that control will be - * {@link #replace(Control, Control) replaced} - * by the given control. If a control has no name defined it cannot be replaced. - * - * @param control the control to add to the container - * @return the control that was added to the container - * @throws IllegalArgumentException if the control is null - */ - public Control add(Control control) { - return insert(control, getControls().size()); - } - - /** - * Add the control to the container at the specified index, and return the - * added instance. - *

- * Please note: if the container contains a control with the same name - * as the given control, that control will be - * {@link #replace(Control, Control) replaced} - * by the given control. If a control has no name defined it cannot be replaced. - *

- * Also note if the specified control already has a parent assigned, - * it will automatically be removed from that parent and inserted into this - * container. - * - * @see org.openidentityplatform.openam.click.control.Container#insert(Control, int) - * - * @param control the control to add to the container - * @param index the index at which the control is to be inserted - * @return the control that was added to the container - * - * @throws IllegalArgumentException if the control is null or if the control - * and container is the same instance - * - * @throws IndexOutOfBoundsException if index is out of range - * (index < 0 || index > getControls().size()) - */ - public Control insert(Control control, int index) { - // Check if panel already contains the control - String controlName = control.getName(); - if (controlName != null) { - // Check if container already contains the control - Control currentControl = getControlMap().get(control.getName()); - - // If container already contains the control do a replace - if (currentControl != null) { - - // Current control and new control are referencing the same object - // so we exit early - if (currentControl == control) { - return control; - } - - // If the two controls are different objects, we remove the current - // control and add the given control - return replace(currentControl, control); - } - } - - return ContainerUtils.insert(this, control, index, getControlMap()); - } - - /** - * @seeorg.openidentityplatform.openam.click.control.Container#remove(Control). - * - * @param control the control to remove from the container - * @return true if the control was removed from the container - * @throws IllegalArgumentException if the control is null - */ - public boolean remove(Control control) { - return ContainerUtils.remove(this, control, getControlMap()); - } - - /** - * Replace the control in the container at the specified index, and return - * the newly added control. - * - * @see org.openidentityplatform.openam.click.control.Container#replace(Control, Control) - * - * @param currentControl the control currently contained in the container - * @param newControl the control to replace the current control contained in - * the container - * @return the new control that replaced the current control - * - * @deprecated this method was used for stateful pages, which have been deprecated - * - * @throws IllegalArgumentException if the currentControl or newControl is - * null - * @throws IllegalStateException if the currentControl is not contained in - * the container - */ - public Control replace(Control currentControl, Control newControl) { - int controlIndex = getControls().indexOf(currentControl); - return ContainerUtils.replace(this, currentControl, newControl, - controlIndex, getControlMap()); - } - - /** - * @see org.apache.click.control.Container#getControls(). - * - * @return the sequential list of controls held by the container - */ - public List getControls() { - if (controls == null) { - controls = new ArrayList(); - } - return controls; - } - - /** - * @see org.apache.click.control.Container#getControl(String) - * - * @param controlName the name of the control to get from the container - * @return the named control from the container if found or null otherwise - */ - public Control getControl(String controlName) { - if (hasControls()) { - return getControlMap().get(controlName); - } - return null; - } - - /** - * @see Container#contains(Control) - * - * @param control the control whose presence in this container is to be tested - * @return true if the container contains the specified control - */ - public boolean contains(Control control) { - return getControls().contains(control); - } - - /** - * Returns true if this container has existing controls, false otherwise. - * - * @return true if the container has existing controls, false otherwise. - */ - public boolean hasControls() { - return (controls != null) && !controls.isEmpty(); - } - - /** - * Return the map of controls where each map's key / value pair will consist - * of the control name and instance. - *

- * Controls added to the container that did not specify a {@link #name}, - * will not be included in the returned map. - * - * @return the map of controls - */ - public Map getControlMap() { - if (controlMap == null) { - controlMap = new HashMap(); - } - return controlMap; - } - - /** - * @see org.openidentityplatform.openam.click.control.AbstractControl#getControlSizeEst(). - * - * @return the estimated rendered control size in characters - */ - @Override - public int getControlSizeEst() { - int size = 20; - - if (getTag() != null && hasAttributes()) { - size += 20 * getAttributes().size(); - } - - if (hasControls()) { - size += getControls().size() * size; - } - - return size; - } - - /** - * @see Control#onProcess(). - * - * @return true to continue Page event processing or false otherwise - */ - @Override - public boolean onProcess() { - - boolean continueProcessing = true; - - for (Control control : getControls()) { - if (!control.onProcess()) { - continueProcessing = false; - } - } - - dispatchActionEvent(); - - return continueProcessing; - } - - /** - * @see Control#onDestroy() - */ - @Override - public void onDestroy() { - for (Control control : getControls()) { - try { - control.onDestroy(); - } catch (Throwable t) { - ClickUtils.getLogService().error("onDestroy error", t); - } - } - } - - /** - * @see Control#onInit() - */ - @Override - public void onInit() { - super.onInit(); - for (Control control : getControls()) { - control.onInit(); - } - } - - /** - * @see Control#onRender() - */ - @Override - public void onRender() { - for (Control control : getControls()) { - control.onRender(); - } - } - - /** - * Render the HTML representation of the container and all its child - * controls to the specified buffer. - *

- * If {@link #getTag()} returns null, this method will render only its - * child controls. - *

- * @see org.openidentityplatform.openam.click.control.AbstractControl#render(HtmlStringBuffer) - * - * @param buffer the specified buffer to render the control's output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - - //If tag is set, render it - if (getTag() != null) { - renderTagBegin(getTag(), buffer); - buffer.closeTag(); - if (hasControls()) { - buffer.append("\n"); - } - renderContent(buffer); - renderTagEnd(getTag(), buffer); - - } else { - - //render only content because no tag is specified - renderContent(buffer); - } - } - - /** - * Returns the HTML representation of this control. - *

- * This method delegates the rendering to the method - * {@link #render(HtmlStringBuffer)}. The size of buffer - * is determined by {@link #getControlSizeEst()}. - * - * @see Object#toString() - * - * @return the HTML representation of this control - */ - @Override - public String toString() { - HtmlStringBuffer buffer = new HtmlStringBuffer(getControlSizeEst()); - render(buffer); - return buffer.toString(); - } - - // Protected Methods ------------------------------------------------------ - - /** - * @see AbstractControl#renderTagEnd(String, HtmlStringBuffer). - * - * @param tagName the name of the tag to close - * @param buffer the buffer to append the output to - */ - @Override - protected void renderTagEnd(String tagName, HtmlStringBuffer buffer) { - buffer.elementEnd(tagName); - } - - /** - * Render this container content to the specified buffer. - * - * @param buffer the buffer to append the output to - */ - protected void renderContent(HtmlStringBuffer buffer) { - renderChildren(buffer); - } - - /** - * Render this container children to the specified buffer. - * - * @see #getControls() - * - * @param buffer the buffer to append the output to - */ - protected void renderChildren(HtmlStringBuffer buffer) { - for (Control control : getControls()) { - - int before = buffer.length(); - control.render(buffer); - - int after = buffer.length(); - if (before != after) { - buffer.append("\n"); - } - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractControl.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractControl.java deleted file mode 100644 index a651f26699..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractControl.java +++ /dev/null @@ -1,1121 +0,0 @@ -package org.openidentityplatform.openam.click.control; - -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ - -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringTokenizer; -import java.util.Map.Entry; - -import jakarta.servlet.ServletContext; - -import org.openidentityplatform.openam.click.ActionEventDispatcher; -import org.openidentityplatform.openam.click.ActionListener; -import org.openidentityplatform.openam.click.Behavior; -import org.openidentityplatform.openam.click.ControlRegistry; -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.Control; -import org.openidentityplatform.openam.click.Page; -import org.openidentityplatform.openam.click.element.Element; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -/** - * Provides a default implementation of the {@link Control} interface - * to make it easier for developers to create their own controls. - *

- * Subclasses are expected to at least override {@link #getTag()} - * to differentiate the control. However some controls do not map cleanly - * to an html tag, in which case you can override - * {@link #render(org.openidentityplatform.openam.click.util.HtmlStringBuffer)} for complete control - * over the output. - *

- * Below is an example of creating a new control called MyField: - *

- * public class MyField extends AbstractControl {
- *
- *     private String value;
- *
- *     public void setValue(String value) {
- *         this.value = value;
- *     }
- *
- *     public String getValue() {
- *         return value;
- *     }
- *
- *     public String getTag() {
- *         // Return the HTML tag
- *         return "input";
- *     }
- *
- *     public boolean onProcess() {
- *         // Bind the request parameter to the field value
- *         String requestValue = getContext().getRequestParameter(getName());
- *         setValue(requestValue);
- *
- *         // Invoke any listener of MyField
- *         return dispatchActionEvent();
- *     }
- * }
- * 
- * By overriding {@link #getTag()} one can specify the html tag to render. - *

- * Overriding {@link #onProcess()} allows one to bind the servlet request - * parameter to MyField value. The {@link #dispatchActionEvent()} method - * registers the listener for this control on the Context. Once the onProcess - * event has finished, all registered listeners will be fired. - *

- * To view the html rendered by MyField invoke the control's {@link #toString()} - * method: - * - *

- * public class Test {
- *     public static void main (String args[]) {
- *         // Create mock context in which to test the control.
- *         MockContext.initContext();
- *
- *         String fieldName = "myfield";
- *         MyField myfield = new MyField(fieldName);
- *         String output = myfield.toString();
- *         System.out.println(output);
- *     }
- * } 
- * - * Executing the above test results in the following output: - * - *
- * <input name="myfield" id="myfield"/>
- * - * Also see {@link org.apache.click.Control} javadoc for an explanation of the - * Control execution sequence. - * - *

Message Resources and Internationalization (i18n)

- * - * Controls support a hierarchy of resource bundles for displaying messages. - * These localized messages can be accessed through the methods: - * - *
    - *
  • {@link #getMessage(String)}
  • - *
  • {@link #getMessage(String, Object...)}
  • - *
  • {@link #getMessages()}
  • - *
- * - * The order in which localized messages resolve are described in the - * user guide. - */ -public abstract class AbstractControl implements Control { - - // Constants -------------------------------------------------------------- - - private static final long serialVersionUID = 1L; - - // Instance Variables ----------------------------------------------------- - - /** The control's action listener. */ - protected ActionListener actionListener; - - /** The control's list of {@link org.apache.click.Behavior behaviors}. */ - protected Set behaviors; - - /** - * The list of page HTML HEAD elements including: Javascript imports, - * Css imports, inline Javascript and inline Css. - */ - protected List headElements; - - /** The Control attributes Map. */ - protected Map attributes; - - /** The Control localized messages Map. */ - protected transient Map messages; - - /** The Control name. */ - protected String name; - - /** The control's parent. */ - protected Object parent; - - /** - * The Map of CSS style attributes. - * - * @deprecated use {@link #addStyleClass(String)} and - * {@link #removeStyleClass(String)} instead. - */ - protected Map styles; - - /** The listener target object. */ - protected Object listener; - - /** The listener method name. */ - protected String listenerMethod; - - // Constructors ----------------------------------------------------------- - - /** - * Create a control with no name defined. - */ - public AbstractControl() { - } - - /** - * Create a control with the given name. - * - * @param name the control name - */ - public AbstractControl(String name) { - if (name != null) { - setName(name); - } - } - - // Public Methods --------------------------------------------------------- - - /** - * Returns the controls html tag. - *

- * Subclasses should override this method and return the correct tag. - *

- * This method returns null by default. - *

- * Example tags include table, form, a and - * input. - * - * @return this controls html tag - */ - public String getTag() { - return null; - } - - /** - * Return the control's action listener. If the control has a listener - * target and listener method defined, this method will return an - * {@link org.apache.click.ActionListener} instance. - * - * @return the control's action listener - */ - public ActionListener getActionListener() { - if (actionListener == null && listener != null && listenerMethod != null) { - actionListener = new ActionListener() { - - private static final long serialVersionUID = 1L; - - public boolean onAction(Control source) { - return ClickUtils.invokeListener(listener, listenerMethod); - } - }; - } - return actionListener; - } - - /** - * Set the control's action listener. - * - * @param listener the control's action listener - */ - public void setActionListener(ActionListener listener) { - this.actionListener = listener; - } - - /** - * Returns true if this control has any - * Behaviors registered, false otherwise. - * - * @return true if this control has any Behaviors registered, - * false otherwise - */ - public boolean hasBehaviors() { - return (behaviors != null && !behaviors.isEmpty()); - } - - /** - * Add the given Behavior to the control's Set of - * {@link #getBehaviors() Behaviors}. - *

- * In addition, the Control will be registered with the - * {@link org.apache.click.ControlRegistry#registerAjaxTarget(org.apache.click.Control) ControlRegistry} - * as a potential Ajax target control and to have it's - * Behaviors processed by the Click runtime. - * - * @param behavior the Behavior to add - */ - public void addBehavior(Behavior behavior) { - if (behavior == null) { - throw new IllegalArgumentException("Behavior cannot be null"); - } - - // Ensure we register the behavior only once - if (!hasBehaviors()) { - ControlRegistry.registerAjaxTarget(this); - } - - getBehaviors().add(behavior); - } - - /** - * Remove the given Behavior from the Control's Set of - * {@link #getBehaviors() Behaviors}. - * - * @param behavior the Behavior to remove - */ - public void removeBehavior(Behavior behavior) { - getBehaviors().remove(behavior); - } - - /** - * Returns the Set of Behaviors for this control. - * - * @return the Set of Behaviors for this control - */ - public Set getBehaviors() { - if (behaviors == null) { - behaviors = new HashSet(); - } - return behaviors; - } - - /** - * Returns true if this control is an AJAX target, false - * otherwise. - *

- * The control is defined as an Ajax target if the control {@link #getId() ID} - * is send as a request parameter. - * - * @param context the request context - * @return true if this control is an AJAX target, false - * otherwise - */ - public boolean isAjaxTarget(Context context) { - // TODO each control could have an optimized version of isAjaxTarget - // targeting specifically that control. For now we just check that the - // control id is present. Not all controls can use an ID for example: - // ActionLink - String id = getId(); - if (id != null) { - return context.hasRequestParameter(id); - } else { - return false; - } - } - - /** - * Return the control HTML attribute with the given name, or null if the - * attribute does not exist. - * - * @param name the name of link HTML attribute - * @return the link HTML attribute - */ - public String getAttribute(String name) { - if (hasAttributes()) { - return getAttributes().get(name); - } - return null; - } - - /** - * Set the control attribute with the given attribute name and value. You would - * generally use attributes if you were creating the entire Control - * programmatically and rendering it with the {@link #toString()} method. - *

- * For example given the ActionLink: - * - *

-     * ActionLink addLink = new ActionLink("addLink", "Add");
-     * addLink.setAttribute("target", "_blank"); 
- * - * Will render the HTML as: - *
-     * <a href=".." target="_blank">Add</a> 
- * - * Note: for style and class attributes you can - * also use the methods {@link #setStyle(String, String)} and - * {@link #addStyleClass(String)}. - * - * @see #setStyle(String, String) - * @see #addStyleClass(String) - * @see #removeStyleClass(String) - * - * @param name the attribute name - * @param value the attribute value - * @throws IllegalArgumentException if name parameter is null - */ - public void setAttribute(String name, String value) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - if (value != null) { - getAttributes().put(name, value); - } else { - getAttributes().remove(name); - } - } - - /** - * Return the control's attributes Map. - * - * @return the control's attributes Map. - */ - public Map getAttributes() { - if (attributes == null) { - attributes = new HashMap(); - } - return attributes; - } - - /** - * Return true if the control has attributes or false otherwise. - * - * @return true if the control has attributes on false otherwise - */ - public boolean hasAttributes() { - return attributes != null && !attributes.isEmpty(); - } - - /** - * Returns true if specified attribute is defined, false otherwise. - * - * @param name the specified attribute to check - * @return true if name is a defined attribute - */ - public boolean hasAttribute(String name) { - return hasAttributes() && getAttributes().containsKey(name); - } - - /** - * @see org.apache.click.Control#getContext() - * - * @return the Page request Context - */ - public Context getContext() { - return Context.getThreadLocalContext(); - } - - /** - * @see Control#getName() - * - * @return the name of the control - */ - public String getName() { - return name; - } - - /** - * @see Control#setName(String) - * - * @param name of the control - * @throws IllegalArgumentException if the name is null - */ - public void setName(String name) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - this.name = name; - } - - /** - * Return the "id" attribute value if defined, or the control name otherwise. - * - * @see org.apache.click.Control#getId() - * - * @return HTML element identifier attribute "id" value - */ - public String getId() { - String id = getAttribute("id"); - - return (id != null) ? id : getName(); - } - - /** - * Set the HTML id attribute for the control with the given value. - * - * @param id the element HTML id attribute value to set - */ - public void setId(String id) { - if (id != null) { - setAttribute("id", id); - } else { - getAttributes().remove("id"); - } - } - - /** - * Return the localized message for the given key or null if not found. - * The resource message returned will use the Locale obtained from the - * Context. - *

- * This method will attempt to lookup the localized message in the - * parent's messages, which resolves to the Page's resource bundle. - *

- * If the message was not found, this method will attempt to look up the - * value in the /click-control.properties message properties file, - * through the method {@link #getMessages()}. - *

- * If still not found, this method will return null. - * - * @param name the name of the message resource - * @return the named localized message for the control, or null if not found - */ - public String getMessage(String name) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - String message = null; - - message = ClickUtils.getParentMessage(this, name); - - if (message == null && getMessages().containsKey(name)) { - message = getMessages().get(name); - } - - return message; - } - - /** - * Return the formatted message for the given resource name and message - * format arguments or null if no message was found. The resource - * message returned will use the Locale obtained from the Context. - *

- * {@link #getMessage(java.lang.String)} is invoked to retrieve the message - * for the specified name. - * - * @param name resource name of the message - * @param args the message arguments to format - * @return the named localized message for the control or null if no message - * was found - */ - public String getMessage(String name, Object... args) { - String value = getMessage(name); - if (value == null) { - return null; - } - return MessageFormat.format(value, args); - } - - /** - * Return a Map of localized messages for the control. The messages returned - * will use the Locale obtained from the Context. - * - * @return a Map of localized messages for the control - * @throws IllegalStateException if the context for the control has not be set - */ - public Map getMessages() { - if (messages == null) { - messages = getContext().createMessagesMap(getClass(), CONTROL_MESSAGES); - } - return messages; - } - - /** - * @see org.apache.click.Control#getParent() - * - * @return the Control's parent - */ - public Object getParent() { - return parent; - } - - /** - * @see org.apache.click.Control#setParent(Object) - * - * @param parent the parent of the Control - * @throws IllegalArgumentException if the given parent instance is - * referencing this object: if (parent == this) - */ - public void setParent(Object parent) { - if (parent == this) { - throw new IllegalArgumentException("Cannot set parent to itself"); - } - this.parent = parent; - } - - /** - * @see org.apache.click.Control#onProcess() - * - * @return true to continue Page event processing or false otherwise - */ - public boolean onProcess() { - dispatchActionEvent(); - return true; - } - - /** - * Set the controls event listener. - *

- * The method signature of the listener is:

    - *
  • must have a valid Java method name
  • - *
  • takes no arguments
  • - *
  • returns a boolean value
  • - *
- *

- * An example event listener method would be: - * - *

-     * public boolean onClick() {
-     *     System.out.println("onClick called");
-     *     return true;
-     * } 
- * - * @param listener the listener object with the named method to invoke - * @param method the name of the method to invoke - */ - public void setListener(Object listener, String method) { - this.listener = listener; - this.listenerMethod = method; - } - - /** - * This method does nothing. Subclasses may override this method to perform - * initialization. - * - * @see org.apache.click.Control#onInit() - */ - public void onInit() { - } - - /** - * This method does nothing. Subclasses may override this method to perform - * clean up any resources. - * - * @see org.apache.click.Control#onDestroy() - */ - public void onDestroy() { - } - - /** - * This method does nothing. Subclasses may override this method to deploy - * static web resources. - * - * @see org.openidentityplatform.openam.click.Control#onDeploy(ServletContext) - * - * @param servletContext the servlet context - */ - public void onDeploy(ServletContext servletContext) { - } - - /** - * This method does nothing. Subclasses may override this method to perform - * pre rendering logic. - * - * @see org.apache.click.Control#onRender() - */ - public void onRender() { - } - - /** - * @deprecated use the new {@link #getHeadElements()} instead - * - * @return the HTML includes statements for the control stylesheet and - * JavaScript files - */ - public final String getHtmlImports() { - throw new UnsupportedOperationException("Use getHeadElements instead"); - } - - /** - * @see org.apache.click.Control#getHeadElements() - * - * @return the list of HEAD elements to be included in the page - */ - public List getHeadElements() { - if (headElements == null) { - // Most controls won't provide their own head elements, so save - // memory by creating an empty array list - headElements = new ArrayList(0); - } - return headElements; - } - - /** - * Return the parent page of this control, or null if not defined. - * - * @return the parent page of this control, or null if not defined - */ - public Page getPage() { - return ClickUtils.getParentPage(this); - } - - /** - * Return the control CSS style for the given name. - * - * @param name the CSS style name - * @return the CSS style for the given name - */ - public String getStyle(String name) { - if (hasAttribute("style")) { - String currentStyles = getAttribute("style"); - Map stylesMap = parseStyles(currentStyles); - return stylesMap.get(name); - } else { - return null; - } - } - - /** - * Set the control CSS style name and value pair. - *

- * For example given the ActionLink: - * - *

-     * ActionLink addLink = new ActionLink("addLink", "Add");
-     * addLink.setStyle("color", "red");
-     * addLink.setStyle("border", "1px solid black"); 
- * - * Will render the HTML as: - *
-     * <a href=".." style="color:red;border:1px solid black;">Add</a>
-     * 
- * To remove an existing style, set the value to null. - * - * @param name the CSS style name - * @param value the CSS style value - */ - public void setStyle(String name, String value) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - String oldStyles = getAttribute("style"); - - if (oldStyles == null) { - - if (value == null) { - //Exit early - return; - } else { - //If value is not null, append the new style and return - getAttributes().put("style", name + ":" + value + ";"); - return; - } - } - - //Create buffer and estimate the new size - HtmlStringBuffer buffer = new HtmlStringBuffer( - oldStyles.length() + 10); - - //Parse the current styles into a map - Map stylesMap = parseStyles(oldStyles); - - //Check if the new style is already present - if (stylesMap.containsKey(name)) { - - //If the style is present and the value is null, remove the style, - //otherwise replace it with the new value - if (value == null) { - stylesMap.remove(name); - } else { - stylesMap.put(name, value); - } - } else { - stylesMap.put(name, value); - } - - //The styles map might be empty if the last style was removed - if (stylesMap.isEmpty()) { - getAttributes().remove("style"); - return; - } - - //Iterate over the stylesMap appending each entry to buffer - for (Entry entry : stylesMap.entrySet()) { - String styleName = entry.getKey(); - String styleValue = entry.getValue(); - buffer.append(styleName); - buffer.append(":"); - buffer.append(styleValue); - buffer.append(";"); - } - getAttributes().put("style", buffer.toString()); - } - - /** - * Return true if CSS styles are defined. - * - * @deprecated use {@link #hasAttribute(String)} instead - * - * @return true if CSS styles are defined - */ - public boolean hasStyles() { - return (styles != null && !styles.isEmpty()); - } - - /** - * Return the Map of control CSS styles. - * - * @deprecated use {@link #getAttribute(String)} instead - * - * @return the Map of control CSS styles - */ - public Map getStyles() { - if (styles == null) { - styles = new HashMap(); - } - return styles; - } - - /** - * Add the CSS class attribute. Null values will be ignored. - *

- * For example given the ActionLink: - * - *

-     * ActionLink addLink = new ActionLink("addLink", "Add");
-     * addLink.addStyleClass("red"); 
- * - * Will render the HTML as: - *
-     * <a href=".." class="red">Add</a> 
- * - * @param value the class attribute to add - */ - public void addStyleClass(String value) { - //If value is null, exit early - if (value == null) { - return; - } - - //If any class attributes already exist, check if the specified class - //exists in the current set of classes. - if (hasAttribute("class")) { - String oldStyleClasses = getAttribute("class").trim(); - - //Check if the specified class exists in the class attribute set - boolean classExists = classExists(value, oldStyleClasses); - - if (classExists) { - //If the class already exist, exit early - return; - } - - //Specified class does not exist so add it with the other class - //attributes - getAttributes().put("class", oldStyleClasses + " " + value); - - } else { - //Since no class attributes exist yet, only add the specified class - setAttribute("class", value); - } - } - - /** - * Removes the CSS class attribute. - * - * @param value the CSS class attribute - */ - public void removeStyleClass(String value) { - // If value is null, exit early - if (value == null) { - return; - } - - // If any class attributes already exist, check if the specified class - // exists in the current set of classes. - if (hasAttribute("class")) { - String oldStyleClasses = getAttribute("class").trim(); - - //Check if the specified class exists in the class attribute set - boolean classExists = classExists(value, oldStyleClasses); - - if (!classExists) { - //If the class does not exist, exit early - return; - } - - // If the class does exist, parse the class attributes into a set - // and remove the specified class - Set styleClassSet = parseStyleClasses(oldStyleClasses); - styleClassSet.remove(value); - - if (styleClassSet.isEmpty()) { - // If there are no more styleClasses left, remove the class - // attribute from the attributes list - getAttributes().remove("class"); - } else { - // Otherwise render the styleClasses. - // Create buffer and estimate the new size - HtmlStringBuffer buffer = new HtmlStringBuffer( - oldStyleClasses.length() + value.length()); - - // Iterate over the styleClassSet appending each entry to buffer - Iterator it = styleClassSet.iterator(); - while (it.hasNext()) { - String entry = it.next(); - buffer.append(entry); - if (it.hasNext()) { - buffer.append(" "); - } - } - getAttributes().put("class", buffer.toString()); - } - } - } - - /** - * Render the control's output to the specified buffer. - *

- * If {@link #getTag()} returns null, this method will return an empty - * string. - *

- * @see org.apache.click.Control#render(org.apache.click.util.HtmlStringBuffer) - * - * @param buffer the specified buffer to render the control's output to - */ - public void render(HtmlStringBuffer buffer) { - if (getTag() == null) { - return; - } - renderTagBegin(getTag(), buffer); - renderTagEnd(getTag(), buffer); - } - - /** - * Returns the HTML representation of this control. - *

- * This method delegates the rendering to the method - * {@link #render(org.openidentityplatform.openam.click.util.HtmlStringBuffer)}. The size of buffer - * is determined by {@link #getControlSizeEst()}. - * - * @see Object#toString() - * - * @return the HTML representation of this control - */ - @Override - public String toString() { - if (getTag() == null) { - return ""; - } - HtmlStringBuffer buffer = new HtmlStringBuffer(getControlSizeEst()); - render(buffer); - return buffer.toString(); - } - - // Protected Methods ------------------------------------------------------ - - /** - * Dispatch an action event to the {@link org.apache.click.ActionEventDispatcher}. - * - * @see org.apache.click.ActionEventDispatcher#dispatchActionEvent(org.apache.click.Control, org.apache.click.ActionListener) - * @see org.apache.click.ActionEventDispatcher#dispatchAjaxBehaviors(org.apache.click.Control) - */ - protected void dispatchActionEvent() { - if (getActionListener() != null) { - ActionEventDispatcher.dispatchActionEvent(this, getActionListener()); - } - - if (hasBehaviors()) { - ActionEventDispatcher.dispatchAjaxBehaviors(this); - } - } - - /** - * Append all the controls attributes to the specified buffer. - * - * @param buffer the specified buffer to append all the attributes - */ - protected void appendAttributes(HtmlStringBuffer buffer) { - if (hasAttributes()) { - buffer.appendAttributes(attributes); - } - } - - /** - * Render the tag and common attributes including {@link #getId() id}, - * class and style. The {@link #getName() name} attribute - * is not rendered by this control. It is up to subclasses - * whether to render the name attribute or not. Generally only - * {@link org.apache.click.control.Field} controls render the name attribute. - *

- * Please note: the tag will not be closed by this method. This - * enables callers of this method to append extra attributes as needed. - *

- * For example the following example: - * - *

-     * Field field = new TextField("mytext");
-     * HtmlStringBuffer buffer = new HtmlStringBuffer();
-     * field.renderTagBegin("input", buffer); 
- * - * will render: - * - *
-     * <input name="mytext" id="mytext" 
- * - * Note that the tag is not closed, so subclasses can render extra - * attributes. - * - * @param tagName the name of the tag to render - * @param buffer the buffer to append the output to - */ - protected void renderTagBegin(String tagName, - HtmlStringBuffer buffer) { - if (tagName == null) { - throw new IllegalStateException("Tag cannot be null"); - } - - buffer.elementStart(tagName); - - String id = getId(); - if (id != null) { - buffer.appendAttribute("id", id); - } - - appendAttributes(buffer); - } - - /** - * Closes the specified tag. - * - * @param tagName the name of the tag to close - * @param buffer the buffer to append the output to - */ - protected void renderTagEnd(String tagName, HtmlStringBuffer buffer) { - buffer.elementEnd(); - } - - /** - * Return the estimated rendered control size in characters. - * - * @return the estimated rendered control size in characters - */ - protected int getControlSizeEst() { - int size = 0; - if (getTag() != null && hasAttributes()) { - //length of the markup -> == 3 - //1 * tag.length() - size += 3 + getTag().length(); - //using 20 as an estimate - size += 20 * getAttributes().size(); - } - return size; - } - - // Private Methods -------------------------------------------------------- - - /** - * Parse the specified string of style attributes and return a Map - * of key/value pairs. Invalid key/value pairs will not be added to - * the map. - * - * @param style the string containing the styles to parse - * @return map containing key/value pairs of the specified style - * @throws IllegalArgumentException if style is null - */ - private Map parseStyles(String style) { - if (style == null) { - throw new IllegalArgumentException("style cannot be null"); - } - - //LinkHashMap is used to keep the order of the style names. Probably - //makes no difference to browser but it makes testing easier since the - //order that styles are added are kept when rendering the control. - Map stylesMap = new LinkedHashMap(); - StringTokenizer tokens = new StringTokenizer(style, ";"); - while (tokens.hasMoreTokens()) { - String token = tokens.nextToken(); - int keyEnd = token.indexOf(":"); - - //If there is no key/value delimiter or value is empty, continue - if (keyEnd == -1 || (keyEnd + 1) == token.length()) { - continue; - } - //Check that the value part of the key/value pair not only - //consists of a ';' char. - if (token.charAt(keyEnd + 1) == ';') { - continue; - } - String styleName = token.substring(0, keyEnd); - String styleValue = token.substring(keyEnd + 1); - stylesMap.put(styleName, styleValue); - } - - return stylesMap; - } - - /** - * Parse the specified string of style attributes and return a Map - * of key/value pairs. Invalid key/value pairs will not be added to - * the map. - * - * @param styleClasses the string containing the styles to parse - * @return map containing key/value pairs of the specified style - * @throws IllegalArgumentException if styleClasses is null - */ - private Set parseStyleClasses(String styleClasses) { - if (styleClasses == null) { - throw new IllegalArgumentException("styleClasses cannot be null"); - } - - //LinkHashMap is used to keep the order of the class names. Probably - //makes no difference to browser but it makes testing easier since the - //order that classes were added in, are kept when rendering the control. - //Thus one can test whether the expected result and actual result match. - Set styleClassesSet = new LinkedHashSet(); - StringTokenizer tokens = new StringTokenizer(styleClasses, " "); - while (tokens.hasMoreTokens()) { - String token = tokens.nextToken(); - styleClassesSet.add(token); - } - - return styleClassesSet; - } - - /** - * Return true if the new value exists in the current value. - * - * @param newValue the value of the class attribute to check - * @param currentValue the current value to test - * @return true if the classToFind exists in the current value set - */ - private boolean classExists(String newValue, String currentValue) { - //To find if a class eg. "myclass" exists, check the following: - - //1. Check if "myclass" is the only value in the string - // -> "myclass" - if (newValue.length() == currentValue.length() - && currentValue.indexOf(newValue) == 0) { - return true; - } - - //2. Check if "myclass" exists at beginning of string - // -> "myclass otherclass" - if (currentValue.startsWith(newValue + " ")) { - return true; - } - - //3. Check if "myclass" exists in middle of string - // -> "anotherclass myclass otherclass" - if (currentValue.indexOf(" " + newValue + " ") >= 0) { - return true; - } - - //4. Check if "myclass" exists at end of string - // -> "anotherclass myclass" - return (currentValue.endsWith(" " + newValue)); - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractLink.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractLink.java deleted file mode 100644 index e920367086..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractLink.java +++ /dev/null @@ -1,818 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Context; -import org.apache.click.Stateful; -import org.apache.click.control.ActionLink; -import org.apache.click.control.Submit; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.apache.commons.lang.StringUtils; - -import jakarta.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -/** - * Provides a Abstract Link control:   <a href=""></a>. - *

- * See also the W3C HTML reference: - * A Links - * - * @see ActionLink - * @see Submit - */ -public abstract class AbstractLink extends AbstractControl implements Stateful { - - private static final long serialVersionUID = 1L; - - // Instance Variables ----------------------------------------------------- - - /** The Field disabled value. */ - protected boolean disabled; - - /** - * The image src path attribute. If the image src is defined then a - * <img/> element will rendered inside the anchor link when - * using the AbstractLink {@link #toString()} method. - *

- * If the image src value is prefixed with '/' then the request context path - * will be prefixed to the src value when rendered by the control. - */ - protected String imageSrc; - - /** The link display label. */ - protected String label; - - /** The link parameters map. */ - protected Map parameters; - - /** The link 'tabindex' attribute. */ - protected int tabindex; - - /** The link title attribute, which acts as a tooltip help message. */ - protected String title; - - /** Flag to set if both icon and text are rendered, default value is false. */ - protected boolean renderLabelAndImage = false; - - // Constructors ----------------------------------------------------------- - - /** - * Create an AbstractLink for the given name. - * - * @param name the page link name - * @throws IllegalArgumentException if the name is null - */ - public AbstractLink(String name) { - setName(name); - } - - /** - * Create an AbstractLink with no name defined. - *

- * Please note the control's name must be defined before it is valid. - */ - public AbstractLink() { - } - - // Public Attributes ------------------------------------------------------ - - /** - * Return the link html tag: a. - * - * @see AbstractControl#getTag() - * - * @return this controls html tag - */ - @Override - public String getTag() { - return "a"; - } - - /** - * Return true if the AbstractLink is a disabled. If the link is disabled - * it will be rendered as <span> element with a HTML class attribute - * of "disabled". - * - * @return true if the AbstractLink is a disabled - */ - public boolean isDisabled() { - return disabled; - } - - /** - * Set the disabled flag. If the link is disabled it will be rendered as - * <span> element with a HTML class attribute of "disabled". - * - * @param disabled the disabled flag - */ - public void setDisabled(boolean disabled) { - this.disabled = disabled; - } - - /** - * Return the AbstractLink anchor <a> tag href attribute. - * This method will encode the URL with the session ID - * if required using HttpServletResponse.encodeURL(). - * - * @return the AbstractLink HTML href attribute - */ - public abstract String getHref(); - - /** - * Return the image src path attribute. If the image src is defined then a - * <img/> element will be rendered inside the anchor link - * when using the AbstractLink {@link #toString()} method. - *

- * Note: the label will not be rendered in this case (default behavior), - * unless the {@link #setRenderLabelAndImage(boolean)} flag is set to true. - *

- * If the src value is prefixed with '/' then the request context path will - * be prefixed to the src value when rendered by the control. - * - * @return the image src path attribute - */ - public String getImageSrc() { - return imageSrc; - } - - /** - * Set the image src path attribute. If the src value is prefixed with - * '/' then the request context path will be prefixed to the src value when - * rendered by the control. - *

- * If the image src is defined then an <img/> element will - * be rendered inside the anchor link when using the AbstractLink - * {@link #toString()} method. - *

- * Note: the label will not be rendered in this case (default behavior), - * unless the {@link #setRenderLabelAndImage(boolean)} flag is set to true. - * - * @param src the image src path attribute - */ - public void setImageSrc(String src) { - this.imageSrc = src; - } - - /** - * Return the "id" attribute value if defined, or null otherwise. - * - * @see org.apache.click.Control#getId() - * - * @return HTML element identifier attribute "id" value - */ - @Override - public String getId() { - if (hasAttributes()) { - return getAttribute("id"); - } else { - return null; - } - } - - /** - * Return the label for the AbstractLink. - *

- * If the label value is null, this method will attempt to find a - * localized label message in the parent messages using the key: - *

- * getName() + ".label" - *
- * If not found then the message will be looked up in the - * /click-control.properties file using the same key. - * If a value still cannot be found then the ActionLink name will be converted - * into a label using the method: {@link ClickUtils#toLabel(String)} - *

- * For example given a OrderPage with the properties file - * OrderPage.properties: - * - *

-     * checkout.label=Checkout
-     * checkout.title=Proceed to Checkout 
- * - * The page ActionLink code: - *
-     * public class OrderPage extends Page {
-     *     ActionLink checkoutLink = new ActionLink("checkout");
-     *     ..
-     * } 
- * - * Will render the AbstractLink label and title properties as: - *
-     * <a href=".." title="Proceed to Checkout">Checkout</a> 
- * - * When a label value is not set, or defined in any properties files, then - * its value will be created from the Fields name. - *

- * For example given the ActionLink code: - * - *

-     * ActionLink nameField = new ActionLink("deleteItem");  
- * - * Will render the ActionLink label as: - *
-     * <a href="..">Delete Item</a> 
- * - * Note the ActionLink label can include raw HTML to render other elements. - *

- * For example the configured label: - * - *

-     * edit.label=<img src="images/edit.png" title="Edit Item"/> 
- * - * Will render the ActionLink label as: - *
-     * <a href=".."><img src="images/edit.png" title="Edit Item"/></a> 
- * - * @return the label for the ActionLink - */ - public String getLabel() { - if (label == null) { - label = getMessage(getName() + ".label"); - } - if (label == null) { - label = ClickUtils.toLabel(getName()); - } - return label; - } - - /** - * Set the label for the ActionLink. - * - * @see #getLabel() - * - * @param label the label for the ActionLink - */ - public void setLabel(String label) { - this.label = label; - } - - /** - * Return the link request parameter value for the given name, or null if - * the parameter value does not exist. - * - * @param name the name of request parameter - * @return the link request parameter value - */ - public String getParameter(String name) { - if (hasParameters()) { - Object value = getParameters().get(name); - - if (value instanceof String) { - return (String) value; - } - - if (value instanceof String[]) { - String[] array = (String[]) value; - if (array.length >= 1) { - return array[0]; - } else { - return null; - } - } - - return (value == null ? null : value.toString()); - } else { - return null; - } - } - - /** - * Set the link parameter with the given parameter name and value. You would - * generally use parameter if you were creating the entire AbstractLink - * programmatically and rendering it with the {@link #toString()} method. - *

- * For example given the ActionLink: - * - *

-     * PageLink editLink = new PageLink("editLink", EditCustomer.class);
-     * editLink.setLabel("Edit Customer");
-     * editLink.setParameter("customerId", customerId); 
- * - * And the page template: - *
-     * $editLink 
- * - * Will render the HTML as: - *
-     * <a href="/mycorp/edit-customer.htm?customerId=13490">Edit Customer</a> 
- * - * @param name the attribute name - * @param value the attribute value - * @throws IllegalArgumentException if name parameter is null - */ - public void setParameter(String name, Object value) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - if (value != null) { - getParameters().put(name, value); - } else { - getParameters().remove(name); - } - } - - /** - * Return the link request parameter values for the given name, or null if - * the parameter values does not exist. - * - * @param name the name of request parameter - * @return the link request parameter values - */ - public String[] getParameterValues(String name) { - if (hasParameters()) { - Object values = getParameters().get(name); - if (values instanceof String) { - return new String[] { values.toString() }; - } - if (values instanceof String[]) { - return (String[]) values; - } else { - return null; - } - } else { - return null; - } - } - - /** - * Set the link parameter with the given parameter name and values. If the - * values are null, the parameter will be removed from the {@link #parameters}. - * - * @see #setParameter(String, Object) - * - * @param name the attribute name - * @param values the attribute values - * @throws IllegalArgumentException if name parameter is null - */ - public void setParameterValues(String name, Object[] values) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - if (values != null) { - getParameters().put(name, values); - } else { - getParameters().remove(name); - } - } - - /** - * Return the AbstractLink parameters Map. - * - * @return the AbstractLink parameters Map - */ - public Map getParameters() { - if (parameters == null) { - parameters = new HashMap(4); - } - return parameters; - } - - /** - * Set the AbstractLink parameter map. - * - * @param parameters the link parameter map - */ - public void setParameters(Map parameters) { - this.parameters = parameters; - } - - /** - * Defines a link parameter that will have its value bound to a matching - * request parameter. {@link #setParameter(String, Object) setParameter} - * implicitly defines a parameter as well. - *

- * Please note: parameters need only be defined for Ajax requests. - * For non-Ajax requests, all incoming request parameters - * are bound, whether they are defined or not. This behavior may change in a - * future release. - *

- * Also note: link parameters are bound to request parameters - * during the {@link #onProcess()} event, so link parameters must be defined - * in the Page constructor or onInit() event. - * - * @param name the name of the parameter to define - */ - public void defineParameter(String name) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - Map localParameters = getParameters(); - if (!localParameters.containsKey(name)) { - localParameters.put(name, null); - } - } - - /** - * Return true if the AbstractLink has parameters, false otherwise. - * - * @return true if the AbstractLink has parameters, false otherwise - */ - public boolean hasParameters() { - return parameters != null && !parameters.isEmpty(); - } - - /** - * Return the link "tabindex" attribute value. - * - * @return the link "tabindex" attribute value - */ - public int getTabIndex() { - return tabindex; - } - - /** - * Set the link "tabindex" attribute value. - * - * @param tabindex the link "tabindex" attribute value - */ - public void setTabIndex(int tabindex) { - this.tabindex = tabindex; - } - - /** - * Return the 'title' attribute, or null if not defined. The title - * attribute acts like tooltip message over the link. - *

- * If the title value is null, this method will attempt to find a - * localized label message in the parent messages using the key: - *

- * getName() + ".title" - *
- * If not found then the message will be looked up in the - * /click-control.properties file using the same key. - *

- * For examle given a ItemsPage with the properties file - * ItemPage.properties: - * - *

-     * edit.label=Edit
-     * edit.title=Edit Item 
- * - * The page ActionLink code: - *
-     * public class ItemsPage extends Page {
-     *     ActionLink editLink = new ActionLink("edit");
-     *     ..
-     * } 
- * - * Will render the ActionLink label and title properties as: - *
-     * <a href=".." title="Edit Item">Edit</a> 
- * - * @return the 'title' attribute tooltip message - */ - public String getTitle() { - if (title == null) { - title = getMessage(getName() + ".title"); - } - return title; - } - - /** - * Set the 'title' attribute tooltip message. - * - * @see #getTitle() - * - * @param value the 'title' attribute tooltip message - */ - public void setTitle(String value) { - title = value; - } - - /** - * Returns true if both {@link #setImageSrc(String) icon} - * and {@link #setLabel(String) label} are rendered, - * false otherwise. - * - * @return true if both icon and text are rendered, - * false otherwise - */ - public boolean isRenderLabelAndImage() { - return renderLabelAndImage; - } - - /** - * Sets whether both {@link #setLabel(String) label} and - * {@link #setImageSrc(String) icon} are rendered for this - * link. - * - * @param renderLabelAndImage sets the rendering type of the link. - */ - public void setRenderLabelAndImage(boolean renderLabelAndImage) { - this.renderLabelAndImage = renderLabelAndImage; - } - - @Override - public boolean isAjaxTarget(Context context) { - String id = getId(); - if (id != null) { - return context.getRequestParameter(id) != null; - } else { - String localName = getName(); - if (localName != null) { - return localName.equals(context.getRequestParameter(ActionLink.ACTION_LINK)); - } - } - return false; - } - - // Public Methods --------------------------------------------------------- - - /** - * This method does nothing by default since AbstractLink does not bind to - * request values. - */ - public void bindRequestValue() { - } - - /** - * Return the link state. The following state is returned: - *
    - *
  • {@link #getParameters() link parameters}
  • - *
- * - * @return the link state - */ - public Object getState() { - if (hasParameters()) { - return getParameters(); - } - return null; - } - - /** - * Set the link state. - * - * @param state the link state to set - */ - public void setState(Object state) { - if (state == null) { - return; - } - - Map linkState = (Map) state; - setParameters(linkState); - } - - /** - * Render the HTML representation of the anchor link. This method - * will render the entire anchor link including the tags, the label and - * any attributes, see {@link #setAttribute(String, String)} for an - * example. - *

- * If the image src is defined then a <img/> element will - * rendered inside the anchor link instead of the label property. - *

- * This method invokes the abstract {@link #getHref()} method. - * - * @see #toString() - * - * @param buffer the specified buffer to render the control's output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - - if (isDisabled()) { - - buffer.elementStart("span"); - buffer.appendAttribute("id", getId()); - addStyleClass("disabled"); - buffer.appendAttribute("class", getAttribute("class")); - - if (hasAttribute("style")) { - buffer.appendAttribute("style", getAttribute("style")); - } - - buffer.closeTag(); - - if (StringUtils.isBlank(getImageSrc())) { - buffer.append(getLabel()); - - } else { - renderImgTag(buffer); - if (isRenderLabelAndImage()) { - buffer.elementStart("span").closeTag(); - buffer.append(getLabel()); - buffer.elementEnd("span"); - } - } - - buffer.elementEnd("span"); - - } else { - buffer.elementStart(getTag()); - removeStyleClass("disabled"); - buffer.appendAttribute("href", getHref()); - buffer.appendAttribute("id", getId()); - buffer.appendAttributeEscaped("title", getTitle()); - if (getTabIndex() > 0) { - buffer.appendAttribute("tabindex", getTabIndex()); - } - - appendAttributes(buffer); - - buffer.closeTag(); - - if (StringUtils.isBlank(getImageSrc())) { - buffer.append(getLabel()); - - } else { - renderImgTag(buffer); - if (isRenderLabelAndImage()) { - buffer.elementStart("span").closeTag(); - buffer.append(getLabel()); - buffer.elementEnd("span"); - } - } - - buffer.elementEnd(getTag()); - } - } - - /** - * Remove the link state from the session for the given request context. - * - * @see #saveState(Context) - * @see #restoreState(Context) - * - * @param context the request context - */ - public void removeState(Context context) { - ClickUtils.removeState(this, getName(), context); - } - - /** - * Restore the link state from the session for the given request context. - *

- * This method delegates to {@link #setState(Object)} to set the - * link restored state. - * - * @see #saveState(Context) - * @see #removeState(Context) - * - * @param context the request context - */ - public void restoreState(Context context) { - ClickUtils.restoreState(this, getName(), context); - } - - /** - * Save the link state to the session for the given request context. - *

- * * This method delegates to {@link #getState()} to retrieve the link state - * to save. - * - * @see #restoreState(Context) - * @see #removeState(Context) - * - * @param context the request context - */ - public void saveState(Context context) { - ClickUtils.saveState(this, getName(), context); - } - - // Protected Methods ------------------------------------------------------ - - /** - * Render the Image tag to the buffer. - * - * @param buffer the buffer to render the image tag to - */ - protected void renderImgTag(HtmlStringBuffer buffer) { - buffer.elementStart("img"); - buffer.appendAttribute("border", 0); - buffer.appendAttribute("hspace", 2); - buffer.appendAttribute("class", "link"); - - if (getTitle() != null) { - buffer.appendAttributeEscaped("alt", getTitle()); - } else { - buffer.appendAttributeEscaped("alt", getLabel()); - } - - String src = getImageSrc(); - if (StringUtils.isNotBlank(src)) { - if (src.charAt(0) == '/') { - src = getContext().getRequest().getContextPath() + src; - } - buffer.appendAttribute("src", src); - } - - buffer.elementEnd(); - } - - /** - * Render the given link parameters to the buffer. - *

- * The parameters will be rendered as URL key/value pairs e.g: - * "firstname=john&lastname=smith". - *

- * Multivalued parameters will be rendered with each value sharing the same - * key e.g: "name=john&name=susan&name=mary". - *

- * The parameter value will be encoded through - * {@link ClickUtils#encodeUrl(Object, Context)}. - * - * @param buffer the buffer to render the parameters to - * @param parameters the parameters to render - * @param context the request context - */ - protected void renderParameters(HtmlStringBuffer buffer, Map parameters, - Context context) { - - Iterator i = parameters.keySet().iterator(); - while (i.hasNext()) { - String paramName = i.next(); - Object paramValue = getParameters().get(paramName); - - // Check for multivalued parameter - if (paramValue instanceof String[]) { - String[] paramValues = (String[]) paramValue; - for (int j = 0; j < paramValues.length; j++) { - buffer.append(paramName); - buffer.append("="); - buffer.append(ClickUtils.encodeUrl(paramValues[j], context)); - if (j < paramValues.length - 1) { - buffer.append("&"); - } - } - } else { - if (paramValue != null) { - buffer.append(paramName); - buffer.append("="); - buffer.append(ClickUtils.encodeUrl(paramValue, context)); - } - } - if (i.hasNext()) { - buffer.append("&"); - } - } - } - - /** - * This method binds the submitted request parameters to the link - * parameters. - *

- * For non-Ajax requests this method will bind all incoming request - * parameters to the link. For Ajax requests this method will only bind - * the parameters already defined on the link. - * - * @param context the request context - */ - @SuppressWarnings("unchecked") - protected void bindRequestParameters(Context context) { - HttpServletRequest request = context.getRequest(); - - Set parameterNames = null; - - if (context.isAjaxRequest()) { - parameterNames = getParameters().keySet(); - } else { - parameterNames = request.getParameterMap().keySet(); - } - - for (String param : parameterNames) { - String[] values = request.getParameterValues(param); - // Do not process request parameters that return null values. Null - // values are only returned if the request parameter is not present. - // A null value can only occur for Ajax requests which processes - // parameters defined on the link, not the incoming parameters. - // The reason for not processing the null value is because it would - // nullify parametesr that was set during onInit - if (values == null) { - continue; - } - - if (values.length == 1) { - getParameters().put(param, values[0]); - } else { - getParameters().put(param, values); - } - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/ActionLink.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/ActionLink.java deleted file mode 100644 index c90f5f0423..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/ActionLink.java +++ /dev/null @@ -1,517 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Context; -import org.apache.click.control.Submit; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.apache.commons.lang.StringUtils; - -/** - * Provides a Action Link control:   <a href=""></a>. - * - * - * - *
- * Action Link - *
- * - * This control can render the "href" URL attribute using - * {@link #getHref()}, or the entire ActionLink anchor tag using - * {@link #toString()}. - *

- * ActionLink support invoking control listeners. - * - *

ActionLink Example

- * - * An example of using ActionLink to call a logout method is illustrated below: - * - *
- * public class MyPage extends Page {
- *
- *     public MyPage() {
- *         ActionLink link = new ActionLink("logoutLink");
- *         link.setListener(this, "onLogoutClick");
- *         addControl(link);
- *     }
- *
- *     public boolean onLogoutClick() {
- *         if (getContext().hasSession()) {
- *            getContext().getSession().invalidate();
- *         }
- *         setRedirect(LogoutPage.class);
- *
- *         return false;
- *     }
- * } 
- * - * The corresponding template code is below. Note href is evaluated by Velocity - * to {@link #getHref()}: - * - *
- * <a href="$logoutLink.href" title="Click to Logout">Logout</a> 
- * - * ActionLink can also support a value parameter which is accessible - * using {@link #getValue()}. - *

- * For example a products table could include rows - * of products, each with a get product details ActionLink and add product - * ActionLink. The ActionLinks include the product's id as a parameter to - * the {@link #getHref(Object)} method, which is then available when the - * control is processed: - * - *

- * <table>
- * #foreach ($product in $productList)
- *   <tr>
- *    <td>
- *      $product.name
- *    </td>
- *    <td>
- *      <a href="$detailsLink.getHref($product.id)" title="Get product information">Details</a>
- *    </td>
- *    <td>
- *      <a href="$addLink.getHref($product.id)" title="Add to basket">Add</a>
- *    </td>
- *   </tr>
- * #end
- * </table> 
- * - * The corresponding Page class for this template is: - * - *
- * public class ProductsPage extends Page {
- *
- *     public ActionLink addLink = new ActionLink("addLink", this, "onAddClick");
- *     public ActionLink detailsLink  = new ActionLink("detailsLink", this, "onDetailsClick");
- *     public List productList;
- *
- *     public boolean onAddClick() {
- *         // Get the product clicked on by the user
- *         Integer productId = addLink.getValueInteger();
- *         Product product = getProductService().getProduct(productId);
- *
- *         // Add product to basket
- *         List basket = (List) getContext().getSessionAttribute("basket");
- *         basket.add(product);
- *         getContext().setSessionAttribute("basket", basket);
- *
- *         return true;
- *     }
- *
- *     public boolean onDetailsClick() {
- *         // Get the product clicked on by the user
- *         Integer productId = detailsLink.getValueInteger();
- *         Product product = getProductService().getProduct(productId);
- *
- *         // Store the product in the request and display in the details page
- *         getContext().setRequestAttribute("product", product);
- *         setForward(ProductDetailsPage.class);
- *
- *         return false;
- *     }
- *
- *     public void onRender() {
- *         // Display the list of available products
- *         productList = getProductService().getProducts();
- *     }
- * } 
- * - * See also the W3C HTML reference: - * A Links - * - * @see org.apache.click.control.AbstractLink - * @see Submit - */ -public class ActionLink extends AbstractLink { - - // Constants -------------------------------------------------------------- - - private static final long serialVersionUID = 1L; - - /** The action link parameter name:   actionLink. */ - public static final String ACTION_LINK = "actionLink"; - - /** The value parameter name:   value. */ - public static final String VALUE = "value"; - - // Instance Variables ----------------------------------------------------- - - /** The link is clicked. */ - protected boolean clicked; - - // Constructors ----------------------------------------------------------- - - /** - * Create an ActionLink for the given name. - *

- * Please note the name 'actionLink' is reserved as a control request - * parameter name and cannot be used as the name of the control. - * - * @param name the action link name - * @throws IllegalArgumentException if the name is null - */ - public ActionLink(String name) { - setName(name); - } - - /** - * Create an ActionLink for the given name and label. - *

- * Please note the name 'actionLink' is reserved as a control request - * parameter name and cannot be used as the name of the control. - * - * @param name the action link name - * @param label the action link label - * @throws IllegalArgumentException if the name is null - */ - public ActionLink(String name, String label) { - setName(name); - setLabel(label); - } - - /** - * Create an ActionLink for the given listener object and listener - * method. - * - * @param listener the listener target object - * @param method the listener method to call - * @throws IllegalArgumentException if the name, listener or method is null - * or if the method is blank - */ - public ActionLink(Object listener, String method) { - if (listener == null) { - throw new IllegalArgumentException("Null listener parameter"); - } - if (StringUtils.isBlank(method)) { - throw new IllegalArgumentException("Blank listener method"); - } - setListener(listener, method); - } - - /** - * Create an ActionLink for the given name, listener object and listener - * method. - *

- * Please note the name 'actionLink' is reserved as a control request - * parameter name and cannot be used as the name of the control. - * - * @param name the action link name - * @param listener the listener target object - * @param method the listener method to call - * @throws IllegalArgumentException if the name, listener or method is null - * or if the method is blank - */ - public ActionLink(String name, Object listener, String method) { - setName(name); - if (listener == null) { - throw new IllegalArgumentException("Null listener parameter"); - } - if (StringUtils.isBlank(method)) { - throw new IllegalArgumentException("Blank listener method"); - } - setListener(listener, method); - } - - /** - * Create an ActionLink for the given name, label, listener object and - * listener method. - *

- * Please note the name 'actionLink' is reserved as a control request - * parameter name and cannot be used as the name of the control. - * - * @param name the action link name - * @param label the action link label - * @param listener the listener target object - * @param method the listener method to call - * @throws IllegalArgumentException if the name, listener or method is null - * or if the method is blank - */ - public ActionLink(String name, String label, Object listener, - String method) { - - setName(name); - setLabel(label); - if (listener == null) { - throw new IllegalArgumentException("Null listener parameter"); - } - if (StringUtils.isBlank(method)) { - throw new IllegalArgumentException("Blank listener method"); - } - setListener(listener, method); - } - - /** - * Create an ActionLink with no name defined. Please note the - * control's name must be defined before it is valid. - */ - public ActionLink() { - } - - // Public Attributes ------------------------------------------------------ - - /** - * Returns true if the ActionLink was clicked, otherwise returns false. - * - * @return true if the ActionLink was clicked, otherwise returns false. - */ - public boolean isClicked() { - return clicked; - } - - /** - * Return the ActionLink anchor <a> tag href attribute for the - * given value. This method will encode the URL with the session ID - * if required using HttpServletResponse.encodeURL(). - * - * @param value the ActionLink value parameter - * @return the ActionLink HTML href attribute - */ - public String getHref(Object value) { - Context context = getContext(); - String uri = ClickUtils.getRequestURI(context.getRequest()); - - HtmlStringBuffer buffer = - new HtmlStringBuffer(uri.length() + getName().length() + 40); - - buffer.append(uri); - buffer.append("?"); - buffer.append(ACTION_LINK); - buffer.append("="); - buffer.append(getName()); - if (value != null) { - buffer.append("&"); - buffer.append(VALUE); - buffer.append("="); - buffer.append(ClickUtils.encodeUrl(value, context)); - } - - if (hasParameters()) { - for (String paramName : getParameters().keySet()) { - if (!paramName.equals(ACTION_LINK) && !paramName.equals(VALUE)) { - Object paramValue = getParameters().get(paramName); - - // Check for multivalued parameter - if (paramValue instanceof String[]) { - String[] paramValues = (String[]) paramValue; - for (int j = 0; j < paramValues.length; j++) { - buffer.append("&"); - buffer.append(paramName); - buffer.append("="); - buffer.append(ClickUtils.encodeUrl(paramValues[j], - context)); - } - } else { - if (paramValue != null) { - buffer.append("&"); - buffer.append(paramName); - buffer.append("="); - buffer.append(ClickUtils.encodeUrl(paramValue, - context)); - } - } - } - } - } - - return context.getResponse().encodeURL(buffer.toString()); - } - - /** - * Return the ActionLink anchor <a> tag href attribute value. - * - * @return the ActionLink anchor <a> tag HTML href attribute value - */ - @Override - public String getHref() { - return getHref(getValue()); - } - - /** - * Set the name of the Control. Each control name must be unique in the - * containing Page model or the containing Form. - *

- * Please note the name 'actionLink' is reserved as a control request - * parameter name and cannot be used as the name of the control. - * - * @see org.apache.click.Control#setName(String) - * - * @param name of the control - * @throws IllegalArgumentException if the name is null - */ - @Override - public void setName(String name) { - if (ACTION_LINK.equals(name)) { - String msg = "Invalid name '" + ACTION_LINK + "'. This name is " - + "reserved for use as a control request parameter name"; - throw new IllegalArgumentException(msg); - } - super.setName(name); - } - - /** - * Set the parent of the ActionLink. - * - * @see org.apache.click.Control#setParent(Object) - * - * @param parent the parent of the Control - * @throws IllegalStateException if {@link #name} is not defined - * @throws IllegalArgumentException if the given parent instance is - * referencing this object: if (parent == this) - */ - @Override - public void setParent(Object parent) { - if (parent == this) { - throw new IllegalArgumentException("Cannot set parent to itself"); - } - if (getName() == null) { - String msg = "ActionLink name not defined."; - throw new IllegalArgumentException(msg); - } - this.parent = parent; - } - - /** - * Returns the ActionLink value if the action link was processed and has - * a value, or null otherwise. - * - * @return the ActionLink value if the ActionLink was processed - */ - public String getValue() { - return getParameter(VALUE); - } - - /** - * Returns the action link Double value if the action link was - * processed and has a value, or null otherwise. - * - * @return the action link Double value if the action link was processed - * - * @throws NumberFormatException if the value cannot be parsed into a Double - */ - public Double getValueDouble() { - String value = getValue(); - if (value != null) { - return Double.valueOf(value); - } else { - return null; - } - } - - /** - * Returns the ActionLink Integer value if the ActionLink was - * processed and has a value, or null otherwise. - * - * @return the ActionLink Integer value if the action link was processed - * - * @throws NumberFormatException if the value cannot be parsed into an Integer - */ - public Integer getValueInteger() { - String value = getValue(); - if (value != null) { - return Integer.valueOf(value); - } else { - return null; - } - } - - /** - * Returns the ActionLink Long value if the ActionLink was - * processed and has a value, or null otherwise. - * - * @return the ActionLink Long value if the action link was processed - * - * @throws NumberFormatException if the value cannot be parsed into a Long - */ - public Long getValueLong() { - String value = getValue(); - if (value != null) { - return Long.valueOf(value); - } else { - return null; - } - } - - /** - * Set the ActionLink value. - * - * @param value the ActionLink value - */ - public void setValue(String value) { - setParameter(VALUE, value); - } - - /** - * Set the value of the ActionLink using the given object. - * - * @param object the object value to set - */ - public void setValueObject(Object object) { - if (object != null) { - setValue(object.toString()); - } else { - setValue(null); - } - } - - /** - * This method binds the submitted request value to the ActionLink's - * value. - */ - @Override - public void bindRequestValue() { - Context context = getContext(); - if (context.isMultipartRequest()) { - return; - } - - clicked = getName().equals(context.getRequestParameter(ACTION_LINK)); - - if (clicked) { - String value = context.getRequestParameter(VALUE); - if (value != null) { - setValue(value); - } - bindRequestParameters(context); - } - } - - // Public Methods --------------------------------------------------------- - - /** - * This method will set the {@link #isClicked()} property to true if the - * ActionLink was clicked, and if an action callback listener was set - * this will be invoked. - * - * @see org.apache.click.Control#onProcess() - * - * @return true to continue Page event processing or false otherwise - */ - @Override - public boolean onProcess() { - bindRequestValue(); - - if (isClicked()) { - dispatchActionEvent(); - } - return true; - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Button.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Button.java deleted file mode 100644 index bdf74b6e4b..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Button.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Context; -import org.apache.click.control.Reset; -import org.apache.click.control.Submit; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -/** - * Provides a Button control:   <input type='button'/>. - * - * - * - *
- * - *
- * - * The Button control is used to render a JavaScript enabled button which can - * perform client side logic. The Button control provides no server side - * processing. If server side processing is required use {@link Submit} instead. - * - *

Button Example

- * - * The example below adds a back button to a form, which when clicked returns - * to the previous page. - * - *
- * Button backButton = new Button("back", " < Back ");
- * backButton.setOnClick("history.back();");
- * backButton.setTitle("Return to previous page");
- * form.add(backButton); 
- * - * HTML output: - *
- * <input type='button' name='back' value=' < Back ' onclick='history.back();'
- *        title='Return to previous page'/> 
- * - * See also W3C HTML reference - * INPUT - * - * @see Reset - * @see Submit - */ -public class Button extends Field { - - // Constants -------------------------------------------------------------- - - private static final long serialVersionUID = 1L; - - // Constructors ----------------------------------------------------------- - - /** - * Create a button with the given name. - * - * @param name the button name - */ - public Button(String name) { - super(name); - } - - /** - * Create a button with the given name and label. The button label is - * rendered as the HTML "value" attribute. - * - * @param name the button name - * @param label the button label - */ - public Button(String name, String label) { - setName(name); - setLabel(label); - } - - /** - * Create a button with no name defined. - *

- * Please note the control's name must be defined before it is valid. - */ - public Button() { - } - - // Public Attributes ------------------------------------------------------ - - /** - * Return the button's html tag: input. - * - * @see org.apache.click.control.AbstractControl#getTag() - * - * @return this controls html tag - */ - @Override - public String getTag() { - return "input"; - } - - /** - * Returns the button onclick attribute value, or null if not defined. - * - * @return the button onclick attribute value, or null if not defined. - */ - public String getOnClick() { - if (attributes != null) { - return attributes.get("onclick"); - } else { - return null; - } - } - - /** - * Sets the button onclick attribute value. - * - * @param value the onclick attribute value. - */ - public void setOnClick(String value) { - setAttribute("onclick", value); - } - - /** - * Return the input type: 'button'. - * - * @return the input type: 'button' - */ - public String getType() { - return "button"; - } - - // Public Methods -------------------------------------------------------- - - /** - * For non Ajax requests this method returns true, as buttons by default - * perform no server side logic. If the button is an Ajax target and a - * behavior is defined, the behavior action will be invoked. - * - * @see Field#onProcess() - * - * @return true to continue Page event processing or false otherwise - */ - @Override - public boolean onProcess() { - Context context = getContext(); - - if (context.isAjaxRequest()) { - - if (isDisabled()) { - // Switch off disabled property if control has incoming request - // parameter. Normally this means the field was enabled via JS - if (context.hasRequestParameter(getName())) { - setDisabled(false); - } else { - // If field is disabled skip process event - return true; - } - } - - if (context.hasRequestParameter(getName())) { - dispatchActionEvent(); - } - } - - return true; - } - - /** - * @see AbstractControl#getControlSizeEst() - * - * @return the estimated rendered control size in characters - */ - @Override - public int getControlSizeEst() { - return 40; - } - - /** - * Render the HTML representation of the Button. Note the button label is - * rendered as the HTML "value" attribute. - * - * @see #toString() - * - * @param buffer the specified buffer to render the control's output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - buffer.elementStart(getTag()); - - buffer.appendAttribute("type", getType()); - buffer.appendAttribute("name", getName()); - buffer.appendAttribute("id", getId()); - buffer.appendAttribute("value", getLabel()); - buffer.appendAttribute("title", getTitle()); - if (getTabIndex() > 0) { - buffer.appendAttribute("tabindex", getTabIndex()); - } - - appendAttributes(buffer); - - if (isDisabled()) { - buffer.appendAttributeDisabled(); - } - - buffer.elementEnd(); - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Column.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Column.java deleted file mode 100644 index a9adbcf431..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Column.java +++ /dev/null @@ -1,1596 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.apache.click.util.PropertyUtils; -import org.apache.commons.lang.math.NumberUtils; - -import java.io.Serializable; -import java.text.MessageFormat; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.StringTokenizer; - -/** - * Provides the Column table data <td> and table header <th> - * renderer. - * - * - *
- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
IdNameCategoryAction
834501Alison SmartResidential PropertyView
238454Angus RobinsBondsView
784191Ann MelanResidential PropertyView
- * - *
- * - *

- * - * The Column object provide column definitions for the {@link org.apache.click.control.Table} object. - * - *

Column Options

- * - * The Column class supports a number of rendering options which include: - * - *
    - *
  • {@link #autolink} - the option to automatically render href links - * for email and URL column values
  • - *
  • {@link #attributes} - the CSS style attributes for the table data cell
  • - *
  • {@link #dataClass} - the CSS class for the table data cell
  • - *
  • {@link #dataStyles} - the CSS styles for the table data cell
  • - *
  • {@link #decorator} - the custom column value renderer
  • - *
  • {@link #format} - the MessageFormat pattern rendering - * the column value
  • - *
  • {@link #headerClass} - the CSS class for the table header cell
  • - *
  • {@link #headerStyles} - the CSS styles for the table header cell
  • - *
  • {@link #headerTitle} - the table header cell value to render
  • - *
  • {@link #sortable} - the table column sortable property
  • - *
  • {@link #width} - the table cell width property
  • - *
- * - *

Format Pattern

- * - * The {@link #format} property which specifies {@link MessageFormat} pattern - * a is very useful for formatting column values. For example to render - * formatted number and date values you simply specify: - * - *
- * Table table = new Table("table");
- * table.setClass("isi");
- *
- * Column idColumn = new Column("purchaseId", "ID");
- * idColumn.setFormat("{0,number,#,###}");
- * table.addColumn(idColumn);
- *
- * Column priceColumn = new Column("purchasePrice", "Price");
- * priceColumn.setFormat("{0,number,currency}");
- * priceColumn.setTextAlign("right");
- * table.addColumn(priceColumn);
- *
- * Column dateColumn = new Column("purchaseDate", "Date");
- * dateColumn.setFormat("{0,date,dd MMM yyyy}");
- * table.addColumn(dateColumn);
- *
- * Column orderIdColumn = new Column("order.id", "Order ID");
- * table.addColumn(orderIdColumn);  
- * - *

Column Decorators

- * - * The support custom column value rendering you can specify a {@link Decorator} - * class on columns. The decorator render method is passed the table - * row object and the page request Context. Using the table row you can access - * all the column values enabling you to render a compound value composed of - * multiple column values. For example: - * - *
- * Column column = new Column("email");
- *
- * column.setDecorator(new Decorator() {
- *     public String render(Object row, Context context) {
- *         Person person = (Person) row;
- *         String email = person.getEmail();
- *         String fullName = person.getFullName();
- *         return "<a href='mailto:" + email + "'>" + fullName + "</a>";
- *     }
- * });
- *
- * table.addColumn(column); 
- - * The Context parameter of the decorator render() method enables you to - * render controls to provide additional functionality. For example: - * - *
- * public class CustomerList extends BorderedPage {
- *
- *     private Table table = new Table("table");
- *     private ActionLink viewLink = new ActionLink("view");
- *
- *     public CustomerList() {
- *
- *         viewLink.setListener(this, "onViewClick");
- *         viewLink.setLabel("View");
- *         viewLink.setAttribute("title", "View customer details");
- *         table.addControl(viewLink);
- *
- *         table.addColumn(new Column("id"));
- *         table.addColumn(new Column("name"));
- *         table.addColumn(new Column("category"));
- *
- *         Column column = new Column("Action");
- *         column.setDecorator(new Decorator() {
- *             public String render(Object row, Context context) {
- *                 Customer customer = (Customer) row;
- *                 viewLink.setValue("" + customer.getId());
- *
- *                 return viewLink.toString();
- *             }
- *          });
- *         table.addColumn(column);
- *
- *         addControl(table);
- *     }
- *
- *     public boolean onViewClick() {
- *         String path = getContext().getPagePath(Logout.class);
- *         setRedirect(path + "?id=" + viewLink.getValue());
- *         return true;
- *     }
- * } 
- * - *

Internationalization

- * - * Column header titles can be localized using the controls parent messages. - * If the column header title value is null, the column will attempt to find a - * localized message in the parent messages using the key: - *
- * getName() + ".headerTitle" - *
- * If not found then the message will be looked up in the - * /click-control.properties file using the same key. - * If a value still cannot be found then the Column name will be converted - * into a header title using the method: {@link ClickUtils#toLabel(String)}. - *

- * - * @see Decorator - * @see org.apache.click.control.Table - */ -public class Column implements Serializable { - - private static final long serialVersionUID = 1L; - - // ----------------------------------------------------- Instance Variables - - /** The Column attributes Map. */ - protected Map attributes; - - /** - * The automatically hyperlink column URL and email address values flag, - * default value is false. - */ - protected boolean autolink; - - /** The column table data <td> CSS class attribute. */ - protected String dataClass; - - /** The Map of column table data <td> CSS style attributes. */ - protected Map dataStyles; - - /** The column row decorator. */ - protected Decorator decorator; - - /** The escape HTML characters flag. The default value is true. */ - protected boolean escapeHtml = true; - - /** The column message format pattern. */ - protected String format; - - /** The CSS class attribute of the column header. */ - protected String headerClass; - - /** The Map of column table header <th> CSS style attributes. */ - protected Map headerStyles; - - /** The title of the column header. */ - protected String headerTitle; - - /** - * The maximum column length. If maxLength is greater than 0 and the column - * data string length is greater than maxLength, the rendered value will be - * truncated with an eclipse(...). - *

- * Autolinked email or URL values will not be constrained. - *

- * The default value is 0. - */ - protected int maxLength; - - /** - * The optional MessageFormat used to render the column table cell value. - */ - protected MessageFormat messageFormat; - - /** The property name of the row object to render. */ - protected String name; - - /** The column render id attribute status. The default value is false. */ - protected Boolean renderId; - - /** The method cached for rendering column values. */ - protected transient Map methodCache; - - /** The column sortable status. The default value is false. */ - protected Boolean sortable; - - /** The parent Table. */ - protected Table table; - - /** - * The property name used to populate the <td> "title" attribute. - * The default value is null. - */ - protected String titleProperty; - - /** The column HTML <td> width attribute. */ - protected String width; - - /** The column comparator object, which is used to sort column row values. */ - @SuppressWarnings("unchecked") - Comparator comparator; - - // ----------------------------------------------------------- Constructors - - /** - * Create a table column with the given property name. The table header - * title will be set as the capitalized property name. - * - * @param name the name of the property to render - */ - public Column(String name) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - this.name = name; - } - - /** - * Create a table column with the given property name and header title. - * - * @param name the name of the property to render - * @param title the column header title to render - */ - public Column(String name, String title) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - if (title == null) { - throw new IllegalArgumentException("Null title parameter"); - } - this.name = name; - this.headerTitle = title; - } - - /** - * Create a Column with no name defined. - *

- * Please note the control's name must be defined before it is valid. - */ - public Column() { - } - - // ------------------------------------------------------ Public Properties - - /** - * Return the Column HTML attribute with the given name, or null if the - * attribute does not exist. - * - * @param name the name of column HTML attribute - * @return the Column HTML attribute - */ - public String getAttribute(String name) { - return getAttributes().get(name); - } - - /** - * Set the Column with the given HTML attribute name and value. These - * attributes will be rendered as HTML attributes, for example: - *

- * If there is an existing named attribute in the Column it will be replaced - * with the new value. If the given attribute value is null, any existing - * attribute will be removed. - * - * @param name the name of the column HTML attribute - * @param value the value of the column HTML attribute - * @throws IllegalArgumentException if attribute name is null - */ - public void setAttribute(String name, String value) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - if (value != null) { - getAttributes().put(name, value); - } else { - getAttributes().remove(name); - } - } - - /** - * Return the Column attributes Map. - * - * @return the column attributes Map. - */ - public Map getAttributes() { - if (attributes == null) { - attributes = new HashMap(); - } - return attributes; - } - - /** - * Return true if the Column has attributes or false otherwise. - * - * @return true if the column has attributes on false otherwise - */ - public boolean hasAttributes() { - return (attributes != null && !getAttributes().isEmpty()); - } - - /** - * Return the flag to automatically render HTML hyperlinks for column URL - * and email addresses values. - * - * @return automatically hyperlink column URL and email addresses flag - */ - public boolean getAutolink() { - return autolink; - } - - /** - * Set the flag to automatically render HTML hyperlinks for column URL - * and email addresses values. - * - * @param autolink the flag to automatically hyperlink column URL and - * email addresses flag - */ - public void setAutolink(boolean autolink) { - this.autolink = autolink; - } - - /** - * Return the column comparator object, which is used to sort column row - * values. - * - * @return the column row data sorting comparator object. - */ - @SuppressWarnings("unchecked") - public Comparator getComparator() { - if (comparator == null) { - comparator = new ColumnComparator(this); - } - return comparator; - } - - /** - * Set the column comparator object, which is used to sort column row - * values. - * - * @param comparator the column row data sorting comparator object - */ - @SuppressWarnings("unchecked") - public void setComparator(Comparator comparator) { - this.comparator = comparator; - } - - /** - * Return the table data <td> CSS class. - * - * @return the table data CSS class - */ - public String getDataClass() { - return dataClass; - } - - /** - * Set the table data <td> CSS class. - * - * @param dataClass the table data CSS class - */ - public void setDataClass(String dataClass) { - this.dataClass = dataClass; - } - - /** - * Return the table data <td> CSS style. - * - * @param name the CSS style name - * @return the table data CSS style for the given name - */ - public String getDataStyle(String name) { - if (hasDataStyles()) { - return getDataStyles().get(name); - - } else { - return null; - } - } - - /** - * Set the table data <td> CSS style name and value pair. - * - * @param name the CSS style name - * @param value the CSS style value - */ - public void setDataStyle(String name, String value) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - if (value != null) { - getDataStyles().put(name, value); - } else { - getDataStyles().remove(name); - } - } - - /** - * Return true if table data <td> CSS styles are defined. - * - * @return true if table data <td> CSS styles are defined - */ - public boolean hasDataStyles() { - return (dataStyles != null && !dataStyles.isEmpty()); - } - - /** - * Return the Map of table data <td> CSS styles. - * - * @return the Map of table data <td> CSS styles - */ - public Map getDataStyles() { - if (dataStyles == null) { - dataStyles = new HashMap(); - } - return dataStyles; - } - - /** - * Return the row column <td> decorator. - * - * @return the row column <td> decorator - */ - public Decorator getDecorator() { - return decorator; - } - - /** - * Set the row column <td> decorator. - * - * @param decorator the row column <td> decorator - */ - public void setDecorator(Decorator decorator) { - this.decorator = decorator; - } - - /** - * Return true if the HTML characters will be escaped when rendering the - * column data. By default this method returns true. - * - * @return true if the HTML characters will be escaped when rendered - */ - public boolean getEscapeHtml() { - return escapeHtml; - } - - /** - * Set the escape HTML characters when rendering column data flag. - * - * @param escape the flag to escape HTML characters - */ - public void setEscapeHtml(boolean escape) { - this.escapeHtml = escape; - } - - /** - * Return the row column message format pattern. - * - * @return the message row column message format pattern - */ - public String getFormat() { - return format; - } - - /** - * Set the row column message format pattern. For example: - * - *

-     * Column idColumn = new Column("purchaseId", "ID");
-     * idColumn.setFormat("{0,number,#,###}");
-     *
-     * Column priceColumn = new Column("purchasePrice", "Price");
-     * priceColumn.setFormat("{0,number,currency}");
-     *
-     * Column dateColumn = new Column("purchaseDate", "Date");
-     * dateColumn.setFormat("{0,date,dd MMM yyyy}"); 
- * - *

MesssageFormat Patterns

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Format Type - * Format Style - * Subformat Created - *
number - * (none) - * NumberFormat.getInstance(getLocale()) - *
integer - * NumberFormat.getIntegerInstance(getLocale()) - *
currency - * NumberFormat.getCurrencyInstance(getLocale()) - *
percent - * NumberFormat.getPercentInstance(getLocale()) - *
SubformatPattern - * new DecimalFormat(subformatPattern, new DecimalFormatSymbols(getLocale())) - *
date - * (none) - * DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale()) - *
short - * DateFormat.getDateInstance(DateFormat.SHORT, getLocale()) - *
medium - * DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale()) - *
long - * DateFormat.getDateInstance(DateFormat.LONG, getLocale()) - *
full - * DateFormat.getDateInstance(DateFormat.FULL, getLocale()) - *
SubformatPattern - * new SimpleDateFormat(subformatPattern, getLocale()) - *
time - * (none) - * DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale()) - *
short - * DateFormat.getTimeInstance(DateFormat.SHORT, getLocale()) - *
medium - * DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale()) - *
long - * DateFormat.getTimeInstance(DateFormat.LONG, getLocale()) - *
full - * DateFormat.getTimeInstance(DateFormat.FULL, getLocale()) - *
SubformatPattern - * new SimpleDateFormat(subformatPattern, getLocale()) - *
choice - * SubformatPattern - * new ChoiceFormat(subformatPattern) - *
- * - *

DecimalFormat Pattern Characters

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Symbol - * Location - * Localized? - * Meaning - *
0 - * Number - * Yes - * Digit - *
# - * Number - * Yes - * Digit, zero shows as absent - *
. - * Number - * Yes - * Decimal separator or monetary decimal separator - *
- - * Number - * Yes - * Minus sign - *
, - * Number - * Yes - * Grouping separator - *
E - * Number - * Yes - * Separates mantissa and exponent in scientific notation. - * Need not be quoted in prefix or suffix. - *
; - * Subpattern boundary - * Yes - * Separates positive and negative subpatterns - *
% - * Prefix or suffix - * Yes - * Multiply by 100 and show as percentage - *
\u2030 - * Prefix or suffix - * Yes - * Multiply by 1000 and show as per mille - *
¤ (\u00A4) - * Prefix or suffix - * No - * Currency sign, replaced by currency symbol. If - * doubled, replaced by international currency symbol. - * If present in a pattern, the monetary decimal separator - * is used instead of the decimal separator. - *
' - * Prefix or suffix - * No - * Used to quote special characters in a prefix or suffix, - * for example, "'#'#" formats 123 to - * "#123". To create a single quote - * itself, use two in a row: "# o''clock". - *
- * - *

SimpleDateFormat Pattern Characters

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Letter - * Date or Time Component - * Presentation - * Examples - *
G - * Era designator - * Text - * AD - *
y - * Year - * Year - * 1996; 96 - *
M - * Month in year - * Month - * July; Jul; 07 - *
w - * Week in year - * Number - * 27 - *
W - * Week in month - * Number - * 2 - *
D - * Day in year - * Number - * 189 - *
d - * Day in month - * Number - * 10 - *
F - * Day of week in month - * Number - * 2 - *
E - * Day in week - * Text - * Tuesday; Tue - *
a - * Am/pm marker - * Text - * PM - *
H - * Hour in day (0-23) - * Number - * 0 - *
k - * Hour in day (1-24) - * Number - * 24 - *
K - * Hour in am/pm (0-11) - * Number - * 0 - *
h - * Hour in am/pm (1-12) - * Number - * 12 - *
m - * Minute in hour - * Number - * 30 - *
s - * Second in minute - * Number - * 55 - *
S - * Millisecond - * Number - * 978 - *
z - * Time zone - * General time zone - * Pacific Standard Time; PST; GMT-08:00 - *
Z - * Time zone - * RFC 822 time zone - * -0800 - *
- * - * @param pattern the message format pattern - */ - public void setFormat(String pattern) { - this.format = pattern; - } - - /** - * The maximum column length. If maxLength is greater than 0 and the column - * data string length is greater than maxLength, the rendered value will be - * truncated with an eclipse(...). - * - * @return the maximum column length, or 0 if not defined - */ - public int getMaxLength() { - return maxLength; - } - - /** - * Set the maximum column length. If maxLength is greater than 0 and the - * column data string length is greater than maxLength, the rendered value - * will be truncated with an eclipse(...). - * - * @param value the maximum column length - */ - public void setMaxLength(int value) { - maxLength = value; - } - - /** - * Return the MessageFormat instance used to format the table cell value. - * - * @return the MessageFormat instance used to format the table cell value - */ - public MessageFormat getMessageFormat() { - return messageFormat; - } - - /** - * Set the MessageFormat instance used to format the table cell value. - * - * @param messageFormat the MessageFormat used to format the table cell - * value - */ - public void setMessageFormat(MessageFormat messageFormat) { - this.messageFormat = messageFormat; - } - - /** - * Return the property name. - * - * @return the name of the property - */ - public String getName() { - return name; - } - - /** - * Set the property name. - * - * @param name the property name to set - */ - public void setName(String name) { - this.name = name; - } - - /** - * Return the table header <th> CSS class. - * - * @return the table header CSS class - */ - public String getHeaderClass() { - return headerClass; - } - - /** - * Set the table header <th> CSS class. - * - * @param headerClass the table header CSS class - */ - public void setHeaderClass(String headerClass) { - this.headerClass = headerClass; - } - - /** - * Return the table header <th> CSS style. - * - * @param name the CSS style name - * @return the table header CSS style value for the given name - */ - public String getHeaderStyle(String name) { - if (hasHeaderStyles()) { - return getHeaderStyles().get(name); - - } else { - return null; - } - } - - /** - * Set the table header <th> CSS style name and value pair. - * - * @param name the CSS style name - * @param value the CSS style value - */ - public void setHeaderStyle(String name, String value) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - if (value != null) { - getHeaderStyles().put(name, value); - } else { - getHeaderStyles().remove(name); - } - } - - /** - * Return true if table header <th> CSS styles are defined. - * - * @return true if table header <th> CSS styles are defined - */ - public boolean hasHeaderStyles() { - return (headerStyles != null && !headerStyles.isEmpty()); - } - - /** - * Return the Map of table header <th> CSS styles. - * - * @return the Map of table header <th> CSS styles - */ - public Map getHeaderStyles() { - if (headerStyles == null) { - headerStyles = new HashMap(); - } - return headerStyles; - } - - /** - * Return the table header <th> title. - *

- * If the header title value is null, this method will attempt to find a - * localized message in the parent messages using the key: - *

- * getName() + ".headerTitle" - *
- * If not found then the message will be looked up in the - * /click-control.properties file using the same key. - * If a value still cannot be found then the Column name will be converted - * into a header title using the method: {@link ClickUtils#toLabel(String)} - *

- * - * @return the table header title - */ - public String getHeaderTitle() { - if (headerTitle == null) { - headerTitle = getTable().getMessage(getName() + ".headerTitle"); - } - if (headerTitle == null) { - headerTitle = ClickUtils.toLabel(getName()); - } - return headerTitle; - } - - /** - * Set the table header <th> title. - * - * @param title the table header title - */ - public void setHeaderTitle(String title) { - headerTitle = title; - } - - /** - * Return the Table and Column id appended:   "table-column" - *

- * Use the field the "id" attribute value if defined, or the name otherwise. - * - * @return HTML element identifier attribute "id" value - */ - public String getId() { - if (hasAttributes() && getAttributes().containsKey("id")) { - return getAttribute("id"); - - } else { - String tableId = (getTable() != null) - ? getTable().getId() + "-" : ""; - - String id = tableId + getName(); - - if (id.indexOf('/') != -1) { - id = id.replace('/', '_'); - } - if (id.indexOf(' ') != -1) { - id = id.replace(' ', '_'); - } - - return id; - } - } - - /** - * Return the column sortable status. If the column sortable status is not - * defined the value will be inherited from the - * {@link org.openidentityplatform.openam.click.control.Table#sortable} property. - * - * @return the column sortable status - */ - public boolean getSortable() { - if (sortable == null) { - if (getTable() != null) { - return getTable().getSortable(); - } else { - return false; - } - - } else { - return sortable; - } - } - - /** - * Set the column render id attribute status. - * - * @param value set the column render id attribute status - */ - public void setRenderId(boolean value) { - renderId = value; - } - - /** - * Returns the column render id attribute status. If the column render id - * status is not defined the value will be inherited from the - * {@link org.openidentityplatform.openam.click.control.Table#renderId} property. - * - * @return the column render id attribute status - */ - public boolean getRenderId() { - if (renderId == null) { - if (getTable() != null) { - return getTable().getRenderId(); - } else { - return false; - } - - } else { - return renderId; - } - } - - /** - * Set the column sortable status. - * - * @param value the column sortable status - */ - public void setSortable(boolean value) { - sortable = value; - } - - /** - * Return the parent Table containing the Column. - * - * @return the parent Table containing the Column - */ - public Table getTable() { - return table; - } - - /** - * Set the Column's the parent Table. - * - * @param table Column's parent Table - */ - public void setTable(Table table) { - this.table = table; - } - - /** - * Set the column CSS "text-align" style for the header <th> and - * data <td> elements. Valid values include: - * [left, right, center] - * - * @param align the CSS "text-align" value: [left, right, center] - */ - public void setTextAlign(String align) { - if (align != null && "middle".equalsIgnoreCase(align)) { - String msg = - "\"middle\" is not a valid CSS \"text-align\" " - + "value, use \"center\" instead"; - throw new IllegalArgumentException(msg); - } - if (!getSortable()) { - setHeaderStyle("text-align", align); - } - setDataStyle("text-align", align); - } - - /** - * Return the property name used to populate the <td> "title" attribute. - * - * @return the property name used to populate the <td> "title" attribute - */ - public String getTitleProperty() { - return titleProperty; - } - - /** - * Set the property name used to populate the <td> "title" attribute. - * - * @param property the property name used to populate the <td> "title" attribute - */ - public void setTitleProperty(String property) { - titleProperty = property; - } - - /** - * Set the column CSS "vertical-align" style for the header <th> and - * data <td> elements. Valid values include: - * [baseline | sub | super | top | text-top | middle | bottom | - * text-bottom | <percentage> | <length> | inherit] - * - * @param align the CSS "vertical-align" value - */ - public void setVerticalAlign(String align) { - if (align != null && "center".equalsIgnoreCase(align)) { - String msg = - "\"center\" is not a valid CSS \"vertical-align\" " - + "value, use \"middle\" instead"; - throw new IllegalArgumentException(msg); - } - setHeaderStyle("vertical-align", align); - setDataStyle("vertical-align", align); - } - - /** - * Return the column HTML <td> width attribute. - * - * @return the column HTML <td> width attribute - */ - public String getWidth() { - return width; - } - - /** - * Set the column HTML <td> width attribute. - * - * @param value the column HTML <td> width attribute - */ - public void setWidth(String value) { - width = value; - } - - // --------------------------------------------------------- Public Methods - - /** - * Render the column table data <td> element to the given buffer using - * the passed row object. - * - * @param row the row object to render - * @param buffer the string buffer to render to - * @param context the request context - * @param rowIndex the index of the current row within the parent table - */ - public void renderTableData(Object row, HtmlStringBuffer buffer, - Context context, int rowIndex) { - - if (getMessageFormat() == null && getFormat() != null) { - Locale locale = context.getLocale(); - setMessageFormat(new MessageFormat(getFormat(), locale)); - } - - buffer.elementStart("td"); - if (getRenderId()) { - String id = getId(); - buffer.append(" id=\"").append(id).append("_").append(rowIndex).append("\""); - } - buffer.appendAttribute("class", getDataClass()); - - if (getTitleProperty() != null) { - Object titleValue = getProperty(getTitleProperty(), row); - buffer.appendAttributeEscaped("title", titleValue); - } - if (hasAttributes()) { - buffer.appendAttributes(getAttributes()); - } - if (hasDataStyles()) { - buffer.appendStyleAttributes(getDataStyles()); - } - buffer.appendAttribute("width", getWidth()); - buffer.closeTag(); - - renderTableDataContent(row, buffer, context, rowIndex); - - buffer.elementEnd("td"); - } - - /** - * Render the column table header <tr> element to the given buffer. - * - * @param buffer the string buffer to render to - * @param context the request context - */ - public void renderTableHeader(HtmlStringBuffer buffer, Context context) { - buffer.elementStart("th"); - - boolean isSortable = getSortable() && !getTable().getRowList().isEmpty(); - boolean sortedColumn = getName().equals(getTable().getSortedColumn()); - boolean ascending = getTable().isSortedAscending(); - - if (isSortable) { - String classValue = - (getHeaderClass() != null) ? getHeaderClass() + " " : ""; - - if (sortedColumn) { - classValue += (ascending) ? "ascending" : "descending"; - } else { - classValue += "sortable"; - } - buffer.appendAttribute("class", classValue); - - } else { - buffer.appendAttribute("class", getHeaderClass()); - } - - if (hasHeaderStyles()) { - buffer.appendStyleAttributes(getHeaderStyles()); - } - - if (hasAttributes()) { - buffer.appendAttributes(getAttributes()); - } - - buffer.closeTag(); - - if (isSortable) { - ActionLink controlLink = getTable().getControlLink(); - - controlLink.setParameter(org.apache.click.control.Table.COLUMN, getName()); - controlLink.setParameter(org.apache.click.control.Table.PAGE, String.valueOf(getTable().getPageNumber())); - - if (sortedColumn) { - controlLink.setParameter(org.apache.click.control.Table.ASCENDING, String.valueOf(ascending)); - controlLink.setParameter(org.apache.click.control.Table.SORT, "true"); - - } else { - controlLink.setParameter(org.apache.click.control.Table.ASCENDING, null); - controlLink.setParameter(org.apache.click.control.Table.SORT, null); - } - - // Provide spacer for sorting icon - controlLink.setLabel(getHeaderTitle() + "   "); - - controlLink.render(buffer); - - } else { - if (getEscapeHtml()) { - buffer.appendEscaped(getHeaderTitle()); - } else { - buffer.append(getHeaderTitle()); - } - } - - buffer.elementEnd("th"); - } - - /** - * Return the column name property value from the given row object. - *

- * If the row object is a Map this method will attempt to return - * the map value for the column name. The row map lookup will be performed - * using the property name, if a value is not found the property name in - * uppercase will be used, if a value is still not found the property name - * in lowercase will be used. If a map value is still not found then this - * method will return null. - *

- * Object property values can also be specified using a path expression. - * - * @param row the row object to obtain the property from - * @return the named row object property value - * @throws RuntimeException if an error occurred obtaining the property - */ - public Object getProperty(Object row) { - return getProperty(getName(), row); - } - - /** - * Return the column property value from the given row object and property name. - *

- * If the row object is a Map this method will attempt to return - * the map value for the column. The row map lookup will be performed using - * the property name, if a value is not found the property name in uppercase - * will be used, if a value is still not found the property name in lowercase - * will be used. If a map value is still not found then this method will - * return null. - *

- * Object property values can also be specified using a path expression. - * - * @param name the name of the property - * @param row the row object to obtain the property from - * @return the named row object property value - * @throws RuntimeException if an error occurred obtaining the property - */ - @SuppressWarnings("unchecked") - public Object getProperty(String name, Object row) { - if (row instanceof Map) { - Map map = (Map) row; - - Object object = map.get(name); - if (object != null) { - return object; - } - - String upperCaseName = name.toUpperCase(); - object = map.get(upperCaseName); - if (object != null) { - return object; - } - - String lowerCaseName = name.toLowerCase(); - object = map.get(lowerCaseName); - if (object != null) { - return object; - } - - return null; - - } else { - if (methodCache == null) { - methodCache = new HashMap(); - } - - return PropertyUtils.getValue(row, name, methodCache); - } - } - - // ------------------------------------------------------ Protected Methods - - /** - * Render the content within the column table data <td> element. - * - * @param row the row object to render - * @param buffer the string buffer to render to - * @param context the request context - * @param rowIndex the index of the current row within the parent table - */ - protected void renderTableDataContent(Object row, HtmlStringBuffer buffer, - Context context, int rowIndex) { - - if (getDecorator() != null) { - String value = getDecorator().render(row, context); - if (value != null) { - buffer.append(value); - } - - } else { - Object columnValue = getProperty(row); - if (columnValue != null) { - if (getAutolink() && renderLink(columnValue, buffer)) { - // Has been rendered - - } else if (getMessageFormat() != null) { - Object[] args = new Object[] { columnValue }; - - String value = getMessageFormat().format(args); - - if (getMaxLength() > 0) { - value = ClickUtils.limitLength(value, getMaxLength()); - } - - if (getEscapeHtml()) { - buffer.appendEscaped(value); - } else { - buffer.append(value); - } - - } else { - String value = columnValue.toString(); - - if (getMaxLength() > 0) { - value = ClickUtils.limitLength(value, getMaxLength()); - } - - if (getEscapeHtml()) { - buffer.appendEscaped(value); - } else { - buffer.append(value); - } - } - } - } - } - - /** - * Render the given table cell value to the buffer as a mailto: - * or http: hyperlink, or as an ordinary string if the value is - * determined not be linkable. - * - * @param value the table cell value to render - * @param buffer the StringBuffer to render to - * @return a rendered email or web hyperlink if applicable - */ - protected boolean renderLink(Object value, HtmlStringBuffer buffer) { - if (value != null) { - String valueStr = value.toString(); - - // If email - if (valueStr.indexOf('@') != -1 - && !valueStr.startsWith("@") - && !valueStr.endsWith("@")) { - - buffer.append(""); - buffer.append(valueStr); - buffer.append(""); - return true; - - } else if (valueStr.startsWith("http")) { - int index = valueStr.indexOf("//"); - if (index != -1) { - index += 2; - } else { - index = 0; - } - buffer.append(""); - buffer.append(valueStr.substring(index)); - buffer.append(""); - return true; - - } else if (valueStr.startsWith("www")) { - buffer.append(""); - buffer.append(valueStr); - buffer.append(""); - return true; - } - } - return false; - } - - // ---------------------------------------------------------- Inner Classes - - /** - * Provides a table Column comparator for sorting table rows. - * - * @see Column - * @see Table - */ - @SuppressWarnings("unchecked") - static class ColumnComparator implements Comparator, Serializable { - - private static final long serialVersionUID = 1L; - - /** The sort ascending flag. */ - protected int ascendingSort; - - /** The column to sort on. */ - protected final Column column; - - /** - * Create a new Column based row comparator. - * - * @param column the column to sort on - */ - public ColumnComparator(Column column) { - this.column = column; - } - - /** - * @see Comparator#compare(Object, Object) - * - * @param row1 the first row to compare - * @param row2 the second row to compare - * @return the comparison result - */ - public int compare(Object row1, Object row2) { - - this.ascendingSort = column.getTable().isSortedAscending() ? 1 : -1; - - Object value1 = column.getProperty(row1); - Object value2 = column.getProperty(row2); - - if (value1 instanceof Comparable && value2 instanceof Comparable) { - - if (value1 instanceof String || value2 instanceof String) { - return stringCompare(value1, value2) * ascendingSort; - - } else { - - return ((Comparable) value1).compareTo(value2) * ascendingSort; - } - - } else if (value1 != null && value2 != null) { - - return value1.toString().compareToIgnoreCase(value2.toString()) - * ascendingSort; - - } else if (value1 != null && value2 == null) { - - return +1 * ascendingSort; - - } else if (value1 == null && value2 != null) { - - return -1 * ascendingSort; - - } else { - return 0; - } - } - - // ------------------------------------------------------ Protected Methods - - /** - * Perform a comparison on the given string values. - * - * @param value1 the first value to compare - * @param value2 the second value to compare - * @return the string comparison result - */ - protected int stringCompare(Object value1, Object value2) { - String string1 = value1.toString().trim(); - String string2 = value2.toString().trim(); - - StringTokenizer st1 = new StringTokenizer(string1); - StringTokenizer st2 = new StringTokenizer(string2); - - String token1 = null; - String token2 = null; - - while (st1.hasMoreTokens()) { - token1 = st1.nextToken(); - - if (st2.hasMoreTokens()) { - token2 = st2.nextToken(); - - int comp = 0; - - if (useNumericSort(token1, token2)) { - comp = numericCompare(token1, token2); - - } else { - comp = token1.compareToIgnoreCase(token2); - } - - if (comp != 0) { - return comp; - } - - } else { - return -1; - } - } - - return 0; - } - - /** - * Return true if a numeric sort should be used. - * - * @param value1 the first value to test - * @param value2 the second value to test - * @return true if a numeric sort should be used - */ - protected boolean useNumericSort(String value1, String value2) { - return NumberUtils.isDigits(value1) && NumberUtils.isDigits(value2); - } - - /** - * Perform a numeric compare on the given string values. - * - * @param string1 the first string value to compare - * @param string2 the second string value to compare - * @return the numeric comparison result - */ - protected int numericCompare(String string1, String string2) { - if (string1.length() > 0 && string2.length() > 0) { - Double double1 = Double.valueOf(string1); - Double double2 = Double.valueOf(string2); - - return double1.compareTo(double2); - - } else if (string1.length() > 0) { - return 1; - - } else if (string2.length() > 0) { - return -1; - - } else { - return 0; - } - } - - } - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Container.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Container.java deleted file mode 100644 index c960e6239f..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Container.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import java.util.List; -import org.openidentityplatform.openam.click.Control; -import org.apache.click.control.AbstractContainer; - -/** - * Provides the interface for a container which holds a list of child controls. - *

- * Container extends {@link org.apache.click.Control} and enables the creation of - * nested controls. - *

- * Container allows one to add, remove and retrieve child controls. - *

- * Please note {@link AbstractContainer} provides - * a default implementation of the Container interface to make it easier for - * developers to create their own containers. - * - * @see org.apache.click.util.ContainerUtils - */ -public interface Container extends Control { - - /** - * Add the control to the container and return the added instance. - * - * @param control the control to add to the container and return - * @return the control that was added to the container - */ - Control add(Control control); - - /** - * Add the control to the container at the specified index, and return the - * added instance. - * - * @param control the control to add to the container and return - * @param index the index at which the control is to be inserted - * @return the control that was added to the container - * @throws IndexOutOfBoundsException if the index is out of range - * (index < 0 || index > getControls().size()). - */ - Control insert(Control control, int index); - - /** - * Replace the current control with the new control, and return the newly - * added control. - * - * @deprecated this method was used for stateful pages, which have been deprecated - * - * @param currentControl the control currently contained in the container - * @param newControl the control to replace the current control contained in - * the container - * @return the new control that replaced the current control - */ - Control replace(Control currentControl, Control newControl); - - /** - * Remove the given control from the container, returning true if the - * control was found in the container and removed, or false if the control - * was not found. - * - * @param control the control to remove from the container - * @return true if the control was removed from the container - */ - boolean remove(Control control); - - /** - * Return the sequential list of controls held by the container. - * - * @return the sequential list of controls held by the container - */ - List getControls(); - - /** - * Return the named control from the container if found or null otherwise. - * - * @param controlName the name of the control to get from the container - * @return the named control from the container if found or null otherwise - */ - Control getControl(String controlName); - - /** - * Return true if the container contains the specified control. - * - * @param control the control whose presence in this container is to be tested - * @return true if the container contains the specified control - */ - boolean contains(Control control); - - /** - * Returns true if this container has existing controls, false otherwise. - * - * @return true if the container has existing controls, false otherwise. - */ - public boolean hasControls(); -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Decorator.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Decorator.java deleted file mode 100644 index 275aa8ca16..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Decorator.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Context; - -/** - * Provides a decorator interface for delegating object rendering. - * - *

Decorator Example

- * - * The following example illustrates how to render a email hyperlink in a - * email table column. - * - *
- * Column column = new Column("email");
- *
- * column.setDecorator(new Decorator() {
- *     public String render(Object row, Context context) {
- *         Customer customer = (Customer) row;
- *         String email = customer.getEmail();
- *         String fullName = customer.getFullName();
- *         return "<a href='mailto:" + email + "'>" + fullName + "</a>";
- *     }
- * });
- *
- * table.addColumn(column); 
- * - * @see Column - * @see Table - */ -public interface Decorator { - - /** - * Returns a decorated string representation of the given object. - * - * @param object the object to render - * @param context the request context - * @return a decorated string representation of the given object - */ - public String render(Object object, Context context); -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Field.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Field.java deleted file mode 100644 index 400beee427..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Field.java +++ /dev/null @@ -1,1288 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.apache.click.Stateful; -import org.apache.click.control.Checkbox; -import org.apache.click.control.FieldSet; -import org.apache.commons.lang.StringUtils; -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.Control; -import org.openidentityplatform.openam.click.Page; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.ContainerUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -/** - * Provides an abstract form Field control. Field controls are contained by - * the {@link Form} control which will orchestrate the processing and - * rendering of the contained fields. All Form field controls must extend this - * abstract class. - * - *

Field Processing

- * - *

Post Requests

- * - * When processing POST requests forms typically invoke the {@link #onProcess()} - * method on all its fields. The Field onProcess() method is used - * to bind the fields request value, validate the submission and invoke any - * control listener method. If the onProcess() method returns true - * the form will continue processing fields, otherwise the form will abort - * further processing. - *

- * The body of the Field onProcess() method is detailed below. - * - *

- * public boolean onProcess() {
- *     bindRequestValue();
- *
- *     if (getValidate()) {
- *         validate();
- *     }
- *
- *     registerActionEvent();
- *
- *     return true;
- * } 
- * - * The Field methods called by onProcess() include: - * - *
- *
{@link #bindRequestValue()}
- *
This method will bind the HTTP request value to the Field's value. - *
- *
{@link #getValidate()}
- *
This method will return true if the Field should validate itself. This - * value is generally inherited from the parent Form, however the Field can - * override this value and specify whether it should be validated. - *
- *
{@link #validate()}
- *
This method will validate the submitted Field value. If the submitted - * value is not valid this method should set the Field {@link #error} property, - * which can be rendered by the Form. - *
- *
{@link #dispatchActionEvent()}
- *
This method will register any Control action listener method which has be - * defined for the Field. - *
- *
- * - * Field subclasses generally only have to override the validate() - * method, and possibly the bindRequestValue() method, to provide their - * own behaviour. - * - *

Get Requests

- * - * When processing GET requests a Page's Form will typically perform no - * processing and simply render itself and its Fields. - * - *

Rendering

- * - * Field subclasses must override the {@link #render(org.openidentityplatform.openam.click.util.HtmlStringBuffer)} - * method to enable themselves to be rendered as HTML. With the increasing use of - * AJAX, Fields should render themselves as valid XHTML, so that they may be parsed - * correctly and used as the innerHtml in the DOM. - *

- * When a Form object renders a Field using autolayout, it renders the - * Field in a table row using the Field's {@link #label} attribute, its - * {@link #error} attribute if defined, and the Fields - * {@link #render(org.openidentityplatform.openam.click.util.HtmlStringBuffer)} method. - *

- * To assist with rendering valid HTML Field subclasses can use the - * {@link org.apache.click.util.HtmlStringBuffer} class. - * - *

Message Resources and Internationalization (i18n)

- * - * Fields support a hierarchy of resource bundles for displaying validation - * error messages and display messages. These localized messages can be accessed - * through the methods: - * - *
    - *
  • {@link #getMessage(String)}
  • - *
  • {@link #getMessage(String, Object...)}
  • - *
  • {@link #getMessages()}
  • - *
  • {@link #setErrorMessage(String)}
  • - *
  • {@link #setErrorMessage(String, Object)}
  • - *
- * - * Fields automatically pick up localized messages where applicable. Please see - * the following methods on how to customize these messages: - *
    - *
  • {@link #getLabel()}
  • - *
  • {@link #getTitle()}
  • - *
  • {@link #getHelp()}
  • - *
  • {@link #setErrorMessage(String)}
  • - *
  • {@link #setErrorMessage(String, Object)}
  • - *
- * - * - * The order in which localized messages resolve are: - *
- *
Page scope messages
- *
Message lookups are first resolved to the Pages message bundle if it - * exists. For example a Login page may define the message properties: - * - *
- * /com/mycorp/page/Login.properties 
- * - * If you want messages to be used only for a specific Page, this is where - * to place them. - *
- * - *
Global page scope messages
- *
Next message lookups are resolved to the global pages message bundle if it - * exists. - * - *
- * /click-page.properties 
- * - * If you want messages to be used across your entire application this is where - * to place them. - *
- * - *
Control scope messages
- *
Next message lookups are resolved to the Control message bundle if it - * exists. For example a CustomTextField control may define the - * message properties: - * - *
- * /com/mycorp/control/CustomTextField.properties 
- *
- * - *
Global control scope messages
- *
Finally message lookups are resolved to the global application control - * message bundle if the message has not already found. The global control - * properties file is: - * - *
- * /click-control.properties 
- * - * You can modify these properties by copying this file into your applications - * root class path and editing these properties. - *

- * Note when customizing the message properties you must include all the - * properties, not just the ones you want to override. - *

- *
- */ -public abstract class Field extends AbstractControl implements Stateful { - - // Constants -------------------------------------------------------------- - - private static final long serialVersionUID = 1L; - - // Instance Variables ----------------------------------------------------- - - /** The Field disabled value. */ - protected boolean disabled; - - /** The Field error message. */ - protected String error; - - /** The request focus flag. */ - protected boolean focus; - - /** The parent Form. */ - protected Form form; - - /** The Field help text. */ - protected String help; - - /** The Field label. */ - protected String label; - - /** The field label "style" attribute value. */ - protected String labelStyle; - - /** The field label "class" attribute value. */ - protected String labelStyleClass; - - /** The field's parent element "style" attribute hint. */ - protected String parentStyleHint; - - /** The field's parent element "class" attribute hint. */ - protected String parentStyleClassHint; - - /** The Field is readonly flag. */ - protected boolean readonly; - - /** The Field is required flag. */ - protected boolean required; - - /** The Field 'tabindex' attribute. */ - protected int tabindex; - - /** The Field 'title' attribute, which acts as a tooltip help message. */ - protected String title; - - /** The Field is trimmed flag, default value is true. */ - protected boolean trim = true; - - /** - * The validate Field value onProcess() invocation flag. - */ - protected Boolean validate; - - /** The Field value. */ - protected String value; - - // Constructors ----------------------------------------------------------- - - /** - * Construct a new Field object. - */ - public Field() { - } - - /** - * Construct the Field with the given name. - * - * @param name the name of the Field - */ - public Field(String name) { - setName(name); - } - - /** - * Construct the Field with the given name and label. - * - * @param name the name of the Field - * @param label the label of the Field - */ - public Field(String name, String label) { - setName(name); - setLabel(label); - } - - // Public Attributes ------------------------------------------------------ - - /** - * Set the parent of the Field. - * - * @see org.apache.click.Control#setParent(Object) - * - * @param parent the parent of the Control - * @throws IllegalStateException if {@link #name} is not defined - * @throws IllegalArgumentException if the given parent instance is - * referencing this object: if (parent == this) - */ - @Override - public void setParent(Object parent) { - if (parent == this) { - throw new IllegalArgumentException("Cannot set parent to itself"); - } - // Guard against fields without names, as fields would throw - // exceptions when binding to request value. - if (StringUtils.isBlank(getName())) { - String msg = "Field name not defined: " + getClass().getName(); - throw new IllegalArgumentException(msg); - } - this.parent = parent; - } - - /** - * Return true if the Field is disabled. The Field will also be disabled - * if the parent FieldSet or Form is disabled. - * - * @see #setDisabled(boolean) - * - * @return true if the Field is disabled - */ - public boolean isDisabled() { - Control control = this; - - // Check parents for instances of either FieldSet or Form - while (control.getParent() != null && !(control.getParent() instanceof Page)) { - control = (Control) control.getParent(); - if (control instanceof FieldSet) { - FieldSet fieldSet = (FieldSet) control; - if (fieldSet.isDisabled()) { - return true; - } else { - return disabled; - } - } else if (control instanceof Form) { - Form localForm = (Form) control; - if (localForm.isDisabled()) { - return true; - } else { - return disabled; - } - } - } - return disabled; - } - - /** - * Set the Field disabled flag. Disabled fields are not processed nor - * validated and their action event is not fired. - *

- * Important Note: an HTML form POST does not submit disabled fields - * values. Similarly disabled Click fields do not get processed or validated. - * However, JavaScript is often used to enable fields prior to - * submission. Click is smart enough to recognize when a field was enabled - * this way by checking if the field has an incoming request parameter. - * If a field is disabled but has an incoming request parameter, Click will - * "enable" the field and process it. - *

- * Caveat: Unfortunately the above behavior does not apply to - * {@link Checkbox} and {@link Radio} buttons. An HTML form POST for a - * disabled checkbox/radio is the same as for an unchecked - * checkbox/radio. In neither case is a value submitted to the server and - * Click cannot make the distinction whether the checkbox/radio is disabled - * or unchecked. - * - * @param disabled the Field disabled flag - */ - public void setDisabled(boolean disabled) { - this.disabled = disabled; - } - - /** - * Return the validation error message if the Field is not valid, or null - * if valid. - * - * @return the Field validation error message, or null if valid - */ - public String getError() { - return error; - } - - /** - * Set the Field validation error message. If the error message is not null - * the Field is invalid, otherwise it is valid. - * - * @param error the validation error message - */ - public void setError(String error) { - this.error = error; - } - - /** - * Return true if the field has requested focus. - * - * @return true if the field has requested focus - */ - public boolean getFocus() { - return focus; - } - - /** - * Set the Field request focus flag. - * - * @param focus the request focus flag - */ - public void setFocus(boolean focus) { - this.focus = focus; - } - - /** - * Return the Field focus JavaScript. - * - * @return the Field focus JavaScript - */ - public String getFocusJavaScript() { - HtmlStringBuffer buffer = new HtmlStringBuffer(32); - - buffer.append("setFocus('"); - buffer.append(getId()); - buffer.append("');"); - - return buffer.toString(); - } - - /** - * Return the parent Form containing the Field or null if no form is present - * in the parent hierarchy. - * - * @return the parent Form containing the Field - */ - public Form getForm() { - if (form == null) { - // Find form in parent hierarchy - form = ContainerUtils.findForm(this); - } - return form; - } - - /** - * Set the Field's the parent Form. - * - * @param form Field's parent Form - */ - public void setForm(Form form) { - this.form = form; - } - - /** - * Return the field help text. - *

- * If the help value is null, this method will attempt to find a - * localized help message in the parent messages using the key: - *

- * getName() + ".help" - *
- * If not found then the message will be looked up in the - * /click-control.properties file using the same key. - *

- * For example given a CustomerPage with the properties file - * CustomerPage.properties: - * - *

-     * name.label=Customer Name
-     * name.help=Full name or Business name 
- * - * The page TextField code: - *
-     * public class CustomerPage extends Page {
-     *     TextField nameField = new TextField("name");
-     *     ..
-     * } 
- * - * Will render the TextField label and title properties as: - *
-     * <td><label>Customer Name</label></td>
-     * <td><input type="text" name="name"/> <span="Full name or Business name"/>/td> 
- * - * How the help text is rendered is depends upon the Field subclass. - * - * @return the help text of the Field - */ - public String getHelp() { - if (help == null) { - help = getMessage(getName() + ".help"); - - if (help != null) { - if (help.indexOf("$context") != -1) { - help = StringUtils.replace(help, "$context", getContext().getRequest().getContextPath()); - - } else if (help.indexOf("${context}") != -1) { - help = StringUtils.replace(help, "${context}", getContext().getRequest().getContextPath()); - } - } - } - return help; - } - - /** - * Set the Field help text. - * - * @param help the help text of the Field - */ - public void setHelp(String help) { - this.help = help; - } - - /** - * Return true if the Field type is hidden (<input type="hidden"/>) or - * false otherwise. By default this method returns false. - * - * @return false - */ - public boolean isHidden() { - return false; - } - - /** - * Return the Form and Field id appended:   "form-field" - *

- * Use the field the "id" attribute value if defined, or the name otherwise. - * - * @see org.apache.click.Control#getId() - * - * @return HTML element identifier attribute "id" value - */ - @Override - public String getId() { - if (hasAttributes() && getAttributes().containsKey("id")) { - return getAttribute("id"); - - } else { - String fieldName = getName(); - - if (fieldName == null) { - // If fieldName is null, exit early - return null; - } - - Form parentForm = getForm(); - String formId = (parentForm != null) ? parentForm.getId() + "_" : ""; - String id = formId + fieldName; - - if (id.indexOf('/') != -1) { - id = id.replace('/', '_'); - } - if (id.indexOf(' ') != -1) { - id = id.replace(' ', '_'); - } - if (id.indexOf('<') != -1) { - id = id.replace('<', '_'); - } - if (id.indexOf('>') != -1) { - id = id.replace('>', '_'); - } - if (id.indexOf('.') != -1) { - id = id.replace('.', '_'); - } - - return id; - } - } - - /** - * Return the field display label. - *

- * If the label value is null, this method will attempt to find a - * localized label message in the parent messages using the key: - *

- * getName() + ".label" - *
- * If not found then the message will be looked up in the - * /click-control.properties file using the same key. - * If a value is still not found, the Field name will be converted - * into a label using the method: {@link ClickUtils#toLabel(String)} - *

- * For example given a CustomerPage with the properties file - * CustomerPage.properties: - * - *

-     * name.label=Customer Name
-     * name.title=Full name or Business name 
- * - * The page TextField code: - *
-     * public class CustomerPage extends Page {
-     *     TextField nameField = new TextField("name");
-     *     ..
-     * } 
- * - * Will render the TextField label and title properties as: - *
-     * <td><label>Customer Name</label></td>
-     * <td><input type="text" name="name" title="Full name or Business name"/></td> 
- * - * When a label value is not set, or defined in any properties files, then - * its value will be created from the Fields name. - *

- * For example given the TextField code: - * - *

-     * TextField nameField = new TextField("faxNumber");  
- * - * Will render the TextField label as: - *
-     * <td><label>Fax Number</label></td>
-     * <td><input type="text" name="faxNumber"/></td> 
- * - * @return the display label of the Field - */ - public String getLabel() { - if (label == null) { - label = getMessage(getName() + ".label"); - } - if (label == null) { - label = ClickUtils.toLabel(getName()); - } - return label; - } - - /** - * Set the Field display caption. - * - * @param label the display label of the Field - */ - public void setLabel(String label) { - this.label = label; - } - - /** - * Return the field label "style" attribute value. - * - * @see #setLabelStyle(java.lang.String) - * - * @return the field label "style" attribute value - */ - public String getLabelStyle() { - return labelStyle; - } - - /** - * Set the field label "style" attribute value. - *

- * Please note: the label is rendered by the containing Form - * or container, not the field itself. It is up to the parent Form - * (or container) on how to apply the label style. - *

- *

-     * nameField.setLabelStyle("color: green; font-weight: bold");
- * - * @param value the field label "style" attribute value - */ - public void setLabelStyle(String value) { - this.labelStyle = value; - } - - /** - * Return the field label "class" attribute value. - * - * @see #setLabelStyleClass(java.lang.String) - * - * @return the field label "class" attribute value - */ - public String getLabelStyleClass() { - return labelStyleClass; - } - - /** - * Set the field label "class" attribute value. - *

- * Please note: the label is rendered by the containing Form - * or container, not the field itself. It is up to the parent Form - * (or container) on how to apply the label style class. - * - * @param value the field label "class" attribute value - */ - public void setLabelStyleClass(String value) { - this.labelStyleClass = value; - } - - /** - * Return the field's parent "style" attribute hint. - * - * @see #setParentStyleHint(java.lang.String) - * - * @return the field's parent "style" attribute hint - */ - public String getParentStyleHint() { - return parentStyleHint; - } - - /** - * Set the field's parent "style" attribute hint. - *

- *

-     * nameField.setParentStyleHint("margin-bottom; 10px");
- * - * Please note:The field's parent style provides a hint to Forms - * (or other containers) what style to render, but it is up to the - * Form (or container) implementation how the style will be applied. - * For example, Form will render the parent style on the table cells - * containing the field and label. - * - * @param styleHint the field's parent "style" attribute hint - */ - public void setParentStyleHint(String styleHint) { - this.parentStyleHint = styleHint; - } - - /** - * Return the field's parent "class" attribute hint. - * - * @see #setParentStyleClassHint(java.lang.String) - * - * @return the field's parent "class" attribute hint - */ - public String getParentStyleClassHint() { - return parentStyleClassHint; - } - - /** - * Set the field's parent "class" attribute hint. - *

- * Please note:The parent style class provides a hint to Forms - * (or other containers) which CSS class to render, but it is up to the - * Form (or container) implementation how the style class will be applied. - * For example, Form will render the parent style class on the table cells - * containing the field and label. - * - * @param styleClassHint the field parent "class" attribute hint - */ - public void setParentStyleClassHint(String styleClassHint) { - this.parentStyleClassHint = styleClassHint; - } - - /** - * The callback listener will only be called during processing if the Field - * value is valid. If the field has validation errors the listener will not - * be called. - * - * @see org.apache.click.Control#getName() - * - * @param listener the listener object with the named method to invoke - * @param method the name of the method to invoke - */ - @Override - public void setListener(Object listener, String method) { - super.setListener(listener, method); - } - - /** - * Return true if the Field is a readonly. The Field will also be readonly - * if the parent FieldSet or Form is readonly. - * - * @return true if the Field is a readonly - */ - public boolean isReadonly() { - Control control = this; - - // Check parents for instances of either FieldSet or Form - while (control.getParent() != null && !(control.getParent() instanceof Page)) { - control = (Control) control.getParent(); - if (control instanceof FieldSet) { - FieldSet fieldSet = (FieldSet) control; - if (fieldSet.isReadonly()) { - return true; - } else { - return readonly; - } - } else if (control instanceof Form) { - Form localForm = (Form) control; - if (localForm.isReadonly()) { - return true; - } else { - return readonly; - } - } - } - return readonly; - } - - /** - * Set the Field readonly flag. - * - * @param readonly the Field readonly flag - */ - public void setReadonly(boolean readonly) { - this.readonly = readonly; - } - - /** - * Return true if the Field's value is required. - * - * @return true if the Field's value is required - */ - public boolean isRequired() { - return required; - } - - /** - * Set the Field required status. - * - * @param required set the Field required status - */ - public void setRequired(boolean required) { - this.required = required; - } - - /** - * Return the field "tabindex" attribute value. - * - * @return the field "tabindex" attribute value - */ - public int getTabIndex() { - return tabindex; - } - - /** - * Set the field "tabindex" attribute value. - * - * @param tabindex the field "tabindex" attribute value - */ - public void setTabIndex(int tabindex) { - this.tabindex = tabindex; - } - - /** - * Return the field CSS "text-align" style, or null if not defined. - * - * @return the field CSS "text-align" style, or null if not defined. - */ - public String getTextAlign() { - return getStyle("text-align"); - } - - /** - * Set the field CSS horizontal "text-align" style. - * - * @param align the CSS "text-align" value: ["left", "right", "center"] - */ - public void setTextAlign(String align) { - setStyle("text-align", align); - } - - /** - * Return the 'title' attribute, or null if not defined. The title - * attribute acts like tooltip message over the Field. - *

- * If the title value is null, this method will attempt to find a - * localized title message in the parent messages using the key: - *

- * getName() + ".title" - *
- * If not found then the message will be looked up in the - * /click-control.properties file using the same key. If still - * not found the title will be left as null and will not be rendered. - *

- * For example given a CustomerPage with the properties file - * CustomerPage.properties: - * - *

-     * name.label=Customer Name
-     * name.title=Full name or Business name 
- * - * The page TextField code: - *
-     * public class CustomerPage extends Page {
-     *     TextField nameField = new TextField("name");
-     *     ..
-     * } 
- * - * Will render the TextField label and title properties as: - *
-     * <td><label>Customer Name</label></td>
-     * <td><input type="text" name="name" title="Full name or Business name"/></td> 
- * - * @return the 'title' attribute tooltip message - */ - public String getTitle() { - if (title == null) { - title = getMessage(getName() + ".title"); - } - return title; - } - - /** - * Set the 'title' attribute tooltip message. - * - * @param value the 'title' attribute tooltip message - */ - public void setTitle(String value) { - title = value; - } - - /** - * Return true if the Field request value should be trimmed, false otherwise. - * The default value is "true". - * - * @return true if the Field request value should be trimmed, false otherwise - */ - public boolean isTrim() { - return trim; - } - - /** - * Set the trim flag to true if the Field request value should be trimmed, - * false otherwise. - * - * @param trim true if the Field request value should be trimmed, false - * otherwise - */ - public void setTrim(boolean trim) { - this.trim = trim; - } - - /** - * Return true if the Field should validate itself when being processed. - *

- * If the validate attribute for the Field is not explicitly set, this - * method will return the validation status of its parent Form, see - * {@link Form#getValidate()}. If the Field validate attribute is not set - * and the parent Form is not set this method will return true. - *

- * This method is called by the {@link #onProcess()} method to determine - * whether the the Field {@link #validate()} method should be invoked. - * - * @return true if the Field should validate itself when being processed. - */ - public boolean getValidate() { - if (validate != null) { - return validate; - - } else if (getForm() != null) { - return getForm().getValidate(); - - } else { - return true; - } - } - - /** - * Set the validate Field value when being processed flag. - * - * @param validate the field value when processed - */ - public void setValidate(boolean validate) { - this.validate = validate; - } - - /** - * Return the field JavaScript client side validation function. - *

- * The function name must follow the format validate_[id], where - * the id is the DOM element id of the fields focusable HTML element, to - * ensure the function has a unique name. - * - * @return the field JavaScript client side validation function - */ - public String getValidationJavaScript() { - return null; - } - - /** - * Return true if the Field is valid after being processed, or false - * otherwise. If the Field has no error message after - * {@link org.apache.click.Control#onProcess()} has been invoked it is considered to be - * valid. - * - * @return true if the Field is valid after being processed - */ - public boolean isValid() { - return (error == null); - } - - /** - * Return the Field value. - * - * @return the Field value - */ - public String getValue() { - return (value != null) ? value : ""; - } - - /** - * Set the Field value. - * - * @param value the Field value - */ - public void setValue(String value) { - this.value = value; - } - - /** - * Return the object representation of the Field value. This method will - * return a string value, or null if the string value is null or is zero - * length. - *

- * Specialized object field subclasses should override this method to - * return a non string object. For examples a DoubleField would - * return a Double value instead. - * - * @return the object representation of the Field value - */ - public Object getValueObject() { - if (value == null || value.length() == 0) { - return null; - } else { - return value; - } - } - - /** - * Set the value of the field using the given object. - * - * @param object the object value to set - */ - public void setValueObject(Object object) { - if (object != null) { - value = object.toString(); - } - } - - /** - * Return the width CSS "width" style, or null if not defined. - * - * @return the CSS "width" style attribute, or null if not defined - */ - public String getWidth() { - return getStyle("width"); - } - - /** - * Set the the CSS "width" style attribute. - * - * @param value the CSS "width" style attribute - */ - public void setWidth(String value) { - setStyle("width", value); - } - - // Public Methods --------------------------------------------------------- - - /** - * This method binds the submitted request value to the Field's value. - *

- * Please note: while it is possible to explicitly bind the field - * value by invoking this method directly, it is recommended to use the - * "bind" utility methods in {@link org.apache.click.util.ClickUtils} - * instead. See {@link org.apache.click.util.ClickUtils#bind(org.apache.click.control.Field)} - * for more details. - */ - public void bindRequestValue() { - setValue(getRequestValue()); - } - - /** - * Return the Field state. The following state is returned: - *

    - *
  • {@link #getValue() field value}
  • - *
- * - * @return the Field state - */ - public Object getState() { - String state = getValue(); - if (StringUtils.isEmpty(state)) { - return null; - } - return state; - } - - /** - * Set the Field state. - * - * @param state the Field state to set - */ - public void setState(Object state) { - if (state == null) { - return; - } - - String fieldState = (String) state; - setValue(fieldState); - } - - /** - * This method processes the page request returning true to continue - * processing or false otherwise. The Field onProcess() method is - * typically invoked by the Form onProcess() method when - * processing POST request. - *

- * This method will bind the Field request parameter value to the field, - * validate the submission and invoke its callback listener if defined. - *

- * Below is a typical onProcess implementation: - * - *

-     * public boolean onProcess() {
-     *     bindRequestValue();
-     *
-     *     if (getValidate()) {
-     *         validate();
-     *     }
-     *
-     *     registerActionEvent();
-     *
-     *     return true
-     * } 
- * - * @return true to continue Page event processing or false otherwise - */ - @Override - public boolean onProcess() { - Context context = getContext(); - - if (context.hasRequestParameter(getName())) { - // Only process field if it participated in the incoming request - setDisabled(false); - - bindRequestValue(); - - if (getValidate()) { - validate(); - } - - dispatchActionEvent(); - } - - return true; - } - - /** - * Remove the Field state from the session for the given request context. - * - * @see #saveState(Context) - * @see #restoreState(Context) - * - * @param context the request context - */ - public void removeState(Context context) { - ClickUtils.removeState(this, getName(), context); - } - - /** - * Restore the Field state from the session for the given request context. - *

- * This method delegates to {@link #setState(java.lang.Object)} to set the - * field restored state. - * - * @see #saveState(Context) - * @see #removeState(Context) - * - * @param context the request context - */ - public void restoreState(Context context) { - ClickUtils.restoreState(this, getName(), context); - } - - /** - * Save the Field state to the session for the given request context. - *

- * * This method delegates to {@link #getState()} to retrieve the field state - * to save. - * - * @see #restoreState(Context) - * @see #removeState(Context) - * - * @param context the request context - */ - public void saveState(Context context) { - ClickUtils.saveState(this, getName(), context); - } - - /** - * The validate method is invoked by onProcess() to validate - * the request submission. Field subclasses should override this method - * to implement request validation logic. - *

- * If the field determines that the submission is invalid it should set the - * {@link #error} property with the error message. - */ - public void validate() { - } - - // Protected Methods ------------------------------------------------------ - - /** - * Return a normalized label for display in error messages. - *

- * The error label is a normalized version of {@link #getLabel()}. - * - * @return a normalized label for error message display - */ - protected String getErrorLabel() { - String localLabel = getLabel().trim(); - localLabel = (localLabel.endsWith(":")) - ? localLabel.substring(0, localLabel.length() - 1) : localLabel; - return localLabel; - } - - /** - * Set the error with the a label formatted message specified by the given - * message bundle key. The message will be formatted with the field label - * using {@link #getErrorLabel()}. - *

- * setErrorMessage will attempt to find a localized error message as - * described here, using the following - * lookup strategy: - *

    - *
  • - * an error message is looked up for a specific Field instance by - * prefixing the key with the Field's name: - *
    - * getMessage(getName() + "." + key); - *
    - *
  • - *
  • - * if no message is found for a specific Field instance, an error - * message is looked up for the Field class based on the key: - *
    - * getMessage(key); - *
    - *
  • - *
- * - * @param key the key of the localized message bundle string - */ - protected void setErrorMessage(String key) { - String errorLabel = getErrorLabel(); - String msg = getMessage(getName() + "." + key, errorLabel); - if (msg == null) { - msg = getMessage(key, errorLabel); - } - setError(msg); - } - - /** - * Set the error with the a label and value formatted message specified by - * the given message bundle key. The message will be formatted with the - * field label {0} using {@link #getErrorLabel()} and the given value {1}. - *

- * Also see {@link #setErrorMessage(java.lang.String)} on how to - * specify error messages for specific Field instances. - * - * @param key the key of the localized message bundle string - * @param value the value to format in the message - */ - protected void setErrorMessage(String key, T value) { - String msg = getMessage(getName() + "." + key, getErrorLabel(), value); - if (msg == null) { - msg = getMessage(key, getErrorLabel(), value); - } - setError(msg); - } - - /** - * Return the field's value from the request. - * - * @return the field's value from the request - */ - protected String getRequestValue() { - String requestValue = getContext().getRequestParameter(getName()); - if (requestValue != null) { - if (isTrim()) { - return requestValue.trim(); - } else { - return requestValue; - } - } else { - return ""; - } - } - - /** - * Render the Field tag and common attributes including {@link #getName() name}, - * {@link #getId() id}, class and style. - * - * @see org.openidentityplatform.openam.click.control.AbstractControl#renderTagBegin(java.lang.String, HtmlStringBuffer) - * - * @param tagName the name of the tag to render - * @param buffer the buffer to append the output to - */ - @Override - protected void renderTagBegin(String tagName, HtmlStringBuffer buffer) { - if (tagName == null) { - throw new IllegalStateException("Tag cannot be null"); - } - - buffer.elementStart(tagName); - - String controlName = getName(); - if (controlName != null) { - buffer.appendAttribute("name", controlName); - } - - String id = getId(); - if (id != null) { - buffer.appendAttribute("id", id); - } - - appendAttributes(buffer); - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/FileField.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/FileField.java deleted file mode 100644 index 91c6b007af..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/FileField.java +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.lang.StringUtils; - -import java.text.MessageFormat; - -/** - * Provides a File Field control:   <input type='file'>. - * - * - * - * - * - * - *
File Field
- * - * The FileField control uses the Jakarta Commons - * FileUpload - * library to provide file processing functionality. - *

- * You can control the {@link org.apache.click.service.CommonsFileUploadService#sizeMax maximum request size} - * and {@link org.apache.click.service.CommonsFileUploadService#fileSizeMax maximum file size} - * by configuring {@link org.apache.click.service.CommonsFileUploadService}. - *

- * Note Browsers enforce the JavaScript value property as readonly - * to prevent script based stealing of users files. - *

- * You can make the file field invisible by setting the CSS display attribute, for - * example: - * - *

- * <form method="POST" enctype="multipart/form-data">
- *    <input type="file" name="myfile" style="display:none" onchange="fileName=this.value">
- *    <input type="button" value="open file" onclick="myfile.click()">
- *    <input type="button" value="show value" onclick="alert(fileName)">
- * </form> 
- * - *

- * Please also see the references: - *

- */ -public class FileField extends Field { - - // -------------------------------------------------------------- Constants - - private static final long serialVersionUID = 1L; - - /** - * The field validation JavaScript function template. - * The function template arguments are:
    - *
  • 0 - is the field id
  • - *
  • 1 - is the Field required status
  • - *
  • 2 - is the localized error message
  • - *
- */ - protected final static String VALIDATE_FILEFIELD_FUNCTION = - "function validate_{0}() '{'\n" - + " var msg = validateFileField(''{0}'',{1}, [''{2}'']);\n" - + " if (msg) '{'\n" - + " return msg + ''|{0}'';\n" - + " '}' else '{'\n" - + " return null;\n" - + " '}'\n" - + "'}'\n"; - - // ----------------------------------------------------- Instance Variables - - /** The text field size attribute. The default size is 20. */ - protected int size = 20; - - /** - * The - * DefaultFileItem - * after processing a file upload request. - */ - protected FileItem fileItem; - - // ----------------------------------------------------------- Constructors - - /** - * Construct the FileField with the given name. - * - * @param name the name of the field - */ - public FileField(String name) { - super(name); - } - - /** - * Construct the FileField with the given name and required status. - * - * @param name the name of the field - * @param required the field required status - */ - public FileField(String name, boolean required) { - super(name); - setRequired(required); - } - - /** - * Construct the FileField with the given name and label. - * - * @param name the name of the field - * @param label the label of the field - */ - public FileField(String name, String label) { - super(name, label); - } - - - /** - * Construct the FileField with the given name, label and required status. - * - * @param name the name of the field - * @param label the label of the field - * @param required the required status - */ - public FileField(String name, String label, boolean required) { - super(name, label); - setRequired(required); - } - - /** - * Construct the FileField with the given name, label and size. - * - * @param name the name of the field - * @param label the label of the field - * @param size the size of the field - */ - public FileField(String name, String label, int size) { - this(name, label); - setSize(size); - } - - /** - * Create an FileField with no name defined. - *

- * Please note the control's name must be defined before it is valid. - */ - public FileField() { - super(); - } - - // ------------------------------------------------------ Public Attributes - - /** - * Return the FileField's html tag: input. - * - * @see org.apache.click.control.AbstractControl#getTag() - * - * @return this controls html tag - */ - @Override - public String getTag() { - return "input"; - } - - /** - * Return the FileItem - * after processing the request, or null otherwise. - * - * @return the FileItem after processing a request - */ - public FileItem getFileItem() { - return fileItem; - } - - /** - * Return the field size. - * - * @return the field size - */ - public int getSize() { - return size; - } - - /** - * Set the field size. - * - * @param size the field size - */ - public void setSize(int size) { - this.size = size; - } - - /** - * Return the input type: 'file'. - * - * @return the input type: 'file' - */ - public String getType() { - return "file"; - } - - /** - * Return the FileField JavaScript client side validation function. - * - * @return the field JavaScript client side validation function - */ - @Override - public String getValidationJavaScript() { - if (isRequired()) { - Object[] args = new Object[3]; - args[0] = getId(); - args[1] = String.valueOf(isRequired()); - args[2] = getMessage("file-required-error", getErrorLabel()); - - return MessageFormat.format(VALIDATE_FILEFIELD_FUNCTION, args); - - } else { - return null; - } - } - - // --------------------------------------------------------- Public Methods - - /** - * Set the {@link #fileItem} property from the multi-part form data - * submission. - */ - @Override - public void bindRequestValue() { - fileItem = getContext().getFileItem(getName()); - } - - /** - * @see AbstractControl#getControlSizeEst() - * - * @return the estimated rendered control size in characters - */ - @Override - public int getControlSizeEst() { - return 96; - } - - /** - * Overrides onProcess to use {@link Context#getFileItem(String)}. - * - * @see Field#onProcess() - * - * @return true to continue Page event processing or false otherwise - */ - @Override - public boolean onProcess() { - Context context = getContext(); - - if (context.getFileItemMap().containsKey(getName())) { - // Only process field if it participated in the incoming request - setDisabled(false); - - bindRequestValue(); - - if (getValidate()) { - validate(); - } - - dispatchActionEvent(); - } - - return true; - } - - /** - * Render the HTML representation of the FileField. - * - * @see #toString() - * - * @param buffer the specified buffer to render the control's output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - buffer.elementStart(getTag()); - - buffer.appendAttribute("type", getType()); - buffer.appendAttribute("name", getName()); - buffer.appendAttribute("id", getId()); - buffer.appendAttributeEscaped("value", getValue()); - buffer.appendAttribute("size", getSize()); - buffer.appendAttribute("title", getTitle()); - if (isValid()) { - removeStyleClass("error"); - } else { - addStyleClass("error"); - } - if (getTabIndex() > 0) { - buffer.appendAttribute("tabindex", getTabIndex()); - } - - appendAttributes(buffer); - - if (isDisabled()) { - buffer.appendAttributeDisabled(); - } - if (isReadonly()) { - buffer.appendAttributeReadonly(); - } - - buffer.elementEnd(); - - if (getHelp() != null) { - buffer.append(getHelp()); - } - } - - /** - * Validate the FileField request submission. - *

- * A field error message is displayed if a validation error occurs. - * These messages are defined in the resource bundle: - *

- *
    - *
  • /click-control.properties - *
      - *
    • file-required-error
    • - *
    - *
  • - *
- *
- */ - @Override - public void validate() { - setError(null); - - if (isRequired()) { - FileItem localFileItem = getFileItem(); - if (localFileItem == null || StringUtils.isBlank(localFileItem.getName())) { - setErrorMessage("file-required-error"); - } - } - } - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Form.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Form.java deleted file mode 100644 index c53a15c637..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Form.java +++ /dev/null @@ -1,3175 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.Control; -import org.openidentityplatform.openam.click.Page; -import org.apache.click.Stateful; -import org.apache.click.control.FieldSet; -import org.apache.click.control.Submit; -import org.openidentityplatform.openam.click.element.CssImport; -import org.openidentityplatform.openam.click.element.Element; -import org.openidentityplatform.openam.click.element.JsImport; -import org.apache.click.service.FileUploadService; -import org.openidentityplatform.openam.click.service.LogService; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.ContainerUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException; -import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException; -import org.apache.commons.fileupload.FileUploadException; -import org.apache.commons.lang.StringUtils; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.StringTokenizer; - -/** - * Provides a Form control:   <form method='post'>. - * - * - * - * - * - *
- * - * - * - * - * - * - * - * - * - * - *
*
*
- * - * - *
- *   - *
- * - *
- * - * When a Form is processed it will process its {@link Field} controls - * in the order they were added to the form, and then it will process the - * {@link Button} controls in the added order. Once all the Fields have been - * processed the form will invoke its action listener if defined. - * - *

Form Example

- * - * The example below illustrates a Form being used in a login Page. - * - *
- * public class Login extends Page {
- *
- *     public Form form = new Form();
- *
- *     public Login() {
- *         form.add(new TextField("username", true));
- *         form.add(new PasswordField("password", true));
- *         form.add(new Submit("ok", "  OK  ", this, "onOkClick"));
- *         form.add(new Submit("cancel", this, "onCancelClick"));
- *     }
- *
- *     public boolean onOkClick() {
- *         if (form.isValid()) {
- *             User user = new User();
- *             form.copyTo(user);
- *
- *             if (getUserService().isAuthenticatedUser(user)) {
- *                 getContext().setSessionAttribute("user", user);
- *                 setRedirect(HomePage.class);
- *             } else {
- *                 form.setError(getMessage("authentication-error"));
- *             }
- *         }
- *         return true;
- *     }
- *
- *     public boolean onCancelClick() {
- *         setRedirect(WelcomePage.class);
- *         return false;
- *     }
- * } 
- * - * The forms corresponding template code is below. Note the form automatically - * renders itself when Velocity invokes its {@link #toString()} method. - * - *
- * $form 
- * - * If a Form has been posted and processed, if it has an {@link #error} defined or - * any of its Fields have validation errors they will be automatically - * rendered, and the {@link #isValid()} method will return false. - * - * - *

Data Binding

- * - * To bind value objects to a forms fields use the copy methods: - *
    - *
  • value object   ->   form fields       - * {@link #copyFrom(Object)}
  • - *
  • form fields   ->   value object       - * {@link #copyTo(Object)}
  • - *
- * To debug the data binding being performed, use the Click application mode to - * "debug" or use the debug copy methods. - *

- * Binding of nested data objects is supported using the - * OGNL library. To use - * nested objects in your form, simply specify the object path as the Field - * name. Note in the object path you exclude the root object, so the path - * customer.address.state is specified as address.state. - *

- * For example: - * - *

- * // The customer.address.state field
- * TextField stateField = new TextField("address.state");
- * form.add(stateField);
- * ..
- *
- * // Loads the customer address state into the form stateField
- * Customer customer = getCustomer();
- * form.copyFrom(customer);
- * ..
- *
- * // Copies form stateField value into the customer address state
- * Customer customer = new Customer();
- * form.copyTo(customer); 
- * - * When populating an object from a form post Click will automatically create - * any null nested objects so their properties can be set. To do this Click - * uses the no-args constructor of the nested objects class. - *

- * {@link #copyTo(Object)} and {@link #copyFrom(Object)} also supports - * java.util.Map as an argument. Examples of using - * java.util.Map are shown in the respective method descriptions. - * - * - *

Form Validation

- * - * The Form control supports automatic field validation. By default when a POST - * request is made the form will validate the field values. To disable - * automatic validation set {@link #setValidate(boolean)} to false. - *

- * Form also provides a {@link #validate()} method where subclasses can provide - * custom cross-field validation. - *

- * File Upload Validation - *

- * The Form's {@link #validateFileUpload()} provides validation for multipart - * requests (multipart requests are used for uploading files from the browser). - * The {@link #validateFileUpload()} method checks that files being uploaded do not exceed the - * {@link org.apache.click.service.CommonsFileUploadService#sizeMax maximum request size} - * or the {@link org.apache.click.service.CommonsFileUploadService#fileSizeMax maximum file size}. - *

- * Note: if the maximum request size or maximum file size - * is exceeded, the request is deemed invalid ({@link #hasPostError hasPostError} - * will return true), and no further processing is performed on the form or fields. - * Instead the form will display the appropriate error message for the invalid request. - * See {@link #validateFileUpload()} for details of the error message properties. - *

- * JavaScript Validation - *

- * The Form control also supports client side JavaScript validation. By default - * JavaScript validation is not enabled. To enable JavaScript validation set - * {@link #setJavaScriptValidation(boolean)} to true. For example: - * - *

- * Form form = new Form("form");
- * form.setJavaScriptValidation(true);
- *
- * // Add form fields
- * ..
- *
- * form.add(new Submit("ok", " OK ", this, "onOkClicked");
- *
- * Submit cancel = new Submit("cancel", "Cancel", this, "onCancelClicked");
- * cancel.setCancelJavaScriptValidation(true);
- *
- * addControl(form); 
- * - * Please note in that is this example the cancel submit button has - * {@link Submit#setCancelJavaScriptValidation(boolean)} set to true. This - * prevents JavaScript form validation being performed when the cancel button is - * clicked. - * - * - *

CSS and JavaScript resources

- * - * The Form control makes use of the following resources (which Click automatically - * deploys to the application directory, /click): - * - *
    - *
  • click/control.css
  • - *
  • click/control.js
  • - *
- * - * To import these files and any form control imports simply reference - * the variables $headElements and - * $jsElements in the page template. For example: - * - *
- * <html>
- * <head>
- * $headElements
- * </head>
- * <body>
- *
- * $form
- *
- * $jsElements
- * </body>
- * </html> 
- * - * - *

Form Layout

- * The Form control supports rendering using automatic and manual layout - * techniques. - * - * - *

Auto Layout

- * - * If you include a form variable in your template the form will be - * automatically laid out and rendered. Auto layout, form and field rendering - * options include: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
{@link #buttonAlign} button alignment:   ["left", "center", "right"]
{@link #buttonStyle} button <td> "style" attribute value
{@link #columns} number of form table columns, the default value number is 1
{@link #errorsAlign} validation error messages alignment:   ["left", "center", "right"]
{@link #errorsPosition} validation error messages position:   ["top", "middle", "bottom"]
{@link #errorsStyle} errors <td> "style" attribute value
{@link #fieldStyle} field <td> "style" attribute value
{@link #labelAlign} field label alignment:   ["left", "center", "right"]
{@link #labelsPosition} label position relative to field:   ["left", "top"]
{@link #labelStyle} label <td> "style" attribute value
click/control.css control CSS styles, automatically deployed to the click web directory
/click-control.properties form and field messages and HTML, located under classpath
- * - * - *

Manual Layout

- * - * You can also manually layout the Form in the page template specifying - * the fields using the named field notation: - * - *
- * $form.{@link #getFields fields}.usernameField 
- * - * Whenever including your own Form markup in a page template or Velocity macro - * always specify: - *
    - *
  • method - * - the form submission method ["post" | "get"]
  • - *
  • name - * - the name of your form, important when using JavaScript
  • - *
  • action - * - directs the Page where the form should be submitted to
  • - *
  • form_name - * - include a hidden field which specifies the {@link #name} of the Form
  • - *
- * The hidden field is used by Click to determine which form was posted on - * a page which may contain multiple forms. - *

- * Alternatively you can use the Form {@link #startTag()} and {@link #endTag()} - * methods to render this information. - *

- * An example of a manually laid out Login form is provided below: - * - *

- * $form.startTag()
- *
- *   <table style="margin: 1em;">
- *
- *     #if ($form.error)
- *     <tr>
- *       <td colspan="2" style="color: red;"> $form.error </td>
- *     </tr>
- *     #end
- *     #if ($form.fields.usernameField.error)
- *     <tr>
- *       <td colspan="2" style="color: red;"> $form.fields.usernameField.error </td>
- *     </tr>
- *     #end
- *     #if ($form.fields.passwordField.error)
- *     <tr>
- *       <td colspan="2" style="color: red;"> $form.fields.passwordField.error </td>
- *     </tr>
- *     #end
- *
- *     <tr>
- *       <td> Username: </td>
- *       <td> $form.fields.usernameField </td>
- *     </tr>
- *     <tr>
- *       <td> Password: </td>
- *       <td> $form.fields.passwordField </td>
- *     </tr>
- *
- *     <tr>
- *       <td>
- *         $form.fields.okSubmit
- *         $form.fields.cancelSubmit
- *       </td>
- *     </tr>
- *
- *   </table>
- *
- * $form.endTag() 
- * - * As you can see in this example most of the code and markup is generic and - * could be reused. This is where Velocity Macros come in. - * - * - *

Velocity Macros

- * - * Velocity Macros - * (velocimacros) - * are a great way to encapsulate customized forms. - *

- * To create a generic form layout you can use the Form {@link #getFieldList()} and - * {@link #getButtonList()} properties within a Velocity macro. If you want to - * access all Form Controls from within a Velocity template or macro use - * {@link #getControls()}. - *

- * The example below provides a generic writeForm() - * macro which you could use through out an application. This Velocity macro code - * would be contained in a macro file, e.g. macro.vm. - * - *

 #* Custom Form Macro Code *#
- * #macro( writeForm[$form] )
- *
- * $form.startTag()
- *
- * <table width="100%">
- *
- * #if ($form.error)
- *   <tr>
- *     <td colspan="2" style="color: red;"> $form.error </td>
- *   </tr>
- * #end
- *
- * #foreach ($field in $form.fieldList)
- *   #if (!$field.hidden)
- *     #if (!$field.valid)
- *     <tr>
- *       <td colspan="2"> $field.error </td>
- *     </tr>
- *     #end
- *
- *   <tr>
- *     <td> $field.label: </td><td> $field </td>
- *   </tr>
- *   #end
- * #end
- *
- *  <tr>
- *    <td colspan="2">
- *    #foreach ($button in $form.buttonList)
- *      $button &nbsp;
- *    #end
- *    </td>
- *  </tr>
- *
- * </table>
- *
- * $form.endTag()
- *
- * #end 
- * - * You would then call this macro in your Page template passing it your - * form object: - * - *
 #writeForm($form) 
- * - * At render time Velocity will execute the macro using the given form and render - * the results to the response output stream. - * - *

Configuring Macros

- * - * To configure your application to use your macros you can: - *
    - *
  • - * Put your macros if a file called macro.vm - * in your applications root directory. - *
  • - *
  • - * Put your macros in the auto deployed - * click/VM_global_macro.vm file. - *
  • - *
  • - * Create a custom named macro file and reference it in a - * WEB-INF/velocity.properties - * file under the property named - * velocimacro.library. - *
  • - *
- * - * - *

Preventing Accidental Form Posts

- * - * Users may accidentally make multiple form submissions by refreshing a page - * or by pressing the back button. - *

- * To prevent multiple form posts from page refreshes use the Post - * Redirect pattern. With this pattern once the user has posted a form you - * redirect to another page. If the user then presses the refresh button, they - * will making a GET request on the current page. Please see the - * Redirect After Post - * article for more information on this topic. - *

- * To prevent multiple form posts from use of the browser back button use one - * of the Form {@link #onSubmitCheck(Page, String)} methods. For example: - * - *

- * public class Purchase extends Page {
- *     ..
- *
- *     public boolean onSecurityCheck() {
- *         return form.onSubmitCheck(this, "/invalid-submit.html");
- *     }
- * } 
- * - * The form submit check methods store a special token in the users session - * and in a hidden field in the form to ensure a form post isn't replayed. - * - * - *

Dynamic Forms and not validating a request

- * - * A common use case for web applications is to create Form fields dynamically - * based upon user selection. For example if a checkbox is ticked another Field - * is added to the Form. A simple way to achieve this is using JavaScript - * to submit the Form when the Field is changed or clicked. - *

- * When submitting a Form using JavaScript, it is often desirable to not - * validate the fields since the user is still filling out the form. - * To cater for this use case, Form provides the {@link #setValidate(boolean)} - * to switch off form and field validation. For example: - * - *

- * public void onInit() {
- *     checkbox.setAttribute("onclick", "form.submit()");
- *
- *     // Since onInit occurs before the onProcess event,
- *     // we have to explicitly bind the submit button in the onInit event if we
- *     // want to check if it was clicked.
- *     // If the submit button wasn't clicked it means the Form was submitted
- *     // using JavaScript and we don't want to validate yet
- *     ClickUtils.bind(submit);
- *
- *     // If submit was not clicked, don't validate
- *     if(form.isFormSubmission() && !submit.isClicked()) {
- *         form.setValidate(false);
- *     }
- * } 
- * - *

 

- * See also the W3C HTML reference: - * FORM - * - * @see org.openidentityplatform.openam.click.control.Field - * @see Submit - */ -public class Form extends AbstractContainer implements Stateful { - - // Constants -------------------------------------------------------------- - - private static final long serialVersionUID = 1L; - - /** The align left, form layout constant:   "left". */ - public static final String ALIGN_LEFT = "left"; - - /** The align center, form layout constant:   "center". */ - public static final String ALIGN_CENTER = "center"; - - /** The align right, form layout constant:   "right". */ - public static final String ALIGN_RIGHT = "right"; - - /** - * The position top, errors and labels form layout constant:   - * "top". - */ - public static final String POSITION_TOP = "top"; - - /** - * The position middle, errors in middle form layout constant:   - * "middle". - */ - public static final String POSITION_MIDDLE = "middle"; - - /** - * The position bottom, errors on bottom form layout constant:   - * "top". - */ - public static final String POSITION_BOTTOM = "bottom"; - - /** - * The position left, labels of left form layout constant:   - * "left". - */ - public static final String POSITION_LEFT = "left"; - - /** - * The form name parameter for multiple forms:   "form_name". - */ - public static final String FORM_NAME = "form_name"; - - /** The HTTP content type header for multipart forms. */ - public static final String MULTIPART_FORM_DATA = "multipart/form-data"; - - /** - * The submit check reserved request parameter prefix:   - * SUBMIT_CHECK_. - */ - public static final String SUBMIT_CHECK = "SUBMIT_CHECK_"; - - /** The Form set field focus JavaScript. */ - protected static final String FOCUS_JAVASCRIPT = - "\n"; - - // Instance Variables ----------------------------------------------------- - - /** The form action URL. */ - protected String actionURL; - - /** The form disabled value. */ - protected boolean disabled; - - /** The form "enctype" attribute. */ - protected String enctype; - - /** The form level error message. */ - protected String error; - - /** The ordered list of fields, excluding buttons. */ - protected final List fieldList = new ArrayList<>(); - - /** - * The form method ["post, "get"], default value:   - * post. - */ - protected String method = "post"; - - /** The form is readonly flag. */ - protected boolean readonly; - - /** The form validate fields when processing flag. */ - protected boolean validate = true; - - /** The button align, default value is "left". */ - protected String buttonAlign = ALIGN_LEFT; - - /** The ordered list of button values. */ - protected final List