/*
* Copyright (c) 2005 Aetrion LLC.
*/
package com.flickr4java.flickr;
import com.flickr4java.flickr.auth.Auth;
import com.flickr4java.flickr.util.Base64;
import com.flickr4java.flickr.util.DebugInputStream;
import com.flickr4java.flickr.util.IOUtilities;
import com.flickr4java.flickr.util.UrlUtilities;
import org.apache.log4j.Logger;
import org.scribe.builder.ServiceBuilder;
import org.scribe.builder.api.FlickrApi;
import org.scribe.model.OAuthRequest;
import org.scribe.model.Token;
import org.scribe.model.Verb;
import org.scribe.oauth.OAuthService;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
/**
* Transport implementation using the REST interface.
*
* @author Anthony Eden
* @version $Id: REST.java,v 1.26 2009/07/01 22:07:08 x-mago Exp $
*/
public class REST extends Transport {
private static final Logger logger = Logger.getLogger(REST.class);
public static final String PATH = "/services/rest/";
private static final String CHARSET_NAME = "UTF-8";
private boolean proxyAuth = false;
private String proxyUser = "";
private String proxyPassword = "";
private final DocumentBuilder builder;
private static Object mutex = new Object();
private Integer connectTimeoutMs;
private Integer readTimeoutMs;
/**
* Construct a new REST transport instance.
*/
public REST() {
setTransportType(REST);
setHost(API_HOST);
setPath(PATH);
setScheme(DEFAULT_SCHEME);
setResponseClass(RESTResponse.class);
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
try {
builder = builderFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new FlickrRuntimeException(e);
}
}
/**
* Construct a new REST transport instance using the specified host endpoint.
*
* @param host
* The host endpoint
*/
public REST(String host) {
this();
setHost(host);
}
/**
* Construct a new REST transport instance using the specified host and port endpoint.
*
* @param host
* The host endpoint
* @param port
* The port
*/
public REST(String host, int port) {
this();
setHost(host);
setPort(port);
}
/**
* Set a proxy for REST-requests.
*
* @param proxyHost
* @param proxyPort
*/
public void setProxy(String proxyHost, int proxyPort) {
System.setProperty("http.proxySet", "true");
System.setProperty("http.proxyHost", proxyHost);
System.setProperty("http.proxyPort", "" + proxyPort);
System.setProperty("https.proxyHost", proxyHost);
System.setProperty("https.proxyPort", "" + proxyPort);
}
/**
* Set a proxy with authentication for REST-requests.
*
* @param proxyHost
* @param proxyPort
* @param username
* @param password
*/
public void setProxy(String proxyHost, int proxyPort, String username, String password) {
setProxy(proxyHost, proxyPort);
proxyAuth = true;
proxyUser = username;
proxyPassword = password;
}
/**
* Invoke an HTTP GET request on a remote host. You must close the InputStream after you are done with.
*
* @param path
* The request path
* @param parameters
* The parameters (collection of Parameter objects)
* @return The Response
*/
@Override
public com.flickr4java.flickr.Response get(String path, Map<String, Object> parameters, String apiKey, String sharedSecret) {
OAuthRequest request = new OAuthRequest(Verb.GET, getScheme() + "://" + getHost() + path);
for (Map.Entry<String, Object> entry : parameters.entrySet()) {
request.addQuerystringParameter(entry.getKey(), String.valueOf(entry.getValue()));
}
if (proxyAuth) {
request.addHeader("Proxy-Authorization", "Basic " + getProxyCredentials());
}
RequestContext requestContext = RequestContext.getRequestContext();
Auth auth = requestContext.getAuth();
if (auth != null) {
Token requestToken = new Token(auth.getToken(), auth.getTokenSecret());
OAuthService service = createOAuthService(parameters, apiKey, sharedSecret);
service.signRequest(requestToken, request);
} else {
// For calls that do not require authorization e.g. flickr.people.findByUsername which could be the
// first call if the user did not supply the user-id (i.e. nsid).
if (!parameters.containsKey(Flickr.API_KEY)) {
request.addQuerystringParameter(Flickr.API_KEY, apiKey);
}
}
if (Flickr.debugRequest) {
logger.debug("GET: " + request.getCompleteUrl());
}
setTimeouts(request);
org.scribe.model.Response scribeResponse = request.send();
try {
com.flickr4java.flickr.Response response = null;
synchronized (mutex) {
String strXml = scribeResponse.getBody().trim();
if (Flickr.debugStream) {
logger.debug(strXml);
}
if (strXml.startsWith("oauth_problem=")) {
throw new FlickrRuntimeException(strXml);
}
Document document = builder.parse(new InputSource(new StringReader(strXml)));
response = (com.flickr4java.flickr.Response) responseClass.newInstance();
response.parse(document);
}
return response;
} catch (IllegalAccessException e) {
throw new FlickrRuntimeException(e);
} catch (InstantiationException e) {
throw new FlickrRuntimeException(e);
} catch (SAXException e) {
throw new FlickrRuntimeException(e);
} catch (IOException e) {
throw new FlickrRuntimeException(e);
}
}
/**
* Invoke a non OAuth HTTP GET request on a remote host.
*
* This is only used for the Flickr OAuth methods checkToken and getAccessToken.
*
* @param path
* The request path
* @param parameters
* The parameters
* @return The Response
*/
@Override
public Response getNonOAuth(String path, Map<String, String> parameters) {
InputStream in = null;
try {
URL url = UrlUtilities.buildUrl(getScheme(), getHost(), getPort(), path, parameters);
if (Flickr.debugRequest) {
logger.debug("GET: " + url);
}
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
if (proxyAuth) {
conn.setRequestProperty("Proxy-Authorization", "Basic " + getProxyCredentials());
}
setTimeouts(conn);
conn.connect();
if (Flickr.debugStream) {
in = new DebugInputStream(conn.getInputStream(), System.out);
} else {
in = conn.getInputStream();
}
Response response = null;
synchronized (mutex) {
Document document = builder.parse(in);
response = (Response) responseClass.newInstance();
response.parse(document);
}
return response;
} catch (IllegalAccessException e) {
throw new FlickrRuntimeException(e);
} catch (InstantiationException e) {
throw new FlickrRuntimeException(e);
} catch (IOException e) {
throw new FlickrRuntimeException(e);
} catch (SAXException e) {
throw new FlickrRuntimeException(e);
} finally {
IOUtilities.close(in);
}
}
/**
* Invoke an HTTP POST request on a remote host.
*
* @param path
* The request path
* @param parameters
* The parameters (collection of Parameter objects)
* @return The Response object
*/
@Override
public com.flickr4java.flickr.Response post(String path, Map<String, Object> parameters, String apiKey, String sharedSecret, boolean multipart) {
OAuthRequest request = new OAuthRequest(Verb.POST, getScheme() + "://" + getHost() + path);
if (multipart) {
buildMultipartRequest(parameters, request);
} else {
buildNormalPostRequest(parameters, request);
}
RequestContext requestContext = RequestContext.getRequestContext();
Auth auth = requestContext.getAuth();
if (auth != null) {
Token requestToken = new Token(auth.getToken(), auth.getTokenSecret());
OAuthService service = createOAuthService(parameters, apiKey, sharedSecret);
service.signRequest(requestToken, request);
}
if (multipart) {
// Ensure all parameters (including oauth) are added to payload so signature matches
parameters.putAll(request.getOauthParameters());
request.addPayload(buildMultipartBody(parameters, getMultipartBoundary()));
}
if (proxyAuth) {
request.addHeader("Proxy-Authorization", "Basic " + getProxyCredentials());
}
if (Flickr.debugRequest) {
logger.debug("POST: " + request.getCompleteUrl());
}
org.scribe.model.Response scribeResponse = request.send();
try {
com.flickr4java.flickr.Response response = null;
synchronized (mutex) {
String strXml = scribeResponse.getBody().trim();
if (Flickr.debugStream) {
logger.debug(strXml);
}
if (strXml.startsWith("oauth_problem=")) {
throw new FlickrRuntimeException(strXml);
}
Document document = builder.parse(new InputSource(new StringReader(strXml)));
response = (com.flickr4java.flickr.Response) responseClass.newInstance();
response.parse(document);
}
return response;
} catch (IllegalAccessException e) {
throw new FlickrRuntimeException(e);
} catch (InstantiationException e) {
throw new FlickrRuntimeException(e);
} catch (SAXException e) {
throw new FlickrRuntimeException(e);
} catch (IOException e) {
throw new FlickrRuntimeException(e);
}
}
/**
*
* @param parameters
* @param sharedSecret
* @return
*/
private OAuthService createOAuthService(Map<String, Object> parameters, String apiKey, String sharedSecret) {
ServiceBuilder serviceBuilder = new ServiceBuilder().provider(FlickrApi.class).apiKey(apiKey).apiSecret(sharedSecret);
if (Flickr.debugRequest) {
serviceBuilder = serviceBuilder.debug();
}
return serviceBuilder.build();
}
/**
*
* @param parameters
* @param request
*/
private void buildNormalPostRequest(Map<String, Object> parameters, OAuthRequest request) {
for (Map.Entry<String, Object> entry : parameters.entrySet()) {
request.addBodyParameter(entry.getKey(), String.valueOf(entry.getValue()));
}
}
/**
*
* @param parameters
* @param request
*/
private void buildMultipartRequest(Map<String, Object> parameters, OAuthRequest request) {
request.addHeader("Content-Type", "multipart/form-data; boundary=" + getMultipartBoundary());
for (Map.Entry<String, Object> entry : parameters.entrySet()) {
String key = entry.getKey();
if (!key.equals("photo") && !key.equals("filename") && !key.equals("filemimetype")) {
request.addQuerystringParameter(key, String.valueOf(entry.getValue()));
}
}
}
/**
*
* @return
*/
private String getMultipartBoundary() {
return "---------------------------7d273f7a0d3";
}
public boolean isProxyAuth() {
return proxyAuth;
}
/**
* Generates Base64-encoded credentials from locally stored username and password.
*
* @return credentials
*/
public String getProxyCredentials() {
return new String(Base64.encode((proxyUser + ":" + proxyPassword).getBytes()));
}
private byte[] buildMultipartBody(Map<String, Object> parameters, String boundary) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try {
String filename = (String) parameters.get("filename");
if (filename == null)
filename = "image.jpg";
String fileMimeType = (String) parameters.get("filemimetype");
if (fileMimeType == null)
fileMimeType = "image/jpeg";
buffer.write(("--" + boundary + "\r\n").getBytes(CHARSET_NAME));
for (Entry<String, Object> entry : parameters.entrySet()) {
String key = entry.getKey();
if (!key.equals("filename") && !key.equals("filemimetype"))
writeParam(key, entry.getValue(), buffer, boundary, filename, fileMimeType);
}
} catch (IOException e) {
throw new FlickrRuntimeException(e);
}
if (Flickr.debugRequest) {
String output = new String(buffer.toByteArray());
logger.debug("Multipart body:\n" + output);
}
return buffer.toByteArray();
}
private void writeParam(String name, Object value, ByteArrayOutputStream buffer, String boundary, String filename, String fileMimeType) throws IOException {
if (value instanceof InputStream) {
buffer.write(("Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + filename + "\";\r\n").getBytes(CHARSET_NAME));
buffer.write(("Content-Type: " + fileMimeType + "\r\n\r\n").getBytes(CHARSET_NAME));
InputStream in = (InputStream) value;
byte[] buf = new byte[512];
int res = -1;
while ((res = in.read(buf)) != -1) {
buffer.write(buf, 0, res);
}
buffer.write(("\r\n" + "--" + boundary + "\r\n").getBytes(CHARSET_NAME));
} else if (value instanceof byte[]) {
buffer.write(("Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + filename + "\";\r\n").getBytes(CHARSET_NAME));
buffer.write(("Content-Type: " + fileMimeType + "\r\n\r\n").getBytes(CHARSET_NAME));
buffer.write((byte[]) value);
buffer.write(("\r\n" + "--" + boundary + "\r\n").getBytes(CHARSET_NAME));
} else {
buffer.write(("Content-Disposition: form-data; name=\"" + name + "\"\r\n\r\n").getBytes(CHARSET_NAME));
buffer.write(((String) value).getBytes(CHARSET_NAME));
buffer.write(("\r\n" + "--" + boundary + "\r\n").getBytes(CHARSET_NAME));
}
}
private void setTimeouts(HttpURLConnection conn) {
if (connectTimeoutMs != null) {
conn.setConnectTimeout(connectTimeoutMs);
}
if (readTimeoutMs != null) {
conn.setReadTimeout(readTimeoutMs);
}
}
private void setTimeouts(OAuthRequest request) {
if (connectTimeoutMs != null) {
request.setConnectTimeout(connectTimeoutMs, TimeUnit.MILLISECONDS);
}
if (readTimeoutMs != null) {
request.setReadTimeout(readTimeoutMs, TimeUnit.MILLISECONDS);
}
}
public void setConnectTimeoutMs(Integer connectTimeoutMs) {
this.connectTimeoutMs = connectTimeoutMs;
}
public void setReadTimeoutMs(Integer readTimeoutMs) {
this.readTimeoutMs = readTimeoutMs;
}
}