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

com.rt.storage.api.client.http.UriTemplate Maven / Gradle / Ivy

package com.rt.storage.api.client.http;

import com.rt.storage.api.client.util.Data;
import com.rt.storage.api.client.util.FieldInfo;
import com.rt.storage.api.client.util.Preconditions;
import com.rt.storage.api.client.util.Types;
import com.rt.storage.api.client.util.escape.CharEscapers;
import com.google.common.base.Splitter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.ListIterator;
import java.util.Map;

/**
 * Expands URI Templates.
 *
 * 

This Class supports Level 1 templates and all Level 4 composite templates as described in: RFC 6570. * *

Specifically, for the variables: var := "value" list := ["red", "green", "blue"] keys := * [("semi", ";"),("dot", "."),("comma", ",")] * *

The following templates results in the following expansions: {var} -> value {list} -> * red,green,blue {list*} -> red,green,blue {keys} -> semi,%3B,dot,.,comma,%2C {keys*} -> * semi=%3B,dot=.,comma=%2C {+list} -> red,green,blue {+list*} -> red,green,blue {+keys} -> * semi,;,dot,.,comma,, {+keys*} -> semi=;,dot=.,comma=, {#list} -> #red,green,blue {#list*} -> * #red,green,blue {#keys} -> #semi,;,dot,.,comma,, {#keys*} -> #semi=;,dot=.,comma=, X{.list} -> * X.red,green,blue X{.list*} -> X.red.green.blue X{.keys} -> X.semi,%3B,dot,.,comma,%2C X{.keys*} * -> X.semi=%3B.dot=..comma=%2C {/list} -> /red,green,blue {/list*} -> /red/green/blue {/keys} -> * /semi,%3B,dot,.,comma,%2C {/keys*} -> /semi=%3B/dot=./comma=%2C {;list} -> ;list=red,green,blue * {;list*} -> ;list=red;list=green;list=blue {;keys} -> ;keys=semi,%3B,dot,.,comma,%2C {;keys*} -> * ;semi=%3B;dot=.;comma=%2C {?list} -> ?list=red,green,blue {?list*} -> * ?list=red&list=green&list=blue {?keys} -> ?keys=semi,%3B,dot,.,comma,%2C {?keys*} -> * ?semi=%3B&dot=.&comma=%2C {&list} -> &list=red,green,blue {&list*} -> * &list=red&list=green&list=blue {&keys} -> &keys=semi,%3B,dot,.,comma,%2C {&keys*} -> * &semi=%3B&dot=.&comma=%2C {?var,list} -> ?var=value&list=red,green,blue * * @since 1.6 * @author Ravi Mistry */ public class UriTemplate { private static final Map COMPOSITE_PREFIXES = new HashMap(); static { CompositeOutput.values(); } private static final String COMPOSITE_NON_EXPLODE_JOINER = ","; /** Contains information on how to output a composite value. */ private enum CompositeOutput { /** Reserved expansion. */ PLUS('+', "", ",", false, true), /** Fragment expansion. */ HASH('#', "#", ",", false, true), /** Label expansion with dot-prefix. */ DOT('.', ".", ".", false, false), /** Path segment expansion. */ FORWARD_SLASH('/', "/", "/", false, false), /** Path segment parameter expansion. */ SEMI_COLON(';', ";", ";", true, false), /** Form-style query expansion. */ QUERY('?', "?", "&", true, false), /** Form-style query continuation. */ AMP('&', "&", "&", true, false), /** Simple expansion. */ SIMPLE(null, "", ",", false, false); private final Character propertyPrefix; private final String outputPrefix; private final String explodeJoiner; private final boolean requiresVarAssignment; private final boolean reservedExpansion; /** * @param propertyPrefix the prefix of a parameter or {@code null} for none. In {+var} the * prefix is '+' * @param outputPrefix the string that should be prefixed to the expanded template. * @param explodeJoiner the delimiter used to join composite values. * @param requiresVarAssignment denotes whether or not the expanded template should contain an * assignment with the variable * @param reservedExpansion reserved expansion allows percent-encoded triplets and characters in * the reserved set */ CompositeOutput( Character propertyPrefix, String outputPrefix, String explodeJoiner, boolean requiresVarAssignment, boolean reservedExpansion) { this.propertyPrefix = propertyPrefix; this.outputPrefix = Preconditions.checkNotNull(outputPrefix); this.explodeJoiner = Preconditions.checkNotNull(explodeJoiner); this.requiresVarAssignment = requiresVarAssignment; this.reservedExpansion = reservedExpansion; if (propertyPrefix != null) { COMPOSITE_PREFIXES.put(propertyPrefix, this); } } /** Returns the string that should be prefixed to the expanded template. */ String getOutputPrefix() { return outputPrefix; } /** Returns the delimiter used to join composite values. */ String getExplodeJoiner() { return explodeJoiner; } /** * Returns whether or not the expanded template should contain an assignment with the variable. */ boolean requiresVarAssignment() { return requiresVarAssignment; } /** * Returns the start index of the var name. If the variable contains a prefix the start index * will be 1 else it will be 0. */ int getVarNameStartIndex() { return propertyPrefix == null ? 0 : 1; } /** * Encodes the specified value. If reserved expansion is turned on, then percent-encoded * triplets and characters are allowed in the reserved set. * * @param value the string to be encoded * @return the encoded string */ private String getEncodedValue(String value) { String encodedValue; if (reservedExpansion) { // Reserved expansion allows percent-encoded triplets and characters in the reserved set. encodedValue = CharEscapers.escapeUriPathWithoutReserved(value); } else { encodedValue = CharEscapers.escapeUriConformant(value); } return encodedValue; } } static CompositeOutput getCompositeOutput(String propertyName) { CompositeOutput compositeOutput = COMPOSITE_PREFIXES.get(propertyName.charAt(0)); return compositeOutput == null ? CompositeOutput.SIMPLE : compositeOutput; } /** * Constructs a new {@code Map} from an {@code Object}. * *

There are no null values in the returned map. */ private static Map getMap(Object obj) { // Using a LinkedHashMap to maintain the original order of insertions. This is done to help // with handling unused parameters and makes testing easier as well. Map map = new LinkedHashMap(); for (Map.Entry entry : Data.mapOf(obj).entrySet()) { Object value = entry.getValue(); if (value != null && !Data.isNull(value)) { map.put(entry.getKey(), value); } } return map; } /** * Expands templates in a URI template that is relative to a base URL. * *

If the URI template starts with a "/" the raw path from the base URL is stripped out. If the * URI template is a full URL then it is used instead of the base URL. * *

Supports Level 1 templates and all Level 4 composite templates as described in: RFC 6570. * * @param baseUrl The base URL which the URI component is relative to. * @param uriTemplate URI component. It may contain one or more sequences of the form "{name}", * where "name" must be a key in variableMap. * @param parameters an object with parameters designated by Key annotations. If the template has * no variable references, parameters may be {@code null}. * @param addUnusedParamsAsQueryParams If true then parameters that do not match the template are * appended to the expanded template as query parameters. * @return The expanded template * @since 1.7 */ public static String expand( String baseUrl, String uriTemplate, Object parameters, boolean addUnusedParamsAsQueryParams) { String pathUri; if (uriTemplate.startsWith("/")) { // Remove the base path from the base URL. GenericUrl url = new GenericUrl(baseUrl); url.setRawPath(null); pathUri = url.build() + uriTemplate; } else if (uriTemplate.startsWith("http://") || uriTemplate.startsWith("https://")) { pathUri = uriTemplate; } else { pathUri = baseUrl + uriTemplate; } return expand(pathUri, parameters, addUnusedParamsAsQueryParams); } /** * Expands templates in a URI. * *

Supports Level 1 templates and all Level 4 composite templates as described in: RFC 6570. * * @param pathUri URI component. It may contain one or more sequences of the form "{name}", where * "name" must be a key in variableMap * @param parameters an object with parameters designated by Key annotations. If the template has * no variable references, parameters may be {@code null}. * @param addUnusedParamsAsQueryParams If true then parameters that do not match the template are * appended to the expanded template as query parameters. * @return The expanded template * @since 1.6 */ public static String expand( String pathUri, Object parameters, boolean addUnusedParamsAsQueryParams) { Map variableMap = getMap(parameters); StringBuilder pathBuf = new StringBuilder(); int cur = 0; int length = pathUri.length(); while (cur < length) { int next = pathUri.indexOf('{', cur); if (next == -1) { if (cur == 0 && !addUnusedParamsAsQueryParams) { // No expansions exist and we do not need to add any query parameters. return pathUri; } pathBuf.append(pathUri.substring(cur)); break; } pathBuf.append(pathUri.substring(cur, next)); int close = pathUri.indexOf('}', next + 2); cur = close + 1; String templates = pathUri.substring(next + 1, close); CompositeOutput compositeOutput = getCompositeOutput(templates); ListIterator templateIterator = Splitter.on(',').splitToList(templates).listIterator(); boolean isFirstParameter = true; while (templateIterator.hasNext()) { String template = templateIterator.next(); boolean containsExplodeModifier = template.endsWith("*"); int varNameStartIndex = templateIterator.nextIndex() == 1 ? compositeOutput.getVarNameStartIndex() : 0; int varNameEndIndex = template.length(); if (containsExplodeModifier) { // The expression contains an explode modifier '*' at the end, update end index. varNameEndIndex = varNameEndIndex - 1; } // Now get varName devoid of any prefixes and explode modifiers. String varName = template.substring(varNameStartIndex, varNameEndIndex); Object value = variableMap.remove(varName); if (value == null) { // The value for this variable is undefined. continue with the next template. continue; } if (!isFirstParameter) { pathBuf.append(compositeOutput.getExplodeJoiner()); } else { pathBuf.append(compositeOutput.getOutputPrefix()); isFirstParameter = false; } if (value instanceof Iterator) { // Get the list property value. Iterator iterator = (Iterator) value; value = getListPropertyValue(varName, iterator, containsExplodeModifier, compositeOutput); } else if (value instanceof Iterable || value.getClass().isArray()) { // Get the list property value. Iterator iterator = Types.iterableOf(value).iterator(); value = getListPropertyValue(varName, iterator, containsExplodeModifier, compositeOutput); } else if (value.getClass().isEnum()) { String name = FieldInfo.of((Enum) value).getName(); value = getSimpleValue(varName, name != null ? name : value.toString(), compositeOutput); } else if (!Data.isValueOfPrimitiveType(value)) { // Parse the value as a key/value map. Map map = getMap(value); value = getMapPropertyValue(varName, map, containsExplodeModifier, compositeOutput); } else { // For everything else... value = getSimpleValue(varName, value.toString(), compositeOutput); } pathBuf.append(value); } } if (addUnusedParamsAsQueryParams) { // Add the parameters remaining in the variableMap as query parameters. GenericUrl.addQueryParams(variableMap.entrySet(), pathBuf, false); } return pathBuf.toString(); } private static String getSimpleValue(String name, String value, CompositeOutput compositeOutput) { if (compositeOutput.requiresVarAssignment()) { return String.format("%s=%s", name, compositeOutput.getEncodedValue(value)); } return compositeOutput.getEncodedValue(value); } /** * Expand the template of a composite list property. Eg: If d := ["red", "green", "blue"] then * {/d*} is expanded to "/red/green/blue" * * @param varName the name of the variable the value corresponds to. E.g. "d" * @param iterator the iterator over list values. E.g. ["red", "green", "blue"] * @param containsExplodeModifiersSet to true if the template contains the explode modifier "*" * @param compositeOutput an instance of CompositeOutput. Contains information on how the * expansion should be done * @return the expanded list template * @throws IllegalArgumentException if the required list path parameter is empty */ private static String getListPropertyValue( String varName, Iterator iterator, boolean containsExplodeModifier, CompositeOutput compositeOutput) { if (!iterator.hasNext()) { return ""; } StringBuilder retBuf = new StringBuilder(); String joiner; if (containsExplodeModifier) { joiner = compositeOutput.getExplodeJoiner(); } else { joiner = COMPOSITE_NON_EXPLODE_JOINER; if (compositeOutput.requiresVarAssignment()) { retBuf.append(CharEscapers.escapeUriPath(varName)); retBuf.append("="); } } while (iterator.hasNext()) { if (containsExplodeModifier && compositeOutput.requiresVarAssignment()) { retBuf.append(CharEscapers.escapeUriPath(varName)); retBuf.append("="); } retBuf.append(compositeOutput.getEncodedValue(iterator.next().toString())); if (iterator.hasNext()) { retBuf.append(joiner); } } return retBuf.toString(); } /** * Expand the template of a composite map property. Eg: If d := [("semi", ";"),("dot", * "."),("comma", ",")] then {/d*} is expanded to "/semi=%3B/dot=./comma=%2C" * * @param varName the name of the variable the value corresponds to. Eg: "d" * @param map the map property value. Eg: [("semi", ";"),("dot", "."),("comma", ",")] * @param containsExplodeModifier Set to true if the template contains the explode modifier "*" * @param compositeOutput contains information on how the expansion should be done * @return the expanded map template * @throws IllegalArgumentException if the required list path parameter is map */ private static String getMapPropertyValue( String varName, Map map, boolean containsExplodeModifier, CompositeOutput compositeOutput) { if (map.isEmpty()) { return ""; } StringBuilder retBuf = new StringBuilder(); String joiner; String mapElementsJoiner; if (containsExplodeModifier) { joiner = compositeOutput.getExplodeJoiner(); mapElementsJoiner = "="; } else { joiner = COMPOSITE_NON_EXPLODE_JOINER; mapElementsJoiner = COMPOSITE_NON_EXPLODE_JOINER; if (compositeOutput.requiresVarAssignment()) { retBuf.append(CharEscapers.escapeUriPath(varName)); retBuf.append("="); } } for (Iterator> mapIterator = map.entrySet().iterator(); mapIterator.hasNext(); ) { Map.Entry entry = mapIterator.next(); String encodedKey = compositeOutput.getEncodedValue(entry.getKey()); String encodedValue = compositeOutput.getEncodedValue(entry.getValue().toString()); retBuf.append(encodedKey); retBuf.append(mapElementsJoiner); retBuf.append(encodedValue); if (mapIterator.hasNext()) { retBuf.append(joiner); } } return retBuf.toString(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy