com.linkedin.restli.internal.common.URIParamUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of restli-common Show documentation
Show all versions of restli-common Show documentation
Pegasus is a framework for building robust, scalable service architectures using dynamic discovery and simple asychronous type-checked REST + JSON APIs.
/*
Copyright (c) 2014 LinkedIn Corp.
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.linkedin.restli.internal.common;
import com.linkedin.data.DataComplex;
import com.linkedin.data.DataList;
import com.linkedin.data.DataMap;
import com.linkedin.data.template.DataTemplateUtil;
import com.linkedin.jersey.api.uri.UriBuilder;
import com.linkedin.jersey.api.uri.UriComponent;
import com.linkedin.jersey.api.uri.UriTemplate;
import com.linkedin.restli.common.ComplexResourceKey;
import com.linkedin.restli.common.CompoundKey;
import com.linkedin.restli.common.ProtocolVersion;
import com.linkedin.restli.common.RestConstants;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
/**
* A utility class for creating URI parameters in the rest.li 2.0 URI style.
*
* @see {@link URIElementParser} for parsing 2.0 URI
*
* @author Moira Tagle
* @version $Revision: $
*/
public class URIParamUtils
{
private static final String[] _EMPTY_STRING_ARRAY = new String[0];
private static Map dataMapToQueryParams(DataMap dataMap)
{
final Map result = encodeDataMapParameters(dataMap);
// Serialize the projection MaskTree values
for (final String parameterName : RestConstants.PROJECTION_PARAMETERS)
{
if (dataMap.containsKey(parameterName))
{
result.put(parameterName, URIMaskUtil.encodeMaskForURI(dataMap.getDataMap(parameterName)));
}
}
return result;
}
/**
* Encode the given {@link DataMap} as a map from query param to value
*
* @param dataMap the {@link com.linkedin.data.DataMap} to be encoded
* @return a {@link Map} from query param key to value
*/
private static Map encodeDataMapParameters(DataMap dataMap)
{
Map flattenedMap = new HashMap();
for (Map.Entry entry : dataMap.entrySet())
{
String flattenedValue = encodeElement(entry.getValue(),
URLEscaper.Escaping.URL_ESCAPING,
UriComponent.Type.QUERY_PARAM);
String encodedKey = encodeString(entry.getKey(), URLEscaper.Escaping.URL_ESCAPING, UriComponent.Type.QUERY_PARAM);
flattenedMap.put(encodedKey, flattenedValue);
}
return flattenedMap;
}
/* package private */ static String encodeElement(Object obj, URLEscaper.Escaping escaping, UriComponent.Type componentType)
{
StringBuilder builder = new StringBuilder();
encodeDataObject(obj, escaping, componentType, builder);
return builder.toString();
}
/**
* Serialize the given key for use in a uri
*
* @param key the key
* @param componentType the uri component type
* @param version the {@link ProtocolVersion}
* @return the serialized key
*/
public static String encodeKeyForUri(Object key, UriComponent.Type componentType, ProtocolVersion version)
{
return keyToString(key, URLEscaper.Escaping.URL_ESCAPING, componentType, true, version);
}
public static Map encodePathKeysForUri(Map pathKeys, ProtocolVersion version)
{
final Map escapedKeys = new HashMap();
for (Map.Entry entry : pathKeys.entrySet())
{
final String value = URIParamUtils.encodeKeyForUri(entry.getValue(), UriComponent.Type.PATH_SEGMENT, version);
if (value == null)
{
throw new IllegalArgumentException("Missing value for path key " + entry.getKey());
}
escapedKeys.put(entry.getKey(), value);
}
return escapedKeys;
}
/**
* Serialize the given key for use in an header. Params are not included.
*
*
* @param key the key
* @param version the {@link com.linkedin.restli.common.ProtocolVersion}
* @return the serialized key
*/
public static String encodeKeyForHeader(Object key, ProtocolVersion version)
{
return encodeKeyForBody(key, false, version);
}
/**
* Serialize the given key for use in a body, such as in a batch response.
*
*
* @param key the key
* @param full encode the full key, including params
* @param version the {@link com.linkedin.restli.common.ProtocolVersion}
* @return the serialized key
*/
public static String encodeKeyForBody(Object key, boolean full, ProtocolVersion version)
{
if (key instanceof ComplexResourceKey && !full && version.compareTo(AllProtocolVersions.RESTLI_PROTOCOL_1_0_0.getProtocolVersion()) <= 0)
{
/**
* in v1, ComplexResourceKeys that are sent over the wire as a response are all URI encoded
* and do not contain params. They are all URI encoded because v1 ComplexResourceKeys can only
* be properly parsed if they are URI encoded.
*
* ComplexResourceKeys in request bodies are full, and are not URI encoded because the key
* decoding is done from the URI itself.
*/
return keyToString(key, URLEscaper.Escaping.URL_ESCAPING, null, full, version);
}
else
{
return keyToString(key, URLEscaper.Escaping.NO_ESCAPING, null, full, version);
}
}
/**
* Universal function for serializing Keys to Strings.
* @see {@link #encodeKeyForUri(Object, com.linkedin.jersey.api.uri.UriComponent.Type, com.linkedin.restli.common.ProtocolVersion)},
* {@link #encodeKeyForBody(Object, boolean, com.linkedin.restli.common.ProtocolVersion)}
*
* @param key the key
* @param escaping determines if the resulting string should be URI escaped or not.
* @param componentType if this key is to be encoded for a URI, the URI component Type of the final result.
* this can be null if you are not encoding for a URI.
* @param full if false, ComplexResourceKey inputs will not have their parameters represented in
* the final string result. Except in the case of response bodies, it should normally
* be true.
* @param version the protocol version.
* @return a stringified version of the key, suitable for insertion into a URI or json body.
*/
public static String keyToString(Object key,
URLEscaper.Escaping escaping,
UriComponent.Type componentType,
boolean full,
ProtocolVersion version)
{
if (version.compareTo(AllProtocolVersions.RESTLI_PROTOCOL_2_0_0.getProtocolVersion()) >= 0)
{
return keyToStringV2(key, escaping, componentType, full);
}
else
{
return keyToStringV1(key, escaping, full);
}
}
private static String keyToStringV1(Object key, URLEscaper.Escaping escaping, boolean full)
{
String result;
if (key == null)
{
result = null;
}
else if (key instanceof ComplexResourceKey)
{
ComplexResourceKey, ?> complexKey = (ComplexResourceKey, ?>) key;
if (full)
{
result = QueryParamsDataMap.dataMapToQueryString(complexKey.toDataMap(), escaping);
}
else
{
result = QueryParamsDataMap.dataMapToQueryString(complexKey.getKey().data(), escaping);
}
}
else if (key instanceof CompoundKey)
{
result = compoundKeyToStringV1((CompoundKey)key);
}
else
{
result = URLEscaper.escape(DataTemplateUtil.stringify(key), escaping);
}
return result;
}
private static String compoundKeyToStringV1(CompoundKey key)
{
List keyList = new ArrayList(key.getPartKeys());
Collections.sort(keyList);
StringBuilder b = new StringBuilder();
boolean delimit=false;
for (String keyPart : keyList)
{
if (delimit)
{
b.append(RestConstants.SIMPLE_KEY_DELIMITER);
}
try
{
b.append(URLEncoder.encode(keyPart, RestConstants.DEFAULT_CHARSET_NAME));
b.append(RestConstants.KEY_VALUE_DELIMITER);
b.append(URLEncoder.encode(DataTemplateUtil.stringify(key.getPart(keyPart)), RestConstants.DEFAULT_CHARSET_NAME));
}
catch (UnsupportedEncodingException e)
{
throw new RuntimeException("UnsupportedEncodingException while trying to encode the key", e);
}
delimit = true;
}
return b.toString();
}
private static String keyToStringV2(Object key,
URLEscaper.Escaping escaping,
UriComponent.Type componentType,
boolean full)
{
if (key == null)
{
return null;
}
if (key instanceof ComplexResourceKey)
{
Object convertedKey;
ComplexResourceKey, ?> complexResourceKey = (ComplexResourceKey, ?>) key;
if (full)
{
convertedKey = complexResourceKey.toDataMap();
}
else
{
convertedKey = complexResourceKey.getKey().data();
}
return URIParamUtils.encodeElement(convertedKey, escaping, componentType);
}
else if (key instanceof CompoundKey)
{
return URIParamUtils.encodeElement(URIParamUtils.compoundKeyToDataMap((CompoundKey) key), escaping, componentType);
}
else
{
return simpleKeyToStringV2(key, escaping, componentType);
}
}
private static String simpleKeyToStringV2(Object key,
URLEscaper.Escaping escaping,
UriComponent.Type componentType)
{
if (escaping == URLEscaper.Escaping.URL_ESCAPING)
{
return URIParamUtils.encodeElement(key, escaping, componentType);
}
else
{
return DataTemplateUtil.stringify(key);
}
}
private static void encodeDataObject(Object obj, URLEscaper.Escaping escaping, UriComponent.Type componentType, StringBuilder stringBuilder)
{
if (obj instanceof DataComplex)
{
if (obj instanceof DataMap)
{
DataMap dataMap = (DataMap) obj;
stringBuilder.append(URIConstants.OBJ_START);
if (!dataMap.isEmpty())
{
List keys = new ArrayList(dataMap.keySet());
Collections.sort(keys);
ListIterator iterator = keys.listIterator();
String currentKey = iterator.next();
mapEncodingHelper(currentKey, dataMap.get(currentKey), escaping, componentType, stringBuilder);
while (iterator.hasNext())
{
stringBuilder.append(URIConstants.ITEM_SEP);
currentKey = iterator.next();
mapEncodingHelper(currentKey, dataMap.get(currentKey), escaping, componentType, stringBuilder);
}
}
stringBuilder.append(URIConstants.OBJ_END);
}
else if (obj instanceof DataList)
{
DataList dataList = (DataList) obj;
stringBuilder.append(URIConstants.LIST_PREFIX);
stringBuilder.append(URIConstants.OBJ_START);
if (!dataList.isEmpty())
{
ListIterator
© 2015 - 2025 Weber Informatics LLC | Privacy Policy