Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Network component #58

Merged
merged 8 commits into from Mar 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Expand Up @@ -41,6 +41,7 @@ jacocoTestReport {
dependencies {
String testFxVersion = '4.0.7-alpha'

compile group: 'org.asynchttpclient', name: 'async-http-client', version: '2.4.3'
compile group: 'org.fxmisc.easybind', name: 'easybind', version: '1.0.3'
compile group: 'org.controlsfx', name: 'controlsfx', version: '8.40.11'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0'
Expand Down
@@ -0,0 +1,20 @@
package seedu.address.commons.events.network;

import seedu.address.commons.events.BaseEvent;

/**
* Indicates a request to obtain details for a book using the Google Books API.
*/
public class ApiBookDetailsRequestEvent extends BaseEvent {

public final String bookId;

public ApiBookDetailsRequestEvent(String bookId) {
this.bookId = bookId;
}

@Override
public String toString() {
return "book details for: " + bookId;
}
}
@@ -0,0 +1,26 @@
package seedu.address.commons.events.network;

import seedu.address.commons.events.BaseEvent;
import seedu.address.model.book.Book;

/**
* Represents the results of a search for book details using the Google Books API.
*/
public class ApiBookDetailsResultEvent extends BaseEvent {

public final ResultOutcome outcome;
public final Book book;

public ApiBookDetailsResultEvent(ResultOutcome outcome, Book book) {
this.outcome = outcome;
this.book = book;
}

@Override
public String toString() {
if (outcome == ResultOutcome.FAILURE) {
return "API failure";
}
return "book: " + book;
}
}
@@ -0,0 +1,20 @@
package seedu.address.commons.events.network;

import seedu.address.commons.events.BaseEvent;

/**
* Indicates a request to search for books using the Google Books API.
*/
public class ApiSearchRequestEvent extends BaseEvent {

public final String searchParameters;

public ApiSearchRequestEvent(String searchParameters) {
this.searchParameters = searchParameters;
}

@Override
public String toString() {
return "searching for: " + searchParameters;
}
}
@@ -0,0 +1,26 @@
package seedu.address.commons.events.network;

import seedu.address.commons.events.BaseEvent;
import seedu.address.model.ReadOnlyBookShelf;

/**
* Represents the results of a search for books using the Google Books API.
*/
public class ApiSearchResultEvent extends BaseEvent {

public final ResultOutcome outcome;
public final ReadOnlyBookShelf bookShelf;

public ApiSearchResultEvent(ResultOutcome outcome, ReadOnlyBookShelf bookShelf) {
this.outcome = outcome;
this.bookShelf = bookShelf;
}

@Override
public String toString() {
if (outcome == ResultOutcome.FAILURE) {
return "API failure";
}
return "number of books " + bookShelf.getBookList().size();
}
}
@@ -0,0 +1,8 @@
package seedu.address.commons.events.network;

/**
* Represents the type of result outcome of a network request.
*/
public enum ResultOutcome {
FAILURE, SUCCESS
}
15 changes: 15 additions & 0 deletions src/main/java/seedu/address/commons/util/StringUtil.java
Expand Up @@ -5,6 +5,8 @@

import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

/**
* Helper functions for handling strings.
Expand Down Expand Up @@ -68,4 +70,17 @@ public static boolean isNonZeroUnsignedInteger(String s) {
return false;
}
}

/**
* Returns the URL encoded form of the string {@code s}, or any empty string
* if UTF-8 encoding is not supported.
*/
public static String urlEncode(String s) {
requireNonNull(s);
try {
return URLEncoder.encode(s, "UTF-8");
} catch (UnsupportedEncodingException e) {
return "";
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should mention in method description that it returns empty string if invalid

}
}
38 changes: 38 additions & 0 deletions src/main/java/seedu/address/model/util/BookDataUtil.java
@@ -0,0 +1,38 @@
package seedu.address.model.util;

import java.util.HashSet;
import java.util.Set;

import seedu.address.model.book.Author;
import seedu.address.model.book.Category;

/**
* Contains utility methods for manipulating book data.
*/
public class BookDataUtil {

/**
* Returns an author set containing the list of strings given.
*/
public static Set<Author> getAuthorSet(String... strings) {
HashSet<Author> authors = new HashSet<>();
for (String s : strings) {
authors.add(new Author(s));
}

return authors;
}

/**
* Returns a category set containing the list of strings given.
*/
public static Set<Category> getCategorySet(String... strings) {
HashSet<Category> categories = new HashSet<>();
for (String s : strings) {
categories.add(new Category(s));
}

return categories;
}

}
24 changes: 0 additions & 24 deletions src/main/java/seedu/address/model/util/SampleDataUtil.java
Expand Up @@ -59,30 +59,6 @@ public static ReadOnlyBookShelf getSampleBookShelf() {
}
}

/**
* Returns an author set containing the list of strings given.
*/
public static Set<Author> getAuthorSet(String... strings) {
HashSet<Author> authors = new HashSet<>();
for (String s : strings) {
authors.add(new Author(s));
}

return authors;
}

/**
* Returns a category set containing the list of strings given.
*/
public static Set<Category> getCategorySet(String... strings) {
HashSet<Category> categories = new HashSet<>();
for (String s : strings) {
categories.add(new Category(s));
}

return categories;
}

@Deprecated
public static Person[] getSamplePersons() {
return new Person[] {
Expand Down
47 changes: 47 additions & 0 deletions src/main/java/seedu/address/network/HttpClient.java
@@ -0,0 +1,47 @@
package seedu.address.network;

import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Logger;

import org.asynchttpclient.AsyncHttpClient;

import seedu.address.commons.core.LogsCenter;

/**
* A wrapper around the AsyncHttpClient class from async-http-client.
*/
public class HttpClient {

private static final Logger logger = LogsCenter.getLogger(HttpClient.class);

private final AsyncHttpClient asyncHttpClient;

public HttpClient(AsyncHttpClient asyncHttpClient) {
this.asyncHttpClient = asyncHttpClient;
}

/**
* Asynchronously executes a HTTP GET request to the specified url.
*/
public CompletableFuture<HttpResponse> makeGetRequest(String url) {
return asyncHttpClient
.prepareGet(url)
.execute()
.toCompletableFuture()
.thenApply(HttpResponse::new);
}

/**
* Stops and closes the underlying AsyncHttpClient.
*/
public void close() {
try {
if (!asyncHttpClient.isClosed()) {
asyncHttpClient.close();
}
} catch (IOException e) {
logger.warning("Failed to shut down AsyncHttpClient.");
}
}
}
35 changes: 35 additions & 0 deletions src/main/java/seedu/address/network/HttpResponse.java
@@ -0,0 +1,35 @@
package seedu.address.network;

import org.asynchttpclient.Response;

/**
* A wrapper around the Response class from async-http-client.
*/
public class HttpResponse {

private final int statusCode;
private final String contentType;
private final String responseBody;

public HttpResponse(int statusCode, String contentType, String responseBody) {
this.statusCode = statusCode;
this.contentType = contentType;
this.responseBody = responseBody;
}

public HttpResponse(Response response) {
this(response.getStatusCode(), response.getContentType(), response.getResponseBody());
}

public int getStatusCode() {
return statusCode;
}

public String getContentType() {
return contentType;
}

public String getResponseBody() {
return responseBody;
}
}
12 changes: 12 additions & 0 deletions src/main/java/seedu/address/network/Network.java
@@ -0,0 +1,12 @@
package seedu.address.network;

/**
* The API of the Network component.
*/
public interface Network {

/**
* Stops the Network component.
*/
void stop();
}
81 changes: 81 additions & 0 deletions src/main/java/seedu/address/network/NetworkManager.java
@@ -0,0 +1,81 @@
package seedu.address.network;

import java.util.logging.Logger;

import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.Dsl;

import com.google.common.eventbus.Subscribe;

import seedu.address.commons.core.ComponentManager;
import seedu.address.commons.core.LogsCenter;
import seedu.address.commons.events.network.ApiBookDetailsRequestEvent;
import seedu.address.commons.events.network.ApiBookDetailsResultEvent;
import seedu.address.commons.events.network.ApiSearchRequestEvent;
import seedu.address.commons.events.network.ApiSearchResultEvent;
import seedu.address.commons.events.network.ResultOutcome;
import seedu.address.commons.util.StringUtil;
import seedu.address.network.api.google.GoogleBooksApi;

/**
* Provides networking functionality (making API calls).
*
* No API methods are directly exposed on this class. To make an API call,
* raise the corresponding *RequestEvent. To receive the results of the call,
* handle the corresponding *ResultEvent.
*/
public class NetworkManager extends ComponentManager implements Network {

private static final Logger logger = LogsCenter.getLogger(NetworkManager.class);
private static final int CONNECTION_TIMEOUT_MILLIS = 1000 * 5; // 5 seconds
private static final int READ_TIMEOUT_MILLIS = 1000 * 5; // 5 seconds
private static final int REQUEST_TIMEOUT_MILLIS = 1000 * 5; // 5 seconds

private final HttpClient httpClient;
private final GoogleBooksApi googleBooksApi;

public NetworkManager() {
super();
AsyncHttpClient asyncHttpClient = Dsl.asyncHttpClient(Dsl.config()
.setConnectTimeout(CONNECTION_TIMEOUT_MILLIS)
.setReadTimeout(READ_TIMEOUT_MILLIS)
.setRequestTimeout(REQUEST_TIMEOUT_MILLIS));
httpClient = new HttpClient(asyncHttpClient);
googleBooksApi = new GoogleBooksApi(httpClient);
}

@Override
public void stop() {
httpClient.close();
}

@Subscribe
private void handleGoogleApiSearchRequestEvent(ApiSearchRequestEvent event) {
logger.info(LogsCenter.getEventHandlingLogMessage(event));
googleBooksApi.searchBooks(event.searchParameters)
.thenApply(bookShelf -> {
raise(new ApiSearchResultEvent(ResultOutcome.SUCCESS, bookShelf));
return bookShelf;
})
.exceptionally(e -> {
logger.warning("Search request failed: " + StringUtil.getDetails(e));
raise(new ApiSearchResultEvent(ResultOutcome.FAILURE, null));
return null;
});
}

@Subscribe
private void handleGoogleApiBookDetailsRequestEvent(ApiBookDetailsRequestEvent event) {
logger.info(LogsCenter.getEventHandlingLogMessage(event));
googleBooksApi.getBookDetails(event.bookId)
.thenApply(book -> {
raise(new ApiBookDetailsResultEvent(ResultOutcome.SUCCESS, book));
return book;
})
.exceptionally(e -> {
logger.warning("Book detail request failed: " + StringUtil.getDetails(e));
raise(new ApiBookDetailsResultEvent(ResultOutcome.FAILURE, null));
return null;
});
}
}