org.apache.pivot.web.Query Maven / Gradle / Ivy
/*
* 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.pivot.web;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLEncoder;
import java.util.concurrent.ExecutorService;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import org.apache.pivot.io.IOTask;
import org.apache.pivot.json.JSONSerializer;
import org.apache.pivot.serialization.SerializationException;
import org.apache.pivot.serialization.Serializer;
import org.apache.pivot.util.ListenerList;
/**
* Abstract base class for web queries. A web query is an asynchronous operation
* that executes one of the following HTTP methods:
*
*
* - GET
* - POST
* - PUT
* - DELETE
*
*
* @param
* The type of the value retrieved or sent via the query. For GET operations,
* it is {@link Object}; for POST operations, the type is {@link URL}. For PUT
* and DELETE, it is {@link Void}.
*/
public abstract class Query extends IOTask {
/**
* Supported HTTP methods.
*/
public enum Method {
GET,
POST,
PUT,
DELETE;
}
/**
* Query status codes.
*/
public static class Status {
public static final int OK = 200;
public static final int CREATED = 201;
public static final int NO_CONTENT = 204;
public static final int BAD_REQUEST = 400;
public static final int UNAUTHORIZED = 401;
public static final int FORBIDDEN = 403;
public static final int NOT_FOUND = 404;
public static final int METHOD_NOT_ALLOWED = 405;
public static final int REQUEST_TIMEOUT = 408;
public static final int CONFLICT = 409;
public static final int LENGTH_REQUIRED = 411;
public static final int PRECONDITION_FAILED = 412;
public static final int REQUEST_ENTITY_TOO_LARGE = 413;
public static final int REQUEST_URI_TOO_LONG = 414;
public static final int UNSUPPORTED_MEDIA_TYPE = 415;
public static final int INTERNAL_SERVER_ERROR = 500;
public static final int NOT_IMPLEMENTED = 501;
public static final int SERVICE_UNAVAILABLE = 503;
public static final int HTTP_VERSION_NOT_SUPPORTED = 505;
}
/**
* Query listener list.
*/
private static class QueryListenerList extends ListenerList>
implements QueryListener {
@Override
public synchronized void add(QueryListener listener) {
super.add(listener);
}
@Override
public synchronized void remove(QueryListener listener) {
super.remove(listener);
}
@Override
public synchronized void connected(Query query) {
for (QueryListener listener : this) {
listener.connected(query);
}
}
@Override
public synchronized void requestSent(Query query) {
for (QueryListener listener : this) {
listener.requestSent(query);
}
}
@Override
public synchronized void responseReceived(Query query) {
for (QueryListener listener : this) {
listener.responseReceived(query);
}
}
@Override
public synchronized void failed(Query query) {
for (QueryListener listener : this) {
listener.failed(query);
}
}
}
private URL locationContext = null;
private HostnameVerifier hostnameVerifier = null;
private Proxy proxy = null;
private QueryDictionary parameters = new QueryDictionary(true);
private QueryDictionary requestHeaders = new QueryDictionary(false);
private QueryDictionary responseHeaders = new QueryDictionary(false);
private int status = 0;
private volatile long bytesExpected = -1;
private Serializer> serializer = new JSONSerializer();
private QueryListenerList queryListeners = new QueryListenerList();
public static final int DEFAULT_PORT = -1;
private static final String HTTP_PROTOCOL = "http";
private static final String HTTPS_PROTOCOL = "https";
private static final String URL_ENCODING = "UTF-8";
static {
try {
// See http://java.sun.com/javase/6/docs/technotes/guides/net/proxies.html
// For more info on this system property
System.setProperty("java.net.useSystemProxies", "true");
} catch (SecurityException exception) {
// No-op
}
}
/**
* Creates a new web query.
*
* @param hostname
* @param port
* @param path
* @param secure
*/
public Query(String hostname, int port, String path, boolean secure,
ExecutorService executorService) {
super(executorService);
try {
locationContext = new URL(secure ? HTTPS_PROTOCOL : HTTP_PROTOCOL,
hostname, port, path);
} catch (MalformedURLException exception) {
throw new IllegalArgumentException("Unable to construct context URL.", exception);
}
}
public abstract Method getMethod();
public String getHostname() {
return locationContext.getHost();
}
public String getPath() {
return locationContext.getFile();
}
public int getPort() {
return locationContext.getPort();
}
public boolean isSecure() {
String protocol = locationContext.getProtocol();
return protocol.equalsIgnoreCase(HTTPS_PROTOCOL);
}
public HostnameVerifier getHostnameVerifier() {
return hostnameVerifier;
}
public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
this.hostnameVerifier = hostnameVerifier;
}
/**
* Gets the proxy associated with this query.
*
* @return
* This query's proxy, or null if the query is using the default
* JVM proxy settings
*/
public Proxy getProxy() {
return proxy;
}
/**
* Sets the proxy associated with this query.
*
* @param proxy
* This query's proxy, or null to use the default JVM proxy
* settings
*/
public void setProxy(Proxy proxy) {
this.proxy = proxy;
}
public URL getLocation() {
StringBuilder queryStringBuilder = new StringBuilder();
for (String key : parameters) {
for (int index = 0; index < parameters.getLength(key); index++) {
try {
if (queryStringBuilder.length() > 0) {
queryStringBuilder.append("&");
}
queryStringBuilder.append(URLEncoder.encode(key, URL_ENCODING)
+ "=" + URLEncoder.encode(parameters.get(key, index),
URL_ENCODING));
} catch (UnsupportedEncodingException exception) {
throw new IllegalStateException("Unable to construct query string.",
exception);
}
}
}
URL location = null;
try {
String queryString = queryStringBuilder.length() > 0 ? "?"
+ queryStringBuilder.toString() : "";
location = new URL(locationContext.getProtocol(), locationContext.getHost(),
locationContext.getPort(), locationContext.getPath() + queryString);
} catch (MalformedURLException exception) {
throw new IllegalStateException("Unable to construct query URL.", exception);
}
return location;
}
/**
* Returns the web query's parameter dictionary. Parameters are passed via
* the query string of the web query's URL.
*/
public QueryDictionary getParameters() {
return parameters;
}
/**
* Returns the web query's request header dictionary. Request headers
* are passed via HTTP headers when the query is executed.
*/
public QueryDictionary getRequestHeaders() {
return requestHeaders;
}
/**
* Returns the web query's response header dictionary. Response headers
* are returned via HTTP headers when the query is executed.
*/
public QueryDictionary getResponseHeaders() {
return responseHeaders;
}
/**
* Returns the status of the most recent execution.
*
* @return An HTTP code representing the most recent execution status.
*/
public int getStatus() {
return status;
}
/**
* Returns the serializer used to stream the value passed to or from the web
* query. By default, an instance of {@link JSONSerializer} is used.
*/
public Serializer> getSerializer() {
return serializer;
}
/**
* Sets the serializer used to stream the value passed to or from the web
* query.
*
* @param serializer
* The serializer (must be non-null).
*/
public void setSerializer(Serializer> serializer) {
if (serializer == null) {
throw new IllegalArgumentException("serializer is null.");
}
this.serializer = serializer;
}
/**
* Gets the number of bytes that have been sent in the body of this query's
* HTTP request. This will only be non-zero for POST and PUT requests, as
* GET and DELETE requests send no content to the server.
*
* For POST and PUT requests, this number will increment in between the
* {@link QueryListener#connected(Query) connected} and
* {@link QueryListener#requestSent(Query) requestSent} phases of the
* QueryListener lifecycle methods. Interested listeners can poll
* for this value during that phase.
*/
public long getBytesSent() {
return bytesSent;
}
/**
* Gets the number of bytes that have been received from the server in the
* body of the server's HTTP response. This will generally only be non-zero
* for GET requests, as POST, PUT, and DELETE requests generally don't
* solicit response content from the server.
*
* This number will increment in between the
* {@link QueryListener#requestSent(Query) requestSent} and
* {@link QueryListener#responseReceived(Query) responseReceived} phases of
* the QueryListener lifecycle methods. Interested listeners can
* poll for this value during that phase.
*/
public long getBytesReceived() {
return bytesReceived;
}
/**
* Gets the number of bytes that are expected to be received from the server
* in the body of the server's HTTP response. This value reflects the
* Content-Length HTTP response header and is thus merely an
* expectation. The actual total number of bytes that will be received is
* not known for certain until the full response has been received.
*
* If the server did not specify a Content-Length HTTP response
* header, a value of -1 will be returned to indicate that this
* value is unknown.
*/
public long getBytesExpected() {
return bytesExpected;
}
@SuppressWarnings("unchecked")
protected Object execute(Method method, Object value) throws QueryException {
URL location = getLocation();
HttpURLConnection connection = null;
Serializer