/*
* Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
*
* 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 org.jboss.errai.ui.nav.client.local;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import com.google.common.collect.ImmutableMultimap;
import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
/**
* Used to extract page state values from the URL path.
* This class generates a URL where the page state values are appropriately encoded for parsing. Thus the URL must be
* appropriately decoded. See @see URLPattern#decodeParsingCharacters() below.
*
* @author Max Barkley <mbarkley@redhat.com>
* @author Divya Dadlani <ddadlani@redhat.com>
*
*/
public class URLPattern {
private final List<String> paramList;
private final RegExp regex;
private final String urlTemplate;
/**
* A regular expression that checks for parameters declared in a URL template.
* For example, in the URL {@code}/pageName/{id}/info{@code}, paramRegex would match 'id'.
*/
static final String paramRegex = "\\{([^}]+)\\}";
/**
* A regular expression that we use to match a path parameter value. Since the value can contain any characters,
* this regex matches everything. Any characters that we use for parsing will later be encoded.
*/
public static final String urlSafe = "([^/]+)";
public URLPattern(RegExp regex, List<String> paramList, String urlTemplate) {
this.regex = regex;
this.paramList = paramList;
this.urlTemplate = urlTemplate;
}
/**
* @return A list of path parameter key names. Never null.
*/
public List<String> getParamList() {
return paramList;
}
/**
* @return The regular expression used to match URLs.
*/
public RegExp getRegex() {
return regex;
}
/**
* @param url
* This URL path should not contain the application context and should not contain a leading slash.
* @return True if this pattern matches the given URL path
*/
public boolean matches(String url) {
return regex.test(url);
}
/**
* Uses the state map to construct the encoded URL for this pattern. Values in state that are not predefined
* path parameters (see {@link #getParamList()}) will be appended as key-value pairs.
* Note that this method only encodes the URL in a format that can be parsed by {@see URLPatternMatcher#parseURL()
* parseURL()}.
*
* @param state
* @throws IllegalStateException
* If a path parameter is missing from the given state map.
* @return The constructed URL path without the application context.
*/
public String printURL(ImmutableMultimap<String, String> state) {
RegExp re = RegExp.compile(paramRegex, "g");
String url = this.urlTemplate;
MatchResult mr;
while ((mr = re.exec(this.urlTemplate)) != null) {
String toReplace = mr.getGroup(0);
String key = mr.getGroup(1);
if (toReplace.contains(key)) {
// Encode all the characters we use to parse URLs.
String encodedValue = URLPattern.encodeParsingCharacters(state.get(key).iterator().next());
url = url.replace(toReplace, encodedValue);
}
else {
throw new IllegalStateException("Path parameter list did not contain required parameter " + mr.getGroup(1));
}
}
if (state.keySet().size() == paramList.size()) {
return url;
}
StringBuilder urlBuilder = new StringBuilder(url);
urlBuilder.append(';');
Iterator<Entry<String, String>> itr = state.entries().iterator();
while (itr.hasNext()) {
Entry<String, String> pageStateField = itr.next();
if (!paramList.contains(pageStateField.getKey())) {
// Encode the parts of the value that may interfere with parsing
urlBuilder.append(URLPattern.encodeParsingCharacters(pageStateField.getKey()));
urlBuilder.append('=');
urlBuilder.append(URLPattern.encodeParsingCharacters(pageStateField.getValue()));
if (itr.hasNext())
urlBuilder.append('&');
}
}
return urlBuilder.toString();
}
@Override
public String toString() {
return urlTemplate;
}
/**
* Replaces the characters used for parsing with their encoded equivalents.
* The characters ";", "/", "&" and "=" are used in URLPattern and URLPatternMatcher to parse the given URL.
* Hence any occurrences of these characters in the actual page state values are 'escaped' so that it doesn't
* interfere with our URL parsing.
*
* @param plainValue The string that may contain any parsing characters.
* @return The same value with the appropriate characters 'escaped'.
*/
static String encodeParsingCharacters(String plainValue) {
return plainValue.replaceAll("%", "%25").replaceAll(";","%3B").replaceAll("/","%2F").replaceAll("&", "%26")
.replaceAll("=", "%3D");
}
/**
* This method is the converse of {@link URLPattern#encodeParsingCharacters}.
* It 'un-escapes' all the parsing characters encoded by {@link URLPattern#encodeParsingCharacters}.
*
* @param escapedValue The string where the characters ";", "/", "&" and "=" have been encoded.
* @return The same string where all the encoded values are replaced by the actual characters.
*/
static String decodeParsingCharacters(String escapedValue) {
return escapedValue.replaceAll("%3B",";").replaceAll("%2F", "/").replaceAll("%26", "&")
.replaceAll("%3D", "=").replace("%25", "%");
}
}