com.microsoft.graph.http.DefaultHttpProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com.liferay.document.library.opener.onedrive.web
Show all versions of com.liferay.document.library.opener.onedrive.web
Liferay Document Library Opener OneDrive Web
// ------------------------------------------------------------------------------
// Copyright (c) 2017 Microsoft Corporation
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ------------------------------------------------------------------------------
package com.microsoft.graph.http;
import com.google.common.annotations.VisibleForTesting;
import com.microsoft.graph.authentication.IAuthenticationProvider;
import com.microsoft.graph.concurrency.ICallback;
import com.microsoft.graph.concurrency.IExecutors;
import com.microsoft.graph.concurrency.IProgressCallback;
import com.microsoft.graph.core.ClientException;
import com.microsoft.graph.core.DefaultConnectionConfig;
import com.microsoft.graph.core.IConnectionConfig;
import com.microsoft.graph.logger.ILogger;
import com.microsoft.graph.logger.LoggerLevel;
import com.microsoft.graph.options.HeaderOption;
import com.microsoft.graph.serializer.ISerializer;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
/**
* HTTP provider based off of URLConnection
*/
public class DefaultHttpProvider implements IHttpProvider {
/**
* The content type header
*/
static final String CONTENT_TYPE_HEADER_NAME = "Content-Type";
/**
* The content type for JSON responses
*/
static final String JSON_CONTENT_TYPE = "application/json";
/**
* The encoding type for getBytes
*/
static final String JSON_ENCODING = "UTF-8";
/**
* The serializer
*/
private final ISerializer serializer;
/**
* The authentication provider
*/
private final IAuthenticationProvider authenticationProvider;
/**
* The executors
*/
private final IExecutors executors;
/**
* The logger
*/
private final ILogger logger;
/**
* The connection factory
*/
private IConnectionFactory connectionFactory;
/**
* The connection config
*/
private IConnectionConfig connectionConfig;
/**
* Creates the DefaultHttpProvider
*
* @param serializer the serializer
* @param authenticationProvider the authentication provider
* @param executors the executors
* @param logger the logger for diagnostic information
*/
public DefaultHttpProvider(final ISerializer serializer,
final IAuthenticationProvider authenticationProvider,
final IExecutors executors,
final ILogger logger) {
this.serializer = serializer;
this.authenticationProvider = authenticationProvider;
this.executors = executors;
this.logger = logger;
connectionFactory = new DefaultConnectionFactory();
}
/**
* Gets the serializer for this HTTP provider
*
* @return the serializer for this provider
*/
@Override
public ISerializer getSerializer() {
return serializer;
}
/**
* Sends the HTTP request asynchronously
*
* @param request the request description
* @param callback the callback to be called after success or failure
* @param resultClass the class of the response from the service
* @param serializable the object to send to the service in the body of the request
* @param the type of the response object
* @param the type of the object to send to the service in the body of the request
*/
@Override
public void send(final IHttpRequest request,
final ICallback callback,
final Class resultClass,
final Body serializable) {
final IProgressCallback progressCallback;
if (callback instanceof IProgressCallback) {
progressCallback = (IProgressCallback) callback;
} else {
progressCallback = null;
}
executors.performOnBackground(new Runnable() {
@Override
public void run() {
try {
executors.performOnForeground(sendRequestInternal(request,
resultClass,
serializable,
progressCallback,
null),
callback);
} catch (final ClientException e) {
executors.performOnForeground(e, callback);
}
}
});
}
/**
* Sends the HTTP request
*
* @param request the request description
* @param resultClass the class of the response from the service
* @param serializable the object to send to the service in the body of the request
* @param the type of the response object
* @param the type of the object to send to the service in the body of the request
* @return the result from the request
* @throws ClientException an exception occurs if the request was unable to complete for any reason
*/
@Override
public Result send(final IHttpRequest request,
final Class resultClass,
final Body serializable)
throws ClientException {
return send(request, resultClass, serializable, null);
}
/**
* Sends the HTTP request
*
* @param request the request description
* @param resultClass the class of the response from the service
* @param serializable the object to send to the service in the body of the request
* @param handler the handler for stateful response
* @param the type of the response object
* @param the type of the object to send to the service in the body of the request
* @param the response handler for stateful response
* @return the result from the request
* @throws ClientException this exception occurs if the request was unable to complete for any reason
*/
public Result send(final IHttpRequest request,
final Class resultClass,
final Body serializable,
final IStatefulResponseHandler handler) throws ClientException {
return sendRequestInternal(request, resultClass, serializable, null, handler);
}
/**
* Sends the HTTP request
*
* @param request the request description
* @param resultClass the class of the response from the service
* @param serializable the object to send to the service in the body of the request
* @param progress the progress callback for the request
* @param handler the handler for stateful response
* @param the type of the response object
* @param the type of the object to send to the service in the body of the request
* @param the response handler for stateful response
* @return the result from the request
* @throws ClientException an exception occurs if the request was unable to complete for any reason
*/
@SuppressWarnings("unchecked")
private Result sendRequestInternal(final IHttpRequest request,
final Class resultClass,
final Body serializable,
final IProgressCallback progress,
final IStatefulResponseHandler handler)
throws ClientException {
final int defaultBufferSize = 4096;
final String binaryContentType = "application/octet-stream";
try {
if (authenticationProvider != null) {
authenticationProvider.authenticateRequest(request);
}
OutputStream out = null;
InputStream in = null;
boolean isBinaryStreamInput = false;
final URL requestUrl = request.getRequestUrl();
logger.logDebug("Starting to send request, URL " + requestUrl.toString());
final IConnection connection = connectionFactory.createFromRequest(request);
if(this.connectionConfig == null) {
this.connectionConfig = new DefaultConnectionConfig();
}
connection.setConnectTimeout(connectionConfig.getConnectTimeout());
connection.setReadTimeout(connectionConfig.getReadTimeout());
try {
logger.logDebug("Request Method " + request.getHttpMethod().toString());
List requestHeaders = request.getHeaders();
final byte[] bytesToWrite;
connection.addRequestHeader("Accept", "*/*");
if (serializable == null) {
// Send an empty body through with a POST request
// This ensures that the Content-Length header is properly set
if (request.getHttpMethod() == HttpMethod.POST) {
bytesToWrite = new byte[0];
}
else {
bytesToWrite = null;
}
} else if (serializable instanceof byte[]) {
logger.logDebug("Sending byte[] as request body");
bytesToWrite = (byte[]) serializable;
// If the user hasn't specified a Content-Type for the request
if (!hasHeader(requestHeaders, CONTENT_TYPE_HEADER_NAME)) {
connection.addRequestHeader(CONTENT_TYPE_HEADER_NAME, binaryContentType);
}
connection.setContentLength(bytesToWrite.length);
} else {
logger.logDebug("Sending " + serializable.getClass().getName() + " as request body");
final String serializeObject = serializer.serializeObject(serializable);
bytesToWrite = serializeObject.getBytes(JSON_ENCODING);
// If the user hasn't specified a Content-Type for the request
if (!hasHeader(requestHeaders, CONTENT_TYPE_HEADER_NAME)) {
connection.addRequestHeader(CONTENT_TYPE_HEADER_NAME, JSON_CONTENT_TYPE);
}
connection.setContentLength(bytesToWrite.length);
}
// Handle cases where we've got a body to process.
if (bytesToWrite != null) {
out = connection.getOutputStream();
int writtenSoFar = 0;
BufferedOutputStream bos = new BufferedOutputStream(out);
int toWrite;
do {
toWrite = Math.min(defaultBufferSize, bytesToWrite.length - writtenSoFar);
bos.write(bytesToWrite, writtenSoFar, toWrite);
writtenSoFar = writtenSoFar + toWrite;
if (progress != null) {
executors.performOnForeground(writtenSoFar, bytesToWrite.length,
progress);
}
} while (toWrite > 0);
bos.close();
}
if (handler != null) {
handler.configConnection(connection);
}
logger.logDebug(String.format("Response code %d, %s",
connection.getResponseCode(),
connection.getResponseMessage()));
if (handler != null) {
logger.logDebug("StatefulResponse is handling the HTTP response.");
return handler.generateResult(
request, connection, this.getSerializer(), this.logger);
}
if (connection.getResponseCode() >= HttpResponseCode.HTTP_CLIENT_ERROR) {
logger.logDebug("Handling error response");
in = connection.getInputStream();
handleErrorResponse(request, serializable, connection);
}
if (connection.getResponseCode() == HttpResponseCode.HTTP_NOBODY
|| connection.getResponseCode() == HttpResponseCode.HTTP_NOT_MODIFIED) {
logger.logDebug("Handling response with no body");
return handleEmptyResponse(connection.getResponseHeaders(), resultClass);
}
if (connection.getResponseCode() == HttpResponseCode.HTTP_ACCEPTED) {
logger.logDebug("Handling accepted response");
return handleEmptyResponse(connection.getResponseHeaders(), resultClass);
}
in = new BufferedInputStream(connection.getInputStream());
final Map headers = connection.getHeaders();
final String contentType = headers.get(CONTENT_TYPE_HEADER_NAME);
if (contentType.contains(JSON_CONTENT_TYPE)) {
logger.logDebug("Response json");
return handleJsonResponse(in, connection.getResponseHeaders(), resultClass);
} else {
logger.logDebug("Response binary");
isBinaryStreamInput = true;
//no inspection unchecked
return (Result) handleBinaryStream(in);
}
} finally {
if (out != null) {
out.close();
}
if (!isBinaryStreamInput && in != null) {
in.close();
connection.close();
}
}
} catch (final GraphServiceException ex) {
final boolean shouldLogVerbosely = logger.getLoggingLevel() == LoggerLevel.DEBUG;
logger.logError("Graph service exception " + ex.getMessage(shouldLogVerbosely), ex);
throw ex;
} catch (final UnsupportedEncodingException ex) {
final ClientException clientException = new ClientException("Unsupported encoding problem: ",
ex);
logger.logError("Unsupported encoding problem: " + ex.getMessage(), ex);
throw clientException;
} catch (final Exception ex) {
final ClientException clientException = new ClientException("Error during http request",
ex);
logger.logError("Error during http request", clientException);
throw clientException;
}
}
/**
* Handles the event of an error response
*
* @param request the request that caused the failed response
* @param serializable the body of the request
* @param connection the URL connection
* @param the type of the request body
* @throws IOException an exception occurs if there were any problems interacting with the connection object
*/
private void handleErrorResponse(final IHttpRequest request,
final Body serializable,
final IConnection connection)
throws IOException {
throw GraphServiceException.createFromConnection(request, serializable, serializer,
connection, logger);
}
/**
* Handles the cause where the response is a binary stream
*
* @param in the input stream from the response
* @return the input stream to return to the caller
*/
private InputStream handleBinaryStream(final InputStream in) {
return in;
}
/**
* Handles the cause where the response is a JSON object
*
* @param in the input stream from the response
* @param responseHeaders the response header
* @param clazz the class of the response object
* @param the type of the response object
* @return the JSON object
*/
private Result handleJsonResponse(final InputStream in, Map> responseHeaders, final Class clazz) {
if (clazz == null) {
return null;
}
final String rawJson = streamToString(in);
return getSerializer().deserializeObject(rawJson, clazz, responseHeaders);
}
/**
* Handles the case where the response body is empty
*
* @param responseHeaders the response headers
* @param clazz the type of the response object
* @return the JSON object
*/
private Result handleEmptyResponse(Map> responseHeaders, final Class clazz)
throws UnsupportedEncodingException{
//Create an empty object to attach the response headers to
InputStream in = new ByteArrayInputStream("{}".getBytes(JSON_ENCODING));
return handleJsonResponse(in, responseHeaders, clazz);
}
/**
* Sets the connection factory for this provider
*
* @param factory the new factory
*/
void setConnectionFactory(final IConnectionFactory factory) {
connectionFactory = factory;
}
/**
* Reads in a stream and converts it into a string
*
* @param input the response body stream
* @return the string result
*/
public static String streamToString(final InputStream input) {
final String httpStreamEncoding = "UTF-8";
final String endOfFile = "\\A";
final Scanner scanner = new Scanner(input, httpStreamEncoding);
String scannerString = "";
try {
scanner.useDelimiter(endOfFile);
scannerString = scanner.next();
} finally {
scanner.close();
}
return scannerString;
}
/**
* Searches for the given header in a list of HeaderOptions
*
* @param headers the list of headers to search through
* @param header the header name to search for (case insensitive)
* @return true if the header has already been set
*/
@VisibleForTesting
static boolean hasHeader(List headers, String header) {
for (HeaderOption option : headers) {
if (option.getName().equalsIgnoreCase(header)) {
return true;
}
}
return false;
}
@VisibleForTesting
public ILogger getLogger() {
return logger;
}
@VisibleForTesting
public IExecutors getExecutors() {
return executors;
}
@VisibleForTesting
public IAuthenticationProvider getAuthenticationProvider() {
return authenticationProvider;
}
/**
* Get connection config for read and connect timeout in requests
*
* @return Connection configuration to be used for timeout values
*/
public IConnectionConfig getConnectionConfig() {
if(this.connectionConfig == null) {
this.connectionConfig = new DefaultConnectionConfig();
}
return connectionConfig;
}
/**
* Set connection config for read and connect timeout in requests
*
* @param connectionConfig Connection configuration to be used for timeout values
*/
public void setConnectionConfig(IConnectionConfig connectionConfig) {
this.connectionConfig = connectionConfig;
}
}