package org.sigmah.client.page; /* * #%L * Sigmah * %% * Copyright (C) 2010 - 2016 URD * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.sigmah.client.util.ClientUtils; import org.sigmah.shared.util.Pair; /** * Page request. * * @author Denis Colliot (dcolliot@ideia.fr) * @author Tom Miette (tmiette@ideia.fr) */ public final class PageRequest { /** * Character used to reference a page token. */ public static final String URL_TOKEN = "#"; /** * Character used to separate optional params in the URL. */ private static final String PARAM_SEPARATOR = "&"; private static final String PARAM_PATTERN = PARAM_SEPARATOR + "(?!" + PARAM_SEPARATOR + ")"; private static final String PARAM_ESCAPE = PARAM_SEPARATOR + PARAM_SEPARATOR; /** * Character used to assign a param value. */ private static final String VALUE_SEPARATOR = "="; private static final String VALUE_PATTERN = VALUE_SEPARATOR + "(?!" + VALUE_SEPARATOR + ")"; private static final String VALUE_ESCAPE = VALUE_SEPARATOR + VALUE_SEPARATOR; /** * The {@link Page} instance. */ private final Page page; /** * Page request <b>URL</b> parameters. * <em>They can be considered as HTTP {@code GET} parameters.</em> */ private final Map<RequestParameter, String> parameters; /** * Page request <b>data object</b> parameters. * <em>They can be considered as HTTP {@code POST} parameters.</em> */ private final Map<RequestParameter, Object> dataParameters; /** * Initializes a new {@code PageRequest} instance. * * @param page * The page associated to the page request. */ public PageRequest(final Page page) { this(page, null, null); } /** * Initializes a new {@code PageRequest} instance, clone of the given {@code request}. * * @param request * The {@code PageRequest} instance to clone. */ public PageRequest(final PageRequest request) { this(request != null ? request.page : null, request != null ? request.parameters : null, request != null ? request.dataParameters : null); } /** * Initializes a new {@code PageRequest} instance with the given arguments. * * @param page * The page. * @param parameters * The URL parameters. If {@code null}, an empty map is initialized. * @param dataParameters * The data parameters. If {@code null}, an empty map is initialized. */ private PageRequest(final Page page, final Map<RequestParameter, String> parameters, final Map<RequestParameter, Object> dataParameters) { this.page = page; this.parameters = parameters != null ? new HashMap<RequestParameter, String>(parameters) : new HashMap<RequestParameter, String>(1); this.dataParameters = dataParameters != null ? new HashMap<RequestParameter, Object>(dataParameters) : new HashMap<RequestParameter, Object>(1); } /** * Returns the {@link Page} associated to the current request. * * @return The {@link Page} associated to the current request. */ public Page getPage() { return page; } /** * Returns all the request parameters map. * * @return The request parameters map (never {@code null}). */ public Map<RequestParameter, String> getParameters() { return getParameters(false); } /** * Returns the request parameters map. * * @param onlyUniqueParameters * If {@code true}, returns only the {@link RequestParameter} with {@code unique} flag. If {@code false}, * returns all parameters. * @return The request parameters map (never {@code null}). */ public Map<RequestParameter, String> getParameters(final boolean onlyUniqueParameters) { if (!onlyUniqueParameters) { return parameters; } final Map<RequestParameter, String> uniqueParams = new HashMap<RequestParameter, String>(); for (final Map.Entry<RequestParameter, String> entry : parameters.entrySet()) { if (entry.getKey().isUnique()) { uniqueParams.put(entry.getKey(), entry.getValue()); } } return uniqueParams; } /** * Returns the request parameters names set. * * @return the request parameters names set (never {@code null}). */ public Set<RequestParameter> getParameterKeys() { if (parameters != null) { return parameters.keySet(); } else { return Collections.emptySet(); } } /** * Gets the parameter value corresponding to the given {@code key}. * * @param key * The parameter key. * @return the parameter value corresponding to the given {@code key}, or {@code null} if the parameter is not present * inside the request. */ public String getParameter(final RequestParameter key) { return getParameter(key, null); } /** * Gets the parameter value corresponding to the given {@code key}. * * @param key * The parameter key. * @param defaultValue * The value returned if the parameter is not found. * @return the parameter value corresponding to the given {@code key}, or given {@code defaultValue} if the parameter * is not present inside the request. */ public String getParameter(final RequestParameter key, final String defaultValue) { String value = null; if (parameters != null) { value = parameters.get(key); } if (value == null) { value = defaultValue; } return value; } /** * Gets the parameter <b>integer</b> value corresponding to the given {@code key}. * * @param key * The parameter key. * @return The parameter <b>integer</b> value corresponding to the given {@code key}, or {@code null} if the parameter * is not present inside the request or is not a valid integer. */ public Integer getParameterInteger(final RequestParameter key) { return ClientUtils.asInt(getParameter(key, null)); } /** * <p> * Adds a new parameter to the current {@code PageRequest} with the given parameter {@code name} and {@code value}. * </p> * <p> * If a parameter with the same {@code name} was previously specified, the returned request contains the new value. * </p> * * @param name * The new parameter name. * @param value * The new parameter value ({@code value.toString()} is used). * @return The {@code PageRequest} instance with its new parameter. */ public PageRequest addParameter(final RequestParameter name, final Object value) { parameters.put(name, String.valueOf(value)); return this; } /** * <p> * Adds all the given {@code parameters} to the current {@code PageRequest} (does not remove existing parameters). * </p> * <p> * Replaces existing parameters with same key. * </p> * * @param parameters * The parameters map. * @return The {@code PageRequest} instance with its new parameter(s). */ public PageRequest addAllParameters(final Map<RequestParameter, ?> parameters) { if (parameters == null) { return this; } for (final Entry<RequestParameter, ?> parameter : parameters.entrySet()) { addParameter(parameter.getKey(), parameter.getValue()); } return this; } /** * <p> * Remove an existing parameter to the current {@code PageRequest}. * </p> * * @param name * The name of the parameter to remove. * @return The {@code PageRequest} instance without the given parameter. */ public PageRequest removeParameter(final RequestParameter name) { parameters.remove(name); return this; } /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { if (obj instanceof PageRequest) { final PageRequest req = (PageRequest) obj; if (!page.equals(req.page)) { return false; } if (parameters == null) { return req.parameters == null; } else { return parameters.equals(req.parameters); } } return false; } /** * {@inheritDoc} */ @Override public int hashCode() { return 11 * (page.hashCode() + (parameters == null ? 0 : parameters.hashCode())); } /** * Outputs the place as a GWT history token. * * @return the place as a GWT history token including optional parameters. */ @Override public String toString() { final StringBuilder out = new StringBuilder(); out.append(page.toString()); if (ClientUtils.isNotEmpty(parameters)) { for (final Map.Entry<RequestParameter, String> entry : parameters.entrySet()) { out.append(PARAM_SEPARATOR); out.append(escape(entry.getKey().getRequestName())).append(VALUE_SEPARATOR).append(escape(entry.getValue())); } } return out.toString(); } /** * <p> * Adds a new <b>data object</b> parameter to the current {@code PageRequest} with the given parameter {@code key} and * {@code value}. * </p> * <p> * If a <b>data object</b> parameter with the same {@code key} was previously specified, the returned request contains * the new value. * </p> * * @param key * The new data parameter key. * @param value * The new data parameter value. * @return The {@code PageRequest} instance with its new <b>data object</b> parameter. */ public PageRequest addData(final RequestParameter key, final Object value) { dataParameters.put(key, value); return this; } /** * Returns the <b>data object</b> contained in the page request instance. * * @param <T> * Data type. * @param key * The data parameter key. * @return The given {@code key} corresponding data contained in the page request, or {@code null} if the data doesn't * exist or if its type is not {@code T}. */ @SuppressWarnings("unchecked") public <T> T getData(final RequestParameter key) { try { return (T) dataParameters.get(key); } catch (final ClassCastException e) { return null; } } // --------------------------------------------------------------------------------- // // UTILITY METHODS. // // --------------------------------------------------------------------------------- /** * Parses a GWT history token into a {@link PageRequest} instance. * * @param token * The token (with optional URL parameters). * @param pages * The pages map storing pages tokens with their corresponding {@link Page}. * @return The page request, or <code>null</code> if the token could not be parsed. * @throws PageParsingException * If the given {@code token} is invalid. */ static PageRequest fromString(final String token, final Map<String, Pair<Page, Boolean>> pages) throws PageParsingException { return fromString(token, pages, null); } /** * Parses a GWT history token into a {@link PageRequest} instance. * * @param token * The token (with optional URL parameters). * @param pages * The pages map storing pages tokens with their corresponding {@link Page}. * @param dataParameters * (optional) Data parameters (object(s)) to include into returned page request instance. * @return The page request, or <code>null</code> if the token could not be parsed. * @throws PageParsingException * If the given {@code token} is invalid. */ static PageRequest fromString(final String token, final Map<String, Pair<Page, Boolean>> pages, final Map<RequestParameter, Object> dataParameters) throws PageParsingException { PageRequest req = null; final int split = token.indexOf(PARAM_SEPARATOR); // Invalid token. if (split == 0) { throw new PageParsingException("Page token is missing."); } // No URL parameters. else if (split == -1) { final Page page = pages.get(token) != null ? pages.get(token).left : null; if (page == null) { throw new PageParsingException("Page token '" + token + "' is not registered among application presenters."); } else { req = new PageRequest(page); } } // URL parameters detected. else if (split >= 0) { final String pageToken = token.substring(0, split); final Page page = pages.get(pageToken) != null ? pages.get(pageToken).left : null; if (page == null) { throw new PageParsingException("Page token '" + token + "' is not registered among application presenters."); } else { req = new PageRequest(page); } final String paramsChunk = token.substring(split + 1); final String[] paramTokens = paramsChunk.split(PARAM_PATTERN); for (final String paramToken : paramTokens) { final String[] param = paramToken.split(VALUE_PATTERN); if (param.length != 2) { throw new PageParsingException("Bad parameter: Parameters require a single '" + VALUE_SEPARATOR + "' between the key and value."); } req = req.addParameter(RequestParameter.fromRequestName(unescape(param[0])), unescape(param[1])); } } if (req != null && dataParameters != null) { for (final Entry<RequestParameter, Object> dataParameter : dataParameters.entrySet()) { req.addData(dataParameter.getKey(), dataParameter.getValue()); } } return req; } /** * Escapes the given {@code value} as a page request parameter. * * @param value * The page request parameter. * @return the given {@code value} escaped as a page request parameter (if necessary). */ private static String escape(final String value) { return value.replaceAll(PARAM_SEPARATOR, PARAM_ESCAPE).replaceAll(VALUE_SEPARATOR, VALUE_ESCAPE); } /** * Unescapes the given {@code value} as a page request parameter. * * @param value * The page request parameter. * @return the given {@code value} unescaped as a page request parameter (if necessary). */ private static String unescape(final String value) { return value.replaceAll(PARAM_ESCAPE, PARAM_SEPARATOR).replaceAll(VALUE_ESCAPE, VALUE_SEPARATOR); } /** * Returns the given arguments corresponding URL string. * * @param pageToken * The URL page token. * @param parameters * The URL parameters. * @return the given arguments corresponding URL string. */ public static String toUrl(final String pageToken, final Map<RequestParameter, String> parameters) { if (ClientUtils.isBlank(pageToken)) { throw new IllegalArgumentException("URL page token is required."); } final StringBuilder builder = new StringBuilder(); builder.append(URL_TOKEN).append(pageToken); if (ClientUtils.isNotEmpty(parameters)) { for (final Entry<RequestParameter, String> param : parameters.entrySet()) { if (param == null || param.getKey() == null) { continue; } builder.append(PARAM_SEPARATOR); builder.append(escape(param.getKey().getRequestName())).append(VALUE_SEPARATOR).append(escape(param.getValue())); } } return builder.toString(); } }