diff --git a/README.md b/README.md index 3be6d2f..4e3ffc3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,84 @@ -# News API SDK for Java -Coming soon... this is where our officially supported SDK for Java is going to live. +# NewsAPI SDK for Java -*** -## Developers... we need you! -We need some help fleshing out this repo. If you're a Java dev with experience building Maven-compatible libraries and web API wrappers, we're offering a reward of $250 to help us get started. For more info please email support@newsapi.org, or dive right in and send us a pull request. +**How to use** + +**0**.Download the latest release here : https://github.com/VIad/News-API-java/releases and add the jar to your build path + + + +**1**.Create the client object +```java +NewsApiClient client = new NewsApiClient("YOUR_API_KEY_HERE"); +``` +**2**.Create the service you require (sources / everything / top headlines) + +*Example: get the top headlines from the united states using an asynchronous service* + +```java +client.newTopHeadlinesServiceAsync() + .country("us") + .send(result -> { + List
articles = result.viewAsArticles().orElseGet(Collections::emptyList); + //Do some processing on the articles + }, + //Error occured, print it + error -> System.err.println(error.getMessage())); +``` + +**You can also use a non-async flow and store the response in an ```APIResponse``` object** + +*Example: get the news sources using a keyword and sort them by popularity* +```java +APIResponse response = client.newSourcesService() + .withKeyword("bulgaria") + .sortBy(SortBy.POPULARITY) + .send(); + +/* + * viewAs methods return an optional, since it's uncertain what the data type inside APIResponse.getData is + */ +List newsSources = response.viewAsNewsSources().orElseGet(Collections::emptyList); +//Do some processing on the news sources +``` + +**Creating your own service** + +**1**.Create a class and implement either ```IService``` or ```IAsyncService``` if you want either synchronous or asynchronous execution + +*Example:* +```java +public class MyCustomAsyncService implements IAsyncService{ +Remainder omitted... +} +``` + +**2**.Create the service using the ```NewsApiClient``` object + +*Example: query all the headlines in english with keyword 'Youtube' from the last 7 days* + +```java +client.newCustomAsyncService(new MyCustomAsyncService(), /*Specify an endpoint for your service*/ Endpoint.EVERYTHING) + .withKeyword("Youtube") + .language("en") + .dateRange(DateRange::LAST_7_DAYS) + .send(result -> { + List
articles = result.viewAsArticles().orElseGet(Collections::emptyList); + //Do some processing on the articles + }, + //Something went wrong, print the stack trace + Throwable::printStackTrace); +``` + +Some request methods ```IService``` and ```IAsyncService``` provide + +```java +withSources(String... sources) //Sets the lookup sources, such as bbc, abc-news, cnn +withDomains(String... domains) //Sets the lookup domains, such as techcrunch.com, bbc.co.uk +from(Date date) //Filters news from the specified date +to(Date date) //Filters news to the specified date +withKeyword(String keyword) //sets the searched keyword +withCategory(Category category) //Filters the news given a category +``` + +If you are unsure as to what a function does, you can always refer to the javadoc provided with the binary diff --git a/examples/Asynchronous.java b/examples/Asynchronous.java new file mode 100644 index 0000000..4f469e9 --- /dev/null +++ b/examples/Asynchronous.java @@ -0,0 +1,49 @@ +package com.vlad.newsapi4j.examples; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import com.vlad.newsapi4j.client.NewsApiClient; +import com.vlad.newsapi4j.model.Article; +import com.vlad.newsapi4j.service.SortBy; + +public class Asynchronous { + + public static void main(String[] args) { + NewsApiClient client = new NewsApiClient("99827975269246bc91c1780b77e7e008"); + /** + * Create the asynchronous request and supply with information + * Get the top headlines in the us today, sort them by date, show the first page(optional it's 1 by default) + */ + client.newTopHeadlinesServiceAsync() + .country("us") + .from(new Date()) + .sortBy(SortBy.DATE) + .page(1) + /** + * send method gets called either with the result callback or with the error one + */ + .send(result -> { + /** + * We are here, everything is fine + */ + List
articles = result.viewAsArticles().orElseGet(Collections::emptyList); + /** + * Get all articles whose author starts with the letter 'a', coz why not :D + */ + articles.stream() +// .filter(article -> article.getAuthor().startsWith("A")) + .forEach(System.out::println); + + }, /** + * We are here, something went wrong, just print the cause + */ + Throwable::printStackTrace); + /** + * Program flow continues freely + */ + System.out.println("Yay, running concurrently"); + } + +} diff --git a/examples/Custom.java b/examples/Custom.java new file mode 100644 index 0000000..3d8b803 --- /dev/null +++ b/examples/Custom.java @@ -0,0 +1,192 @@ +package com.vlad.newsapi4j.examples; + +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.function.Supplier; + +import org.json.JSONException; +import org.json.JSONObject; + +import com.vlad.newsapi4j.access.APIConnection; +import com.vlad.newsapi4j.client.NewsApiClient; +import com.vlad.newsapi4j.client.ResponseImpl; +import com.vlad.newsapi4j.model.Article; +import com.vlad.newsapi4j.response.APIResponse; +import com.vlad.newsapi4j.service.Endpoint; +import com.vlad.newsapi4j.service.IAsyncService; +import com.vlad.newsapi4j.service.SortBy; +import com.vlad.newsapi4j.utils.Callback; +import com.vlad.newsapi4j.utils.Category; +import com.vlad.newsapi4j.utils.DateRange; +import com.vlad.newsapi4j.utils.LinkBuilder; +import com.vlad.newsapi4j.utils.NewsAPIException; +import com.vlad.newsapi4j.utils.ResponseStatus; + +public class Custom { + + public static void main(String[] args) { + NewsApiClient client = new NewsApiClient("99827975269246bc91c1780b77e7e008"); + client.newCustomAsyncService(new MyCustomAsyncService(), Endpoint.EVERYTHING) + .withKeyword("Youtube") + .language("en") + .pageSize(100) + /** + * I HAD to break convention (All caps methods and all that), just look at how beautiful this looks + */ + .dateRange(DateRange::LAST_7_DAYS) + .send(result -> { + System.out.println("Result matches : " + result.totalResults()); + + List
articles = result.viewAsArticles().orElseGet(Collections::emptyList); + + //Print the articles + articles.forEach(System.out::println); + }, Throwable::printStackTrace); + + } + + private static class MyCustomAsyncService implements IAsyncService { + + private String apiKey; + + private Endpoint endpoint; + + private LinkBuilder link; + + public MyCustomAsyncService() { + + } + + @Override + public IAsyncService withSources(String... sources) { + link.sources(sources); + return this; + } + + @Override + public IAsyncService withDomains(String... domains) { + link.domains(domains); + return this; + } + + @Override + public IAsyncService withKeyword(String keyword) { + link.keyword(keyword); + return this; + } + + @Override + public IAsyncService withCategory(Category category) { + link.category(category.toLink()); + return this; + } + + @Override + public IAsyncService language(String lang) { + link.language(lang); + return this; + } + + @Override + public IAsyncService sortBy(SortBy sortBy) { + link.sortBy(sortBy); + return this; + } + + @Override + public IAsyncService country(String country) { + link.country(country); + return this; + } + + @Override + public IAsyncService page(int page) { + link.page(page); + return this; + } + + @Override + public IAsyncService to(Date to) { + link.to(to); + return this; + } + + @Override + public IAsyncService from(Date date) { + link.from(date); + return this; + } + + @Override + public IAsyncService prepare(String apiKey, Endpoint endpoint) { + this.apiKey = apiKey; + this.endpoint = endpoint; + this.link = new LinkBuilder(endpoint); + return this; + } + + + + @Override + public void send(Callback result, Callback onError) { + new Thread(() -> { + link.finish(apiKey); + String content = new APIConnection(link).getContents(); + int totalRes = APIResponse.INFORMATION_UNAVAILABLE; + JSONObject obj = null; + try { + obj = new JSONObject(content); + // System.out.println(obj); + } catch (JSONException e1) { + // throw new NewsAPIException("Something went wrong while parsing json"); + e1.printStackTrace(); + } + + System.out.println(link); + ResponseStatus status = ResponseStatus.OK; + /** + * Returns at least an empty string, not null + */ + try { + if (obj.getString("status").equals("error") || content.equals("")) { + status = ResponseStatus.ERROR; + String message = ""; + try { + message = obj.getString("message"); + } catch (JSONException e) { + onError.invoke(new NewsAPIException("Something went wrong while parsing json")); + } + onError.invoke(new NewsAPIException(message)); + } + } catch (JSONException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + + if (endpoint != Endpoint.SOURCES) + try { + totalRes = obj.getInt("totalResults"); + } catch (JSONException e) { + onError.invoke(new NewsAPIException("Something went wrong while parsing json")); + } + result.invoke(new ResponseImpl(endpoint, content, status, totalRes)); + }).start(); + } + + @Override + public IAsyncService dateRange(Supplier range) { + this.link.from(range.get().getFrom()); + this.link.to(range.get().getTo()); + return this; + } + + @Override + public IAsyncService pageSize(int pageSize) { + this.link.pageSize(pageSize); + return this; + } + + } + +} diff --git a/examples/Standart.java b/examples/Standart.java new file mode 100644 index 0000000..e3a3fe6 --- /dev/null +++ b/examples/Standart.java @@ -0,0 +1,44 @@ +package com.vlad.newsapi4j.examples; + +import java.util.Collections; +import java.util.List; + +import com.vlad.newsapi4j.client.NewsApiClient; +import com.vlad.newsapi4j.model.NewsSource; +import com.vlad.newsapi4j.response.APIResponse; +import com.vlad.newsapi4j.service.SortBy; +import com.vlad.newsapi4j.utils.Category; +import com.vlad.newsapi4j.utils.NewsAPIException; + +public class Standart { + + public static void main(String[] args) throws NewsAPIException { + NewsApiClient client = new NewsApiClient("99827975269246bc91c1780b77e7e008"); + /** + * Wait and receive response from server + */ + APIResponse response = client.newSourcesService() + .withKeyword("bulgaria") + .sortBy(SortBy.POPULARITY) + .send(); + + System.out.println("Response type : "+response.getResponseType()); + System.out.println("Response total results : "+response.totalResults()); + + /** + * viewAs methods return an optional, since it's uncertain what the data type inside APIResponse.getData is + */ + List newsSources = response.viewAsNewsSources().orElseGet(Collections::emptyList); + + /** + * Let's say we wanted to be extra cool and printed only the news sources that specialize in sports + */ + newsSources.stream() + .filter(nSource -> nSource.getCategory() == Category.Sports) + .forEach(System.out::println); + + + } + + +} diff --git a/src/com/vlad/newsapi4j/access/APIConnection.java b/src/com/vlad/newsapi4j/access/APIConnection.java new file mode 100644 index 0000000..ee86757 --- /dev/null +++ b/src/com/vlad/newsapi4j/access/APIConnection.java @@ -0,0 +1,43 @@ +package com.vlad.newsapi4j.access; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +import com.vlad.newsapi4j.utils.LinkBuilder; + +public class APIConnection { + + public APIConnection(LinkBuilder link) { + this.fromWhere = link.toString(); + } + + public APIConnection(String linkRaw) { + this.fromWhere = linkRaw; + } + + private String fromWhere; + + public String getContents() { + StringBuilder builder = new StringBuilder(); + try { + HttpURLConnection connection = (HttpURLConnection) new URL(fromWhere).openConnection(); + connection.setDoInput(true); + connection.setRequestMethod("GET"); + connection.setRequestProperty("Content-Type", "application/json; charset=utf-8"); + connection.setRequestProperty("Data-Type", "json"); + InputStreamReader streamReader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8); + BufferedReader reader = new BufferedReader(streamReader); + String line; + while ((line = reader.readLine()) != null) { + builder.append(line); + } + } catch (Exception ex) { + + } + return builder.toString(); + } + +} diff --git a/src/com/vlad/newsapi4j/client/NewsApiClient.java b/src/com/vlad/newsapi4j/client/NewsApiClient.java new file mode 100644 index 0000000..7e1c292 --- /dev/null +++ b/src/com/vlad/newsapi4j/client/NewsApiClient.java @@ -0,0 +1,174 @@ +package com.vlad.newsapi4j.client; + +import com.vlad.newsapi4j.service.Endpoint; +import com.vlad.newsapi4j.service.IAsyncService; +import com.vlad.newsapi4j.service.IService; + +public class NewsApiClient { + + private String apiKey; + + public NewsApiClient(String apiKey) { + this.apiKey = apiKey; + } + + /** + * creates an 'everything' request
+ * + * To query everything, you must include at least a keyword,
+ * source or domain + * + * via the appropriate method from IService + * + *
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @return + */ + public IService newEverythingService() { + return new StandartService().prepare(apiKey, Endpoint.EVERYTHING); + } + + /** + * Creates a 'top-headlines' request
+ * + * All options passed are optional, however at least one must be included, in + * order to
+ * query top-headlines + * + *
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @return + */ + public IService newTopHeadlinesService() { + return new StandartService().prepare(apiKey, Endpoint.TOP_HEADLINES); + } + + /** + * Creates a 'sources' request
+ * + * No options are mandatory + * + *
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @return + */ + public IService newSourcesService() { + return new StandartService().prepare(apiKey, Endpoint.SOURCES); + } + + /** + * Creates a custom service
+ * In order to create a custom service, implement IService without overriding + * the default constructor!
+ * then, simply pass it to the method : + * NewsApiClient.newCustomService(new MyCustomService(), Endpoint.[Some endpoint]); + * + *
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param service + * @param endpoint + * @return + */ + public IService newCustomService(IService service, Endpoint endpoint) { + return service.prepare(apiKey, endpoint); + } + + /** + * creates an 'everything' request that will run asynchronously
+ * + * To query everything, you must include at least a keyword,
+ * source or domain + * + * via the appropriate method from IService + * + *
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @return + */ + public IAsyncService newEverythingServiceAsync() { + return new StandartAsyncService().prepare(apiKey, Endpoint.EVERYTHING); + } + + /** + * Creates a 'top-headlines' request that will run asynchronously
+ * + * All options passed are optional, however at least one must be included, in + * order to
+ * query top-headlines + * + *
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @return + */ + public IAsyncService newTopHeadlinesServiceAsync() { + return new StandartAsyncService().prepare(apiKey, Endpoint.TOP_HEADLINES); + } + + /** + * Creates a 'sources' request that will run asynchronously
+ * + * No options are mandatory
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @return + */ + public IAsyncService newSourcesServiceAsync() { + return new StandartAsyncService().prepare(apiKey, Endpoint.SOURCES); + } + + /** + * Creates a custom asynchronous service
+ * In order to create a custom service, implement IAsyncService without + * overriding the default constructor!
+ * then, simply pass it to the method : + * NewsApiClient.newCustomAsyncService(new MyCustomService(), Endpoint.[Some endpoint]); + * + *
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param service + * @param endpoint + * @return + */ + public IAsyncService newCustomAsyncService(IAsyncService service, Endpoint endpoint) { + return service.prepare(apiKey, endpoint); + } + + /** + * @return The API key used by this object + */ + public String getApiKey() { + return new String(apiKey); + } + +} diff --git a/src/com/vlad/newsapi4j/client/ResponseImpl.java b/src/com/vlad/newsapi4j/client/ResponseImpl.java new file mode 100644 index 0000000..3e3e3df --- /dev/null +++ b/src/com/vlad/newsapi4j/client/ResponseImpl.java @@ -0,0 +1,65 @@ +package com.vlad.newsapi4j.client; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import com.vlad.newsapi4j.response.APIResponse; +import com.vlad.newsapi4j.service.Endpoint; +import com.vlad.newsapi4j.utils.ResponseStatus; + +public class ResponseImpl implements APIResponse { + + private ResponseStatus status; + private int totalRes; + private JSONObject content; + private List list; + private Endpoint endpoint; + + public ResponseImpl(Endpoint reqType, String content, ResponseStatus status, int totalRes) { + this.totalRes = totalRes; + this.status = status; + List dataActual = new ArrayList<>(); + try { + this.content = new JSONObject(content); + JSONArray jsonData = this.content.getJSONArray(reqType != Endpoint.SOURCES ? "articles" : "sources"); + for (int i = 0; i < jsonData.length(); i++) { + dataActual.add(jsonData.getJSONObject(i)); + } + } catch (JSONException ignored) { + } + this.list = dataActual; + this.endpoint = reqType; + } + + @Override + public ResponseStatus getStatus() { + return status; + } + + @Override + public int totalResults() { + return totalRes; + } + + @Override + public JSONObject getResponseRaw() { + return content; + } + + @Override + public List getData() { + return list; + } + + @Override + public Type getResponseType() { + return endpoint == Endpoint.SOURCES ? + Type.SOURCES + : Type.ARTICLES; + } + +} \ No newline at end of file diff --git a/src/com/vlad/newsapi4j/client/StandartAsyncService.java b/src/com/vlad/newsapi4j/client/StandartAsyncService.java new file mode 100644 index 0000000..4ca7010 --- /dev/null +++ b/src/com/vlad/newsapi4j/client/StandartAsyncService.java @@ -0,0 +1,114 @@ +package com.vlad.newsapi4j.client; + +import java.util.Date; +import java.util.function.Supplier; + +import com.vlad.newsapi4j.response.APIResponse; +import com.vlad.newsapi4j.service.Endpoint; +import com.vlad.newsapi4j.service.IAsyncService; +import com.vlad.newsapi4j.service.SortBy; +import com.vlad.newsapi4j.utils.Callback; +import com.vlad.newsapi4j.utils.Category; +import com.vlad.newsapi4j.utils.DateRange; +import com.vlad.newsapi4j.utils.NewsAPIException; + +class StandartAsyncService implements IAsyncService { + + private StandartService stdService; + + public StandartAsyncService() { + stdService = new StandartService(); + } + + @Override + public IAsyncService withSources(String... sources) { + stdService.withSources(sources); + return this; + } + + @Override + public IAsyncService withDomains(String... domains) { + stdService.withDomains(domains); + return this; + } + + @Override + public IAsyncService withKeyword(String keyword) { + stdService.withKeyword(keyword); + return this; + } + + @Override + public IAsyncService language(String lang) { + stdService.language(lang); + return this; + } + + @Override + public IAsyncService sortBy(SortBy sortBy) { + stdService.sortBy(sortBy); + return this; + } + + @Override + public IAsyncService country(String country) { + stdService.country(country); + return this; + } + + @Override + public IAsyncService page(int page) { + stdService.page(page); + return this; + } + + @Override + public IAsyncService to(Date to) { + stdService.to(to); + return this; + } + + @Override + public IAsyncService from(Date date) { + stdService.from(date); + return this; + } + + @Override + public void send(Callback result, Callback onError) { + new Thread(() -> { + try { + result.invoke(stdService.send()); + } catch (NewsAPIException exception) { + onError.invoke(exception); + } + }).start(); + } + + @Override + public IAsyncService prepare(String apiKey, Endpoint endpoint) { + stdService.prepare(apiKey, endpoint); + return this; + } + + @Override + public IAsyncService withCategory(Category category) { + stdService.withCategory(category); + return this; + } + + @Override + public IAsyncService dateRange(Supplier range) { + stdService.dateRange(range); + return this; + } + + @Override + public IAsyncService pageSize(int pageSize) { + stdService.pageSize(pageSize); + return this; + } + + + +} diff --git a/src/com/vlad/newsapi4j/client/StandartService.java b/src/com/vlad/newsapi4j/client/StandartService.java new file mode 100644 index 0000000..bc9d266 --- /dev/null +++ b/src/com/vlad/newsapi4j/client/StandartService.java @@ -0,0 +1,157 @@ +package com.vlad.newsapi4j.client; + +import java.util.Date; +import java.util.function.Supplier; + +import org.json.JSONException; +import org.json.JSONObject; + +import com.vlad.newsapi4j.access.APIConnection; +import com.vlad.newsapi4j.response.APIResponse; +import com.vlad.newsapi4j.service.Endpoint; +import com.vlad.newsapi4j.service.IService; +import com.vlad.newsapi4j.service.SortBy; +import com.vlad.newsapi4j.utils.Category; +import com.vlad.newsapi4j.utils.DateRange; +import com.vlad.newsapi4j.utils.LinkBuilder; +import com.vlad.newsapi4j.utils.NewsAPIException; +import com.vlad.newsapi4j.utils.ResponseStatus; + +class StandartService implements IService { + + private String apiKey; + + private Endpoint endpoint; + + private LinkBuilder link; + + StandartService() { + + } + + @Override + public IService withSources(String... sources) { + link.sources(sources); + return this; + } + + @Override + public IService withDomains(String... domains) { + link.domains(domains); + return this; + } + + @Override + public IService withKeyword(String keyword) { + link.keyword(keyword); + return this; + } + + @Override + public IService language(String lang) { + link.language(lang); + return this; + } + + @Override + public IService sortBy(SortBy sortBy) { + link.sortBy(sortBy); + return this; + } + + @Override + public IService page(int page) { + link.page(page); + return this; + } + + @Override + public IService to(Date to) { + link.to(to); + return this; + } + + @Override + public IService from(Date date) { + link.from(date); + return this; + } + + @Override + public APIResponse send() throws NewsAPIException { + /** + * Insert the API Key + */ + link.finish(apiKey); + /** + * Connect to NewsAPI + */ + System.out.println("Sending API request to ["+link+"]..."); + String content = new APIConnection(link).getContents(); + int totalResults = APIResponse.INFORMATION_UNAVAILABLE; + JSONObject obj = null; + try { + obj = new JSONObject(content); + // System.out.println(obj); + } catch (JSONException e1) { + // throw new NewsAPIException("Something went wrong while parsing json"); + } + + ResponseStatus status = ResponseStatus.OK; + /** + * Returns at least an empty string, not null + */ + if (content.contains("error") || content.equals("")) { + status = ResponseStatus.ERROR; + String message = ""; + try { + message = obj.getString("message"); + } catch (JSONException e) { + throw new NewsAPIException("Something went wrong while parsing json"); + } + throw new NewsAPIException(message); + } + + if (endpoint != Endpoint.SOURCES) + try { + totalResults = obj.getInt("totalResults"); + } catch (JSONException e) { + throw new NewsAPIException("Something went wrong while parsing json"); + } + return new ResponseImpl(endpoint, content, status, totalResults); + } + + @Override + public IService country(String country) { + link.country(country); + return this; + } + + @Override + public IService prepare(String apiKey, Endpoint endpoint) { + this.apiKey = apiKey; + this.endpoint = endpoint; + this.link = new LinkBuilder(endpoint); + return this; + } + + @Override + public IService withCategory(Category category) { + link.category(category.toLink()); + return this; + } + + @Override + public IService dateRange(Supplier range) { + link.from(range.get().getFrom()); + link.to(range.get().getTo()); + return this; + } + + @Override + public IService pageSize(int pageSize) { + link.pageSize(pageSize); + return this; + } + +} diff --git a/src/com/vlad/newsapi4j/model/Article.java b/src/com/vlad/newsapi4j/model/Article.java new file mode 100644 index 0000000..80a69e0 --- /dev/null +++ b/src/com/vlad/newsapi4j/model/Article.java @@ -0,0 +1,173 @@ +package com.vlad.newsapi4j.model; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.json.JSONException; +import org.json.JSONObject; + +public class Article implements java.io.Serializable { + + /** + * + */ + private static final long serialVersionUID = 4316843304782307619L; + + private Source source; + + private String author; + + private String title; + + private String description; + + private String url, urlToImage; + + private Date publishedAt; + + public Article(JSONObject from) { + this.deserialize(from); + } + + private void deserialize(JSONObject from) { + try { + JSONObject source = from.getJSONObject("source"); + this.source = new Source(source.getString("id"), source.getString("name")); + this.author = from.getString("author"); + this.title = from.getString("title"); + this.description = from.getString("description"); + this.url = from.getString("url"); + this.urlToImage = from.getString("urlToImage"); + String publishedAt = from.getString("publishedAt"); + if (publishedAt != null) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + this.publishedAt = sdf.parse(publishedAt); + } + } catch (JSONException | ParseException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public String getAuthor() { + return author; + } + + public String getDescription() { + return description; + } + + public Date getPublishedAt() { + return publishedAt; + } + + public Source getSource() { + return source; + } + + public String getTitle() { + return title; + } + + public String getUrl() { + return url; + } + + public String getUrlToImage() { + return urlToImage; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((author == null) ? 0 : author.hashCode()); + result = prime * result + ((description == null) ? 0 : description.hashCode()); + result = prime * result + ((publishedAt == null) ? 0 : publishedAt.hashCode()); + result = prime * result + ((source == null) ? 0 : source.hashCode()); + result = prime * result + ((title == null) ? 0 : title.hashCode()); + result = prime * result + ((url == null) ? 0 : url.hashCode()); + result = prime * result + ((urlToImage == null) ? 0 : urlToImage.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Article other = (Article) obj; + if (author == null) { + if (other.author != null) + return false; + } else if (!author.equals(other.author)) + return false; + if (description == null) { + if (other.description != null) + return false; + } else if (!description.equals(other.description)) + return false; + if (publishedAt == null) { + if (other.publishedAt != null) + return false; + } else if (!publishedAt.equals(other.publishedAt)) + return false; + if (source == null) { + if (other.source != null) + return false; + } else if (!source.equals(other.source)) + return false; + if (title == null) { + if (other.title != null) + return false; + } else if (!title.equals(other.title)) + return false; + if (url == null) { + if (other.url != null) + return false; + } else if (!url.equals(other.url)) + return false; + if (urlToImage == null) { + if (other.urlToImage != null) + return false; + } else if (!urlToImage.equals(other.urlToImage)) + return false; + return true; + } + + @Override + public String toString() { + return "Article [source=" + source + ", author=" + author + ", title=" + title + ", description=" + description + + ", url=" + url + ", urlToImage=" + urlToImage + ", publishedAt=" + publishedAt + "]"; + } + + public class Source { + + private String id; + private String name; + + public Source(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "Source [id=" + id + ", name=" + name + "]"; + } + + } + +} diff --git a/src/com/vlad/newsapi4j/model/NewsSource.java b/src/com/vlad/newsapi4j/model/NewsSource.java new file mode 100644 index 0000000..9410e8e --- /dev/null +++ b/src/com/vlad/newsapi4j/model/NewsSource.java @@ -0,0 +1,136 @@ +package com.vlad.newsapi4j.model; + +import org.json.JSONException; +import org.json.JSONObject; + +import com.vlad.newsapi4j.utils.Category; + +public class NewsSource implements java.io.Serializable { + + /** + * + */ + private static final long serialVersionUID = -5784025163604945320L; + + private String id; + private String name; + private String description; + private String url; + private Category category; + private String language, country; + + public NewsSource(JSONObject from) { + this.deserialize(from); + } + + private void deserialize(JSONObject from) { + try { + this.id = from.getString("id"); + this.name = from.getString("name"); + this.description = from.getString("description"); + this.url = from.getString("url"); + this.category = Category.parse(from.getString("category")); + this.language = from.getString("language"); + this.country = from.getString("country"); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public Category getCategory() { + return category; + } + + public String getCountry() { + return country; + } + + public String getDescription() { + return description; + } + + public String getId() { + return id; + } + + public String getLanguage() { + return language; + } + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((category == null) ? 0 : category.hashCode()); + result = prime * result + ((country == null) ? 0 : country.hashCode()); + result = prime * result + ((description == null) ? 0 : description.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((language == null) ? 0 : language.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((url == null) ? 0 : url.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + NewsSource other = (NewsSource) obj; + if (category != other.category) + return false; + if (country == null) { + if (other.country != null) + return false; + } else if (!country.equals(other.country)) + return false; + if (description == null) { + if (other.description != null) + return false; + } else if (!description.equals(other.description)) + return false; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + if (language == null) { + if (other.language != null) + return false; + } else if (!language.equals(other.language)) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (url == null) { + if (other.url != null) + return false; + } else if (!url.equals(other.url)) + return false; + return true; + } + + @Override + public String toString() { + return "NewsSource [id=" + id + ", name=" + name + ", description=" + description + ", url=" + url + + ", category=" + category + ", language=" + language + ", country=" + country + "]"; + } + + + + +} diff --git a/src/com/vlad/newsapi4j/response/APIResponse.java b/src/com/vlad/newsapi4j/response/APIResponse.java new file mode 100644 index 0000000..4aaba41 --- /dev/null +++ b/src/com/vlad/newsapi4j/response/APIResponse.java @@ -0,0 +1,70 @@ +package com.vlad.newsapi4j.response; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.json.JSONObject; + +import com.vlad.newsapi4j.model.Article; +import com.vlad.newsapi4j.model.NewsSource; +import com.vlad.newsapi4j.utils.ResponseStatus; + +/** + * Returned by querying newsapi + */ +public interface APIResponse { + + public enum Type { + SOURCES, + + ARTICLES + } + + /** + * Returned whenever the 'totalResults' info is unavailable + */ + int INFORMATION_UNAVAILABLE = -0xff; + + /** + * @return the json object obtained; + */ + JSONObject getJSON(); + + /** + * Returns the status of the request + */ + ResponseStatus getStatus(); + + /** + * Returns the type of information the object is holding + * + * @return + */ + APIResponse.Type getResponseType(); + + /** + * @return either the total amount of elements collected by the query or + * IAPIRespnse.INFORMATION_UNAVAILABLE + */ + int totalResults(); + + /** + * @return a list view of the jsonArray containing all the items queried
+ * + */ + java.util.List getData(); + + default Optional> viewAsArticles() { + if (getResponseType() != Type.ARTICLES) + return Optional.empty(); + return Optional.of(getData().stream().map(Article::new).collect(Collectors.toList())); + } + + default Optional> viewAsNewsSources() { + if (getResponseType() != Type.SOURCES) + return Optional.empty(); + return Optional.of(getData().stream().map(NewsSource::new).collect(Collectors.toList())); + } + +} diff --git a/src/com/vlad/newsapi4j/service/Endpoint.java b/src/com/vlad/newsapi4j/service/Endpoint.java new file mode 100644 index 0000000..4abf8ec --- /dev/null +++ b/src/com/vlad/newsapi4j/service/Endpoint.java @@ -0,0 +1,38 @@ +package com.vlad.newsapi4j.service; + +/** + * + * Enum representing the different news query types + * + */ +public enum Endpoint { + + /** + * Info from here
+ *
+ * + * All options passed are optional, however at least one must be included, in + * order to
+ * query top-headlines + */ + TOP_HEADLINES, + /** + * Info from here
+ *
+ * + * No options are mandatory for a sources request + * + */ + SOURCES, + + /** + * Info from here
+ *
+ * + * To query everything, you must include at least a keyword,
+ * source or domain + * + */ + EVERYTHING + +} diff --git a/src/com/vlad/newsapi4j/service/IAsyncService.java b/src/com/vlad/newsapi4j/service/IAsyncService.java new file mode 100644 index 0000000..82b8bb9 --- /dev/null +++ b/src/com/vlad/newsapi4j/service/IAsyncService.java @@ -0,0 +1,189 @@ +package com.vlad.newsapi4j.service; + +import com.vlad.newsapi4j.response.APIResponse; +import com.vlad.newsapi4j.utils.Callback; +import com.vlad.newsapi4j.utils.Category; +import com.vlad.newsapi4j.utils.DateRange; + +public interface IAsyncService { + /** + * Set the lookup sources
+ * such as abc-news, bbc, cnn, etc + * + *
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param sources + * @return + */ + IAsyncService withSources(String... sources); + + /** + * Set the lookup domains
+ * example : (techcrunch.com, bbc.co.uk) + * + *
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param domains + * @return + */ + IAsyncService withDomains(String... domains); + + /** + * Set the searched keyword + * + *
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param keyword + * @return + */ + IAsyncService withKeyword(String keyword); + + /** + * Filters the news given a category + * + *
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param category + * @return + */ + IAsyncService withCategory(Category category); + + /** + * Sets the language
+ * eg : find all articles written in English.
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param lang + * @return + */ + IAsyncService language(String lang); + + /** + * Sort the results by
+ * Date
+ * Relevancy to search keyword
+ * Popularity of source
+ *
+ * + * For more information, visit NewsAPI's official website + * here
+ * + * @see {@link SortBy} + * @param sortBy + * @return + */ + IAsyncService sortBy(SortBy sortBy); + + /** + * Sets the country of origin
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param country + * @return + */ + IAsyncService country(String country); + + /** + * Sets the lookup page
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param page + * @return + */ + IAsyncService page(int page); + + /** + * Sets the lookup page size
+ * I.E the amount of articles requested
+ * + * For more information, visit NewsAPI's official website + * here + * @param pageSize + * @return this + */ + IAsyncService pageSize(int pageSize); + + /** + * Sets the 'to' date
+ * i.e every request after to will be ignored
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param to + * @return + */ + IAsyncService to(java.util.Date to); + + /** + * Sets the 'from' date
+ * i.e every request before from will be ignored
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param from + * @return + */ + IAsyncService from(java.util.Date date); + + /** + * Sets the range of the news (both from and to) + * + *
+ * For more information, visit NewsAPI's official website + * here + * @param range + * @return + */ + IAsyncService dateRange(java.util.function.Supplier range); + + + /** + * Supplies the AsyncService object with the given api key and + * endpoint
+ * Not meant to be used by the user of the API + * + * @param apiKey + * @param endpoint + * @return + */ + IAsyncService prepare(String apiKey, Endpoint endpoint); + + /** + * Sends the request to the newsapi server on a new thread
+ * returns either the result in a callback or the error that occured + * + * @param result + * @param onError + */ + void send(Callback result, Callback onError); + +} diff --git a/src/com/vlad/newsapi4j/service/IService.java b/src/com/vlad/newsapi4j/service/IService.java new file mode 100644 index 0000000..6a89fb0 --- /dev/null +++ b/src/com/vlad/newsapi4j/service/IService.java @@ -0,0 +1,184 @@ +package com.vlad.newsapi4j.service; + +import com.vlad.newsapi4j.response.APIResponse; +import com.vlad.newsapi4j.utils.Category; +import com.vlad.newsapi4j.utils.DateRange; +import com.vlad.newsapi4j.utils.NewsAPIException; + +public interface IService { + + /** + * Set the lookup sources
+ * such as abc-news, bbc, cnn, etc
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param sources + * @return + */ + IService withSources(String... sources); + + /** + * Set the lookup domains
+ * example : (techcrunch.com, bbc.co.uk)
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param domains + * @return + */ + IService withDomains(String... domains); + + /** + * Set the searched keyword
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param keyword + * @return + */ + IService withKeyword(String keyword); + + /** + * Filters the news given a category
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param category + * @return + */ + IService withCategory(Category category); + + /** + * Sets the language
+ * eg : find all articles written in English.
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param lang + * @return + */ + IService language(String lang); + + /** + * Sort the results by
+ * Date
+ * Relevancy to search keyword
+ * Popularity of source
+ *
+ * + * For more information, visit NewsAPI's official website + * here
+ * + * @see {@link SortBy} + * @param sortBy + * @return + */ + IService sortBy(SortBy sortBy); + + /** + * Sets the country of origin
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param country + * @return + */ + IService country(String country); + + /** + * Sets the lookup page
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param page + * @return + */ + IService page(int page); + + /** + * Sets the lookup page size
+ * I.E the amount of articles requested
+ * + * For more information, visit NewsAPI's official website + * here + * @param pageSize + * @return this + */ + IService pageSize(int pageSize); + + /** + * Sets the 'to' date
+ * i.e every request after to will be ignored
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param to + * @return + */ + IService to(java.util.Date to); + + /** + * Sets the 'from' date
+ * i.e every request before from will be ignored
+ *
+ * + * For more information, visit NewsAPI's official website + * here + * + * @param from + * @return + */ + IService from(java.util.Date date); + + /** + * Sets the range of the news (both from and to) + * + *
+ * For more information, visit NewsAPI's official website + * here + * @param range + * @return + */ + IService dateRange(java.util.function.Supplier range); + + /** + * Sends the request to the newsapi server and returns the result
+ *
+ * + * + * @return + * @throws NewsAPIException + * if anything went wrong
+ * i.e wrong request, etc. + */ + APIResponse send() throws NewsAPIException; + + /** + * Supplies the Service object with the given api key and + * endpoint
+ * Not meant to be used by the user of the API + * + * @param apiKey + * @param endpoint + * @return + */ + IService prepare(String apiKey, Endpoint endpoint); + +} diff --git a/src/com/vlad/newsapi4j/service/SortBy.java b/src/com/vlad/newsapi4j/service/SortBy.java new file mode 100644 index 0000000..7084553 --- /dev/null +++ b/src/com/vlad/newsapi4j/service/SortBy.java @@ -0,0 +1,19 @@ +package com.vlad.newsapi4j.service; + +/** + * Indicates the response sort order + */ +public enum SortBy { + + RELEVANCE, + + POPULARITY, + + DATE; + + public String toLink() { + return this.name().toLowerCase(); + } + + +} diff --git a/src/com/vlad/newsapi4j/utils/Callback.java b/src/com/vlad/newsapi4j/utils/Callback.java new file mode 100644 index 0000000..3684728 --- /dev/null +++ b/src/com/vlad/newsapi4j/utils/Callback.java @@ -0,0 +1,8 @@ +package com.vlad.newsapi4j.utils; + +@FunctionalInterface +public interface Callback { + + void invoke(T t); + +} diff --git a/src/com/vlad/newsapi4j/utils/Category.java b/src/com/vlad/newsapi4j/utils/Category.java new file mode 100644 index 0000000..f22c2a5 --- /dev/null +++ b/src/com/vlad/newsapi4j/utils/Category.java @@ -0,0 +1,27 @@ +package com.vlad.newsapi4j.utils; + +public enum Category { + + Business, Entertainment, Health, Science, Sports, Technology, General; + + public String toLink() { + return this.name().toLowerCase(); + } + + public static Category parse(String category) { + if (category.equalsIgnoreCase(Category.Business.name())) + return Category.Business; + if (category.equalsIgnoreCase(Category.Entertainment.name())) + return Category.Entertainment; + if (category.equalsIgnoreCase(Category.Health.name())) + return Category.Health; + if (category.equalsIgnoreCase(Category.Science.name())) + return Category.Science; + if (category.equalsIgnoreCase(Category.Sports.name())) + return Category.Sports; + if (category.equalsIgnoreCase(Category.Technology.name())) + return Category.Technology; + return Category.General; + } + +} diff --git a/src/com/vlad/newsapi4j/utils/DateRange.java b/src/com/vlad/newsapi4j/utils/DateRange.java new file mode 100644 index 0000000..8a47604 --- /dev/null +++ b/src/com/vlad/newsapi4j/utils/DateRange.java @@ -0,0 +1,39 @@ +package com.vlad.newsapi4j.utils; + +import java.util.Date; + +public class DateRange { + + private Date from, to; + + public DateRange(Date from, Date to) { + this.from = from; + System.out.println("from :: "+from); + this.to = to; + } + + public static final DateRange SINCE_YESTERDAY() { + return new DateRange(new Date(System.currentTimeMillis() - 3600 * 1000 * 24), new Date()); + } + + public static final DateRange LAST_7_DAYS() { + return new DateRange(new Date(System.currentTimeMillis() - 3600 * 1000 * 24 * 7), new Date()); + } + + public static final DateRange LAST_30_DAYS() { + return new DateRange(new Date(System.currentTimeMillis() - 3600 * 1000 * 24 * 30), new Date()); + } + + public static final DateRange LAST_100_DAYS() { + return new DateRange(new Date(System.currentTimeMillis() - 3600 * 1000 * 24 * 100), new Date()); + } + + public Date getFrom() { + return from; + } + + public Date getTo() { + return to; + } + +} diff --git a/src/com/vlad/newsapi4j/utils/LinkBuilder.java b/src/com/vlad/newsapi4j/utils/LinkBuilder.java new file mode 100644 index 0000000..38e5547 --- /dev/null +++ b/src/com/vlad/newsapi4j/utils/LinkBuilder.java @@ -0,0 +1,142 @@ +package com.vlad.newsapi4j.utils; + +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.stream.Collectors; + +import com.vlad.newsapi4j.service.Endpoint; +import com.vlad.newsapi4j.service.SortBy; + +/** + * Utility class used for building the request link for NewsAPI + * + */ +public class LinkBuilder { + + public static final String NEWSAPI_LINK_V2 = "https://newsapi.org/v2/"; + + private StringBuilder current = new StringBuilder(NEWSAPI_LINK_V2); + private boolean firstAddition = true; + + private Endpoint endPoint; + + public LinkBuilder(Endpoint ePoint) { + current.append( + ePoint == Endpoint.EVERYTHING ? "everything" + : + ePoint == Endpoint.TOP_HEADLINES ? "top-headlines" : "sources"); + current.append('?'); + this.endPoint = ePoint; + } + + public LinkBuilder sources(String... sources) { + addTag("sources"); + current.append(join(sources)); + return this; + } + + public LinkBuilder domains(String... domains) { + addTag("domains"); + current.append(join(domains)); + return this; + } + + public LinkBuilder category(String category) { + if(endPoint == Endpoint.EVERYTHING) + throw new NewsAPIRuntimeException("The category param is not currently supported on the /everything endpoint"); + addTag("category"); + current.append(category); + return this; + } + + public LinkBuilder pageSize(int pageSize) { + addTag("pageSize"); + current.append(pageSize); + return this; + } + + private void addTag(String tag) { + if (firstAddition) { + current.append(tag).append('='); + firstAddition = false; + } else + current.append('&').append(tag).append('='); + } + + public LinkBuilder language(String lang) { + addTag("language"); + current.append(lang); + return this; + } + + private CharSequence join(String... strings) { + return Arrays.stream(strings) + .collect(Collectors.joining(",")); + } + + @Override + public String toString() { + return current.toString(); + } + + public LinkBuilder keyword(String string) { + addTag("q"); + current.append(string); + return this; + } + + public LinkBuilder finish(String apiKey) { + addTag("apiKey"); + current.append(apiKey); + return this; + } + + public LinkBuilder country(String country) { + if (endPoint == Endpoint.EVERYTHING) + throw new NewsAPIRuntimeException("Cannot use an everything request with a country"); + addTag("country"); + current.append(country); + return this; + } + + public LinkBuilder sortBy(SortBy sortBy) { + addTag("sortBy"); + current.append(sortBy.toLink()); + return this; + } + + public LinkBuilder page(int page) { + addTag("page"); + current.append(page); + return this; + } + + /* + * YY-MM-DD + */ + public LinkBuilder from(Date from) { + Calendar c = Calendar.getInstance(); + c.setTime(from); + int dd = c.get(Calendar.DAY_OF_MONTH); + int yy = c.get(Calendar.YEAR); + int mm = c.get(Calendar.MONTH) + 1; + addTag("from"); + current.append(yy).append('-').append((String.valueOf(mm).length() != 2 ? "0" : "") + mm).append('-').append( + (String.valueOf(dd).length() != 2 ? "0" : "") + dd); + return this; + } + + public LinkBuilder to(Date to) { + Calendar c = Calendar.getInstance(); + c.setTime(to); + int dd = c.get(Calendar.DAY_OF_MONTH); + int yy = c.get(Calendar.YEAR); + int mm = c.get(Calendar.MONTH) + 1; + addTag("to"); + current.append(yy).append('-').append((String.valueOf(mm).length() != 2 ? "0" : "") + mm).append('-').append( + (String.valueOf(dd).length() != 2 ? "0" : "") + dd); + return this; + } + +} diff --git a/src/com/vlad/newsapi4j/utils/NewsAPIException.java b/src/com/vlad/newsapi4j/utils/NewsAPIException.java new file mode 100644 index 0000000..571901f --- /dev/null +++ b/src/com/vlad/newsapi4j/utils/NewsAPIException.java @@ -0,0 +1,14 @@ +package com.vlad.newsapi4j.utils; + +public class NewsAPIException extends Exception { + + /** + * + */ + private static final long serialVersionUID = -7569894523616376122L; + + public NewsAPIException(String message) { + super(message); + } + +} diff --git a/src/com/vlad/newsapi4j/utils/NewsAPIRuntimeException.java b/src/com/vlad/newsapi4j/utils/NewsAPIRuntimeException.java new file mode 100644 index 0000000..08770e6 --- /dev/null +++ b/src/com/vlad/newsapi4j/utils/NewsAPIRuntimeException.java @@ -0,0 +1,14 @@ +package com.vlad.newsapi4j.utils; + +public class NewsAPIRuntimeException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = 6895946820744739720L; + + public NewsAPIRuntimeException(String message) { + super(message); + } + +} diff --git a/src/com/vlad/newsapi4j/utils/ResponseStatus.java b/src/com/vlad/newsapi4j/utils/ResponseStatus.java new file mode 100644 index 0000000..f059489 --- /dev/null +++ b/src/com/vlad/newsapi4j/utils/ResponseStatus.java @@ -0,0 +1,9 @@ +package com.vlad.newsapi4j.utils; + +public enum ResponseStatus { + + OK, + + ERROR + +}