diff --git a/CHANGELOG.md b/CHANGELOG.md index 796c8e28ff5..ed9808660c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - We added an option to show the preview as an extra tab in the entry editor (instead of in a split view). [#5244](https://github.com/JabRef/jabref/issues/5244) - A custom Open/LibreOffice jstyle file now requires a layout line for the entry type `default` [#5452](https://github.com/JabRef/jabref/issues/5452) - The entry editor is now open by default when JabRef starts up. [#5460](https://github.com/JabRef/jabref/issues/5460) +- We add a new ADS fetcher to use the new ADS API [#4949](https://github.com/JabRef/jabref/issues/4949) ### Fixed 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 708039914e3..70f81f095dd 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java @@ -1,15 +1,16 @@ package org.jabref.logic.importer.fetcher; import java.io.IOException; -import java.io.InputStream; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; -import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; import org.jabref.logic.cleanup.MoveFieldCleanup; import org.jabref.logic.formatter.bibtexfields.ClearFormatter; @@ -25,7 +26,6 @@ import org.jabref.logic.importer.Parser; import org.jabref.logic.importer.SearchBasedParserFetcher; import org.jabref.logic.importer.fileformat.BibtexParser; -import org.jabref.logic.l10n.Localization; import org.jabref.logic.net.URLDownload; import org.jabref.model.cleanup.FieldFormatterCleanup; import org.jabref.model.entry.BibEntry; @@ -35,85 +35,96 @@ import org.jabref.model.util.DummyFileUpdateMonitor; import org.apache.http.client.utils.URIBuilder; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; /** - * Fetches data from the SAO/NASA Astrophysics Data System (http://www.adsabs.harvard.edu/) - * - * Search query-based: http://adsabs.harvard.edu/basic_search.html - * Entry -based: http://adsabs.harvard.edu/abstract_service.html - * - * There is also a new API (https://github.com/adsabs/adsabs-dev-api) but it returns JSON - * (or at least needs multiple calls to get BibTeX, status: September 2016) + * Fetches data from the SAO/NASA Astrophysics Data System (https://ui.adsabs.harvard.edu/) */ public class AstrophysicsDataSystem implements IdBasedParserFetcher, SearchBasedParserFetcher, EntryBasedParserFetcher { - private static String API_QUERY_URL = "http://adsabs.harvard.edu/cgi-bin/nph-basic_connect"; - private static String API_ENTRY_URL = "http://adsabs.harvard.edu/cgi-bin/nph-abs_connect"; - private static String API_DOI_URL = "http://adsabs.harvard.edu/doi/"; + private static final String API_SEARCH_URL = "https://api.adsabs.harvard.edu/v1/search/query"; + private static final String API_EXPORT_URL = "https://api.adsabs.harvard.edu/v1/export/bibtexabs"; - private final String patternRemoveDOI = "^(doi:|DOI:)"; + private static final String API_KEY = "tDueGIu6zl96OqkcCS5LOHboWbTgEEx8yAR7Etta"; private final ImportFormatPreferences preferences; public AstrophysicsDataSystem(ImportFormatPreferences preferences) { this.preferences = Objects.requireNonNull(preferences); } + /** + * @param bibcodes collection of bibcodes for which a JSON object should be created + */ + private static String buildPostData(Collection bibcodes) { + JSONObject obj = new JSONObject(); + obj.put("bibcode", bibcodes); + return obj.toString(); + } + + /** + * @return export URL endpoint + */ + private static URL getURLforExport() throws URISyntaxException, MalformedURLException { + return new URIBuilder(API_EXPORT_URL).build().toURL(); + } + @Override public String getName() { return "SAO/NASA Astrophysics Data System"; } - private URIBuilder getBaseUrl(String apiUrl) throws URISyntaxException { - URIBuilder uriBuilder = new URIBuilder(apiUrl); - uriBuilder.addParameter("data_type", "BIBTEXPLUS"); - uriBuilder.addParameter("start_nr", String.valueOf(1)); - uriBuilder.addParameter("nr_to_return", String.valueOf(200)); - return uriBuilder; - } - + /** + * @param query query string, matching the apache solr format + * @return URL which points to a search request for given query + */ @Override public URL getURLForQuery(String query) throws URISyntaxException, MalformedURLException, FetcherException { - URIBuilder uriBuilder = getBaseUrl(API_QUERY_URL); - uriBuilder.addParameter("qsearch", query); - return uriBuilder.build().toURL(); + URIBuilder builder = new URIBuilder(API_SEARCH_URL); + builder.addParameter("q", query); + builder.addParameter("fl", "bibcode"); + return builder.build().toURL(); } + /** + * @param entry BibEntry for which a search URL is created + * @return URL which points to a search request for given entry + */ @Override public URL getURLForEntry(BibEntry entry) throws URISyntaxException, MalformedURLException, FetcherException { - URIBuilder uriBuilder = getBaseUrl(API_ENTRY_URL); - - // Search astronomy + physics + arXiv db - uriBuilder.addParameter("db_key", "AST"); - uriBuilder.addParameter("db_key", "PHY"); - uriBuilder.addParameter("db_key", "PRE"); - - // Add title search - entry.getFieldOrAlias(StandardField.TITLE).ifPresent(title -> { - uriBuilder.addParameter("ttl_logic", "OR"); - uriBuilder.addParameter("title", title); - uriBuilder.addParameter("ttl_syn", "YES"); // Synonym replacement - uriBuilder.addParameter("ttl_wt", "0.3"); // Weight - uriBuilder.addParameter("ttl_wgt", "YES"); // Consider Weight - }); - - // Add author search - entry.getFieldOrAlias(StandardField.AUTHOR).ifPresent(author -> { - uriBuilder.addParameter("aut_logic", "OR"); - uriBuilder.addParameter("author", author); - uriBuilder.addParameter("aut_syn", "YES"); // Synonym replacement - uriBuilder.addParameter("aut_wt", "1.0"); // Weight - uriBuilder.addParameter("aut_wgt", "YES"); // Consider weight - }); - - return uriBuilder.build().toURL(); + StringBuilder stringBuilder = new StringBuilder(); + + Optional title = entry.getFieldOrAlias(StandardField.TITLE).map(t -> "title:\"" + t + "\""); + Optional author = entry.getFieldOrAlias(StandardField.AUTHOR).map(a -> "author:\"" + a + "\""); + + if (title.isPresent()) { + stringBuilder.append(title.get()) + .append(author.map(s -> " AND " + s) + .orElse("")); + } else { + stringBuilder.append(author.orElse("")); + } + String query = stringBuilder.toString().trim(); + + URIBuilder builder = new URIBuilder(API_SEARCH_URL); + builder.addParameter("q", query); + builder.addParameter("fl", "bibcode"); + builder.addParameter("rows", "20"); + return builder.build().toURL(); } + /** + * @param identifier bibcode or doi for which a search URL is created + * @return URL which points to a search URL for given identifier + */ @Override - public URL getURLForID(String identifier) throws URISyntaxException, MalformedURLException, FetcherException { - String key = identifier.replaceAll(patternRemoveDOI, ""); - URIBuilder uriBuilder = new URIBuilder(API_DOI_URL + key); - uriBuilder.addParameter("data_type", "BIBTEXPLUS"); - return uriBuilder.build().toURL(); + public URL getURLForID(String identifier) throws FetcherException, URISyntaxException, MalformedURLException { + String query = "doi:\"" + identifier + "\" OR " + "bibcode:\"" + identifier + "\""; + URIBuilder builder = new URIBuilder(API_SEARCH_URL); + builder.addParameter("q", query); + builder.addParameter("fl", "bibcode"); + return builder.build().toURL(); } @Override @@ -126,45 +137,143 @@ public Parser getParser() { return new BibtexParser(preferences, new DummyFileUpdateMonitor()); } + @Override + public void doPostCleanup(BibEntry entry) { + new FieldFormatterCleanup(StandardField.ABSTRACT, new RemoveBracesFormatter()).cleanup(entry); + new FieldFormatterCleanup(StandardField.ABSTRACT, new RemoveNewlinesFormatter()).cleanup(entry); + new FieldFormatterCleanup(StandardField.TITLE, new RemoveBracesFormatter()).cleanup(entry); + new FieldFormatterCleanup(StandardField.AUTHOR, new NormalizeNamesFormatter()).cleanup(entry); + + // Remove ADS note + new FieldFormatterCleanup(new UnknownField("adsnote"), new ClearFormatter()).cleanup(entry); + // Move adsurl to url field + new MoveFieldCleanup(new UnknownField("adsurl"), StandardField.URL).cleanup(entry); + // The fetcher adds some garbage (number of found entries etc before) + entry.setCommentsBeforeEntry(""); + } + + @Override + public List performSearch(BibEntry entry) throws FetcherException { + + if (entry.getFieldOrAlias(StandardField.TITLE).isEmpty() && entry.getFieldOrAlias(StandardField.AUTHOR).isEmpty()) { + return Collections.emptyList(); + } + + try { + List bibcodes = fetchBibcodes(getURLForEntry(entry)); + return performSearchByIds(bibcodes); + } catch (URISyntaxException e) { + throw new FetcherException("Search URI is malformed", e); + } catch (IOException e) { + throw new FetcherException("A network error occurred", e); + } + } + @Override public List performSearch(String query) throws FetcherException { + if (StringUtil.isBlank(query)) { return Collections.emptyList(); } try { - URLConnection connection = getURLForQuery(query).openConnection(); - connection.setRequestProperty("User-Agent", URLDownload.USER_AGENT); - try (InputStream stream = connection.getInputStream()) { - List fetchedEntries = getParser().parseEntries(stream); + List bibcodes = fetchBibcodes(getURLForQuery(query)); + return performSearchByIds(bibcodes); + } catch (URISyntaxException e) { + throw new FetcherException("Search URI is malformed", e); + } catch (IOException e) { + throw new FetcherException("A network error occurred", e); + } + } + + /** + * @param url search ul for which bibcode will be returned + * @return list of bibcodes matching the search request. May be empty + */ + private List fetchBibcodes(URL url) throws FetcherException { + + try { + URLDownload download = new URLDownload(url); + download.addHeader("Authorization", "Bearer " + API_KEY); + String content = download.asString(); + JSONObject obj = new JSONObject(content); + JSONArray codes = obj.getJSONObject("response").getJSONArray("docs"); + List bibcodes = new ArrayList<>(); + for (int i = 0; i < codes.length(); i++) { + bibcodes.add(codes.getJSONObject(i).getString("bibcode")); + } + return bibcodes; + } catch (IOException e) { + throw new FetcherException("A network error occurred", e); + } catch (JSONException e) { + return Collections.emptyList(); + } + } + + @Override + public Optional performSearchById(String identifier) throws FetcherException { + if (StringUtil.isBlank(identifier)) { + return Optional.empty(); + } + + try { + List bibcodes = fetchBibcodes(getURLForID(identifier)); + List fetchedEntries = performSearchByIds(bibcodes); + + if (fetchedEntries.isEmpty()) { + return Optional.empty(); + } + if (fetchedEntries.size() > 1) { + LOGGER.info("Fetcher " + getName() + "found more than one result for identifier " + identifier + + ". We will use the first entry."); + } + BibEntry entry = fetchedEntries.get(0); + return Optional.of(entry); + } catch (URISyntaxException e) { + throw new FetcherException("Search URI is malformed", e); + } catch (IOException e) { + throw new FetcherException("A network error occurred", e); + } + } + + /** + * @param identifiers bibcodes for which bibentries ahould be fetched + * @return list of bibentries matching the bibcodes. Can be empty and differ in size to the size of requested + * bibcodes + */ + private List performSearchByIds(Collection identifiers) throws FetcherException { + List ids = identifiers.stream().filter(identifier -> !StringUtil.isBlank(identifier)).collect(Collectors.toList()); + if (ids.isEmpty()) { + return Collections.emptyList(); + } + try { + String postData = buildPostData(ids); + URLDownload download = new URLDownload(getURLforExport()); + download.addHeader("Authorization", "Bearer " + API_KEY); + download.addHeader("ContentType", "application/json"); + download.setPostData(postData); + String content = download.asString(); + JSONObject obj = new JSONObject(content); + + try { + List fetchedEntries = getParser().parseEntries(obj.optString("export")); + if (fetchedEntries.isEmpty()) { + return Collections.emptyList(); + } // Post-cleanup fetchedEntries.forEach(this::doPostCleanup); + return fetchedEntries; - } catch (IOException e) { - throw new FetcherException("An I/O exception occurred", e); + } catch (JSONException e) { + return Collections.emptyList(); } - } catch (URISyntaxException | MalformedURLException e) { + } catch (URISyntaxException e) { throw new FetcherException("Search URI is malformed", e); } catch (IOException e) { - throw new FetcherException("An I/O exception occurred", e); + throw new FetcherException("A network error occurred", e); } catch (ParseException e) { - throw new FetcherException("Error occurred when parsing entry", Localization.lang("Error occurred when parsing entry"), e); + throw new FetcherException("An internal parser error occurred", e); } } - - @Override - public void doPostCleanup(BibEntry entry) { - new FieldFormatterCleanup(StandardField.ABSTRACT, new RemoveBracesFormatter()).cleanup(entry); - new FieldFormatterCleanup(StandardField.ABSTRACT, new RemoveNewlinesFormatter()).cleanup(entry); - new FieldFormatterCleanup(StandardField.TITLE, new RemoveBracesFormatter()).cleanup(entry); - new FieldFormatterCleanup(StandardField.AUTHOR, new NormalizeNamesFormatter()).cleanup(entry); - - // Remove ADS note - new FieldFormatterCleanup(new UnknownField("adsnote"), new ClearFormatter()).cleanup(entry); - // Move adsurl to url field - new MoveFieldCleanup(new UnknownField("adsurl"), StandardField.URL).cleanup(entry); - // The fetcher adds some garbage (number of found entries etc before) - entry.setCommentsBeforeEntry(""); - } } diff --git a/src/test/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystemTest.java b/src/test/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystemTest.java index 4ece520646d..f3141019524 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystemTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystemTest.java @@ -1,6 +1,5 @@ package org.jabref.logic.importer.fetcher; -import java.util.Collections; import java.util.List; import java.util.Optional; @@ -8,7 +7,6 @@ import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; -import org.jabref.model.entry.field.UnknownField; import org.jabref.model.entry.types.StandardEntryType; import org.jabref.testutils.category.FetcherTest; @@ -17,6 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -35,95 +34,89 @@ public void setUp() throws Exception { diezSliceTheoremEntry = new BibEntry(); diezSliceTheoremEntry.setType(StandardEntryType.Article); - diezSliceTheoremEntry.setCiteKey("2014arXiv1405.2249D"); - diezSliceTheoremEntry.setField(StandardField.AUTHOR, "Diez, T."); - diezSliceTheoremEntry.setField(StandardField.TITLE, "Slice theorem for Fr$\\backslash$'echet group actions and covariant symplectic field theory"); - diezSliceTheoremEntry.setField(StandardField.YEAR, "2014"); + diezSliceTheoremEntry.setCiteKey("2018arXiv181204698D"); + diezSliceTheoremEntry.setField(StandardField.AUTHOR, "Diez, Tobias and Rudolph, Gerd"); + diezSliceTheoremEntry.setField(StandardField.TITLE, "Slice theorem and orbit type stratification in infinite dimensions"); + diezSliceTheoremEntry.setField(StandardField.YEAR, "2018"); diezSliceTheoremEntry.setField(StandardField.ARCHIVEPREFIX, "arXiv"); - diezSliceTheoremEntry.setField(StandardField.EPRINT, "1405.2249"); - diezSliceTheoremEntry.setField(StandardField.JOURNAL, "ArXiv e-prints"); - diezSliceTheoremEntry.setField(StandardField.KEYWORDS, "Mathematical Physics, Mathematics - Differential Geometry, Mathematics - Symplectic Geometry, 58B99, 58Z05, 58B25, 22E65, 58D19, 53D20, 53D42"); - diezSliceTheoremEntry.setField(StandardField.MONTH, "#may#"); - diezSliceTheoremEntry.setField(new UnknownField("primaryclass"), "math-ph"); - diezSliceTheoremEntry.setField(StandardField.URL, "http://adsabs.harvard.edu/abs/2014arXiv1405.2249D"); + diezSliceTheoremEntry.setField(StandardField.EPRINT, "1812.04698"); + diezSliceTheoremEntry.setField(StandardField.JOURNAL, "arXiv e-prints"); + diezSliceTheoremEntry.setField(StandardField.KEYWORDS, "Mathematics - Differential Geometry, Mathematical Physics, 58B25, (58D19, 58B20, 22E99, 58A35)"); + diezSliceTheoremEntry.setField(StandardField.MONTH, "Dec"); + diezSliceTheoremEntry.setField(StandardField.PAGES, "arXiv:1812.04698"); + diezSliceTheoremEntry.setField(StandardField.EID, "arXiv:1812.04698"); + diezSliceTheoremEntry.setField(StandardField.PRIMARYCLASS, "math.DG"); + diezSliceTheoremEntry.setField(StandardField.URL, "https://ui.adsabs.harvard.edu/abs/2018arXiv181204698D"); diezSliceTheoremEntry.setField(StandardField.ABSTRACT, - "A general slice theorem for the action of a Fr$\\backslash$'echet Lie group on a " - + "Fr$\\backslash$'echet manifolds is established. The Nash-Moser theorem provides the " - + "fundamental tool to generalize the result of Palais to this " - + "infinite-dimensional setting. The presented slice theorem is illustrated " - + "by its application to gauge theories: the action of the gauge " - + "transformation group admits smooth slices at every point and thus the " - + "gauge orbit space is stratified by Fr$\\backslash$'echet manifolds. Furthermore, a " - + "covariant and symplectic formulation of classical field theory is " - + "proposed and extensively discussed. At the root of this novel framework " - + "is the incorporation of field degrees of freedom F and spacetime M into " - + "the product manifold F * M. The induced bigrading of differential forms " - + "is used in order to carry over the usual symplectic theory to this new " - + "setting. The examples of the Klein-Gordon field and general Yang-Mills " - + "theory illustrate that the presented approach conveniently handles the " - + "occurring symmetries."); + "We establish a general slice theorem for the action of a locally convex Lie group on a locally convex manifold, which generalizes the classical slice theorem of Palais to infinite dimensions. We discuss two important settings under which the assumptions of this theorem are fulfilled. First, using Gl{\\\"o}ckner's inverse function theorem, we show that the linear action of a compact Lie group on a Fr{\\'e}chet space admits a slice. Second, using the Nash--Moser theorem, we establish a slice theorem for the tame action of a tame Fr{\\'e}chet Lie group on a tame Fr{\\'e}chet manifold. For this purpose, we develop the concept of a graded Riemannian metric, which allows the construction of a path-length metric compatible with the manifold topology and of a local addition. Finally, generalizing a classical result in finite dimensions, we prove that the existence of a slice implies that the decomposition of the manifold into orbit types of the group action is a stratification."); famaeyMcGaughEntry = new BibEntry(); famaeyMcGaughEntry.setType(StandardEntryType.Article); famaeyMcGaughEntry.setCiteKey("2012LRR....15...10F"); - famaeyMcGaughEntry.setField(StandardField.AUTHOR, "Famaey, B. and McGaugh, S. S."); + famaeyMcGaughEntry.setField(StandardField.AUTHOR, "Famaey, Beno{\\^\\i}t and McGaugh, Stacy S."); famaeyMcGaughEntry.setField(StandardField.TITLE, "Modified Newtonian Dynamics (MOND): Observational Phenomenology and Relativistic Extensions"); famaeyMcGaughEntry.setField(StandardField.JOURNAL, "Living Reviews in Relativity"); famaeyMcGaughEntry.setField(StandardField.YEAR, "2012"); famaeyMcGaughEntry.setField(StandardField.VOLUME, "15"); - famaeyMcGaughEntry.setField(StandardField.MONTH, "#sep#"); + famaeyMcGaughEntry.setField(StandardField.MONTH, "Sep"); + famaeyMcGaughEntry.setField(StandardField.NUMBER, "1"); famaeyMcGaughEntry.setField(StandardField.ARCHIVEPREFIX, "arXiv"); famaeyMcGaughEntry.setField(StandardField.DOI, "10.12942/lrr-2012-10"); - famaeyMcGaughEntry.setField(new UnknownField("eid"), "10"); + famaeyMcGaughEntry.setField(StandardField.PRIMARYCLASS, "astro-ph.CO"); + famaeyMcGaughEntry.setField(StandardField.EID, "10"); famaeyMcGaughEntry.setField(StandardField.EPRINT, "1112.3960"); famaeyMcGaughEntry.setField(StandardField.PAGES, "10"); - famaeyMcGaughEntry.setField(StandardField.KEYWORDS, "astronomical observations, Newtonian limit, equations of motion, extragalactic astronomy, cosmology, theories of gravity, fundamental physics, astrophysics"); - famaeyMcGaughEntry.setField(StandardField.URL, "http://adsabs.harvard.edu/abs/2012LRR....15...10F"); + famaeyMcGaughEntry.setField(StandardField.KEYWORDS, "astronomical observations, Newtonian limit, equations of motion, extragalactic astronomy, cosmology, theories of gravity, fundamental physics, astrophysics, Astrophysics - Cosmology and Nongalactic Astrophysics, Astrophysics - Astrophysics of Galaxies, General Relativity and Quantum Cosmology, High Energy Physics - Phenomenology, High Energy Physics - Theory"); + famaeyMcGaughEntry.setField(StandardField.URL, "https://ui.adsabs.harvard.edu/abs/2012LRR....15...10F"); sunWelchEntry = new BibEntry(); sunWelchEntry.setType(StandardEntryType.Article); sunWelchEntry.setCiteKey("2012NatMa..11...44S"); - sunWelchEntry.setField(StandardField.AUTHOR, "Sun, Y. and Welch, G. C. and Leong, W. L. and Takacs, C. J. and Bazan, G. C. and Heeger, A. J."); + sunWelchEntry.setField(StandardField.AUTHOR, "Sun, Yanming and Welch, Gregory C. and Leong, Wei Lin and Takacs, Christopher J. and Bazan, Guillermo C. and Heeger, Alan J."); sunWelchEntry.setField(StandardField.DOI, "10.1038/nmat3160"); sunWelchEntry.setField(StandardField.JOURNAL, "Nature Materials"); - sunWelchEntry.setField(StandardField.MONTH, "#jan#"); + sunWelchEntry.setField(StandardField.MONTH, "Jan"); + sunWelchEntry.setField(StandardField.NUMBER, "1"); sunWelchEntry.setField(StandardField.PAGES, "44-48"); sunWelchEntry.setField(StandardField.TITLE, "Solution-processed small-molecule solar cells with 6.7\\% efficiency"); sunWelchEntry.setField(StandardField.VOLUME, "11"); sunWelchEntry.setField(StandardField.YEAR, "2012"); - sunWelchEntry.setField(StandardField.URL, "http://adsabs.harvard.edu/abs/2012NatMa..11...44S"); + sunWelchEntry.setField(StandardField.URL, "https://ui.adsabs.harvard.edu/abs/2012NatMa..11...44S"); xiongSunEntry = new BibEntry(); xiongSunEntry.setType(StandardEntryType.Article); xiongSunEntry.setCiteKey("2007ITGRS..45..879X"); - xiongSunEntry.setField(StandardField.AUTHOR, "Xiong, X. and Sun, J. and Barnes, W. and Salomonson, V. and Esposito, J. and Erives, H. and Guenther, B."); + xiongSunEntry.setField(StandardField.AUTHOR, "Xiong, Xiaoxiong and Sun, Junqiang and Barnes, William and Salomonson, Vincent and Esposito, Joseph and Erives, Hector and Guenther, Bruce"); xiongSunEntry.setField(StandardField.DOI, "10.1109/TGRS.2006.890567"); xiongSunEntry.setField(StandardField.JOURNAL, "IEEE Transactions on Geoscience and Remote Sensing"); - xiongSunEntry.setField(StandardField.MONTH, "#apr#"); + xiongSunEntry.setField(StandardField.MONTH, "Apr"); + xiongSunEntry.setField(StandardField.NUMBER, "4"); xiongSunEntry.setField(StandardField.PAGES, "879-889"); xiongSunEntry.setField(StandardField.TITLE, "Multiyear On-Orbit Calibration and Performance of Terra MODIS Reflective Solar Bands"); xiongSunEntry.setField(StandardField.VOLUME, "45"); xiongSunEntry.setField(StandardField.YEAR, "2007"); - xiongSunEntry.setField(StandardField.URL, "http://adsabs.harvard.edu/abs/2007ITGRS..45..879X"); + xiongSunEntry.setField(StandardField.URL, "https://ui.adsabs.harvard.edu/abs/2007ITGRS..45..879X"); ingersollPollardEntry = new BibEntry(); ingersollPollardEntry.setType(StandardEntryType.Article); ingersollPollardEntry.setCiteKey("1982Icar...52...62I"); + ingersollPollardEntry.setField(StandardField.ABSTRACT, "If Jupiter's and Saturn's fluid interiors were inviscid and adiabatic, any steady zonal motion would take the form of differentially rotating cylinders concentric about the planetary axis of rotation. B. A. Smith et al. [ Science215, 504-537 (1982)] showed that Saturn's observed zonal wind profile extends a significant distance below cloud base. Further extension into the interior occurs if the values of the eddy viscosity and superadiabaticity are small. We estimate these values using a scaling analysis of deep convection in the presence of differential rotation. The differential rotation inhibits the convection and reduces the effective eddy viscosity. Viscous dissipation of zonal mean kinetic energy is then within the bounds set by the internal heat source. The differential rotation increases the superadiabaticity, but not so much as to eliminate the cylindrical structure of the flow. Very large departures from adiabaticity, necessary for decoupling the atmosphere and interior, do not occur. Using our scaling analysis we develop the anelastic equations that describe motions in Jupiter's and Saturn's interiors. A simple problem is solved, that of an adiabatic fluid with a steady zonal wind varying as a function of cylindrical radius. Low zonal wavenumber perturbations are two dimensional (independent of the axial coordinate) and obey a modified barotropic stability equation. The parameter analogous to {\\ensuremath{\\beta}} is negative and is three to four times larger than the {\\ensuremath{\\beta}} for thin atmospheres. Jupiter's and Saturn's observed zonal wind profiles are close to marginal stability according to this deep sphere criterion, but are several times supercritical according to the thin atmosphere criterion."); ingersollPollardEntry.setField(StandardField.AUTHOR, "Ingersoll, A. P. and Pollard, D."); ingersollPollardEntry.setField(StandardField.DOI, "10.1016/0019-1035(82)90169-5"); ingersollPollardEntry.setField(StandardField.JOURNAL, "\\icarus"); - ingersollPollardEntry.setField(StandardField.KEYWORDS, "Atmospheric Circulation, Barotropic Flow, Convective Flow, Flow Stability, Jupiter Atmosphere, Rotating Fluids, Saturn Atmosphere, Adiabatic Flow, Anelasticity, Compressible Fluids, Planetary Rotation, Rotating Cylinders, Scaling Laws, Wind Profiles, PLANETS, JUPITER, SATURN, MOTION, INTERIORS, ATMOSPHERE, ANALYSIS, SCALE, BAROTROPY, CHARACTERISTICS, STRUCTURE, WINDS, VISCOSITY, DATA, CONVECTION, ROTATION, EDDY EFFECTS, ENERGY, ADIABATICITY, DIAGRAMS, REVIEW, LATITUDE, ZONES, VELOCITY, MATHEMATICAL MODELS, HEAT FLOW, EQUATIONS OF MOTION, FLUIDS, DYNAMICS, TEMPERATURE, GRADIENTS"); - ingersollPollardEntry.setField(StandardField.MONTH, "#oct#"); + ingersollPollardEntry.setField(StandardField.KEYWORDS, "Atmospheric Circulation, Barotropic Flow, Convective Flow, Flow Stability, Jupiter Atmosphere, Rotating Fluids, Saturn Atmosphere, Adiabatic Flow, Anelasticity, Compressible Fluids, Planetary Rotation, Rotating Cylinders, Scaling Laws, Wind Profiles, PLANETS, JUPITER, SATURN, MOTION, INTERIORS, ATMOSPHERE, ANALYSIS, SCALE, BAROTROPY, CHARACTERISTICS, STRUCTURE, WINDS, VISCOSITY, DATA, CONVECTION, ROTATION, EDDY EFFECTS, ENERGY, ADIABATICITY, DIAGRAMS, REVIEW, LATITUDE, ZONES, VELOCITY, MATHEMATICAL MODELS, HEAT FLOW, EQUATIONS OF MOTION, FLUIDS, DYNAMICS, TEMPERATURE, GRADIENTS, Lunar and Planetary Exploration; Planets"); + ingersollPollardEntry.setField(StandardField.MONTH, "Oct"); + ingersollPollardEntry.setField(StandardField.NUMBER, "1"); ingersollPollardEntry.setField(StandardField.PAGES, "62-80"); - ingersollPollardEntry.setField(StandardField.TITLE, "Motion in the interiors and atmospheres of Jupiter and Saturn - Scale analysis, anelastic equations, barotropic stability criterion"); + ingersollPollardEntry.setField(StandardField.TITLE, "Motion in the interiors and atmospheres of Jupiter and Saturn: scale analysis, anelastic equations, barotropic stability criterion"); ingersollPollardEntry.setField(StandardField.VOLUME, "52"); ingersollPollardEntry.setField(StandardField.YEAR, "1982"); - ingersollPollardEntry.setField(StandardField.URL, "http://adsabs.harvard.edu/abs/1982Icar...52...62I"); + ingersollPollardEntry.setField(StandardField.URL, "https://ui.adsabs.harvard.edu/abs/1982Icar...52...62I"); luceyPaulEntry = new BibEntry(); luceyPaulEntry.setType(StandardEntryType.Article); luceyPaulEntry.setCiteKey("2000JGR...10520297L"); - luceyPaulEntry.setField(StandardField.AUTHOR, "Lucey, P. G. and Blewett, D. T. and Jolliff, B. L."); + luceyPaulEntry.setField(StandardField.AUTHOR, "Lucey, Paul G. and Blewett, David T. and Jolliff, Bradley L."); luceyPaulEntry.setField(StandardField.DOI, "10.1029/1999JE001117"); luceyPaulEntry.setField(StandardField.JOURNAL, "\\jgr"); luceyPaulEntry.setField(StandardField.KEYWORDS, "Planetology: Solid Surface Planets: Composition, Planetology: Solid Surface Planets: Remote sensing, Planetology: Solid Surface Planets: Surface materials and properties, Planetology: Solar System Objects: Moon (1221)"); @@ -131,7 +124,9 @@ public void setUp() throws Exception { luceyPaulEntry.setField(StandardField.TITLE, "Lunar iron and titanium abundance algorithms based on final processing of Clementine ultraviolet-visible images"); luceyPaulEntry.setField(StandardField.VOLUME, "105"); luceyPaulEntry.setField(StandardField.YEAR, "2000"); - luceyPaulEntry.setField(StandardField.URL, "http://adsabs.harvard.edu/abs/2000JGR...10520297L"); + luceyPaulEntry.setField(StandardField.URL, "https://ui.adsabs.harvard.edu/abs/2000JGR...10520297L"); + luceyPaulEntry.setField(StandardField.MONTH, "Jan"); + luceyPaulEntry.setField(StandardField.NUMBER, "E8"); } @Test @@ -147,7 +142,7 @@ public void testGetName() { @Test public void searchByQueryFindsEntry() throws Exception { List fetchedEntries = fetcher.performSearch("Diez slice theorem Lie"); - assertEquals(Collections.singletonList(diezSliceTheoremEntry), fetchedEntries); + assertTrue(fetchedEntries.contains(diezSliceTheoremEntry)); } @Test @@ -200,7 +195,7 @@ public void testPerformSearchByIngersollPollardEntry() throws Exception { @Test public void testPerformSearchByLuceyPaulEntry() throws Exception { - Optional fetchedEntry = fetcher.performSearchById("10.1029/1999JE001117"); + Optional fetchedEntry = fetcher.performSearchById("2000JGR...10520297L"); assertEquals(Optional.of(luceyPaulEntry), fetchedEntry); } }