All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gwtplatform.mvp.client.proxy.RouteTokenFormatter Maven / Gradle / Ivy

There is a newer version: 1.6
Show newest version
/**
 * Copyright 2013 ArcBees Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.gwtplatform.mvp.client.proxy;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import com.google.gwt.http.client.URL;
import com.google.inject.Inject;
import com.gwtplatform.mvp.client.gin.DefaultModule;

/**
 * Implementation of {@link TokenFormatter} with support for route like place names.
 *
 * 

* Instead of wiring a hierarchy of several places bound to multiple presenters this implements a flat structure where * every history token is bound to a single presenter. *

* *

* Usage: *

* *

* Replace the default binding to {@link ParameterTokenFormatter} with {@link RouteTokenFormatter}. In case you use * GWTPs {@link DefaultModule}: *

* *
 * install(new DefaultModule(DefaultPlaceManager.class, RouteTokenFormatter.class));
 * 
* *

* Now all @NameToken's are treated as routes. Routes are expected to start with an '/' and can contain path parameters * as well as query parameters. *

* *
 * @NameToken("/user/{userId}/privacy")         // Token for PrivacyPresenter
 * @NameToken("/user/{userId}/privacy/profile") // Token for PrivacyProfilePresenter
 * @NameToken("/user/{userId}/privacy/photos")  // Token for PrivacyPhotosPresenter
 * 
* *

* Static-parts of an route tie stronger than parameter-parts. This way following works: *

* *
 * @NameToken("/{vanityId}") // Token for VanityUrlPresenter
 * @NameToken("/privacy")    // Token for PrivacyPresenter
 * 
* *

* Note: For the moment this is implemented on top of the hierarchical-place API to not an big structural changes prior * 1.0 release. *

*/ public class RouteTokenFormatter implements TokenFormatter { /** * Helper class which wraps calls to code which require a running GWT environment and make testing slow. */ static class UrlUtils { public String decodeQueryString(final String encodedUrlComponent) { return URL.decodeQueryString(encodedUrlComponent); } public String encodeQueryString(final String decodedUrlComponent) { return URL.encodeQueryString(decodedUrlComponent); } } /** * Helper class to store matches to routes in {@link #toPlaceRequest(String)}. */ private class RouteMatch implements Comparable { /** * Route/place-token associated to the match. */ String route; /** * Number of static matches in this route. */ int staticMatches; /** * Parsed parameters of this route. */ Map parameters; /** * Construct a new {@link RouteMatch}. * * @param route Place token associated to the match. * @param staticMatches Number of static matches in this route. * @param parameters Parsed parameters of this route. */ RouteMatch(String route, int staticMatches, Map parameters) { this.route = route; this.staticMatches = staticMatches; this.parameters = parameters; } /** * Sort {@link RouteMatch}s by the amount of {@link #staticMatches}. */ @Override public int compareTo(RouteMatch other) { return Integer.valueOf(staticMatches).compareTo(other.staticMatches); } } /** * Helper class for parsing and matching place-tokens against routes. */ private class RouteMatcher { /** * All matching routes of the place-token. * * Sorted in ascending order by the number of static matches. */ final TreeSet allMatches; final String[] placeParts; /** * Parse and match the given place-token. * * @param placeToken The place-token. */ RouteMatcher(String placeToken) { assert placeToken.startsWith("/") : "Place-token should start with a '/'"; assert placeToken.indexOf('?') == -1 : "No Query string expected here"; this.allMatches = new TreeSet(); this.placeParts = placeToken.split("/"); for (String route : allRegisteredPlaceTokens.getAllPlaceTokens()) { RouteMatch match = matchRoute(route); if (match != null) { allMatches.add(match); } } } /** * Check if the current place-token matches the given route and return the associated {@link RouteMatch}. * * @param route The route to check. * @return Associated {@link RouteMatch} or null if the place-token doesn't match the given route. */ RouteMatch matchRoute(String route) { String[] routeParts = route.split("/"); if (placeParts.length != routeParts.length) { return null; } if (placeParts.length == 0) { assert "/".equals(route); return new RouteMatch(route, 0, null); } Map recordedParameters = new HashMap(); int staticMatches = 0; for (int i = 0; i < placeParts.length; i++) { if (placeParts[i].equals(routeParts[i])) { staticMatches++; } else if (routeParts[i].matches("\\{.*\\}")) { String parameterName = routeParts[i].substring(1, routeParts[i].length() - 1); recordedParameters.put(parameterName, placeParts[i]); } else { return null; } } return new RouteMatch(route, staticMatches, recordedParameters); } } private final UrlUtils urlUtils; private final PlaceTokenRegistry allRegisteredPlaceTokens; @Inject public RouteTokenFormatter(UrlUtils urlUtils, PlaceTokenRegistry tokenRegistry) { this.urlUtils = urlUtils; this.allRegisteredPlaceTokens = tokenRegistry; } @Override public String toPlaceToken(PlaceRequest placeRequest) throws TokenFormatException { String placeToken = placeRequest.getNameToken(); String queryString = ""; String querySeperator = ""; for (String parameterName : placeRequest.getParameterNames()) { String parameterValue = placeRequest.getParameter(parameterName, null); if (parameterValue != null) { if (placeToken.contains("/{" + parameterName + "}")) { // route parameter placeToken = placeToken.replace("{" + parameterName + "}", parameterValue); } else { // query parameter queryString = queryString + querySeperator + parameterName + "=" + urlUtils.decodeQueryString(parameterValue); querySeperator = "&"; } } } if (!"".equals(queryString)) { placeToken = placeToken + "?" + queryString; } return placeToken; } @Override public String toHistoryToken(List placeRequestHierarchy) throws TokenFormatException { assert placeRequestHierarchy.size() == 1 : "Expected a place hierarchy with exactly one place."; return toPlaceToken(placeRequestHierarchy.get(0)); } @Override public PlaceRequest toPlaceRequest(final String placeToken) throws TokenFormatException { /* * To support the native GWT history as well as HTML pushstate a slash is added when needed. */ if (!placeToken.startsWith("/")) { return toPlaceRequest("/" + placeToken); } int split = placeToken.indexOf('?'); String place = (split != -1) ? placeToken.substring(0, split) : placeToken; String query = (split != -1) ? placeToken.substring(split + 1) : ""; RouteMatcher matcher = new RouteMatcher(place); RouteMatch match = (matcher.allMatches.size() > 0) ? matcher.allMatches.last() : new RouteMatch(place, 0, null); match.parameters = parseQueryString(query, match.parameters); return new PlaceRequest.Builder().nameToken(match.route).with(match.parameters).build(); } @Override public List toPlaceRequestHierarchy(String historyToken) throws TokenFormatException { List result = new ArrayList(); result.add(toPlaceRequest(historyToken)); return result; } /** * Parse the given query-string and store all parameters into a map. * * @param queryString The query-string. * @param into The map to use. If the given map is null a new map will be created. * @return A map containing all keys value pairs of the query-string. */ Map parseQueryString(final String queryString, Map into) { Map result = (into != null) ? into : new HashMap(); if (queryString != null && !queryString.isEmpty()) { for (String keyValuePair : queryString.split("&")) { String[] keyValue = keyValuePair.split("=", 2); if (keyValue.length > 1) { result.put(keyValue[0], urlUtils.encodeQueryString(keyValue[1])); } else { result.put(keyValue[0], ""); } } } return result; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy