blob: 7071d375cae1a73d8c5b88ead7201d2d5d189db5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.olingo.client.core.uri;
import java.net.URI;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.apache.olingo.client.api.Configuration;
import org.apache.olingo.client.api.uri.QueryOption;
import org.apache.olingo.client.api.uri.SegmentType;
import org.apache.olingo.client.api.uri.URIBuilder;
import org.apache.olingo.client.api.uri.URIFilter;
import org.apache.olingo.client.api.uri.URISearch;
import org.apache.olingo.commons.api.edm.EdmEnumType;
import org.apache.olingo.commons.core.Decoder;
import org.apache.olingo.commons.core.Encoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class URIBuilderImpl implements URIBuilder {
/**
* Constructor.
*
* @param serviceRoot absolute URL (schema, host and port included) representing the location of the root of the data
* service.
*/
public URIBuilderImpl(final Configuration configuration, final String serviceRoot) {
this.configuration = configuration;
segments.add(new Segment(SegmentType.SERVICEROOT, serviceRoot));
}
protected static class Segment {
private final SegmentType type;
private final String value;
public Segment(final SegmentType type, final String value) {
this.type = type;
this.value = value;
}
public SegmentType getType() {
return type;
}
public String getValue() {
return value;
}
}
/**
* Logger.
*/
protected static final Logger LOG = LoggerFactory.getLogger(URIBuilderImpl.class);
private final Configuration configuration;
protected final List<Segment> segments = new ArrayList<Segment>();
/**
* Insertion-order map of query options.
*/
protected final Map<String, String> queryOptions = new LinkedHashMap<String, String>();
/**
* Insertion-order map of custom query options.
*/
protected final Map<String, String> customQueryOptions = new LinkedHashMap<String, String>();
/**
* Insertion-order map of parameter aliases.
*/
protected final Map<String, String> parameters = new LinkedHashMap<String, String>();
@Override
public URIBuilder addQueryOption(final QueryOption option, final String value) {
return addQueryOption(option.toString(), value, false);
}
@Override
public URIBuilder replaceQueryOption(final QueryOption option, final String value) {
return addQueryOption(option.toString(), value, true);
}
@Override
public URIBuilder addQueryOption(final String option, final String value, final boolean replace) {
final StringBuilder builder = new StringBuilder();
if (!replace && queryOptions.containsKey(option)) {
builder.append(queryOptions.get(option)).append(',');
}
builder.append(value);
queryOptions.put(option, builder.toString());
return this;
}
@Override
public URIBuilder addParameterAlias(final String alias, final String exp) {
parameters.put(alias, exp);
return this;
}
@Override
public URIBuilder appendEntitySetSegment(final String segmentValue) {
segments.add(new Segment(SegmentType.ENTITYSET, segmentValue));
return this;
}
@Override
public URIBuilder appendKeySegment(final Object val) {
final String segValue = URIUtils.escape(val);
segments.add(configuration.isKeyAsSegment()
? new Segment(SegmentType.KEY_AS_SEGMENT, segValue)
: new Segment(SegmentType.KEY, "(" + segValue + ")"));
return this;
}
@Override
public URIBuilder appendKeySegment(final Map<String, Object> segmentValues) {
if (!configuration.isKeyAsSegment()) {
final String key = buildMultiKeySegment(segmentValues, true, ',');
if (StringUtils.isEmpty(key)) {
segments.add(new Segment(SegmentType.KEY, noKeysWrapper()));
} else {
segments.add(new Segment(SegmentType.KEY, key));
}
}
return this;
}
@Override
public URIBuilder appendPropertySegment(final String segmentValue) {
segments.add(new Segment(SegmentType.PROPERTY, segmentValue));
return this;
}
@Override
public URIBuilder appendNavigationSegment(final String segmentValue) {
segments.add(new Segment(SegmentType.NAVIGATION, segmentValue));
return this;
}
@Override
public URIBuilder appendDerivedEntityTypeSegment(final String segmentValue) {
segments.add(new Segment(SegmentType.DERIVED_ENTITY_TYPE, segmentValue));
return this;
}
@Override
public URIBuilder appendValueSegment() {
segments.add(new Segment(SegmentType.VALUE, SegmentType.VALUE.getValue()));
return this;
}
@Override
public URIBuilder appendCountSegment() {
segments.add(new Segment(SegmentType.COUNT, SegmentType.COUNT.getValue()));
return this;
}
@Override
public URIBuilder appendActionCallSegment(final String action) {
segments.add(new Segment(
segments.size() == 1 ? SegmentType.UNBOUND_ACTION : SegmentType.BOUND_ACTION, action));
return this;
}
@Override
public URIBuilder appendOperationCallSegment(final String operation) {
segments.add(new Segment(
segments.size() == 1 ? SegmentType.UNBOUND_OPERATION : SegmentType.BOUND_OPERATION, operation));
return this;
}
@Override
public URIBuilder appendMetadataSegment() {
segments.add(new Segment(SegmentType.METADATA, SegmentType.METADATA.getValue()));
return this;
}
@Override
public URIBuilder appendBatchSegment() {
segments.add(new Segment(SegmentType.BATCH, SegmentType.BATCH.getValue()));
return this;
}
@Override
public URIBuilder count() {
segments.add(new Segment(SegmentType.ROOT_QUERY_OPTION, "$" + QueryOption.COUNT.toString()));
return this;
}
@Override
public URIBuilder expand(final String... expandItems) {
return addQueryOption(QueryOption.EXPAND, StringUtils.join(expandItems, ","));
}
@Override
public URIBuilder format(final String format) {
return replaceQueryOption(QueryOption.FORMAT, format);
}
@Override
public URIBuilder filter(final URIFilter filter) {
URIBuilder result;
// decode in order to support @ in parameter aliases
result = filter(Decoder.decode(filter.build()));
return result;
}
@Override
public URIBuilder filter(final String filter) {
return replaceQueryOption(QueryOption.FILTER, filter);
}
@Override
public URIBuilder select(final String... selectItems) {
return addQueryOption(QueryOption.SELECT, StringUtils.join(selectItems, ","));
}
@Override
public URIBuilder orderBy(final String order) {
return replaceQueryOption(QueryOption.ORDERBY, order);
}
@Override
public URIBuilder top(final int top) {
return replaceQueryOption(QueryOption.TOP, String.valueOf(top));
}
@Override
public URIBuilder skip(final int skip) {
return replaceQueryOption(QueryOption.SKIP, String.valueOf(skip));
}
@Override
public URIBuilder skipToken(final String skipToken) {
return replaceQueryOption(QueryOption.SKIPTOKEN, skipToken);
}
@Override
public URI build() {
final StringBuilder segmentsBuilder = new StringBuilder();
for (Segment seg : segments) {
if (segmentsBuilder.length() > 0 && seg.getType() != SegmentType.KEY) {
switch (seg.getType()) {
case BOUND_OPERATION:
segmentsBuilder.append(getBoundOperationSeparator());
break;
case BOUND_ACTION:
segmentsBuilder.append(getBoundOperationSeparator());
break;
default:
if (segmentsBuilder.length() > 0 && segmentsBuilder.charAt(segmentsBuilder.length() - 1) != '/') {
segmentsBuilder.append('/');
}
}
}
if (seg.getType() == SegmentType.ENTITY) {
segmentsBuilder.append(seg.getType().getValue());
} else {
segmentsBuilder.append(seg.getValue());
}
if (seg.getType() == SegmentType.BOUND_OPERATION || seg.getType() == SegmentType.UNBOUND_OPERATION) {
segmentsBuilder.append(getOperationInvokeMarker());
}
}
try {
if ((customQueryOptions.size() + queryOptions.size() + parameters.size()) > 0) {
segmentsBuilder.append("?");
List<NameValuePair> list1 = new LinkedList<NameValuePair>();
for (Map.Entry<String, String> option : queryOptions.entrySet()) {
list1.add(new BasicNameValuePair("$" + option.getKey(), option.getValue()));
}
for (Map.Entry<String, String> parameter : parameters.entrySet()) {
list1.add(new BasicNameValuePair("@" + parameter.getKey(), parameter.getValue()));
}
for (Map.Entry<String, String> customOption : customQueryOptions.entrySet()) {
list1.add(new BasicNameValuePair(customOption.getKey(), customOption.getValue()));
}
// don't use UriBuilder.build():
// it will try to call URLEncodedUtils.format(Iterable<>,Charset) method,
// which works in desktop java application, however, throws NoSuchMethodError in android OS,
// so here manually construct the URL by its overload URLEncodedUtils.format(List<>,String).
final String queryStr = encodeQueryParameter(list1);
segmentsBuilder.append(queryStr);
}
return URI.create(segmentsBuilder.toString());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Could not build valid URI", e);
}
}
private String encodeQueryParameter(List<NameValuePair> list) {
final StringBuilder builder = new StringBuilder();
for (NameValuePair pair : list) {
if (builder.length() > 0) {
builder.append("&");
}
builder.append(Encoder.encode(pair.getName()));
builder.append("=");
builder.append(Encoder.encode(pair.getValue()));
}
return builder.toString();
}
@Override
public String toString() {
return build().toASCIIString();
}
protected String buildMultiKeySegment(final Map<String, Object> segmentValues, final boolean escape,
final char sperator) {
if (segmentValues == null || segmentValues.isEmpty()) {
return StringUtils.EMPTY;
} else {
final StringBuilder keyBuilder = new StringBuilder().append('(');
for (Map.Entry<String, Object> entry : segmentValues.entrySet()) {
keyBuilder.append(entry.getKey()).append('=').append(
escape ? URIUtils.escape(entry.getValue()) : entry.getValue());
keyBuilder.append(sperator);
}
keyBuilder.deleteCharAt(keyBuilder.length() - 1).append(')');
return keyBuilder.toString();
}
}
@Override
public URIBuilder appendKeySegment(final EdmEnumType enumType, final String memberName) {
return appendKeySegment(enumType.toUriLiteral(memberName));
}
@Override
public URIBuilder appendKeySegment(final Map<String, Pair<EdmEnumType, String>> enumValues,
final Map<String, Object> segmentValues) {
final Map<String, Object> values = new LinkedHashMap<String, Object>();
for (Map.Entry<String, Pair<EdmEnumType, String>> entry : enumValues.entrySet()) {
values.put(entry.getKey(), entry.getValue().getKey().toUriLiteral(entry.getValue().getValue()));
}
values.putAll(segmentValues);
return appendKeySegment(values);
}
protected String noKeysWrapper() {
return "()";
}
protected char getBoundOperationSeparator() {
return '/';
}
protected String getOperationInvokeMarker() {
return "()";
}
@Override
public URIBuilder appendSingletonSegment(final String segmentValue) {
segments.add(new Segment(SegmentType.SINGLETON, segmentValue));
return this;
}
@Override
public URIBuilder appendEntityIdSegment(final String segmentValue) {
segments.add(new Segment(SegmentType.ENTITY, null));
return addQueryOption(QueryOption.ID, segmentValue);
}
@Override
public URIBuilder appendRefSegment() {
segments.add(new Segment(SegmentType.REF, SegmentType.REF.getValue()));
return this;
}
@Override
public URIBuilder appendCrossjoinSegment(final String... segmentValues) {
final StringBuilder segValue = new StringBuilder(SegmentType.CROSS_JOIN.getValue()).
append('(').append(StringUtils.join(segmentValues, ",")).append(')');
segments.add(new Segment(SegmentType.CROSS_JOIN, segValue.toString()));
return this;
}
@Override
public URIBuilder appendAllSegment() {
segments.add(new Segment(SegmentType.ALL, SegmentType.ALL.getValue()));
return this;
}
@Override
public URIBuilder id(final String idValue) {
return addQueryOption(QueryOption.ID, idValue);
}
@Override
public URIBuilder search(final URISearch search) {
return search(search.build());
}
@Override
public URIBuilder search(final String expression) {
return addQueryOption(QueryOption.SEARCH, expression);
}
@Override
public URIBuilder count(final boolean value) {
return addQueryOption(QueryOption.COUNT, Boolean.toString(value));
}
@Override
public URIBuilder expandWithOptions(final String expandItem, final Map<QueryOption, Object> options) {
return expandWithOptions(expandItem, false, false, options);
}
@Override
public URIBuilder expandWithOptions(String expandItem, boolean pathRef,
boolean pathCount, Map<QueryOption, Object> options) {
final Map<String, Object> _options = new LinkedHashMap<String, Object>();
for (Map.Entry<QueryOption, Object> entry : options.entrySet()) {
_options.put("$" + entry.getKey().toString(), entry.getValue());
}
String path = pathRef?"/$ref":pathCount?"/$count":StringUtils.EMPTY;
return expand(expandItem + buildMultiKeySegment(_options, false, ';')+path);
}
@Override
public URIBuilder expandWithSelect(final String expandItem, final String... selectItems) {
return expand(expandItem + "($select=" + StringUtils.join(selectItems, ",") + ")");
}
@Override
public URIBuilder addCustomQueryOption(String customName, String customValue) {
customQueryOptions.put(customName, customValue);
return this;
}
}