com.jcabi.http.request.BaseRequest Maven / Gradle / Ivy
/**
* Copyright (c) 2011-2014, jcabi.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met: 1) Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer. 2) Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution. 3) Neither the name of the jcabi.com nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jcabi.http.request;
import com.jcabi.aspects.Immutable;
import com.jcabi.aspects.Loggable;
import com.jcabi.http.ImmutableHeader;
import com.jcabi.http.Request;
import com.jcabi.http.RequestBody;
import com.jcabi.http.RequestURI;
import com.jcabi.http.Response;
import com.jcabi.http.Wire;
import com.jcabi.immutable.Array;
import com.jcabi.log.Logger;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
import javax.json.Json;
import javax.json.JsonStructure;
import javax.validation.constraints.NotNull;
import javax.ws.rs.core.UriBuilder;
import lombok.EqualsAndHashCode;
/**
* Base implementation of {@link Request}.
*
* @author Yegor Bugayenko ([email protected])
* @version $Id$
* @since 0.8
* @checkstyle ClassDataAbstractionCoupling (500 lines)
* @see Request
* @see Response
*/
@Immutable
@EqualsAndHashCode(of = { "home", "mtd", "hdrs", "content" })
@Loggable(Loggable.DEBUG)
@SuppressWarnings("PMD.TooManyMethods")
final class BaseRequest implements Request {
/**
* The encoding to use.
*/
private static final String ENCODING = "UTF-8";
/**
* The Charset to use.
*/
private static final Charset CHARSET = Charset.forName(ENCODING);
/**
* An empty immutable {@code byte} array.
*/
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
/**
* Wire to use.
*/
private final transient Wire wire;
/**
* Request URI.
*/
private final transient String home;
/**
* Method to use.
*/
private final transient String mtd;
/**
* Headers.
*/
private final transient Array> hdrs;
/**
* Body to use.
*/
private final transient byte[] content;
/**
* Public ctor.
* @param wre Wire
* @param uri The resource to work with
*/
BaseRequest(final Wire wre, final String uri) {
this(
wre, uri,
new Array>(),
Request.GET, BaseRequest.EMPTY_BYTE_ARRAY
);
}
/**
* Public ctor.
* @param wre Wire
* @param uri The resource to work with
* @param headers Headers
* @param method HTTP method
* @param body HTTP request body
* @checkstyle ParameterNumber (5 lines)
*/
BaseRequest(final Wire wre, final String uri,
final Iterable> headers,
final String method, final byte[] body) {
this.wire = wre;
URI addr = URI.create(uri);
if (addr.getPath().isEmpty()) {
addr = UriBuilder.fromUri(addr).path("/").build();
}
this.home = addr.toString();
this.hdrs = new Array>(headers);
this.mtd = method;
this.content = body.clone();
}
@Override
@NotNull
public RequestURI uri() {
return new BaseRequest.BaseURI(this, this.home);
}
@Override
public Request header(
@NotNull(message = "header name can't be NULL") final String name,
@NotNull(message = "header value can't be NULL") final Object value) {
return new BaseRequest(
this.wire,
this.home,
this.hdrs.with(new ImmutableHeader(name, value.toString())),
this.mtd,
this.content
);
}
@Override
public Request reset(
@NotNull(message = "header name can't be NULL") final String name) {
final Collection> headers =
new LinkedList>();
final String key = ImmutableHeader.normalize(name);
for (final Map.Entry header : this.hdrs) {
if (!header.getKey().equals(key)) {
headers.add(header);
}
}
return new BaseRequest(
this.wire,
this.home,
headers,
this.mtd,
this.content
);
}
@Override
public RequestBody body() {
return new BaseRequest.BaseBody(this, this.content);
}
@Override
public Request method(
@NotNull(message = "method can't be NULL") final String method) {
return new BaseRequest(
this.wire,
this.home,
this.hdrs,
method,
this.content
);
}
@Override
public Response fetch() throws IOException {
final long start = System.currentTimeMillis();
final Response response = this.wire.send(
this, this.home, this.mtd,
this.hdrs, this.content
);
final URI uri = URI.create(this.home);
Logger.info(
this,
"#fetch(%s %s%s %s): [%d %s] in %[ms]s",
this.mtd,
uri.getHost(),
// @checkstyle AvoidInlineConditionalsCheck (1 line)
uri.getPort() > 0 ? String.format(":%d", uri.getPort()) : "",
uri.getPath(),
response.status(),
response.reason(),
System.currentTimeMillis() - start
);
return response;
}
@Override
public Request through(final Class type,
final Object... args) {
Constructor> ctor = null;
for (final Constructor> opt : type.getDeclaredConstructors()) {
if (opt.getParameterTypes().length == args.length + 1) {
ctor = opt;
break;
}
}
if (ctor == null) {
throw new IllegalArgumentException(
String.format(
"class %s doesn't have a ctor with %d argument(s)",
type.getName(), args.length
)
);
}
final Object[] params = new Object[args.length + 1];
params[0] = this.wire;
System.arraycopy(args, 0, params, 1, args.length);
final Wire decorated;
try {
decorated = Wire.class.cast(ctor.newInstance(params));
} catch (final InstantiationException ex) {
throw new IllegalStateException(ex);
} catch (final IllegalAccessException ex) {
throw new IllegalStateException(ex);
} catch (final InvocationTargetException ex) {
throw new IllegalStateException(ex);
}
return new BaseRequest(
decorated,
this.home,
this.hdrs,
this.mtd,
this.content
);
}
@Override
public String toString() {
final URI uri = URI.create(this.home);
final StringBuilder text = new StringBuilder("HTTP/1.1 ")
.append(this.mtd).append(' ')
.append(uri.getPath())
.append(" (")
.append(uri.getHost())
.append(")\n");
for (final Map.Entry header : this.hdrs) {
text.append(
Logger.format(
"%s: %s\n",
header.getKey(),
header.getValue()
)
);
}
return text.append('\n')
.append(RequestBody.Printable.toString(this.content))
.toString();
}
/**
* Base URI.
*/
@Immutable
@EqualsAndHashCode(of = "address")
@Loggable(Loggable.DEBUG)
private static final class BaseURI implements RequestURI {
/**
* URI encapsulated.
*/
private final transient String address;
/**
* Base request encapsulated.
*/
private final transient BaseRequest owner;
/**
* Public ctor.
* @param req Request
* @param uri The URI to start with
*/
public BaseURI(final BaseRequest req, final String uri) {
this.owner = req;
this.address = uri;
}
@Override
public String toString() {
return this.address;
}
@Override
public Request back() {
return new BaseRequest(
this.owner.wire,
this.address,
this.owner.hdrs,
this.owner.mtd,
this.owner.content
);
}
@Override
public URI get() {
return URI.create(this.owner.home);
}
@Override
public RequestURI set(@NotNull(message = "URI can't be NULL")
final URI uri) {
return new BaseRequest.BaseURI(this.owner, uri.toString());
}
@Override
public RequestURI queryParam(
@NotNull(message = "param name can't be NULL") final String name,
@NotNull(message = "value can't be NULL") final Object value) {
return new BaseRequest.BaseURI(
this.owner,
UriBuilder.fromUri(this.address)
.queryParam(name, "{value}")
.build(value).toString()
);
}
@Override
public RequestURI queryParams(@NotNull(message = "map can't be NULL")
final Map map) {
final UriBuilder uri = UriBuilder.fromUri(this.address);
final Object[] values = new Object[map.size()];
int idx = 0;
for (final Map.Entry pair : map.entrySet()) {
uri.queryParam(pair.getKey(), String.format("{x%d}", idx));
values[idx] = pair.getValue();
++idx;
}
return new BaseRequest.BaseURI(
this.owner,
uri.build(values).toString()
);
}
@Override
public RequestURI path(
@NotNull(message = "path can't be NULL") final String segment) {
return new BaseRequest.BaseURI(
this.owner,
UriBuilder.fromUri(this.address)
.path(segment)
.build().toString()
);
}
@Override
public RequestURI userInfo(
@NotNull(message = "info can't be NULL") final String info) {
return new BaseRequest.BaseURI(
this.owner,
UriBuilder.fromUri(this.address)
.userInfo(info)
.build().toString()
);
}
@Override
public RequestURI port(final int num) {
return new BaseRequest.BaseURI(
this.owner,
UriBuilder.fromUri(this.address)
.port(num).build().toString()
);
}
}
/**
* Base URI.
*/
@Immutable
@EqualsAndHashCode(of = "text")
@Loggable(Loggable.DEBUG)
private static final class BaseBody implements RequestBody {
/**
* Content encapsulated.
*/
private final transient byte[] text;
/**
* Base request encapsulated.
*/
private final transient BaseRequest owner;
/**
* Public ctor.
* @param req Request
* @param body Text to encapsulate
*/
public BaseBody(final BaseRequest req, final byte[] body) {
this.owner = req;
this.text = body.clone();
}
@Override
public String toString() {
return RequestBody.Printable.toString(this.text);
}
@Override
public Request back() {
return new BaseRequest(
this.owner.wire,
this.owner.home,
this.owner.hdrs,
this.owner.mtd,
this.text
);
}
@Override
public String get() {
return new String(this.text, BaseRequest.CHARSET);
}
@Override
public RequestBody set(@NotNull(message = "content can't be NULL")
final String txt) {
return this.set(txt.getBytes(BaseRequest.CHARSET));
}
@Override
public RequestBody set(@NotNull(message = "JSON can't be NULL")
final JsonStructure json) {
final StringWriter writer = new StringWriter();
Json.createWriter(writer).write(json);
return this.set(writer.toString());
}
@Override
public RequestBody set(@NotNull(message = "body can't be NULL")
final byte[] txt) {
return new BaseRequest.BaseBody(this.owner, txt);
}
@Override
public RequestBody formParam(
@NotNull(message = "name can't be NULL") final String name,
@NotNull(message = "value can't be NULL") final Object value) {
try {
return new BaseRequest.BaseBody(
this.owner,
new StringBuilder(this.get())
.append(name)
.append('=')
.append(
URLEncoder.encode(
value.toString(),
BaseRequest.ENCODING
)
)
.append('&')
.toString()
.getBytes(BaseRequest.CHARSET)
);
} catch (final UnsupportedEncodingException ex) {
throw new IllegalStateException(ex);
}
}
@Override
public RequestBody formParams(
@NotNull(message = "map of params can't be NULL")
final Map params) {
RequestBody body = this;
for (final Map.Entry param : params.entrySet()) {
body = body.formParam(param.getKey(), param.getValue());
}
return body;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy