package org.etk.core.rest.impl;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import javax.ws.rs.Path;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriBuilderException;
import org.etk.core.rest.impl.uri.UriComponent;
import org.etk.core.rest.impl.uri.UriPattern;
public class UriBuilderImpl extends UriBuilder {
/**
* Scheme, e.g. http, https, etc.
*/
private String schema;
/**
* User info, such as user:password.
*/
private String userInfo;
/**
* Host name.
*/
private String host;
/**
* Server port.
*/
private int port = -1;
/**
* Path.
*/
private StringBuffer path = new StringBuffer();
/**
* Query string.
*/
private StringBuffer query = new StringBuffer();
/**
* Fragment.
*/
private String fragment;
/**
* Default constructor.
*/
public UriBuilderImpl() {
}
/**
* {@inheritDoc}
*/
@Override
public URI buildFromMap(Map<String, ? extends Object> values) {
encode();
String uri = UriPattern.createUriWithValues(schema,
userInfo,
host,
port,
path.toString(),
query.toString(),
fragment,
values,
true);
try {
return new URI(uri);
} catch (URISyntaxException e) {
throw new UriBuilderException(e);
}
}
/**
* {@inheritDoc}
*/
@Override
public URI buildFromEncodedMap(Map<String, ? extends Object> values) {
encode();
String uri = UriPattern.createUriWithValues(schema,
userInfo,
host,
port,
path.toString(),
query.toString(),
fragment,
values,
false);
try {
return new URI(uri);
} catch (URISyntaxException e) {
throw new UriBuilderException(e);
}
}
/**
* {@inheritDoc}
*/
@Override
public URI build(Object... values) {
encode();
String uri = UriPattern.createUriWithValues(schema,
userInfo,
host,
port,
path.toString(),
query.toString(),
fragment,
values,
true);
try {
return new URI(uri);
} catch (URISyntaxException e) {
throw new UriBuilderException(e);
}
}
/**
* {@inheritDoc}
*/
@Override
public URI buildFromEncoded(Object... values) {
encode();
String uri = UriPattern.createUriWithValues(schema,
userInfo,
host,
port,
path.toString(),
query.toString(),
fragment,
values,
false);
try {
return new URI(uri);
} catch (URISyntaxException e) {
throw new UriBuilderException(e);
}
}
/**
* Encode URI path, query and fragment components.
*/
private void encode() {
// Should do this even path segment already encoded.
// The reason is matrix parameters that is not encoded yet.
encodePath();
encodeQuery();
encodeFragment();
}
/**
* Encode URI path.
*/
private void encodePath() {
if (path.length() == 0)
return;
String t = path.toString();
path.setLength(0);
path.append(UriComponent.recognizeEncode(t, UriComponent.PATH, true));
}
/**
* Encode query parameters.
*/
private void encodeQuery() {
if (query.length() == 0)
return;
String str = query.toString();
query.setLength(0);
int p = 0;
int n = 0;
while (p < str.length()) {
if (str.charAt(p) == '=') // something like a=x&=y
throw new UriBuilderException("Query parameter length is 0");
n = str.indexOf('&', p);
if (n < 0)
n = str.length();
if (n > p) { // skip empty pair, like a=x&&b=y
String pair = str.substring(p, n);
if (query.length() > 0)
query.append('&');
if (pair.charAt(pair.length() - 1) == '=')
pair = pair.substring(0, pair.length() - 1);
// decode string and keep special character '='
query.append(UriComponent.recognizeEncode(pair, UriComponent.QUERY, true));
}
p = n + 1;
}
}
/**
* Encode URI fragment.
*/
private void encodeFragment() {
if (fragment == null || fragment.length() == 0)
return;
fragment = UriComponent.recognizeEncode(fragment, UriComponent.FRAGMENT, true);
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder clone() {
return new UriBuilderImpl(this);
}
/**
* For #clone() method.
*
* @param cloned current UriBuilder.
*/
private UriBuilderImpl(UriBuilderImpl cloned) {
this.schema = cloned.schema;
this.userInfo = cloned.userInfo;
this.host = cloned.host;
this.port = cloned.port;
this.path = new StringBuffer().append(cloned.path);
this.query = new StringBuffer().append(cloned.query);
this.fragment = cloned.fragment;
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder fragment(String fragment) {
if (fragment == null) {
this.fragment = null;
return this;
}
this.fragment = UriComponent.encode(fragment, UriComponent.FRAGMENT, true);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder host(String host) {
if (host != null)
this.host = UriComponent.recognizeEncode(host, UriComponent.HOST, true);
else
this.host = null;
return this;
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder matrixParam(String name, Object... values) {
if (name == null)
throw new IllegalArgumentException("Name is null");
if (values == null)
throw new IllegalArgumentException("Values are null");
if (path.length() > 0)
path.append(';');
int length = values.length;
for (int i = 0; i < length; i++) {
Object o = values[i];
if (o == null)
throw new IllegalArgumentException("Value is null");
String value = o.toString();
path.append(name).append('=');
if (value.length() > 0)
path.append(value);
// don't add ';' after last matrix parameter
if (i < length - 1)
path.append(';');
}
return this;
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder path(String p) {
if (path == null)
throw new IllegalArgumentException("Path segments are null");
if (p.length() == 0)
return this;
// each segment can contains own '/' DO NOT escape it (UriComponent.PATH)
p = UriComponent.recognizeEncode(p, UriComponent.PATH, true);
boolean finalSlash = path.length() > 0 && path.charAt(path.length() - 1) == '/';
boolean startSlash = p.charAt(0) == '/';
if (finalSlash && startSlash) {
if (p.length() > 1)
path.append(p.substring(1));
} else if (path.length() > 0 && !finalSlash && !startSlash) {
path.append('/').append(p);
} else {
path.append(p);
}
return this;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public UriBuilder path(Class resource) {
if (resource == null)
throw new IllegalArgumentException("Resource is null");
if (resource.getAnnotation(Path.class) == null)
throw new IllegalArgumentException("Resource is not annotated with javax.ws.rs.Path");
Path p = (Path) resource.getAnnotation(Path.class);
return path(p.value());
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder path(Method method) {
if (method == null)
throw new IllegalArgumentException("Methods are null");
Path p = method.getAnnotation(Path.class);
if (p == null)
throw new IllegalArgumentException("Method " + method.getName()
+ " is not annotated with javax.ws.rs.Path");
path(p.value());
return this;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public UriBuilder path(Class resource, String method) {
if (resource == null)
throw new IllegalArgumentException("Resource is null");
if (method == null)
throw new IllegalArgumentException("Method name is null");
path(resource);
boolean found = false;
Method[] methods = resource.getMethods();
for (Method m : methods) {
if (found && m.getName().equals(method)) {
throw new IllegalArgumentException("More then one method with name " + method + " found");
} else if (m.getName().equals(method)) {
path(m);
found = true;
}
}
// no one method found
if (!found)
throw new IllegalArgumentException("Method " + method + " not found at resource class "
+ resource.getName());
return this;
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder port(int port) {
if (port < -1)
throw new IllegalArgumentException();
this.port = port;
return this;
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder queryParam(String name, Object... values) {
if (name == null)
throw new IllegalArgumentException("Name is null");
if (values == null)
throw new IllegalArgumentException("Values are null");
if (query.length() > 0)
query.append('&');
int length = values.length;
// add values
for (int i = 0; i < length; i++) {
Object o = values[i];
if (o == null)
throw new IllegalArgumentException("Value is null");
String s = o.toString();
query.append(name).append('=');
if (s.length() > 0)
query.append(s);
// don't add '&' after last query pair
if (i < length - 1)
query.append('&');
}
return this;
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder replaceMatrixParam(String name, Object... values) {
if (name == null)
throw new IllegalArgumentException("Name is null");
if (path.length() > 0) {
int p = path.lastIndexOf("/");
// slash not found , then start search for ; from begin of string
if (p == -1)
p = 0;
p = path.indexOf(";", p); // <<<<<<< start point for processing
while ((p = path.indexOf(name, p)) > 0) {
int n = path.indexOf(";", p);
if (n == -1)
n = path.length();
p = p > 0 ? p - 1 : 0; // p decrements, because want remove previous ';'
path.replace(p, n, "");
}
}
if (values != null && values.length > 0)
matrixParam(name, values);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder replaceMatrix(String matrix) {
// search ';' which goes after '/' at final path segment
if (path.length() > 0) {
int p = path.lastIndexOf("/");
// slash not found , then start search for ; from begin of string
if (p == -1)
p = 0;
p = path.indexOf(";", p);
path.setLength(p + 1);
}
// if have values add it
if (matrix != null && matrix.length() > 0)
path.append(matrix);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder replacePath(String p) {
path.setLength(0);
path(p);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder replaceQueryParam(String name, Object... values) {
if (name == null)
throw new IllegalArgumentException("Name is null");
// first remove old parameters, they can go not one by one,
// try found all of it
int p = 0;
while ((p = query.indexOf(name, p)) >= 0) {
int n = query.indexOf("&", p);
if (n < 0)
n = query.length();
p = p > 0 ? p - 1 : 0; // want remove previous '&' if it exists
query.replace(p, n, "");
// remove first '&' if presents
if (query.charAt(0) == '&')
query.deleteCharAt(0);
}
// now add new one
if (values != null && values.length > 0)
queryParam(name, values);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder replaceQuery(String queryString) {
query.setLength(0);
if (queryString != null && queryString.length() > 0)
query.append(queryString);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder scheme(String schema) {
if (schema != null)
this.schema = UriComponent.validate(schema, UriComponent.SCHEME, true);
else
this.schema = null;
return this;
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder schemeSpecificPart(String ssp) {
if (ssp == null)
throw new IllegalArgumentException("Scheme specific part (ssp) is null");
StringBuffer sb = new StringBuffer();
if (schema != null)
sb.append(schema).append(':').append(UriComponent.recognizeEncode(ssp, UriComponent.SSP, true));
if (fragment != null && fragment.length() > 0)
sb.append('#').append(fragment);
URI uri;
try {
uri = new URI(sb.toString());
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
userInfo = uri.getRawUserInfo();
host = uri.getHost();
port = uri.getPort();
path.setLength(0);
path.append(uri.getRawPath());
query.setLength(0);
query.append(uri.getRawQuery() != null ? uri.getRawQuery() : "");
return this;
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder segment(String... segments) {
if (segments == null)
throw new IllegalArgumentException("Path segments is null");
for (String p : segments)
path(p);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder uri(URI uri) {
if (uri == null)
throw new IllegalArgumentException("URI is null");
if (uri.getScheme() != null)
schema = uri.getScheme();
if (uri.getRawUserInfo() != null)
userInfo = uri.getRawUserInfo();
if (uri.getHost() != null)
host = uri.getHost();
if (uri.getPort() != -1)
port = uri.getPort();
if (uri.getRawPath() != null && uri.getRawPath().length() > 0) {
path.setLength(0);
path.append(uri.getRawPath());
}
if (uri.getRawQuery() != null && uri.getRawQuery().length() > 0) {
query.setLength(0);
query.append(uri.getRawQuery());
}
if (uri.getRawFragment() != null)
fragment = uri.getRawFragment();
return this;
}
/**
* {@inheritDoc}
*/
@Override
public UriBuilder userInfo(String userInfo) {
if (userInfo != null)
this.userInfo = UriComponent.recognizeEncode(userInfo, UriComponent.USER_INFO, true);
else
this.userInfo = null;
return this;
}
}