/* Copyright (c) 2007 Roland Sch�r
*
* 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 ch.rolandschaer.ascrblr;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.util.logging.Logger;
import ch.rolandschaer.ascrblr.util.AuthenticationException;
import ch.rolandschaer.ascrblr.util.NotSupportedOperationException;
import ch.rolandschaer.ascrblr.util.ResourceNotFoundException;
import ch.rolandschaer.ascrblr.util.ServiceException;
public class HttpRequest {
/** Logger instance. */
public static final Logger logger = Logger.getLogger(HttpRequest.class
.getName());
/** Request type. */
public enum RequestType {
QUERY, HANDSHAKE, SUBMISSION, NOTIFY, TUNEIN, STREAM
}
/** Indicates if request execution has taken place. */
private boolean executed = false;
/** Indicated if request has any input data. */
private boolean hasInput = false;
/** Indicates if response has any output data. */
private boolean hasOutput = false;
/** HTTP connection instance. */
private HttpURLConnection httpConn;
/** Request URL. */
private URL requestUrl;
/** The Request type. */
private RequestType type;
/**
* Connection timeout value. A value of -1 means that no value has been
* configured (JDK default is used)
*/
private int connectTimeout = -1;
/**
* Request timeout value. A value of -1 means that no value has been
* configured (JDK default is used)
*/
private int readTimeout = -1;
/**
* HttpRequest constructor used by the factory.
*
* @param type
* <code>RequestType</code>
* @param requestUrl
* String representation of the request URL
* @throws IOException
* @throws ServiceException
*/
private HttpRequest(RequestType type, URL requestUrl) throws IOException,
ServiceException {
this.type = type;
this.requestUrl = requestUrl;
httpConn = getRequestConnection(requestUrl);
logger.info(type + " request type found");
switch (type) {
case QUERY:
hasOutput = true;
break;
case HANDSHAKE:
hasOutput = true;
break;
case NOTIFY:
hasOutput = true;
setRequestMethod("POST");
break;
case SUBMISSION:
hasOutput = true;
setRequestMethod("POST");
break;
case TUNEIN:
hasOutput = true;
break;
case STREAM:
hasOutput = true;
break;
default:
throw new NotSupportedOperationException("Unknown request type: "
+ type);
}
}
/**
* Gets actual <code>HttpURLConnection</code>. The only supported
* protocol is <code>http://<uri></code>
*
* @param requestUrl
* String representation of the request URL
* @return Active <code>HttpURLConnection</code>
* @throws IOException
*/
public HttpURLConnection getRequestConnection(URL requestUrl)
throws IOException {
if (!requestUrl.getProtocol().startsWith("http")) {
throw new UnsupportedOperationException("Unsupported protocol:"
+ requestUrl.getProtocol());
}
HttpURLConnection uc = (HttpURLConnection) requestUrl.openConnection();
uc.setUseCaches(false);
uc.setInstanceFollowRedirects(true);
return (HttpURLConnection) uc;
}
/**
* Returns the <code>OutputStream</code> of the active
* <code>HttpURLConnection</code>.
*
* @return <code>OutputStream</code> of active
* <code>HttpURLConnection</code>
* @throws IOException
*/
public OutputStream getRequestStream() throws IOException {
if (!hasInput) {
throw new IllegalStateException(
"Request does not have any request data");
}
return httpConn.getOutputStream();
}
/**
* Returns the <code>InputStream</code> of the actual
* <code>HttpURLConnection</code>.
*
* @return <code>HttpURLConnection</code> response as
* <code>InputStream</code>
* @throws IOException
*/
public InputStream getResponseStream() throws IOException {
if (!executed) {
throw new IllegalStateException(
"Must call execute() before attempting to read response");
}
if (!hasOutput) {
throw new IllegalStateException(
"Request dont have any reponse data");
}
return httpConn.getInputStream();
}
/**
* Executes the current HTTP request and handles the response. If a read or
* connection timeout has been defined these values will be set before the
* request. After a successful request a internal flag <code>executed</code>
* will be set to <code>true</code>.
*
* @throws IOException
* @throws ServiceException
*/
public void execute() throws IOException, ServiceException {
if (readTimeout > 0) {
httpConn.setReadTimeout(readTimeout);
}
if (connectTimeout > 0) {
httpConn.setConnectTimeout(connectTimeout);
}
logger.info(httpConn.getRequestMethod() + " "
+ httpConn.getURL().toExternalForm());
httpConn.connect();
handleResponse();
executed = true;
}
/**
* Set request method. Default request method is "GET".
*
* @param method
* HTTP request method string (POST, GET , UPDATE etc.)
* @throws ProtocolException
*/
private void setRequestMethod(String method) throws ProtocolException {
httpConn.setRequestMethod(method);
}
/**
* Adds a request header property to the actual request specified by a
* key-value pair. Already defined properties with the same key will not be
* overridden.
*
* @param key
* Request keyword (e.g. accept)
* @param value
* Associated value
*/
public void setHeader(String key, String value) {
httpConn.addRequestProperty(key, value);
}
/**
* Set a read timeout value in milliseconds.
*
* @param timeout
* Read timeout value in milliseconds
*/
public void setReadTimeout(int timeout) {
if (timeout < 0) {
throw new IllegalArgumentException("Timeout can not be negative.");
}
readTimeout = timeout;
}
/**
* Set a connection timeout value.
*
* @param timeout
* Connection timeout value in milliseconds
*/
public void setConnectTimeout(int timeout) {
if (timeout < 0) {
throw new IllegalArgumentException("Timeout can not be negative.");
}
connectTimeout = timeout;
}
/**
* Handles HTTP response. If the returned response code is larger or equal
* than 300 error handling is done.
*
* @throws IOException
* @throws ServiceException
*/
private void handleResponse() throws IOException, ServiceException {
if (httpConn.getResponseCode() >= 300) {
handleErrorResponse();
}
}
/**
* Handles error responses by the returned HTTP connection response code.
*
* @throws IOException
* @throws ServiceException
*/
private void handleErrorResponse() throws IOException, ServiceException {
logger.info("Handling error response " + httpConn.getResponseCode());
switch (httpConn.getResponseCode()) {
case HttpURLConnection.HTTP_NOT_FOUND:
throw new ResourceNotFoundException(httpConn);
case HttpURLConnection.HTTP_BAD_GATEWAY:
throw new ResourceNotFoundException(httpConn);
case HttpURLConnection.HTTP_FORBIDDEN:
throw new AuthenticationException(httpConn);
default:
throw new ServiceException(httpConn);
}
}
/**
* Request factory which creates new <code>HttpRequest</code> instances.
*/
public static class Factory {
/**
* Get a new <code>HttpRequest</code>.
*
* @param type
* @param requestUrl
* @return
* @throws IOException ?
* @throws ServiceException ?
*/
public final HttpRequest getRequest(RequestType type, URL requestUrl)
throws IOException, ServiceException {
return new HttpRequest(type, requestUrl);
}
}
}