From 4e11bfe847d72e7f11590c9eefbf18c48b6b39ef Mon Sep 17 00:00:00 2001 From: mitalpha Date: Wed, 14 Feb 2018 13:55:56 -0500 Subject: [PATCH] Initial push --- .gitignore | 4 + README.md | 79 ++++- pom.xml | 94 +++++ .../google/newsapi/client/NewsApiClient.java | 322 ++++++++++++++++++ .../com/google/newsapi/config/Constants.java | 36 ++ .../google/newsapi/models/ApiResponse.java | 21 ++ .../com/google/newsapi/models/Article.java | 22 ++ .../google/newsapi/models/ArticlesResult.java | 20 ++ .../java/com/google/newsapi/models/Error.java | 15 + .../newsapi/models/EverythingRequest.java | 58 ++++ .../com/google/newsapi/models/Source.java | 14 + .../newsapi/models/TopHeadlinesRequest.java | 48 +++ .../google/newsapi/client/ClientTestCase.java | 85 +++++ 13 files changed, 813 insertions(+), 5 deletions(-) create mode 100644 pom.xml create mode 100644 src/main/java/com/google/newsapi/client/NewsApiClient.java create mode 100644 src/main/java/com/google/newsapi/config/Constants.java create mode 100644 src/main/java/com/google/newsapi/models/ApiResponse.java create mode 100644 src/main/java/com/google/newsapi/models/Article.java create mode 100644 src/main/java/com/google/newsapi/models/ArticlesResult.java create mode 100644 src/main/java/com/google/newsapi/models/Error.java create mode 100644 src/main/java/com/google/newsapi/models/EverythingRequest.java create mode 100644 src/main/java/com/google/newsapi/models/Source.java create mode 100644 src/main/java/com/google/newsapi/models/TopHeadlinesRequest.java create mode 100644 src/test/java/com/google/newsapi/client/ClientTestCase.java diff --git a/.gitignore b/.gitignore index 6143e53..58377f7 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,7 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* +/target/ +# settings +/.settings/* +/config/* diff --git a/README.md b/README.md index 3be6d2f..6739b52 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,76 @@ -# News API SDK for Java -Coming soon... this is where our officially supported SDK for Java is going to live. +# News API client library for Java +News API is a simple HTTP REST API for searching and retrieving live articles from all over the web. It can help you answer questions like: -*** +- What top stories is the NY Times running right now? +- What new articles were published about the next iPhone today? +- Has my company or product been mentioned or reviewed by any blogs recently? -## 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. +You can search for articles with any combination of the following criteria: + +- Keyword or phrase. Eg: find all articles containing the word 'Microsoft'. +- Date published. Eg: find all articles published yesterday. +- Source name. Eg: find all articles by 'TechCrunch'. +- Source domain name. Eg: find all articles published on nytimes.com. +- Language. Eg: find all articles written in English. + +You can sort the results in the following orders: + +- Date published +- Relevancy to search keyword +- Popularity of source + +You need an API key to use the API - this is a unique key that identifies your requests. They're free for development, open-source, and non-commercial use. You can get one here: [https://newsapi.org](https://newsapi.org). + + +## Usage example + +```java + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import com.google.newsapi.client.NewsApiClient; +import com.google.newsapi.config.Constants; +import com.google.newsapi.models.*; + +public class MyApp +{ + + private static SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd"); + + public static void main(String[] args) + { + String apikey = args[0]; + NewsApiClient client = new NewsApiClient(apikey); + EverythingRequest request = new EverythingRequest(); + request.language = Constants.LANGUAGES.EN; + request.q = "AAPL"; + request.sortBy = Constants.SORT_BYS.Popularity; + String strDate = "2018-02-01"; + Date date = null; + try + { + date = parser.parse(strDate); + } + catch (ParseException e) + { + e.printStackTrace();; + } + if (date != null) + request.from = date; + ArticlesResult res = client.getEverything(request); + List
list = res.articles; + if (list != null && list.size()>0) + { + for (Article article : list) + { + System.out.println(article.toString()); + } + } + client.stopHttpClient(); + } +} + + +``` \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..dd2438b --- /dev/null +++ b/pom.xml @@ -0,0 +1,94 @@ + + 4.0.0 + com.google + newsapi + 0.1.0 + jar + + + profile-jdk1.8 + + true + + + + + 1.8 + 1.8 + 9.4.8.v20171121 + + + + + com.google.code.gson + gson + 2.8.0 + + + + org.eclipse.jetty + jetty-client + ${jetty-version} + + + + ch.qos.logback + logback-core + 1.3.0-alpha3 + + + ch.qos.logback + logback-classic + 1.3.0-alpha3 + + + org.slf4j + slf4j-api + 1.8.0-beta1 + + + junit + junit + 4.12 + test + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.1 + + 1.8 + 1.8 + + + + + + + maven-assembly-plugin + + + package + + single + + + + + + jar-with-dependencies + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/google/newsapi/client/NewsApiClient.java b/src/main/java/com/google/newsapi/client/NewsApiClient.java new file mode 100644 index 0000000..2644317 --- /dev/null +++ b/src/main/java/com/google/newsapi/client/NewsApiClient.java @@ -0,0 +1,322 @@ +package com.google.newsapi.client; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.FutureResponseListener; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.newsapi.config.Constants; +import com.google.newsapi.models.Article; +import com.google.newsapi.models.ArticlesResult; +import com.google.newsapi.models.EverythingRequest; +import com.google.newsapi.models.TopHeadlinesRequest; + +/** + * + * + * @author Mit + * + */ +public class NewsApiClient +{ + private static final Logger logger = LoggerFactory.getLogger(NewsApiClient.class); + private static final String BASE_URL = "https://newsapi.org/v2/"; + private static final String AGENT = "News-API-java/0.1"; + private static SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd"); + private HttpClient httpClient; + private String apiKey; + + private int maxContentSize = 1024 * 1024; // 1limit response size to 1M + private int timeout = 5; // sec. + + public int getMaxContentSize() + { + return maxContentSize; + } + + public void setMaxContentSize(int maxContentSize) + { + this.maxContentSize = maxContentSize; + } + + public int getTimeout() + { + return timeout; + } + + public void setTimeout(int timeout) + { + this.timeout = timeout; + } + + /** + * + * @param apiKey + * News API key. You can create one for free at https://newsapi.org + */ + public NewsApiClient(String apiKey) + { + this.apiKey = apiKey; + SslContextFactory sslContextFactory = new SslContextFactory(); + httpClient = new HttpClient(sslContextFactory); + try + { + httpClient.start(); + } + catch (Exception e) + { + logger.error("ex: ", e); + } + + } + + /** + * use method for blocking calls + */ + + public void setHttpClientBlocking() + { + httpClient.setConnectBlocking(true); + } + + /** + * use method to clean stop using httpClient + */ + public void stopHttpClient() + { + try + { + httpClient.stop(); + } + catch (Exception e) + { + logger.error("ex: ", e); + } + } + + private ArticlesResult makeRequest(String endpoint, String querystring) + { + ArticlesResult res = new ArticlesResult(); + String url = BASE_URL + endpoint + "?" + querystring; + Request request = httpClient.newRequest(url); + request.method(HttpMethod.GET); + request.agent(AGENT); + request.header("x-api-key", apiKey); + FutureResponseListener listener = new FutureResponseListener(request, maxContentSize); + request.send(listener); + try + { + ContentResponse response = listener.get(timeout, TimeUnit.SECONDS); + String content = response.getContentAsString(); + res = new Gson().fromJson(content, ArticlesResult.class); + } + catch (InterruptedException | ExecutionException | TimeoutException e) + { + logger.error("ex: ", e); + } + return res; + } + + public ArticlesResult getTopHeadlines(TopHeadlinesRequest request) + { + StringBuffer queryParams = new StringBuffer(); + // build the querystring + + // q + if ((request.q != null && !request.q.isEmpty())) + { + queryParams.append("q=" + request.q + "&"); + } + + // sources + if (request.sources.size() > 0) + { + List all = request.sources; + StringBuffer buf = new StringBuffer(); + for (String str : all) + { + buf.append(str + ","); + } + String sourcesStr = new String(buf.substring(0, buf.length() - 1)); + queryParams.append("sources=" + sourcesStr + "&"); + } + + if (request.category != null) + { + queryParams.append("category=" + request.category.toString().toLowerCase() + "&"); + } + + if (request.language != null) + { + queryParams.append("language=" + request.language.toString().toLowerCase() + "&"); + } + + if (request.country != null) + { + queryParams.append("country=" + request.country.toString().toLowerCase() + "&"); + } + + // page + if (request.page > 1) + { + queryParams.append("page=" + request.page + "&"); + } + + // page size + if (request.pageSize > 0) + { + queryParams.append("pageSize=" + request.pageSize); + } + + String querystring = new String(queryParams); + if (querystring.endsWith("&")) + querystring = querystring.substring(0, querystring.length() - 1); + + return makeRequest("top-headlines", querystring); + } + + public ArticlesResult getEverything(EverythingRequest request) + { + StringBuffer queryParams = new StringBuffer(); + // build the querystring + + // q + if ((request.q != null && !request.q.isEmpty())) + { + queryParams.append("q=" + request.q + "&"); + } + + // sources + if (request.sources.size() > 0) + { + List all = request.sources; + StringBuffer buf = new StringBuffer(); + for (String str : all) + { + buf.append(str + ","); + } + String sourcesStr = new String(buf.substring(0, buf.length() - 1)); + queryParams.append("sources=" + sourcesStr + "&"); + } + + // domains + if (request.domains.size() > 0) + { + List all = request.domains; + StringBuffer buf = new StringBuffer(); + for (String str : all) + { + buf.append(str + ","); + } + String domainsStr = new String(buf.substring(0, buf.length() - 1)); + queryParams.append("sources=" + domainsStr + "&"); + } + // from + if (request.from != null) + { + // queryParams.append("from=" + parser.format(request.from)); + queryParams.append("from=" + parser.format(request.from) + "&"); + } + + // to + if (request.to != null) + { + queryParams.append("to=" + parser.format(request.to) + "&"); + } + + if (request.language != null) + { + queryParams.append("language=" + request.language.toString().toLowerCase() + "&"); + } + + // page + if (request.page > 1) + { + queryParams.append("page=" + request.page + "&"); + } + + // page size + if (request.pageSize > 0) + { + queryParams.append("pageSize=" + request.pageSize); + } + + String querystring = new String(queryParams); + if (querystring.endsWith("&")) + querystring = querystring.substring(0, querystring.length() - 1); + + return makeRequest("everything", querystring); + } + + /** + * api usage + * + * @param args + * @throws Exception + */ + public static void main1(String[] args) + { + + String apikey = args[0]; + NewsApiClient nac = new NewsApiClient(apikey); + ArticlesResult res = nac.makeRequest("everything", "q=AAPL"); + List
list = res.articles; + int count = 0; + for (Article article : list) + { + count++; + System.out.println(count + ": " + article.toString()); + } + // stop should be used by api users + nac.stopHttpClient(); + + } + + public static void main(String[] args) + { + String apikey = args[0]; + NewsApiClient nac = new NewsApiClient(apikey); + EverythingRequest evr = new EverythingRequest(); + evr.language = Constants.LANGUAGES.EN; + evr.q = "AAPL"; + evr.sortBy = Constants.SORT_BYS.Popularity; + String strDate = "2018-02-01"; + Date date = null; + try + { + date = parser.parse(strDate); + } + catch (ParseException e) + { + logger.error("ex: ", e); + } + if (date != null) + evr.from = date; + ArticlesResult res = nac.getEverything(evr); + List
list = res.articles; + if (list != null) + { + int count = 0; + for (Article article : list) + { + count++; + System.out.println(count + ": " + article.toString()); + } + } + nac.stopHttpClient(); + } + +} diff --git a/src/main/java/com/google/newsapi/config/Constants.java b/src/main/java/com/google/newsapi/config/Constants.java new file mode 100644 index 0000000..8b725e8 --- /dev/null +++ b/src/main/java/com/google/newsapi/config/Constants.java @@ -0,0 +1,36 @@ +package com.google.newsapi.config; + +public class Constants +{ + public enum LANGUAGES + { + AF, AN, AR, AZ, BG, BN, BR, BS, CA, CS, CY, DA, DE, EL, EN, EO, ES, ET, EU, FA, FI, FR, GL, HE, HI, HR, HT, HU, HY, ID, IS, IT, JP, JV, KK, KO, LA, LB, LT, LV, MG, MK, ML, MR, MS, NL, NN, NO, OC, PL, PT, RO, RU, SH, SK, SL, SQ, SR, SV, SW, TA, TE, TH, TL, TR, UK, UR, VI, VO, ZH + }; + + public enum COUNTRIES + { + AE, AR, AT, AU, BE, BG, BR, CA, CH, CN, CO, CU, CZ, DE, EG, FR, GB, GR, HK, HU, ID, IE, IL, IN, IT, JP, KR, LT, LV, MA, MX, MY, NG, NL, NO, NZ, PH, PL, PT, RO, RS, RU, SA, SE, SG, SI, SK, TH, TR, TW, UA, US, VE, ZA + }; + + public enum CATEGORIES + { + Business, Entertainment, Health, Science, Sports, Technology + }; + + public enum SORT_BYS + { + Popularity, PublishedAt, Relevancy + }; + + public enum ERROR_CODES + { + ApiKeyExhausted, ApiKeyMissing, ApiKeyInvalid, ApiKeyDisabled, ParametersMissing, ParametersIncompatible, ParameterInvalid, RateLimited, RequestTimeout, SourcesTooMany, SourceDoesNotExist, SourceUnavailableSortedBy, SourceTemporarilyUnavailable, UnexpectedError, UnknownError + }; + + public enum STATUSES + { + Ok, Error + }; + + +} diff --git a/src/main/java/com/google/newsapi/models/ApiResponse.java b/src/main/java/com/google/newsapi/models/ApiResponse.java new file mode 100644 index 0000000..5c56959 --- /dev/null +++ b/src/main/java/com/google/newsapi/models/ApiResponse.java @@ -0,0 +1,21 @@ +package com.google.newsapi.models; + +import static com.google.newsapi.config.Constants.*; + +import java.util.List; + +public class ApiResponse +{ + public STATUSES status; + public ERROR_CODES code; + public String message; + public List
articles; + public int totalResults; + + @Override + public String toString() + { + return "ApiResponse [status=" + status + ", code=" + code + ", message=" + message + ", articles=" + articles + + ", totalResults=" + totalResults + "]"; + } +} diff --git a/src/main/java/com/google/newsapi/models/Article.java b/src/main/java/com/google/newsapi/models/Article.java new file mode 100644 index 0000000..abb9016 --- /dev/null +++ b/src/main/java/com/google/newsapi/models/Article.java @@ -0,0 +1,22 @@ +package com.google.newsapi.models; + +import java.util.Date; + +public class Article +{ + public Source source; + public String author; + public String title; + public String description; + public String url; + public String urlToImage; + public Date publishedAt; + + @Override + public String toString() + { + return "Article [Source=" + source + ", author=" + author + ", title=" + title + ", description=" + description + + ", url=" + url + ", urlToImage=" + urlToImage + ", publishedAt=" + publishedAt + "]"; + } + +} diff --git a/src/main/java/com/google/newsapi/models/ArticlesResult.java b/src/main/java/com/google/newsapi/models/ArticlesResult.java new file mode 100644 index 0000000..a0ed233 --- /dev/null +++ b/src/main/java/com/google/newsapi/models/ArticlesResult.java @@ -0,0 +1,20 @@ +package com.google.newsapi.models; +import static com.google.newsapi.config.Constants.*; + +import java.util.List; +public class ArticlesResult +{ + public STATUSES status; + public Error error; + public int totalResults; + public List
articles; + + @Override + public String toString() + { + return "ArticlesResult [status=" + status + ", error=" + error + ", totalResults=" + totalResults + ", articles=" + + articles + "]"; + } + + +} diff --git a/src/main/java/com/google/newsapi/models/Error.java b/src/main/java/com/google/newsapi/models/Error.java new file mode 100644 index 0000000..a3aaa84 --- /dev/null +++ b/src/main/java/com/google/newsapi/models/Error.java @@ -0,0 +1,15 @@ +package com.google.newsapi.models; +import static com.google.newsapi.config.Constants.*; +public class Error +{ + public ERROR_CODES code; + public String Message; + + @Override + public String toString() + { + return "Error [code=" + code + ", Message=" + Message + "]"; + } + + +} diff --git a/src/main/java/com/google/newsapi/models/EverythingRequest.java b/src/main/java/com/google/newsapi/models/EverythingRequest.java new file mode 100644 index 0000000..872e613 --- /dev/null +++ b/src/main/java/com/google/newsapi/models/EverythingRequest.java @@ -0,0 +1,58 @@ +package com.google.newsapi.models; + +import static com.google.newsapi.config.Constants.*; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/// +/// Params for making a request to the /everything endpoint. +/// +public class EverythingRequest +{ + /// + /// The keyword or phrase to search for. Boolean operators are supported. + /// + public String q; + /// + /// If you want to restrict the search to specific sources, add their Ids here. You can find source Ids with the /sources endpoint or on newsapi.org. + /// + public List sources = new ArrayList(); + /// + /// If you want to restrict the search to specific web domains, add these here. Example: nytimes.com. + /// + public List domains = new ArrayList(); + /// + /// The earliest date to retrieve articles from. Note that how far back you can go is constrained by your plan type. See newsapi.org/pricing for plan details. + /// + public Date from; + /// + /// The latest date to retrieve articles from. + /// + public Date to; + /// + /// The language to restrict articles to. + /// + public LANGUAGES language; + /// + /// How should the results be sorted? Relevancy = articles relevant to the Q param come first. PublishedAt = most recent articles come first. Publisher = popular publishers come first. + /// + public SORT_BYS sortBy; + /// + /// Each request returns a fixed amount of results. Page through them by increasing this. + /// + public int page; + /// + /// Set the max number of results to retrieve per request. The max is 100. + /// + public int pageSize; + @Override + public String toString() + { + return "EverythingRequest [q=" + q + ", sources=" + sources + ", domains=" + domains + ", from=" + from + ", to=" + + to + ", language=" + language + ", sortBy=" + sortBy + ", page=" + page + ", pageSize=" + pageSize + "]"; + } + + +} diff --git a/src/main/java/com/google/newsapi/models/Source.java b/src/main/java/com/google/newsapi/models/Source.java new file mode 100644 index 0000000..b368e36 --- /dev/null +++ b/src/main/java/com/google/newsapi/models/Source.java @@ -0,0 +1,14 @@ +package com.google.newsapi.models; + +public class Source +{ + public String id; + public String name; + + @Override + public String toString() + { + return "Source [id=" + id + ", name=" + name + "]"; + } + +} diff --git a/src/main/java/com/google/newsapi/models/TopHeadlinesRequest.java b/src/main/java/com/google/newsapi/models/TopHeadlinesRequest.java new file mode 100644 index 0000000..51c443b --- /dev/null +++ b/src/main/java/com/google/newsapi/models/TopHeadlinesRequest.java @@ -0,0 +1,48 @@ +package com.google.newsapi.models; +import static com.google.newsapi.config.Constants.*; + +import java.util.ArrayList; +import java.util.List; + + /// + /// Params for making a request to the /top-headlines endpoint. + /// + public class TopHeadlinesRequest + { + /// + /// The keyword or phrase to search for. Boolean operators are supported. + /// + public String q; + /// + /// If you want to restrict the results to specific sources, add their Ids here. You can find source Ids with the /sources endpoint or on newsapi.org. + /// + public List sources = new ArrayList(); + /// + /// If you want to restrict the headlines to a specific news category, add these here. + /// + public CATEGORIES category; + /// + /// The language to restrict articles to. + /// + public LANGUAGES language; + /// + /// The country of the source to restrict articles to. + /// + public COUNTRIES country; + /// + /// Each request returns a fixed amount of results. Page through them by increasing this. + /// + public int page; + /// + /// Set the max number of results to retrieve per request. The max is 100. + /// + public int pageSize; + @Override + public String toString() + { + return "TopHeadlinesRequest [q=" + q + ", sources=" + sources + ", category=" + category + ", language=" + + language + ", country=" + country + ", page=" + page + ", pageSize=" + pageSize + "]"; + } + + +} diff --git a/src/test/java/com/google/newsapi/client/ClientTestCase.java b/src/test/java/com/google/newsapi/client/ClientTestCase.java new file mode 100644 index 0000000..7983b49 --- /dev/null +++ b/src/test/java/com/google/newsapi/client/ClientTestCase.java @@ -0,0 +1,85 @@ +package com.google.newsapi.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.google.newsapi.config.Constants; +import com.google.newsapi.models.ArticlesResult; +import com.google.newsapi.models.EverythingRequest; +import com.google.newsapi.models.TopHeadlinesRequest; + +public class ClientTestCase +{ + NewsApiClient newsclient = null; + + @Before + public void setUp() throws Exception + { + String apikey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + newsclient = new NewsApiClient(apikey); + } + + @After + public void tearDown() throws Exception + { + newsclient.stopHttpClient(); + } + + @Test + public void testEverythingBasic() + { + EverythingRequest request = new EverythingRequest(); + request.q = "AAPL"; + ArticlesResult result = newsclient.getEverything(request); + assertEquals(Constants.STATUSES.Ok, result.status.Ok); + assertTrue(result.totalResults > 0); + assertTrue(result.articles.size() > 0); + assertNull(result.error); + } + + @Test + public void testEverythingComplex() + { + EverythingRequest request = new EverythingRequest(); + request.q = "AAPL"; + request.sortBy = Constants.SORT_BYS.PublishedAt; + request.language = Constants.LANGUAGES.EN; + ArticlesResult result = newsclient.getEverything(request); + + assertEquals(Constants.STATUSES.Ok, result.status.Ok); + assertTrue(result.totalResults > 0); + assertTrue(result.articles.size() > 0); + assertNull(result.error); + } + + + @Test + public void testTopHeadlinesBasic() + { + TopHeadlinesRequest request = new TopHeadlinesRequest(); + request.sources.add("techcrunch"); + ArticlesResult result = newsclient.getTopHeadlines(request); + + assertEquals(Constants.STATUSES.Ok, result.status.Ok); + assertTrue(result.totalResults > 0); + assertTrue(result.articles.size() > 0); + assertNull(result.error); + } + + @Test + public void testTopHeadlinesWithoutParams() + { + TopHeadlinesRequest request = new TopHeadlinesRequest(); + ArticlesResult result = newsclient.getTopHeadlines(request); + + assertTrue(result.totalResults == 0); + assertNull(result.articles); + + } + +}