/*******************************************************************************
* Copyright (c) 2010 Yadu.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Yadu - initial API and implementation
******************************************************************************/
package code.google.restclient.client;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.entity.FileEntity;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.message.BasicNameValuePair;
import org.apache.log4j.Logger;
import code.google.restclient.common.RCConstants;
import code.google.restclient.common.RCUtil;
import code.google.restclient.core.Hitter;
import code.google.restclient.core.HttpHandler;
import code.google.restclient.exception.RCException;
import code.google.restclient.init.Configurator;
import code.google.restclient.mime.MimeTypeUtil;
/**
* @author Yaduvendra.Singh
*/
public class HitterClient {
private static final Logger LOG = Logger.getLogger(HitterClient.class);
private static final boolean DEBUG_ENABLED = LOG.isDebugEnabled();
private static final SimpleDateFormat SDF = new SimpleDateFormat("MMddyyyyHHmmssSS");
private volatile boolean abort = false;
public boolean isAbort() {
return abort;
}
public void setAbort(boolean abort) {
this.abort = abort;
}
public void hit(ViewRequest req, ViewResponse resp) throws RCException {
Hitter hitter = new Hitter();
HttpHandler handler = new HttpHandler();
// hitter.setProxy("", -1); // set proxy taking values from UI
try {
String url = req.getUrlToHit();
HttpEntity reqBodyEntity = null;
if ( RCUtil.isEntityEnclosingMethod(req.getMethod()) ) { // if request method has body
if ( req.isPostParams() ) reqBodyEntity = getUrlEncodedFormEntity(req);
else if ( req.isMultipart() ) reqBodyEntity = getMultipartEntity(req);
else reqBodyEntity = getStringOrFileEntity(req.getBodyToPost());
handler.setReqBodyEntity(reqBodyEntity);
}
hitter.hit(url, req.getMethod(), handler, req.getInputHeaders());
} catch ( UnknownHostException uhe ) {
LOG.error("hit(): Error: Uknown host", uhe);
throw new RCException("Error: Uknown host");
} catch ( Exception e ) {
LOG.error("hit(): Error occured while hiting url", e);
throw new RCException("Error: " + RCUtil.removeMethodName(e.getMessage()));
} finally {
req = prepareViewRequest(req, handler);
resp = prepareViewResponse(resp, handler);
if ( !handler.isReqAborted() ) handler.closeConnection();
}
}
/* ***************** Entity creator methods ***************** */
private HttpEntity getStringOrFileEntity(String body) throws RCException {
if ( body == null ) body = "";
HttpEntity reqEntity = null;
if ( body.startsWith("@") ) {
String path = Pattern.compile("^@").matcher(body).replaceAll("");
String mimeType = MimeTypeUtil.getMimeType(path);
// TODO not specifying charset as part of mime type i.e. "text/plain" and not "text/plain; charset=UTF-8"
reqEntity = new FileEntity(new File(path), mimeType); // second argument is contentType e.g. "text/plain; charset=\"UTF-8\"");
} else {
try {
reqEntity = new StringEntity(body, RCConstants.DEFAULT_CHARSET);
} catch ( UnsupportedEncodingException e ) {
throw new RCException("getStringOrFileEntity(): Could not prepare string post entity due to unsupported encoding", e);
}
}
return reqEntity;
}
private UrlEncodedFormEntity getUrlEncodedFormEntity(ViewRequest req) throws RCException {
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
Map<String, String> params = req.getParams();
for ( String paramName : params.keySet() ) {
formparams.add(new BasicNameValuePair(paramName, params.get(paramName)));
}
try {
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, RCConstants.DEFAULT_CHARSET);
if ( DEBUG_ENABLED ) LOG.debug("getParamsPostEntity() - post body: " + streamToString(entity.getContent()));
return entity;
} catch ( UnsupportedEncodingException e ) {
throw new RCException("getParamsPostEntity(): Could not prepare params post entity due to unsupported encoding", e);
} catch ( IOException e ) {
throw new RCException("getParamsPostEntity(): request entity body could not be read");
}
}
private MultipartEntity getMultipartEntity(ViewRequest req) throws RCException {
MultipartEntity reqEntity = new MultipartEntity();
Map<String, String> params = req.getParams();
String paramValue = null;
StringBody stringBody = null;
try {
for ( String paramName : params.keySet() ) {
paramValue = params.get(paramName);
stringBody = new StringBody(paramValue, Charset.forName(RCConstants.DEFAULT_CHARSET));
reqEntity.addPart(paramName, stringBody);
}
String fileParamName = req.getFileParamName();
File selectedFile = new File(req.getFilePath());
if ( selectedFile.exists() && !RCUtil.isEmpty(fileParamName) ) {
String mimeType = MimeTypeUtil.getMimeType(req.getFilePath());
FileBody fileBody = new FileBody(selectedFile, mimeType);
reqEntity.addPart(fileParamName, fileBody);
}
} catch ( UnsupportedEncodingException e ) {
throw new RCException("getMultipartEntity(): Could not prepare multipart entity due to unsupported encoding", e);
}
return reqEntity;
}
/**
* Method to get file entity from file object
*/
private FileEntity hit(File body) throws Exception {
FileEntity fileEntity = new FileEntity(body, ""); // second argument is contentType e.g. "text/plain; charset=\"UTF-8\"");
return fileEntity;
}
/**
* Method to make get input stream entity from input stream object
*/
private InputStreamEntity hit(InputStream body) throws Exception {
InputStreamEntity isEntity = new InputStreamEntity(body, -1); // content length is unknown so -1
return isEntity;
}
/* ***************** Methods to prepare view request and response objects ***************** */
private ViewRequest prepareViewRequest(ViewRequest req, HttpHandler handler) {
if ( handler != null && req != null ) {
req.setReqLine(handler.getRequestLine());
// req.setUrl(handler.getUrl());
req.setHeaders(handler.getRequestHeaders());
URI uri = handler.getUri();
if ( uri != null ) {
req.setHost(uri.getHost());
req.setPort(uri.getPort());
req.setPath(uri.getRawPath());
req.setScheme(uri.getScheme());
req.setQueryStrRaw(uri.getRawQuery());
}
req.setProtocolVersion(handler.getProtocolVersion());
}
return req;
}
private ViewResponse prepareViewResponse(ViewResponse resp, HttpHandler handler) throws RCException {
if ( handler != null && resp != null ) {
resp.setStatusLine(handler.getStatusLine());
resp.setUrl(handler.getUrl());
resp.setHeaders(handler.getResponseHeaders());
String contentType = handler.getResponseContentType();
resp.setContentType(contentType);
String respStr = null;
if ( isTextOrXmlResponse(handler) ) {
respStr = streamToString(handler);
resp.setBodyStr(respStr);
} else resp.setBodyFile(streamToFile(handler, contentType));
// writing xml response to file as well as keeping it as body string. This is just for showing
// xml in browser with syntax highlighting until there is syntax highlighter for response pane
if ( !RCUtil.isEmpty(respStr) && isXmlResponse(handler) ) {
resp.setBodyFile(stringToFile(respStr, contentType));
}
}
return resp;
}
/* ***************** Helper methods ***************** */
private String streamToString(HttpHandler handler) throws RCException {
InputStream is = handler.getResponseStream();
if ( is == null ) return null;
StringBuilder sb = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line = null;
try {
while ( (line = reader.readLine()) != null ) {
sb.append("\n");
if ( abort ) {
handler.abort();
abort = false;
break;
}
sb.append(line);
}
} catch ( IOException e ) {
throw new RCException("streamToString(): error occurred while converting response stream to string", e);
} finally {
try {
if ( reader != null && !handler.isReqAborted() ) reader.close();
} catch ( IOException e ) {
throw new RCException("streamToString(): error occurred while closing input stream", e);
}
}
return sb.toString().replaceFirst("\n", "");
}
private String streamToString(InputStream is) throws RCException {
if ( is == null ) return null;
StringBuilder sb = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line = null;
try {
while ( (line = reader.readLine()) != null ) {
sb.append("\n");
sb.append(line);
}
} catch ( IOException e ) {
throw new RCException("streamToString(): error occurred while converting response stream to string", e);
} finally {
try {
if ( reader != null ) reader.close();
} catch ( IOException e ) {
throw new RCException("streamToString(): error occurred while closing input stream", e);
}
}
return sb.toString().replaceFirst("\n", "");
}
private File streamToFile(HttpHandler handler, String contentType) throws RCException {
InputStream is = handler.getResponseStream();
if ( is == null ) return null;
String timeStamp = SDF.format(new Date());
File tmpFile = new File(Configurator.getTempRespFilesDir(), "output_" + timeStamp + "." + RCConstants.TEMP_FILE_EXT);
File outputFile = null;
FileOutputStream fos = null;
byte[] buf = new byte[2 * 1024];
int len;
try {
fos = new FileOutputStream(tmpFile);
while ( (len = is.read(buf)) != -1 ) {
if ( abort ) {
handler.abort();
abort = false;
break;
}
fos.write(buf, 0, len);
}
fos.flush();
} catch ( Exception e ) {
throw new RCException("streamToFile(): error occurred while writing to file", e);
} finally {
try {
if ( fos != null ) fos.close();
outputFile = changeFileExtension(tmpFile, contentType);
} catch ( IOException e ) {
throw new RCException("streamToFile(): error occurred while closing input/output stream", e);
}
}
return outputFile;
}
private File stringToFile(String str, String contentType) throws RCException {
if ( RCUtil.isEmpty(str) ) return null;
String timeStamp = SDF.format(new Date());
File tmpFile = new File(Configurator.getTempRespFilesDir(), "output_" + timeStamp + "." + RCConstants.TEMP_FILE_EXT);
File outputFile = null;
FileWriter fw = null;
try {
fw = new FileWriter(tmpFile);
fw.write(str);
fw.flush();
} catch ( Exception e ) {
throw new RCException("stringToFile(): error occurred while writing to file", e);
} finally {
try {
if ( fw != null ) fw.close();
outputFile = changeFileExtension(tmpFile, contentType);
} catch ( IOException e ) {
throw new RCException("stringToFile(): error occurred while closing file writer", e);
}
}
return outputFile;
}
private File changeFileExtension(File tmpFile, String contentType) throws IOException {
// change extension of file to its media type
String ext = "";
if ( RCUtil.isEmpty(contentType) ) contentType = MimeTypeUtil.getMimeType(tmpFile);
ext = getSubType(contentType);
if ( ext.contains("xml") ) ext = "xml";
/*
if ( contentType != null && contentType.indexOf("/") > 0 ) { // i.e. application/ssml+xml; charset=UTF-8
ext = contentType.substring(contentType.indexOf("/") + 1);
if ( ext.indexOf(";") > 0 ) ext = ext.substring(0, ext.indexOf(";")).trim();
if ( ext.contains("xml") ) ext = "xml";
}
*/
if ( !RCUtil.isEmpty(ext) ) {
String filePath = tmpFile.getCanonicalPath();
String newFileName = filePath.replaceFirst("\\." + RCConstants.TEMP_FILE_EXT + "$", "." + ext);
File outputFile = new File(newFileName);
boolean success = tmpFile.renameTo(outputFile);
if ( success ) return outputFile;
}
return tmpFile;
}
/**
* Returns true if response is of text or xml type or if content type is null
*/
private boolean isTextOrXmlResponse(HttpHandler handler) {
String contentType = handler.getResponseContentType();
if ( contentType != null ) {
if ( contentType.indexOf(";") > 0 ) {
contentType = contentType.substring(0, contentType.indexOf(";")).trim();
}
// check for extra text content types specified in config file
String[] extraTextContTypes = RCConstants.EXTRA_TEXT_CONTENT_TYPES.split(",");
for ( String extraContType : extraTextContTypes ) {
if ( extraContType.equalsIgnoreCase(contentType) ) return true;
}
}
if ( contentType != null && !contentType.startsWith("text") ) return false;
return true;
}
private boolean isXmlResponse(HttpHandler handler) {
String contentType = handler.getResponseContentType();
String subType = getSubType(contentType);
if ( subType != null ) {
if ( contentType.startsWith("text") && subType.contains("xml") ) return true;
// check for extra text content types specified in config file
String[] extraTextContTypes = RCConstants.EXTRA_TEXT_CONTENT_TYPES.split(",");
for ( String extraContType : extraTextContTypes ) {
String xSubType = extraContType.substring(extraContType.indexOf("/") + 1);
if ( xSubType.equalsIgnoreCase(subType) ) return true;
}
}
return false;
}
private String getSubType(String contentType) {
// process content type which is generally in format "type/subtype; charset=XYZ"
// i.e. application/ssml+xml; charset=UTF-8
String subType = null;
if ( contentType != null && contentType.indexOf("/") > 0 ) {
// remove type
subType = contentType.substring(contentType.indexOf("/") + 1);
// remove charset
if ( subType.indexOf(";") > 0 ) subType = subType.substring(0, subType.indexOf(";")).trim();
}
if ( DEBUG_ENABLED ) LOG.debug("getSubType() - returning sub type " + subType);
return subType;
}
/*
public static void main(String[] args) {
HitterClient client = new HitterClient();
ViewRequest req = new ViewRequest();
ViewResponse resp = new ViewResponse();
req.setUrl("http://localhost.mlbam.com:8080/stage/v1.1/14/content/item/read/6845668?test= you # me");
//req.setUrl("http://localhost.mlbam.com:8080/stage/v1.1/14/content/item/viewImage?fileLocation=/images/04022010/6845668/Sunset.jpg");
req.setHeadersStr("fingerprint=offline-fingerprint\nidentitypointid=9364");
req.setMethod("GET");
req.setParamsStr("output=json");
//client.setAbort();
try {
client.hit(req, resp);
} catch (RCException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("************ Request ****************");
System.out.println("Request Headers: \n" + req.getDisplayHeaderPart());
System.out.println("Request Body: \n" + req.getDisplayBodyPart());
System.out.println("************ Response ****************");
System.out.println("ViewResponse Headers:\n" + resp.getDisplayHeaderPart());
System.out.println("ViewResponse Body:\n" + resp.getDisplayBodyPart());
System.out.println("ViewResponse Body File Path:\n" + resp.getBodyFile());
}
*/
}