diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b1f80f9cc7..55fc8d8410e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve ### Added +- We added a new fetcher to enable users to search mEDRA DOIs [#6602](https://github.com/JabRef/jabref/issues/6602) - We added a new fetcher to enable users to search "[Collection of Computer Science Bibliographies](https://liinwww.ira.uka.de/bibliography/index.html)". [#6638](https://github.com/JabRef/jabref/issues/6638) - We added default values for delimiters in Add Subgroup window [#6624](https://github.com/JabRef/jabref/issues/6624) - We improved responsiveness of general fields specification dialog window. [#6643](https://github.com/JabRef/jabref/issues/6604) diff --git a/src/main/java/org/jabref/logic/importer/IdBasedParserFetcher.java b/src/main/java/org/jabref/logic/importer/IdBasedParserFetcher.java index 19d65d15e0c..e2d23227304 100644 --- a/src/main/java/org/jabref/logic/importer/IdBasedParserFetcher.java +++ b/src/main/java/org/jabref/logic/importer/IdBasedParserFetcher.java @@ -31,7 +31,7 @@ public interface IdBasedParserFetcher extends IdBasedFetcher { * * @param identifier the ID */ - URL getURLForID(String identifier) throws URISyntaxException, MalformedURLException, FetcherException; + URL getUrlForIdentifier(String identifier) throws URISyntaxException, MalformedURLException, FetcherException; /** * Returns the parser used to convert the response to a list of {@link BibEntry}. @@ -61,7 +61,7 @@ default Optional performSearchById(String identifier) throws FetcherEx return Optional.empty(); } - try (InputStream stream = new URLDownload(getURLForID(identifier)).asInputStream()) { + try (InputStream stream = getUrlDownload(getUrlForIdentifier(identifier)).asInputStream()) { List fetchedEntries = getParser().parseEntries(stream); if (fetchedEntries.isEmpty()) { diff --git a/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java b/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java index 30b0d49a5b6..447f8dd4efd 100644 --- a/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java +++ b/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java @@ -43,31 +43,20 @@ public interface SearchBasedParserFetcher extends SearchBasedFetcher { * {@code new FieldFormatterCleanup(StandardField.TITLE, new RemoveBracesFormatter()).cleanup(entry);} * * By default, no cleanup is done. + * * @param entry the entry to be cleaned-up */ default void doPostCleanup(BibEntry entry) { // Do nothing by default } - /** - * Gets the {@link URLDownload} object for downloading content. Overwrite, if you need to send additional headers for the download - * - * @param query The search query - * @throws MalformedURLException - * @throws FetcherException - * @throws URISyntaxException - */ - default URLDownload getUrlDownload(String query) throws MalformedURLException, FetcherException, URISyntaxException { - return new URLDownload(getURLForQuery(query)); - } - @Override default List performSearch(String query) throws FetcherException { if (StringUtil.isBlank(query)) { return Collections.emptyList(); } - try (InputStream stream = getUrlDownload(query).asInputStream()) { + try (InputStream stream = getUrlDownload(getURLForQuery(query)).asInputStream()) { List fetchedEntries = getParser().parseEntries(stream); // Post-cleanup diff --git a/src/main/java/org/jabref/logic/importer/WebFetcher.java b/src/main/java/org/jabref/logic/importer/WebFetcher.java index 6f300d67df2..a1745cba6d1 100644 --- a/src/main/java/org/jabref/logic/importer/WebFetcher.java +++ b/src/main/java/org/jabref/logic/importer/WebFetcher.java @@ -1,8 +1,10 @@ package org.jabref.logic.importer; +import java.net.URL; import java.util.Optional; import org.jabref.logic.help.HelpFile; +import org.jabref.logic.net.URLDownload; /** * Searches web resources for bibliographic information. @@ -25,4 +27,11 @@ public interface WebFetcher { default Optional getHelpPage() { return Optional.empty(); // no help page by default } + + /** + * Constructs an {@link URLDownload} object for downloading content based on the given URL. Overwrite, if you need to send additional headers for the download. + */ + default URLDownload getUrlDownload(URL url) { + return new URLDownload(url); + } } diff --git a/src/main/java/org/jabref/logic/importer/WebFetchers.java b/src/main/java/org/jabref/logic/importer/WebFetchers.java index 6fae6d91d33..3d7c50fd87b 100644 --- a/src/main/java/org/jabref/logic/importer/WebFetchers.java +++ b/src/main/java/org/jabref/logic/importer/WebFetchers.java @@ -29,6 +29,7 @@ import org.jabref.logic.importer.fetcher.LibraryOfCongress; import org.jabref.logic.importer.fetcher.MathSciNet; import org.jabref.logic.importer.fetcher.MedlineFetcher; +import org.jabref.logic.importer.fetcher.Medra; import org.jabref.logic.importer.fetcher.OpenAccessDoi; import org.jabref.logic.importer.fetcher.RfcFetcher; import org.jabref.logic.importer.fetcher.ScienceDirect; @@ -123,6 +124,7 @@ public static SortedSet getIdBasedFetchers(ImportFormatPreferenc set.add(new LibraryOfCongress(importFormatPreferences)); set.add(new IacrEprintFetcher(importFormatPreferences)); set.add(new RfcFetcher(importFormatPreferences)); + set.add(new Medra()); return set; } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java b/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java index dcfc1949270..33fdcabbea5 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java @@ -121,7 +121,7 @@ public URL getURLForEntry(BibEntry entry) throws URISyntaxException, MalformedUR * @return URL which points to a search URL for given identifier */ @Override - public URL getURLForID(String identifier) throws FetcherException, URISyntaxException, MalformedURLException { + public URL getUrlForIdentifier(String identifier) throws FetcherException, URISyntaxException, MalformedURLException { String query = "doi:\"" + identifier + "\" OR " + "bibcode:\"" + identifier + "\""; URIBuilder builder = new URIBuilder(API_SEARCH_URL); builder.addParameter("q", query); @@ -227,7 +227,7 @@ public Optional performSearchById(String identifier) throws FetcherExc } try { - List bibcodes = fetchBibcodes(getURLForID(identifier)); + List bibcodes = fetchBibcodes(getUrlForIdentifier(identifier)); List fetchedEntries = performSearchByIds(bibcodes); if (fetchedEntries.isEmpty()) { diff --git a/src/main/java/org/jabref/logic/importer/fetcher/CrossRef.java b/src/main/java/org/jabref/logic/importer/fetcher/CrossRef.java index e07591bbb2a..6eb3d8d07db 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/CrossRef.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/CrossRef.java @@ -4,6 +4,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -69,7 +70,7 @@ public URL getURLForQuery(String query) throws URISyntaxException, MalformedURLE } @Override - public URL getURLForID(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { + public URL getUrlForIdentifier(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { URIBuilder uriBuilder = new URIBuilder(API_URL + "/" + identifier); return uriBuilder.build().toURL(); } @@ -77,23 +78,30 @@ public URL getURLForID(String identifier) throws URISyntaxException, MalformedUR @Override public Parser getParser() { return inputStream -> { - JSONObject response = JsonReader.toJsonObject(inputStream).getJSONObject("message"); - - List entries = new ArrayList<>(); - if (response.has("items")) { - // Response contains a list - JSONArray items = response.getJSONArray("items"); - for (int i = 0; i < items.length(); i++) { - JSONObject item = items.getJSONObject(i); - BibEntry entry = jsonItemToBibEntry(item); - entries.add(entry); - } - } else { + JSONObject response = JsonReader.toJsonObject(inputStream); + if (response.isEmpty()) { + return Collections.emptyList(); + } + + response = response.getJSONObject("message"); + if (response.isEmpty()) { + return Collections.emptyList(); + } + + if (!response.has("items")) { // Singleton response BibEntry entry = jsonItemToBibEntry(response); - entries.add(entry); + return Collections.singletonList(entry); } + // Response contains a list + JSONArray items = response.getJSONArray("items"); + List entries = new ArrayList<>(items.length()); + for (int i = 0; i < items.length(); i++) { + JSONObject item = items.getJSONObject(i); + BibEntry entry = jsonItemToBibEntry(item); + entries.add(entry); + } return entries; }; } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/DiVA.java b/src/main/java/org/jabref/logic/importer/fetcher/DiVA.java index 5a876fd9ff8..ae6a02a9e85 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/DiVA.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/DiVA.java @@ -38,7 +38,7 @@ public Optional getHelpPage() { } @Override - public URL getURLForID(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { + public URL getUrlForIdentifier(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { URIBuilder uriBuilder = new URIBuilder("http://www.diva-portal.org/smash/getreferences"); uriBuilder.addParameter("referenceFormat", "BibTex"); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java index f018be468ba..a59c02882f0 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java @@ -25,9 +25,15 @@ import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.model.util.OptionalUtil; +import kong.unirest.json.JSONException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class DoiFetcher implements IdBasedFetcher, EntryBasedFetcher { public static final String NAME = "DOI"; + private static final Logger LOGGER = LoggerFactory.getLogger(DoiFetcher.class); + private final ImportFormatPreferences preferences; public DoiFetcher(ImportFormatPreferences preferences) { @@ -47,18 +53,27 @@ public Optional getHelpPage() { @Override public Optional performSearchById(String identifier) throws FetcherException { Optional doi = DOI.parse(identifier); + try { if (doi.isPresent()) { + Optional fetchedEntry; + + // mEDRA does not return a parsable bibtex string + if (doi.get().getAgency().isPresent() && "medra".equalsIgnoreCase(doi.get().getAgency().get())) { + return new Medra().performSearchById(identifier); + } + URL doiURL = new URL(doi.get().getURIAsASCIIString()); // BibTeX data - URLDownload download = new URLDownload(doiURL); + URLDownload download = getUrlDownload(doiURL); download.addHeader("Accept", MediaTypes.APPLICATION_BIBTEX); String bibtexString = download.asString(); // BibTeX entry - Optional fetchedEntry = BibtexParser.singleFromString(bibtexString, preferences, new DummyFileUpdateMonitor()); + fetchedEntry = BibtexParser.singleFromString(bibtexString, preferences, new DummyFileUpdateMonitor()); fetchedEntry.ifPresent(this::doPostCleanup); + return fetchedEntry; } else { throw new FetcherException(Localization.lang("Invalid DOI: '%0'.", identifier)); @@ -67,6 +82,8 @@ public Optional performSearchById(String identifier) throws FetcherExc throw new FetcherException(Localization.lang("Connection error"), e); } catch (ParseException e) { throw new FetcherException("Could not parse BibTeX entry", e); + } catch (JSONException e) { + throw new FetcherException("Could not retrieve Registration Agency", e); } } @@ -84,4 +101,5 @@ public List performSearch(BibEntry entry) throws FetcherException { return Collections.emptyList(); } } + } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/INSPIREFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/INSPIREFetcher.java index 8c276766fce..69ef0a86fdc 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/INSPIREFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/INSPIREFetcher.java @@ -54,8 +54,8 @@ public URL getURLForQuery(String query) throws URISyntaxException, MalformedURLE } @Override - public URLDownload getUrlDownload(String query) throws MalformedURLException, FetcherException, URISyntaxException { - URLDownload download = new URLDownload(getURLForQuery(query)); + public URLDownload getUrlDownload(URL url) { + URLDownload download = new URLDownload(url); download.addHeader("Accept", MediaTypes.APPLICATION_BIBTEX); return download; } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/IsbnViaEbookDeFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/IsbnViaEbookDeFetcher.java index 1f78b877813..fa9c7b74e9a 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/IsbnViaEbookDeFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/IsbnViaEbookDeFetcher.java @@ -30,7 +30,7 @@ public String getName() { } @Override - public URL getURLForID(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { + public URL getUrlForIdentifier(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { this.ensureThatIsbnIsValid(identifier); URIBuilder uriBuilder = new URIBuilder(BASE_URL); uriBuilder.addParameter("isbn", identifier); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/IsbnViaOttoBibFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/IsbnViaOttoBibFetcher.java index ba00ba543c9..79081628d6e 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/IsbnViaOttoBibFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/IsbnViaOttoBibFetcher.java @@ -39,7 +39,7 @@ public String getName() { * @return null, because the identifier is passed using form data. This method is not used. */ @Override - public URL getURLForID(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { + public URL getUrlForIdentifier(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { return null; } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/LibraryOfCongress.java b/src/main/java/org/jabref/logic/importer/fetcher/LibraryOfCongress.java index 2db7bce827e..caa236c8edc 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/LibraryOfCongress.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/LibraryOfCongress.java @@ -29,7 +29,7 @@ public String getName() { } @Override - public URL getURLForID(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { + public URL getUrlForIdentifier(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { URIBuilder uriBuilder = new URIBuilder("https://lccn.loc.gov/" + identifier + "/mods"); return uriBuilder.build().toURL(); } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/MathSciNet.java b/src/main/java/org/jabref/logic/importer/fetcher/MathSciNet.java index c8bad2247d4..f8085c6afe9 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/MathSciNet.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/MathSciNet.java @@ -57,7 +57,7 @@ public URL getURLForEntry(BibEntry entry) throws URISyntaxException, MalformedUR Optional mrNumberInEntry = entry.getField(StandardField.MR_NUMBER); if (mrNumberInEntry.isPresent()) { // We are lucky and already know the id, so use it instead - return getURLForID(mrNumberInEntry.get()); + return getUrlForIdentifier(mrNumberInEntry.get()); } URIBuilder uriBuilder = new URIBuilder("https://mathscinet.ams.org/mrlookup"); @@ -83,7 +83,7 @@ public URL getURLForQuery(String query) throws URISyntaxException, MalformedURLE } @Override - public URL getURLForID(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { + public URL getUrlForIdentifier(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { URIBuilder uriBuilder = new URIBuilder("https://mathscinet.ams.org/mathscinet/search/publications.html"); uriBuilder.addParameter("pg1", "MR"); // search MR number uriBuilder.addParameter("s1", identifier); // identifier diff --git a/src/main/java/org/jabref/logic/importer/fetcher/MedlineFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/MedlineFetcher.java index c655a4dd735..5b1017b5d52 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/MedlineFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/MedlineFetcher.java @@ -135,7 +135,7 @@ public Optional getHelpPage() { } @Override - public URL getURLForID(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { + public URL getUrlForIdentifier(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { URIBuilder uriBuilder = new URIBuilder(ID_URL); uriBuilder.addParameter("db", "pubmed"); uriBuilder.addParameter("retmode", "xml"); @@ -206,7 +206,7 @@ private URL createSearchUrl(String term) throws URISyntaxException, MalformedURL private List fetchMedline(List ids) throws FetcherException { try { // Separate the IDs with a comma to search multiple entries - URL fetchURL = getURLForID(String.join(",", ids)); + URL fetchURL = getUrlForIdentifier(String.join(",", ids)); URLConnection data = fetchURL.openConnection(); ParserResult result = new MedlineImporter().importDatabase( new BufferedReader(new InputStreamReader(data.getInputStream(), StandardCharsets.UTF_8))); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/Medra.java b/src/main/java/org/jabref/logic/importer/fetcher/Medra.java new file mode 100644 index 00000000000..a7f18942888 --- /dev/null +++ b/src/main/java/org/jabref/logic/importer/fetcher/Medra.java @@ -0,0 +1,126 @@ +package org.jabref.logic.importer.fetcher; + +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Collections; +import java.util.Optional; + +import org.jabref.logic.cleanup.DoiCleanup; +import org.jabref.logic.importer.FetcherException; +import org.jabref.logic.importer.IdBasedParserFetcher; +import org.jabref.logic.importer.ParseException; +import org.jabref.logic.importer.Parser; +import org.jabref.logic.importer.util.JsonReader; +import org.jabref.logic.importer.util.MediaTypes; +import org.jabref.logic.net.URLDownload; +import org.jabref.model.entry.AuthorList; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.EntryType; +import org.jabref.model.entry.types.StandardEntryType; + +import kong.unirest.json.JSONArray; +import kong.unirest.json.JSONException; +import kong.unirest.json.JSONObject; + +/** + * A class for fetching DOIs from Medra + * + * @see mEDRA Content Negotiation API for an overview of the API + *

+ * It requires "Accept" request Header attribute to be set to desired content-type. + */ +public class Medra implements IdBasedParserFetcher { + + public static final String API_URL = "https://data.medra.org"; + + @Override + public String getName() { + return "mEDRA"; + } + + @Override + public Parser getParser() { + return inputStream -> { + JSONObject response = JsonReader.toJsonObject(inputStream); + if (response.isEmpty()) { + return Collections.emptyList(); + } + return Collections.singletonList(jsonItemToBibEntry(response)); + }; + } + + private BibEntry jsonItemToBibEntry(JSONObject item) throws ParseException { + try { + + return new BibEntry(convertType(item.getString("type"))) + .withField(StandardField.TITLE, item.getString("title")) + .withField(StandardField.AUTHOR, toAuthors(item.optJSONArray("author"))) + .withField(StandardField.YEAR, + Optional.ofNullable(item.optJSONObject("issued")) + .map(array -> array.optJSONArray("date-parts")) + .map(array -> array.optJSONArray(0)) + .map(array -> array.optInt(0)) + .map(year -> Integer.toString(year)).orElse("")) + .withField(StandardField.DOI, item.getString("DOI")) + .withField(StandardField.PAGES, item.optString("page")) + .withField(StandardField.ISSN, item.optString("ISSN")) + .withField(StandardField.JOURNAL, item.optString("container-title")) + .withField(StandardField.PUBLISHER, item.optString("publisher")) + .withField(StandardField.URL, item.optString("URL")) + .withField(StandardField.VOLUME, item.optString("volume")); + + } catch (JSONException exception) { + throw new ParseException("mEdRA API JSON format has changed", exception); + } + } + + private EntryType convertType(String type) { + switch (type) { + case "article-journal": + return StandardEntryType.Article; + default: + return StandardEntryType.Misc; + } + } + + private String toAuthors(JSONArray authors) { + if (authors == null) { + return ""; + } + + // input: list of {"literal":"A."} + AuthorList authorsParsed = new AuthorList(); + String name = ""; + + for (int i = 0; i < authors.length(); i++) { + JSONObject author = authors.getJSONObject(i); + if (author.has("literal")) { + // quickly route through the literal string + authorsParsed.addAuthor(author.getString("literal"), "", "", "", ""); + } else { + authorsParsed.addAuthor(author.optString("given", ""), "", "", author.optString("family", ""), ""); + } + } + return authorsParsed.getAsFirstLastNamesWithAnd(); + } + + @Override + public URLDownload getUrlDownload(URL url) { + URLDownload download = new URLDownload(url); + download.addHeader("Accept", MediaTypes.CITATIONSTYLES_JSON); + return download; + } + + @Override + public URL getUrlForIdentifier(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { + return new URL(API_URL + "/" + identifier); + } + + @Override + public void doPostCleanup(BibEntry entry) { + new DoiCleanup().cleanup(entry); + } + +} diff --git a/src/main/java/org/jabref/logic/importer/fetcher/RfcFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/RfcFetcher.java index cba36d0dabe..b1108a82744 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/RfcFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/RfcFetcher.java @@ -39,7 +39,7 @@ public Optional getHelpPage() { } @Override - public URL getURLForID(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { + public URL getUrlForIdentifier(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { // Add "rfc" prefix if user's search entry was numerical String prefixedIdentifier = identifier; prefixedIdentifier = (!identifier.toLowerCase().startsWith("rfc")) ? "rfc" + prefixedIdentifier : prefixedIdentifier; diff --git a/src/main/java/org/jabref/logic/importer/util/JsonReader.java b/src/main/java/org/jabref/logic/importer/util/JsonReader.java index d2a3e0d064e..60652696b6a 100644 --- a/src/main/java/org/jabref/logic/importer/util/JsonReader.java +++ b/src/main/java/org/jabref/logic/importer/util/JsonReader.java @@ -1,13 +1,12 @@ package org.jabref.logic.importer.util; -import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; import org.jabref.logic.importer.ParseException; +import com.google.common.base.Charsets; +import kong.unirest.json.JSONException; import kong.unirest.json.JSONObject; /** @@ -15,25 +14,21 @@ */ public class JsonReader { - public static JSONObject toJsonObject(InputStreamReader input) throws ParseException { - BufferedReader streamReader = new BufferedReader(input); - StringBuilder responseStrBuilder = new StringBuilder(); - + /** + * Converts the given input stream into a {@link JSONObject}. + * + * @return A {@link JSONObject}. An empty JSON object is returned in the case an empty stream is passed. + */ + public static JSONObject toJsonObject(InputStream inputStream) throws ParseException { try { - String inputStr; - while ((inputStr = streamReader.readLine()) != null) { - responseStrBuilder.append(inputStr); - } - if (responseStrBuilder.toString().isBlank()) { - throw new ParseException("Empty input!"); + String inputStr = new String((inputStream.readAllBytes()), Charsets.UTF_8); + // Fallback: in case an empty stream was passed, return an empty JSON object + if (inputStr.isBlank()) { + return new JSONObject(); } - return new JSONObject(responseStrBuilder.toString()); - } catch (IOException e) { + return new JSONObject(inputStr); + } catch (IOException | JSONException e) { throw new ParseException(e); } } - - public static JSONObject toJsonObject(InputStream input) throws ParseException { - return toJsonObject(new InputStreamReader(input, StandardCharsets.UTF_8)); - } } diff --git a/src/main/java/org/jabref/logic/importer/util/MediaTypes.java b/src/main/java/org/jabref/logic/importer/util/MediaTypes.java index 23fa8da6d91..2edfc03cf29 100644 --- a/src/main/java/org/jabref/logic/importer/util/MediaTypes.java +++ b/src/main/java/org/jabref/logic/importer/util/MediaTypes.java @@ -5,4 +5,5 @@ */ public class MediaTypes { public static final String APPLICATION_BIBTEX = "application/x-bibtex"; + public static final String CITATIONSTYLES_JSON = "application/vnd.citationstyles.csl+json"; } diff --git a/src/main/java/org/jabref/model/entry/identifier/DOI.java b/src/main/java/org/jabref/model/entry/identifier/DOI.java index 742d4460430..9d687069831 100644 --- a/src/main/java/org/jabref/model/entry/identifier/DOI.java +++ b/src/main/java/org/jabref/model/entry/identifier/DOI.java @@ -1,16 +1,22 @@ package org.jabref.model.entry.identifier; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.jabref.logic.net.URLDownload; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.StandardField; +import kong.unirest.json.JSONArray; +import kong.unirest.json.JSONException; +import kong.unirest.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,10 +25,14 @@ * (DOIs) and Short DOIs */ public class DOI implements Identifier { + + public static final URI AGENCY_RESOLVER = URI.create("https://doi.org/doiRA"); + private static final Logger LOGGER = LoggerFactory.getLogger(DOI.class); // DOI/Short DOI resolver private static final URI RESOLVER = URI.create("https://doi.org"); + // Regex // (see http://www.doi.org/doi_handbook/2_Numbering.html) private static final String DOI_EXP = "" @@ -238,7 +248,7 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if ((o == null) || (getClass() != o.getClass())) { return false; } DOI other = (DOI) o; @@ -249,4 +259,23 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(doi.toLowerCase(Locale.ENGLISH)); } + + /** + * Returns registration agency. Optional.empty() if no agency is found. + */ + public Optional getAgency() throws IOException { + Optional agency = Optional.empty(); + try { + URLDownload download = new URLDownload(new URL(DOI.AGENCY_RESOLVER + "/" + doi)); + JSONObject response = new JSONArray(download.asString()).getJSONObject(0); + if (response != null) { + agency = Optional.ofNullable(response.optString("RA")); + } + } catch (JSONException e) { + LOGGER.error("Cannot parse agency fetcher repsonse to JSON"); + return Optional.empty(); + } + + return agency; + } } diff --git a/src/test/java/org/jabref/logic/importer/fetcher/CrossRefTest.java b/src/test/java/org/jabref/logic/importer/fetcher/CrossRefTest.java index 1ae95774833..7409b317838 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/CrossRefTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/CrossRefTest.java @@ -4,6 +4,7 @@ import java.util.Locale; import java.util.Optional; +import org.jabref.logic.importer.FetcherException; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.StandardEntryType; @@ -138,4 +139,12 @@ public void performSearchByEmptyId() throws Exception { public void performSearchByEmptyQuery() throws Exception { assertEquals(Collections.emptyList(), fetcher.performSearch("")); } + + /** + * reveal fetching error on crossref performSearchById + */ + @Test + public void testPerformSearchValidReturnNothingDOI() throws FetcherException { + assertEquals(Optional.empty(), fetcher.performSearchById("10.1392/BC1.0")); + } } diff --git a/src/test/java/org/jabref/logic/importer/fetcher/DoiFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/DoiFetcherTest.java index eac8a4021f3..ade75d2c307 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/DoiFetcherTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/DoiFetcherTest.java @@ -23,6 +23,7 @@ public class DoiFetcherTest { private DoiFetcher fetcher; private BibEntry bibEntryBurd2011; private BibEntry bibEntryDecker2007; + private BibEntry bibEntryIannarelli2019; @BeforeEach public void setUp() { @@ -48,6 +49,26 @@ public void setUp() { bibEntryDecker2007.setField(StandardField.TITLE, "{BPEL}4Chor: Extending {BPEL} for Modeling Choreographies"); bibEntryDecker2007.setField(StandardField.YEAR, "2007"); bibEntryDecker2007.setField(StandardField.DOI, "10.1109/icws.2007.59"); + + // mEDRA BibEntry + bibEntryIannarelli2019 = new BibEntry(StandardEntryType.Article) + .withField(StandardField.AUTHOR, + "" + + "Iannarelli Riccardo and " + + "Novello Anna and " + + "Stricker Damien and " + + "Cisternino Marco and " + + "Gallizio Federico and " + + "Telib Haysam and " + + "Meyer Thierry ") + .withField(StandardField.PUBLISHER, "AIDIC: Italian Association of Chemical Engineering") + .withField(StandardField.TITLE, "Safety in research institutions: how to better communicate the risks using numerical simulations") + .withField(StandardField.YEAR, "2019") + .withField(StandardField.DOI, "10.3303/CET1977146") + .withField(StandardField.JOURNAL, "Chemical Engineering Transactions") + .withField(StandardField.PAGES, "871-876") + .withField(StandardField.URL, "http://doi.org/10.3303/CET1977146") + .withField(StandardField.VOLUME, "77"); } @Test @@ -67,6 +88,12 @@ public void testPerformSearchDecker2007() throws FetcherException { assertEquals(Optional.of(bibEntryDecker2007), fetchedEntry); } + @Test + public void testPerformSearchIannarelli2019() throws FetcherException { + Optional fetchedEntry = fetcher.performSearchById("10.3303/CET1977146"); + assertEquals(Optional.of(bibEntryIannarelli2019), fetchedEntry); + } + @Test public void testPerformSearchEmptyDOI() { assertThrows(FetcherException.class, () -> fetcher.performSearchById("")); diff --git a/src/test/java/org/jabref/logic/importer/fetcher/MedraTest.java b/src/test/java/org/jabref/logic/importer/fetcher/MedraTest.java new file mode 100644 index 00000000000..09ff050029e --- /dev/null +++ b/src/test/java/org/jabref/logic/importer/fetcher/MedraTest.java @@ -0,0 +1,90 @@ +package org.jabref.logic.importer.fetcher; + +import java.util.Optional; +import java.util.stream.Stream; + +import org.jabref.logic.importer.FetcherException; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MedraTest { + + private final Medra fetcher = new Medra(); + + private static Stream getDoiBibEntryPairs() { + return Stream.of( + Arguments.of("10.1016/j.bjoms.2007.08.004", + Optional.empty()), + + Arguments.of("10.2143/TVF.80.3.3285690", + Optional.of( + new BibEntry(StandardEntryType.Article) + .withField(StandardField.AUTHOR, "SPILEERS, Steven ") + .withField(StandardField.PUBLISHER, "Peeters online journals") + .withField(StandardField.TITLE, "Algemene kroniek") + .withField(StandardField.YEAR, "2018") + .withField(StandardField.DOI, "10.2143/TVF.80.3.3285690") + .withField(StandardField.ISSN, "2031-8952") + .withField(StandardField.JOURNAL, "Tijdschrift voor Filosofie") + .withField(StandardField.PAGES, "625-629") + )), + + Arguments.of("10.3303/CET1977146", + Optional.of( + new BibEntry(StandardEntryType.Article) + .withField(StandardField.AUTHOR, + "" + + "Iannarelli Riccardo and " + + "Novello Anna and " + + "Stricker Damien and " + + "Cisternino Marco and " + + "Gallizio Federico and " + + "Telib Haysam and " + + "Meyer Thierry ") + .withField(StandardField.PUBLISHER, "AIDIC: Italian Association of Chemical Engineering") + .withField(StandardField.TITLE, "Safety in research institutions: how to better communicate the risks using numerical simulations") + .withField(StandardField.YEAR, "2019") + .withField(StandardField.DOI, "10.3303/CET1977146") + .withField(StandardField.JOURNAL, "Chemical Engineering Transactions") + .withField(StandardField.PAGES, "871-876") + .withField(StandardField.VOLUME, "77"))), + Arguments.of("10.1400/115378", + Optional.of( + new BibEntry(StandardEntryType.Article) + .withField(StandardField.AUTHOR, "Paola Cisternino") + .withField(StandardField.PUBLISHER, "Edizioni Otto Novecento") + .withField(StandardField.TITLE, "Diagramma semantico dei lemmi : casa, parola, silenzio e attesa in È fatto giorno e Margherite e rosolacci di Rocco Scotellaro") + .withField(StandardField.ISSN, "0391-2639") + .withField(StandardField.YEAR, "1999") + .withField(StandardField.DOI, "10.1400/115378") + .withField(StandardField.JOURNAL, "Otto/Novecento : rivista quadrimestrale di critica e storia letteraria") + )) + ); + } + + @Test + public void testGetName() { + assertEquals("mEDRA", fetcher.getName()); + } + + @Test + public void testPerformSearchEmptyDOI() throws FetcherException { + assertEquals(Optional.empty(), fetcher.performSearchById("")); + } + + @ParameterizedTest + @MethodSource("getDoiBibEntryPairs") + public void testDoiBibEntryPairs(String identifier, Optional expected) throws FetcherException { + Optional fetchedEntry = fetcher.performSearchById(identifier); + assertEquals(expected, fetchedEntry); + } + +} diff --git a/src/test/java/org/jabref/logic/importer/util/JsonReaderTest.java b/src/test/java/org/jabref/logic/importer/util/JsonReaderTest.java new file mode 100644 index 00000000000..cc486623512 --- /dev/null +++ b/src/test/java/org/jabref/logic/importer/util/JsonReaderTest.java @@ -0,0 +1,50 @@ +package org.jabref.logic.importer.util; + +import java.io.ByteArrayInputStream; + +import org.jabref.logic.importer.ParseException; + +import kong.unirest.json.JSONObject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class JsonReaderTest { + + @Test + void nullStreamThrowsNullPointerException() { + Assertions.assertThrows(NullPointerException.class, () -> { + JsonReader.toJsonObject(null); + }); + } + + @Test + void invalidJsonThrowsParserException() { + Assertions.assertThrows(ParseException.class, () -> { + JsonReader.toJsonObject(new ByteArrayInputStream("invalid JSON".getBytes())); + }); + } + + @Test + void emptyStringResultsInEmptyObject() throws Exception { + JSONObject result = JsonReader.toJsonObject(new ByteArrayInputStream("".getBytes())); + assertEquals("{}", result.toString()); + } + + @Test + void arrayThrowsParserException() { + // Reason: We expect a JSON object, not a JSON array + Assertions.assertThrows(ParseException.class, () -> { + JsonReader.toJsonObject(new ByteArrayInputStream("[]".getBytes())); + }); + } + + @Test + void exampleJsonResultsInSameJson() throws Exception { + String input = "{\"name\":\"test\"}"; + JSONObject result = JsonReader.toJsonObject(new ByteArrayInputStream(input.getBytes())); + assertEquals(input, result.toString()); + } + +}