/*
* Copyright 2015 Nicolas Morel
*
* 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.github.nmorel.gwtjackson.rest.api;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.github.nmorel.gwtjackson.client.ObjectReader;
import com.github.nmorel.gwtjackson.client.ObjectWriter;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestBuilder.Method;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.URL;
/**
* @author Nicolas Morel
*/
public class RestRequestBuilder<B, R> {
private static String defaultApplicationPath = "";
public static void setDefaultApplicationPath( String defaultApplicationPath ) {
if ( null == defaultApplicationPath ) {
throw new IllegalArgumentException( "Application path cannot be null" );
}
RestRequestBuilder.defaultApplicationPath = defaultApplicationPath;
}
/**
* HTTP method to use when opening a JavaScript XmlHttpRequest object.
*/
private Method method;
/**
* Application path to concatenate before the url
*/
private String applicationPath = defaultApplicationPath;
/**
* URL to use when opening a JavaScript XmlHttpRequest object.
*/
private String url;
/**
* User to use when opening a JavaScript XmlHttpRequest object.
*/
private String user;
/**
* Password to use when opening a JavaScript XmlHttpRequest object.
*/
private String password;
/**
* Whether to include credentials for a Cross Origin Request.
*/
private Boolean includeCredentials;
/**
* Timeout in milliseconds before the request timeouts and fails.
*/
private Integer timeoutMillis;
/**
* Map of header name to value that will be added to the JavaScript
* XmlHttpRequest object before sending a request.
*/
private Map<String, String> headers;
private Map<String, List<Object>> queryParams;
private Map<String, Object> pathParams;
private B body;
private ObjectWriter<B> bodyConverter;
private ObjectReader<R> responseConverter;
private RestCallback<R> callback;
public RestRequestBuilder() {
}
public RestRequestBuilder<B, R> method( Method method ) {
this.method = method;
return this;
}
public RestRequestBuilder<B, R> applicationPath( String applicationPath ) {
this.applicationPath = applicationPath;
return this;
}
public RestRequestBuilder<B, R> url( String url ) {
this.url = url;
return this;
}
public RestRequestBuilder<B, R> user( String user ) {
this.user = user;
return this;
}
public RestRequestBuilder<B, R> password( String password ) {
this.password = password;
return this;
}
public RestRequestBuilder<B, R> includeCredentials( boolean includeCredentials ) {
this.includeCredentials = includeCredentials;
return this;
}
public RestRequestBuilder<B, R> timeout( int timeoutMillis ) {
this.timeoutMillis = timeoutMillis;
return this;
}
public RestRequestBuilder<B, R> addHeader( String name, String value ) {
if ( null == headers ) {
headers = new LinkedHashMap<String, String>();
}
headers.put( name, value );
return this;
}
public Map<String, String> getHeaders() {
return headers;
}
/**
* Special case where you want to add a query param without value like ?key1&key2=value
*
* @param name Name of the parameter
*
* @return this builder
*/
public RestRequestBuilder<B, R> addQueryParam( String name ) {
List<Object> allValues = getQueryParams( name );
allValues.add( null );
return this;
}
/**
* Add a query parameter. If a null value is passed, the param is ignored.
*
* @param name Name of the parameter
* @param value Value of the parameter
*
* @return this builder
*/
public RestRequestBuilder<B, R> addQueryParam( String name, Object value ) {
if ( null != value ) {
List<Object> allValues = getQueryParams( name );
allValues.add( value );
}
return this;
}
/**
* Add a query parameter. If a null or empty collection is passed, the param is ignored.
*
* @param name Name of the parameter
* @param values Value of the parameter
*
* @return this builder
*/
public RestRequestBuilder<B, R> addQueryParam( String name, Collection<?> values ) {
if ( null != values ) {
List<Object> allValues = getQueryParams( name );
allValues.addAll( values );
}
return this;
}
/**
* Add a query parameter. If a null or empty array is passed, the param is ignored.
*
* @param name Name of the parameter
* @param values Value of the parameter
*
* @return this builder
*/
public RestRequestBuilder<B, R> addQueryParam( String name, Object[] values ) {
if ( null != values ) {
List<Object> allValues = getQueryParams( name );
for ( Object value : values ) {
allValues.add( value );
}
}
return this;
}
/**
* Add a query parameter. If a null or empty collection is passed, the param is ignored.
*
* @param name Name of the parameter
* @param values Value of the parameter
*
* @return this builder
*/
public RestRequestBuilder<B, R> addQueryParam( String name, Iterable<?> values ) {
if ( null != values ) {
List<Object> allValues = getQueryParams( name );
for ( Object value : values ) {
allValues.add( value );
}
}
return this;
}
private List<Object> getQueryParams( String name ) {
if ( null == queryParams ) {
queryParams = new LinkedHashMap<String, List<Object>>();
}
List<Object> allValues = queryParams.get( name );
if ( null == allValues ) {
allValues = new ArrayList<Object>();
queryParams.put( name, allValues );
}
return allValues;
}
public Map<String, List<Object>> getQueryParams() {
return queryParams;
}
public RestRequestBuilder<B, R> addPathParam( String name, Object value ) {
if ( null == pathParams ) {
pathParams = new LinkedHashMap<String, Object>();
}
pathParams.put( name, value );
return this;
}
public Map<String, Object> getPathParams() {
return pathParams;
}
public RestRequestBuilder<B, R> body( B body ) {
this.body = body;
return this;
}
public RestRequestBuilder<B, R> bodyConverter( ObjectWriter<B> bodyConverter ) {
this.bodyConverter = bodyConverter;
return this;
}
public RestRequestBuilder<B, R> responseConverter( ObjectReader<R> responseConverter ) {
this.responseConverter = responseConverter;
return this;
}
public RestRequestBuilder<B, R> callback( RestCallback<R> callback ) {
this.callback = callback;
return this;
}
public Request send() {
if ( null == method ) {
throw new IllegalArgumentException( "The method is required" );
}
if ( null == url ) {
throw new IllegalArgumentException( "The url is required" );
}
String urlWithParams = url;
if ( null != pathParams && !pathParams.isEmpty() ) {
for ( Entry<String, Object> pathParam : pathParams.entrySet() ) {
urlWithParams = urlWithParams.replace( "{" + pathParam.getKey() + "}", pathParam.getValue() == null ? "" : pathParam
.getValue().toString() );
}
}
StringBuilder urlBuilder = new StringBuilder( applicationPath );
if ( !applicationPath.endsWith( "/" ) && !urlWithParams.startsWith( "/" ) ) {
urlBuilder.append( '/' );
}
urlBuilder.append( urlWithParams );
if ( null != queryParams && !queryParams.isEmpty() ) {
boolean first = true;
for ( Entry<String, List<Object>> params : queryParams.entrySet() ) {
String name = URL.encodeQueryString( params.getKey() );
if ( null != params.getValue() && !params.getValue().isEmpty() ) {
for ( Object param : params.getValue() ) {
if ( first ) {
urlBuilder.append( '?' );
first = false;
} else {
urlBuilder.append( '&' );
}
urlBuilder.append( name );
if ( null != param ) {
urlBuilder.append( '=' );
urlBuilder.append( URL.encodeQueryString( param.toString() ) );
}
}
}
}
}
RequestBuilder builder = new RequestBuilder( method, urlBuilder.toString() );
builder.setHeader( "Content-Type", "application/json; charset=utf-8" );
builder.setHeader( "Accept", "application/json" );
if ( null != headers && !headers.isEmpty() ) {
for ( Entry<String, String> header : headers.entrySet() ) {
builder.setHeader( header.getKey(), header.getValue() );
}
}
if ( null != user ) {
builder.setUser( user );
}
if ( null != password ) {
builder.setPassword( password );
}
if ( null != includeCredentials ) {
builder.setIncludeCredentials( includeCredentials );
}
if ( null != timeoutMillis ) {
builder.setTimeoutMillis( timeoutMillis );
}
if ( null != body ) {
if ( null != bodyConverter ) {
builder.setRequestData( bodyConverter.write( body ) );
} else {
builder.setRequestData( body.toString() );
}
}
builder.setCallback( new RestRequestCallback<R>( responseConverter, callback ) );
try {
return builder.send();
} catch ( RequestException e ) {
throw new RestException( e );
}
}
}