Updated Branches: refs/heads/master 2a8b6d0a5 -> 9133bc44b
Prototype for DELTASPIKE-454 Project: http://git-wip-us.apache.org/repos/asf/deltaspike/repo Commit: http://git-wip-us.apache.org/repos/asf/deltaspike/commit/c156d05d Tree: http://git-wip-us.apache.org/repos/asf/deltaspike/tree/c156d05d Diff: http://git-wip-us.apache.org/repos/asf/deltaspike/diff/c156d05d Branch: refs/heads/master Commit: c156d05d8a9cad30a0d3860749091a36d2d9926c Parents: 2a8b6d0 Author: andraschko <andrasc...@dev-4.ifvuf.de> Authored: Wed Jan 8 18:23:30 2014 +0100 Committer: andraschko <andrasc...@dev-4.ifvuf.de> Committed: Wed Jan 8 18:23:30 2014 +0100 ---------------------------------------------------------------------- .../jsf/spi/scope/window/ClientWindow.java | 74 +++++++++++- .../spi/scope/window/ClientWindowConfig.java | 2 + .../DeltaSpikeExternalContextWrapper.java | 4 +- .../impl/scope/window/ClientWindowHelper.java | 99 ++++++++++++++++ .../impl/scope/window/DefaultClientWindow.java | 117 +++++++++++++++++-- .../deltaspike/jsf/impl/util/JsfUtils.java | 96 ++++++++++++++- .../jsf/impl/view/DeltaSpikeViewHandler.java | 7 ++ 7 files changed, 383 insertions(+), 16 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/deltaspike/blob/c156d05d/deltaspike/modules/jsf/api/src/main/java/org/apache/deltaspike/jsf/spi/scope/window/ClientWindow.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/jsf/api/src/main/java/org/apache/deltaspike/jsf/spi/scope/window/ClientWindow.java b/deltaspike/modules/jsf/api/src/main/java/org/apache/deltaspike/jsf/spi/scope/window/ClientWindow.java index d272f33..dba12c9 100644 --- a/deltaspike/modules/jsf/api/src/main/java/org/apache/deltaspike/jsf/spi/scope/window/ClientWindow.java +++ b/deltaspike/modules/jsf/api/src/main/java/org/apache/deltaspike/jsf/spi/scope/window/ClientWindow.java @@ -18,6 +18,7 @@ */ package org.apache.deltaspike.jsf.spi.scope.window; +import java.util.Map; import javax.faces.context.FacesContext; /** @@ -33,8 +34,10 @@ import javax.faces.context.FacesContext; * ClientWindow instances are most likely @ApplicationScoped. * </p> */ -public interface ClientWindow +public abstract class ClientWindow { + private static final String PER_USE_CLIENT_WINDOW_URL_QUERY_PARAMETER_DISABLED_KEY = + ClientWindow.class.getName() + ".ClientWindowRenderModeEnablement"; /** * Extract the windowId for the current request. @@ -47,5 +50,72 @@ public interface ClientWindow * @param facesContext for the request * @return the extracted WindowId of the Request, or <code>null</code> if there is no window assigned. */ - String getWindowId(FacesContext facesContext); + public abstract String getWindowId(FacesContext facesContext); + + /** + * <p>Components that permit per-use disabling + * of the appending of the ClientWindow in generated URLs must call this method + * first before rendering those URLs. The caller must call + * {@link #enableClientWindowRenderMode(javax.faces.context.FacesContext)} + * from a <code>finally</code> block after rendering the URL. If + * {@link #CLIENT_WINDOW_MODE_PARAM_NAME} is "url" without the quotes, all generated + * URLs that cause a GET request must append the ClientWindow by default. + * This is specified as a static method because callsites need to access it + * without having access to an actual {@code ClientWindow} instance.</p> + * + * @param context the {@link FacesContext} for this request. + */ + public void disableClientWindowRenderMode(FacesContext context) + { + Map<Object, Object> attrMap = context.getAttributes(); + attrMap.put(PER_USE_CLIENT_WINDOW_URL_QUERY_PARAMETER_DISABLED_KEY, Boolean.TRUE); + } + + /** + * <p>Components that permit per-use disabling + * of the appending of the ClientWindow in generated URLs must call this method + * first after rendering those URLs. If + * {@link #CLIENT_WINDOW_MODE_PARAM_NAME} is "url" without the quotes, all generated + * URLs that cause a GET request must append the ClientWindow by default. + * This is specified as a static method because callsites need to access it + * without having access to an actual {@code ClientWindow} instance.</p> + * + * @param context the {@link FacesContext} for this request. + */ + public void enableClientWindowRenderMode(FacesContext context) + { + Map<Object, Object> attrMap = context.getAttributes(); + attrMap.remove(PER_USE_CLIENT_WINDOW_URL_QUERY_PARAMETER_DISABLED_KEY); + } + + /** + * <p>Methods that append the ClientWindow to generated + * URLs must call this method to see if they are permitted to do so. If + * {@link #CLIENT_WINDOW_MODE_PARAM_NAME} is "url" without the quotes, all generated + * URLs that cause a GET request must append the ClientWindow by default. + * This is specified as a static method because callsites need to access it + * without having access to an actual {@code ClientWindow} instance.</p> + * + * @param context the {@link FacesContext} for this request. + */ + public boolean isClientWindowRenderModeEnabled(FacesContext context) + { + Map<Object, Object> attrMap = context.getAttributes(); + boolean result = !attrMap.containsKey(PER_USE_CLIENT_WINDOW_URL_QUERY_PARAMETER_DISABLED_KEY); + return result; + } + + /** + * <p>This method will be called whenever a URL + * is generated by the runtime where client window related parameters need + * to be inserted into the URL. This guarantees custom {@code ClientWindow} implementations + * that they will have the opportunity to insert any additional client window specific + * information in any case where a URL is generated, such as the rendering + * of hyperlinks. The returned map must be immutable. The default implementation of this method returns + * the empty map.</p> + + * @param context the {@code FacesContext} for this request. + * @return {@code null} or a map of parameters to insert into the URL query string. + */ + public abstract Map<String, String> getQueryURLParameters(FacesContext context); } http://git-wip-us.apache.org/repos/asf/deltaspike/blob/c156d05d/deltaspike/modules/jsf/api/src/main/java/org/apache/deltaspike/jsf/spi/scope/window/ClientWindowConfig.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/jsf/api/src/main/java/org/apache/deltaspike/jsf/spi/scope/window/ClientWindowConfig.java b/deltaspike/modules/jsf/api/src/main/java/org/apache/deltaspike/jsf/spi/scope/window/ClientWindowConfig.java index 2828bd4..d9aa688 100644 --- a/deltaspike/modules/jsf/api/src/main/java/org/apache/deltaspike/jsf/spi/scope/window/ClientWindowConfig.java +++ b/deltaspike/modules/jsf/api/src/main/java/org/apache/deltaspike/jsf/spi/scope/window/ClientWindowConfig.java @@ -53,6 +53,8 @@ public interface ClientWindowConfig */ DELEGATED, + URL, + /** * If you set this mode, you also need to provide an own {@link ClientWindow} implementation. */ http://git-wip-us.apache.org/repos/asf/deltaspike/blob/c156d05d/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/listener/request/DeltaSpikeExternalContextWrapper.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/listener/request/DeltaSpikeExternalContextWrapper.java b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/listener/request/DeltaSpikeExternalContextWrapper.java index afa9782..c67bbff 100644 --- a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/listener/request/DeltaSpikeExternalContextWrapper.java +++ b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/listener/request/DeltaSpikeExternalContextWrapper.java @@ -24,6 +24,8 @@ import org.apache.deltaspike.jsf.impl.util.JsfUtils; import javax.faces.context.ExternalContext; import javax.faces.context.ExternalContextWrapper; import java.io.IOException; +import javax.faces.context.FacesContext; +import org.apache.deltaspike.jsf.impl.scope.window.ClientWindowHelper; public class DeltaSpikeExternalContextWrapper extends ExternalContextWrapper implements Deactivatable { @@ -38,7 +40,7 @@ public class DeltaSpikeExternalContextWrapper extends ExternalContextWrapper imp public void redirect(String url) throws IOException { JsfUtils.saveFacesMessages(this.wrapped); - this.wrapped.redirect(url); + this.wrapped.redirect(ClientWindowHelper.appendWindowId(FacesContext.getCurrentInstance(), url)); } public ExternalContext getWrapped() http://git-wip-us.apache.org/repos/asf/deltaspike/blob/c156d05d/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/scope/window/ClientWindowHelper.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/scope/window/ClientWindowHelper.java b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/scope/window/ClientWindowHelper.java new file mode 100644 index 0000000..5137054 --- /dev/null +++ b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/scope/window/ClientWindowHelper.java @@ -0,0 +1,99 @@ +/* + * 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.apache.deltaspike.jsf.impl.scope.window; + +import java.util.Map; +import java.util.Map.Entry; +import javax.faces.component.UIViewRoot; +import javax.faces.context.ExternalContext; +import javax.faces.context.FacesContext; +import org.apache.deltaspike.core.api.provider.BeanProvider; +import org.apache.deltaspike.jsf.impl.util.JsfUtils; +import org.apache.deltaspike.jsf.spi.scope.window.ClientWindow; + +public final class ClientWindowHelper +{ + private ClientWindowHelper() + { + + } + + /** + * Handles the initial redirect for the URL modus, if no windowId is available in the current request URL. + * + * @param facesContext the {@link FacesContext} + */ + public static void handleInitialRedirect(FacesContext facesContext) + { + ExternalContext externalContext = facesContext.getExternalContext(); + + // send initial redirect to add the windowId to the current request URL + String viewId = facesContext.getApplication().getViewHandler().deriveViewId( + facesContext, externalContext.getRequestServletPath()); + + // The NavigationHandler tries to access the UIViewRoot but it isn't available because our + // ClientWindow will be initialized before the normal JSF lifecycle + UIViewRoot viewRoot = new UIViewRoot(); + viewRoot.setViewId(viewId); + facesContext.setViewRoot(viewRoot); + + String outcome = viewId + "?faces-redirect=true&includeViewParams=true"; + // append it manually - includeViewParams doesn't work here because of the not fully initialized UIViewRoot + outcome = JsfUtils.addRequestParameters(externalContext, outcome, true); + + facesContext.getApplication().getNavigationHandler().handleNavigation(facesContext, null, outcome); + } + + /** + * Appends the current windowIf to the given url, if enabled via + * {@link ClientWindow#isClientWindowRenderModeEnabled(javax.faces.context.FacesContext)} + * + * @param facesContext the {@link FacesContext} + * @param url the url + * @return if enabled, the url with windowId, otherwise the umodified url + */ + public static String appendWindowId(FacesContext facesContext, String url) + { + ClientWindow clientWindow = BeanProvider.getContextualReference(ClientWindow.class); + if (clientWindow != null && clientWindow.isClientWindowRenderModeEnabled(FacesContext.getCurrentInstance())) + { + Map<String, String> parameters = clientWindow.getQueryURLParameters(facesContext); + + if (parameters != null && !parameters.isEmpty()) + { + String targetUrl = url; + + for (Entry<String, String> entry : parameters.entrySet()) + { + // NOTE: each call will instantiate a new StringBuilder + // i didn't optimized this call because it's unlikely that there will be multiple parameters + targetUrl = JsfUtils.addParameter(facesContext.getExternalContext(), + targetUrl, + true, + entry.getKey(), + entry.getValue()); + } + + return targetUrl; + } + } + + return url; + } +} http://git-wip-us.apache.org/repos/asf/deltaspike/blob/c156d05d/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/scope/window/DefaultClientWindow.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/scope/window/DefaultClientWindow.java b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/scope/window/DefaultClientWindow.java index 725007e..a34c84f 100644 --- a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/scope/window/DefaultClientWindow.java +++ b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/scope/window/DefaultClientWindow.java @@ -28,6 +28,7 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.OutputStream; +import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.logging.Logger; @@ -50,7 +51,7 @@ import static org.apache.deltaspike.jsf.spi.scope.window.ClientWindowConfig.Clie * */ @ApplicationScoped -public class DefaultClientWindow implements ClientWindow +public class DefaultClientWindow extends ClientWindow { /** @@ -65,6 +66,11 @@ public class DefaultClientWindow implements ClientWindow public static final String DELTASPIKE_WINDOW_ID_POST_PARAM = "dsPostWindowId"; public static final String JSF_WINDOW_ID_POST_PARAM = "javax.faces.ClientWindow"; + /** + * GET request parameter + */ + public static final String DELTASPIKE_WINDOW_ID_URL_PARAM = "dswid"; + private static final Logger logger = Logger.getLogger(DefaultClientWindow.class.getName()); @@ -75,6 +81,7 @@ public class DefaultClientWindow implements ClientWindow private static final String WINDOW_ID_REPLACE_PATTERN = "$$windowIdValue$$"; private static final String NOSCRIPT_URL_REPLACE_PATTERN = "$$noscriptUrl$$"; + private static final String NEW_WINDOW_ID = DefaultClientWindow.class.getName() + ".NEW_WINDOW_ID"; /** * Use this parameter to force a 'direct' request from the clients without any windowId detection @@ -110,6 +117,29 @@ public class DefaultClientWindow implements ClientWindow return ClientWindowAdapter.getWindowIdFromJsf(facesContext); } + if (ClientWindowRenderMode.URL.equals(clientWindowRenderMode)) + { + ExternalContext externalContext = facesContext.getExternalContext(); + + if (facesContext.getAttributes().containsKey(NEW_WINDOW_ID)) + { + return (String) facesContext.getAttributes().get(NEW_WINDOW_ID); + } + else if (externalContext.getRequestParameterMap().containsKey(DELTASPIKE_WINDOW_ID_URL_PARAM)) + { + return externalContext.getRequestParameterMap().get(DELTASPIKE_WINDOW_ID_URL_PARAM); + } + else + { + // store the new windowId as context attribute to prevent infinite loops + // the #sendRedirect will append the windowId (from #getWindowId again) to the redirectUrl + facesContext.getAttributes().put(NEW_WINDOW_ID, generateNewWindowId()); + ClientWindowHelper.handleInitialRedirect(facesContext); + facesContext.responseComplete(); + return null; + } + } + if (facesContext.isPostback()) { return getPostBackWindowId(facesContext); @@ -237,17 +267,7 @@ public class DefaultClientWindow implements ClientWindow // add request parameter url = JsfUtils.addPageParameters(externalContext, url, true); - - // add noscript parameter - if (url.contains("?")) - { - url = url + "&"; - } - else - { - url = url + "?"; - } - url = url + NOSCRIPT_PARAMETER + "=true"; + url = JsfUtils.addParameter(externalContext, url, false, NOSCRIPT_PARAMETER, "true"); // NOTE that the url could contain data for an XSS attack // like e.g. ?"></a><a href%3D"http://hacker.org/attack.html?a @@ -287,4 +307,77 @@ public class DefaultClientWindow implements ClientWindow return ""; } + /** + * {@inheritDoc} + */ + @Override + public void disableClientWindowRenderMode(FacesContext context) + { + ClientWindowRenderMode clientWindowRenderMode = clientWindowConfig.getClientWindowRenderMode(context); + + if (ClientWindowRenderMode.DELEGATED.equals(clientWindowRenderMode)) + { + context.getExternalContext().getClientWindow().disableClientWindowRenderMode(context); + } + else if (ClientWindowRenderMode.URL.equals(clientWindowRenderMode)) + { + super.disableClientWindowRenderMode(context); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void enableClientWindowRenderMode(FacesContext context) + { + ClientWindowRenderMode clientWindowRenderMode = clientWindowConfig.getClientWindowRenderMode(context); + + if (ClientWindowRenderMode.DELEGATED.equals(clientWindowRenderMode)) + { + context.getExternalContext().getClientWindow().enableClientWindowRenderMode(context); + } + else if (ClientWindowRenderMode.URL.equals(clientWindowRenderMode)) + { + super.enableClientWindowRenderMode(context); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isClientWindowRenderModeEnabled(FacesContext context) + { + ClientWindowRenderMode clientWindowRenderMode = clientWindowConfig.getClientWindowRenderMode(context); + + if (ClientWindowRenderMode.URL.equals(clientWindowRenderMode)) + { + return super.isClientWindowRenderModeEnabled(context); + } + + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public Map<String, String> getQueryURLParameters(FacesContext context) + { + ClientWindowRenderMode clientWindowRenderMode = clientWindowConfig.getClientWindowRenderMode(context); + + if (ClientWindowRenderMode.URL.equals(clientWindowRenderMode)) + { + String windowId = getWindowId(context); + if (windowId != null) + { + Map<String, String> params = new HashMap<String, String>(); + params.put(DELTASPIKE_WINDOW_ID_URL_PARAM, getWindowId(context)); + return params; + } + } + + return null; + } } http://git-wip-us.apache.org/repos/asf/deltaspike/blob/c156d05d/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/util/JsfUtils.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/util/JsfUtils.java b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/util/JsfUtils.java index 63a752a..f5aea23 100644 --- a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/util/JsfUtils.java +++ b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/util/JsfUtils.java @@ -76,7 +76,7 @@ public abstract class JsfUtils } /** - * Adds the current request-parameters to the given url + * Adds the current page-parameters to the given url * * @param externalContext current external-context * @param url current url @@ -124,6 +124,100 @@ public abstract class JsfUtils } /** + * Adds a paramter to the given url. + * + * @param externalContext current external-context + * @param url current url + * @param encodeValues flag which indicates if parameter values should be encoded or not + * @param name the paramter name + * @param value the paramter value + * @return url with appended parameter + */ + public static String addParameter(ExternalContext externalContext, String url, boolean encodeValues, + String name, String value) + { + // don't append if already available + if (url.contains(name + "=" + value) + || url.contains(name + "=" + encodeURLParameterValue(value, externalContext))) + { + return url; + } + + StringBuilder finalUrl = new StringBuilder(url); + + if (url.contains("?")) + { + finalUrl.append("&"); + } + else + { + finalUrl.append("?"); + } + + finalUrl.append(name); + finalUrl.append("="); + + if (encodeValues) + { + finalUrl.append(JsfUtils.encodeURLParameterValue(value, externalContext)); + } + else + { + finalUrl.append(value); + } + + return finalUrl.toString(); + } + + /** + * Adds the current request-parameters to the given url + * + * @param externalContext current external-context + * @param url current url + * @param encodeValues flag which indicates if parameter values should be encoded or not + * @return url with request-parameters + */ + public static String addRequestParameters(ExternalContext externalContext, String url, boolean encodeValues) + { + StringBuilder finalUrl = new StringBuilder(url); + boolean existingParameters = url.contains("?"); + + for (Map.Entry<String, String[]> entry : externalContext.getRequestParameterValuesMap().entrySet()) + { + for (String value : entry.getValue()) + { + if (!url.contains(entry.getKey() + "=" + value) && + !url.contains(entry.getKey() + "=" + encodeURLParameterValue(value, externalContext))) + { + if (!existingParameters) + { + finalUrl.append("?"); + existingParameters = true; + } + else + { + finalUrl.append("&"); + } + + finalUrl.append(entry.getKey()); + finalUrl.append("="); + + if (encodeValues) + { + finalUrl.append(JsfUtils.encodeURLParameterValue(value, externalContext)); + } + else + { + finalUrl.append(value); + } + } + } + } + + return finalUrl.toString(); + } + + /** * Encodes the given value using URLEncoder.encode() with the charset returned * from ExternalContext.getResponseCharacterEncoding(). * This is exactly how the ExternalContext impl encodes URL parameter values. http://git-wip-us.apache.org/repos/asf/deltaspike/blob/c156d05d/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/view/DeltaSpikeViewHandler.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/view/DeltaSpikeViewHandler.java b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/view/DeltaSpikeViewHandler.java index 7c10c30..234a50e 100644 --- a/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/view/DeltaSpikeViewHandler.java +++ b/deltaspike/modules/jsf/impl/src/main/java/org/apache/deltaspike/jsf/impl/view/DeltaSpikeViewHandler.java @@ -26,6 +26,7 @@ import javax.faces.application.ViewHandler; import javax.faces.application.ViewHandlerWrapper; import javax.faces.component.UIViewRoot; import javax.faces.context.FacesContext; +import org.apache.deltaspike.jsf.impl.scope.window.ClientWindowHelper; /** * Aggregates all {@link ViewHandler} implementations provided by DeltaSpike @@ -72,6 +73,12 @@ public class DeltaSpikeViewHandler extends ViewHandlerWrapper implements Deactiv } @Override + public String getActionURL(FacesContext context, String viewId) + { + return ClientWindowHelper.appendWindowId(context, this.wrapped.getActionURL(context, viewId)); + } + + @Override public ViewHandler getWrapped() { return this.wrapped;