Java tutorial
/* * Copyright (c) 2017 Public Library of Science * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ package org.ambraproject.rhino.rest.controller; import com.google.common.collect.ImmutableMap; import com.wordnik.swagger.annotations.ApiImplicitParam; import com.wordnik.swagger.annotations.ApiImplicitParams; import com.wordnik.swagger.annotations.ApiOperation; import com.wordnik.swagger.annotations.ApiParam; import org.ambraproject.rhino.identity.ArticleIdentifier; import org.ambraproject.rhino.identity.ArticleIngestionIdentifier; import org.ambraproject.rhino.identity.ArticleRevisionIdentifier; import org.ambraproject.rhino.model.Article; import org.ambraproject.rhino.model.ArticleRevision; import org.ambraproject.rhino.model.Category; import org.ambraproject.rhino.model.Syndication; import org.ambraproject.rhino.rest.DoiEscaping; import org.ambraproject.rhino.rest.RestClientException; import org.ambraproject.rhino.rest.response.ServiceResponse; import org.ambraproject.rhino.service.ArticleCrudService; import org.ambraproject.rhino.service.ArticleListCrudService; import org.ambraproject.rhino.service.ArticleRevisionWriteService; import org.ambraproject.rhino.service.CommentCrudService; import org.ambraproject.rhino.service.SolrIndexService; import org.ambraproject.rhino.service.SyndicationCrudService; import org.ambraproject.rhino.service.taxonomy.TaxonomyService; import org.ambraproject.rhino.view.article.ArticleRevisionView; import org.ambraproject.rhino.view.article.RelationshipSetView; import org.ambraproject.rhino.view.article.SyndicationInputView; import org.ambraproject.rhino.view.article.SyndicationView; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; /** * Controller for _c_reate, _r_ead, _u_pdate, and _d_elete operations on article entities and files. */ @Controller public class ArticleCrudController extends RestController { private static final Logger log = LoggerFactory.getLogger(ArticleCrudController.class); private static final String FROM_DATE = "fromDate"; private static final String TO_DATE = "toDate"; @Autowired private ArticleCrudService articleCrudService; @Autowired private ArticleRevisionWriteService articleRevisionWriteService; @Autowired private SolrIndexService solrIndexService; @Autowired private CommentCrudService commentCrudService; @Autowired private AssetFileCrudController assetFileCrudController; @Autowired private ArticleListCrudService articleListCrudService; @Autowired private SyndicationCrudService syndicationCrudService; @Autowired private TaxonomyService taxonomyService; @Autowired private RelationshipSetView.Factory relationshipSetViewFactory; /** * Calculate the date range using the specified rule. For example: * * <ul> * <li>sinceRule=2y - 2 years</li> * <li>sinceRule=5m - 5 months</li> * <li>sinceRule=10d - 10 days</li> * <li>sinceRule=5h - 5 hours</li> * <li>sinceRule=33 - 33 minutes</li> * </ul> * * The method will result in a {@link java.util.Map Map} containing the following keys: * * <ul> * <li><b>fromDate</b> - the starting date * <li><b>toDate</b> - the ending date, which will be the current system date (i.e. now()) * </ul> * * @param sinceRule The rule to calculate the date range * * @return A {@link java.util.Map Map} */ public static final Map<String, LocalDateTime> calculateDateRange(String sinceRule) { if (StringUtils.isBlank(sinceRule)) { return ImmutableMap.of(); } final String timeDesignation = StringUtils.right(sinceRule, 1); long timeDelta = 0; try { // Assume last character is NOT a letter (i.e. all characters are digits). timeDelta = Long.parseLong(sinceRule); } catch (NumberFormatException exception) { // If an exception, then last character MUST have been a letter, // so we now exclude the last character and re-try conversion. try { timeDelta = Long.parseLong(sinceRule.substring(0, sinceRule.length() - 1)); } catch (NumberFormatException error) { log.warn("Failed to convert {} to a timeDelta/timeDesignation!", sinceRule); timeDelta = 0; } } if (timeDelta < 1) { return ImmutableMap.of(); } final LocalDateTime toDate = LocalDateTime.now(); final LocalDateTime fromDate; if (timeDesignation.equalsIgnoreCase("y")) { fromDate = toDate.minusYears(timeDelta); } else if (timeDesignation.equalsIgnoreCase("m")) { fromDate = toDate.minusMonths(timeDelta); } else if (timeDesignation.equalsIgnoreCase("d")) { fromDate = toDate.minusDays(timeDelta); } else if (timeDesignation.equalsIgnoreCase("h")) { fromDate = toDate.minus(timeDelta, ChronoUnit.HOURS); } else { fromDate = toDate.minus(timeDelta, ChronoUnit.MINUTES); } final ImmutableMap<String, LocalDateTime> dateRange = ImmutableMap.of(FROM_DATE, fromDate, TO_DATE, toDate); return dateRange; } @Transactional(readOnly = true) @RequestMapping(value = "/articles/page/{pageNumber}", method = RequestMethod.GET) public ResponseEntity<?> listDois(@PathVariable(value = "pageNumber") int pageNumber, @RequestParam(value = "pageSize", required = false, defaultValue = "100") int pageSize, @RequestParam(value = "orderBy", required = false, defaultValue = "newest") String orderBy, @RequestParam(value = "since", required = false, defaultValue = "") String sinceRule) throws IOException { final ArticleCrudService.SortOrder sortOrder = ArticleCrudService.SortOrder .valueOf(StringUtils.upperCase(StringUtils.defaultString(orderBy, "newest" /* defaultStr */))); final Map<String, LocalDateTime> dateRange = calculateDateRange(sinceRule); final Optional<LocalDateTime> fromDate = Optional.ofNullable(dateRange.getOrDefault(FROM_DATE, null)); final Optional<LocalDateTime> toDate = Optional.ofNullable(dateRange.getOrDefault(TO_DATE, null)); final Collection<String> articleDois = articleCrudService.getArticleDoisForDateRange(pageNumber, pageSize, sortOrder, fromDate, toDate); return ServiceResponse.serveView(articleDois).asJsonResponse(entityGson); } /** * Read article metadata. * * @throws IOException */ @Transactional(readOnly = true) @RequestMapping(value = "/articles/{doi}/ingestions/{number}", method = RequestMethod.GET) public ResponseEntity<?> read( @RequestHeader(value = HttpHeaders.IF_MODIFIED_SINCE, required = false) Date ifModifiedSince, @PathVariable("doi") String doi, @PathVariable("number") int ingestionNumber) throws IOException { ArticleIngestionIdentifier ingestionId = ArticleIngestionIdentifier.create(DoiEscaping.unescape(doi), ingestionNumber); return articleCrudService.serveMetadata(ingestionId).getIfModified(ifModifiedSince) .asJsonResponse(entityGson); } @Transactional() @RequestMapping(value = "/articles/{doi}/ingestions/{number}", method = RequestMethod.POST) public ResponseEntity<?> updatePreprintDoi( @RequestHeader(value = HttpHeaders.IF_MODIFIED_SINCE, required = false) Date ifModifiedSince, @PathVariable("doi") String doi, @PathVariable("number") int ingestionNumber, @RequestParam("preprintDoi") String preprintDoi) throws IOException { ArticleIngestionIdentifier ingestionId = ArticleIngestionIdentifier.create(DoiEscaping.unescape(doi), ingestionNumber); articleCrudService.updatePreprintDoi(ingestionId, preprintDoi); return articleCrudService.serveMetadata(ingestionId).getIfModified(ifModifiedSince) .asJsonResponse(entityGson); } @Transactional() @RequestMapping(value = "/articles/{doi}/ingestions/{number}", method = RequestMethod.DELETE) public ResponseEntity<?> removePreprintDoi(@PathVariable("doi") String doi, @PathVariable("number") int ingestionNumber) throws IOException { ArticleIngestionIdentifier ingestionId = ArticleIngestionIdentifier.create(DoiEscaping.unescape(doi), ingestionNumber); articleCrudService.updatePreprintDoi(ingestionId, null); return new ResponseEntity<>(HttpStatus.OK); } @Transactional(readOnly = true) @RequestMapping(value = "/articles/{doi:.+}", method = RequestMethod.GET) public ResponseEntity<?> getRevisions(@PathVariable("doi") String doi) throws IOException { ArticleIdentifier id = ArticleIdentifier.create(DoiEscaping.unescape(doi)); return articleCrudService.serveOverview(id).asJsonResponse(entityGson); } @Transactional(readOnly = true) @RequestMapping(value = "/articles/{doi}/revisions", method = RequestMethod.GET) public ResponseEntity<?> readRevisions(@PathVariable("doi") String doi) throws IOException { ArticleIdentifier id = ArticleIdentifier.create(DoiEscaping.unescape(doi)); return articleCrudService.serveRevisions(id).asJsonResponse(entityGson); } @Transactional(readOnly = true) @RequestMapping(value = "/articles/{doi}/revisions/{revision}", method = RequestMethod.GET) public ResponseEntity<?> readRevision( @RequestHeader(value = HttpHeaders.IF_MODIFIED_SINCE, required = false) Date ifModifiedSince, @PathVariable("doi") String doi, @PathVariable(value = "revision") Integer revisionNumber) throws IOException { ArticleRevisionIdentifier id = ArticleRevisionIdentifier.create(DoiEscaping.unescape(doi), revisionNumber); return articleCrudService.serveRevision(id).getIfModified(ifModifiedSince).asJsonResponse(entityGson); } @Transactional(readOnly = false) @RequestMapping(value = "/articles/{doi}/revisions", method = RequestMethod.POST) public ResponseEntity<?> writeRevision(@PathVariable("doi") String doi, @RequestParam(value = "revision", required = false) Integer revisionNumber, @RequestParam(value = "ingestion", required = true) Integer ingestionNumber) throws IOException { ArticleIdentifier articleId = ArticleIdentifier.create(DoiEscaping.unescape(doi)); ArticleIngestionIdentifier ingestionId = ArticleIngestionIdentifier.create(articleId, ingestionNumber); final ArticleRevision revision; if (revisionNumber == null) { revision = articleRevisionWriteService.createRevision(ingestionId); } else { ArticleRevisionIdentifier revisionId = ArticleRevisionIdentifier.create(articleId, revisionNumber); revision = articleRevisionWriteService.writeRevision(revisionId, ingestionId); } return ServiceResponse.reportCreated(ArticleRevisionView.getView(revision)).asJsonResponse(entityGson); } @Transactional(readOnly = false) @RequestMapping(value = "/articles/{doi}/revisions/{revision}", method = RequestMethod.DELETE) public ResponseEntity<?> deleteRevision(@PathVariable("doi") String doi, @PathVariable("revision") int revisionNumber) { ArticleRevisionIdentifier revisionId = ArticleRevisionIdentifier.create(DoiEscaping.unescape(doi), revisionNumber); articleRevisionWriteService.deleteRevision(revisionId); return new ResponseEntity<>(HttpStatus.OK); } @RequestMapping(value = "/articles/{doi}/ingestions/{number}/items", method = RequestMethod.GET) public ResponseEntity<?> readItems( @RequestHeader(value = HttpHeaders.IF_MODIFIED_SINCE, required = false) Date ifModifiedSince, @PathVariable("doi") String doi, @PathVariable("number") int ingestionNumber) throws IOException { ArticleIngestionIdentifier ingestionId = ArticleIngestionIdentifier.create(DoiEscaping.unescape(doi), ingestionNumber); return articleCrudService.serveItems(ingestionId).getIfModified(ifModifiedSince).asJsonResponse(entityGson); } /** * Retrieves a list of objects representing comments associated with the article. Each comment has a "replies" list * that contains any replies (recursively). * * @throws IOException */ @Transactional(readOnly = true) @RequestMapping(value = "/articles/{doi}/comments", method = RequestMethod.GET) public ResponseEntity<?> readComments(@PathVariable("doi") String doi) throws IOException { ArticleIdentifier id = ArticleIdentifier.create(DoiEscaping.unescape(doi)); return commentCrudService.serveComments(id).asJsonResponse(entityGson); } // TODO: Get rid of this? @Transactional(readOnly = true) @RequestMapping(value = "/articles/{doi:.+}/comments", method = RequestMethod.GET, params = "count") @ApiImplicitParam(name = "count", value = "count flag (any value)", required = true, defaultValue = "count", paramType = "query", dataType = "string") public ResponseEntity<?> getCommentCount(@PathVariable("doi") String doi) throws IOException { ArticleIdentifier id = ArticleIdentifier.create(DoiEscaping.unescape(doi)); Article article = articleCrudService.readArticle(id); return commentCrudService.getCommentCount(article).asJsonResponse(entityGson); } @Transactional(readOnly = true) @RequestMapping(value = "/articles/{doi}/relationships", method = RequestMethod.GET) public ResponseEntity<?> readRelationships(HttpServletRequest request, HttpServletResponse response, @PathVariable("doi") String doi) throws IOException { ArticleIdentifier id = ArticleIdentifier.create(DoiEscaping.unescape(doi)); return ServiceResponse.serveView(relationshipSetViewFactory.getSetView(id)).asJsonResponse(entityGson); } /** * Retrieves a list of objects representing the authors of the article. While the article metadata contains author * names, this list will contain more author information than the article metadata, such as author affiliations, * corresponding author, etc. * * @throws IOException */ @Transactional(readOnly = true) @RequestMapping(value = "/articles/{doi}/ingestions/{number}/authors", method = RequestMethod.GET) public ResponseEntity<?> readAuthors( @RequestHeader(value = HttpHeaders.IF_MODIFIED_SINCE, required = false) Date ifModifiedSince, @PathVariable("doi") String doi, @PathVariable("number") int ingestionNumber) throws IOException { ArticleIngestionIdentifier ingestionId = ArticleIngestionIdentifier.create(DoiEscaping.unescape(doi), ingestionNumber); return articleCrudService.serveAuthors(ingestionId).getIfModified(ifModifiedSince) .asJsonResponse(entityGson); } /** * Populates article category information by making a call to the taxonomy server. * * @throws IOException */ @Transactional(rollbackFor = { Throwable.class }) @RequestMapping(value = "/articles/{doi}/categories", method = RequestMethod.POST) public ResponseEntity<?> populateCategories(@PathVariable("doi") String doi) throws IOException { ArticleIdentifier articleId = ArticleIdentifier.create(DoiEscaping.unescape(doi)); articleCrudService.populateCategories(articleId); // Report the current categories return articleCrudService.serveCategories(articleId).asJsonResponse(entityGson); } /** * Retrieves a list of objects representing categories associated with the article. * * @throws IOException */ @Transactional(readOnly = true) @RequestMapping(value = "/articles/{doi}/categories", method = RequestMethod.GET) public ResponseEntity<?> readCategories(@PathVariable("doi") String doi) throws IOException { ArticleIdentifier articleId = ArticleIdentifier.create(DoiEscaping.unescape(doi)); return articleCrudService.serveCategories(articleId).asJsonResponse(entityGson); } /** * Retrieves a list of objects representing raw taxonomy categories associated with the article. * * @throws IOException */ // TODO: Get rid of this? @Transactional(readOnly = true) @RequestMapping(value = "/articles/{doi}/categories", method = RequestMethod.GET, params = "raw") @ApiImplicitParam(name = "raw", value = "raw flag (any value)", required = true, defaultValue = "raw", paramType = "query", dataType = "string") public ResponseEntity<?> getRawCategories(@PathVariable("doi") String doi) throws IOException { ArticleIdentifier articleId = ArticleIdentifier.create(DoiEscaping.unescape(doi)); return articleCrudService.serveRawCategories(articleId).asJsonResponse(entityGson); } /** * Retrieves the raw taxonomy categories associated with the article along with the text that is sent to the taxonomy * server for classification * * @param request * @return a String containing the text and raw categories in the form of <text> \n\n <categories> * @throws IOException */ // TODO: Get rid of this? @Transactional(readOnly = true) @RequestMapping(value = "/articles/{doi}/categories", method = RequestMethod.GET, params = "rawCategoriesAndText") @ApiImplicitParam(name = "rawCategoriesAndText", value = "rawCategoriesAndText flag (any value)", required = true, defaultValue = "rawCategoriesAndText", paramType = "query", dataType = "string") public ResponseEntity<String> getRawCategoriesAndText(HttpServletRequest request, @PathVariable("doi") String doi) throws IOException { ArticleIdentifier articleId = ArticleIdentifier.create(DoiEscaping.unescape(doi)); String categoriesAndText = articleCrudService.getRawCategoriesAndText(articleId); HttpHeaders responseHeader = new HttpHeaders(); responseHeader.setContentType(MediaType.TEXT_HTML); return new ResponseEntity<>(categoriesAndText, responseHeader, HttpStatus.OK); } @Transactional(rollbackFor = { Throwable.class }) @RequestMapping(value = "/articles/{doi}/categories", params = { "flag" }, method = RequestMethod.POST) @ResponseBody @ApiImplicitParam(name = "flag", value = "category flagged flag (any value)", required = true, defaultValue = "flag", paramType = "query", dataType = "string") public Map<String, String> flagArticleCategory(@PathVariable("doi") String articleDoi, @RequestParam(value = "categoryTerm") String categoryTerm, @RequestParam(value = "userId", required = false) String userId, @RequestParam(value = "flag") String action) throws IOException { ArticleIdentifier articleId = ArticleIdentifier.create(DoiEscaping.unescape(articleDoi)); Article article = articleCrudService.readArticle(articleId); Optional<Long> userIdObj = Optional.ofNullable(userId).map(Long::parseLong); Collection<Category> categories = taxonomyService.getArticleCategoriesWithTerm(article, categoryTerm); switch (action) { case "add": for (Category category : categories) { taxonomyService.flagArticleCategory(article, category, userIdObj); } break; case "remove": for (Category category : categories) { taxonomyService.deflagArticleCategory(article, category, userIdObj); } break; default: throw new RestClientException("action must be 'add' or 'remove'", HttpStatus.BAD_REQUEST); } return ImmutableMap.of(); // ajax call expects returned data so provide an empty map for the body } /** * Retrieves a collection of article lists that contain an article. */ @Transactional(readOnly = true) @RequestMapping( // Not "/articles/{doi}/lists" because a list isn't a child object of the article. This is kind of a search query. value = "/articles/{doi:.+}", method = RequestMethod.GET, params = "lists") @ApiImplicitParam(name = "lists", value = "lists flag (any value)", required = true, defaultValue = "lists", paramType = "query", dataType = "string") public ResponseEntity<?> getContainingLists(@PathVariable("doi") String doi) throws IOException { ArticleIdentifier id = ArticleIdentifier.create(DoiEscaping.unescape(doi)); return articleListCrudService.readContainingLists(id).asJsonResponse(entityGson); } @RequestMapping(value = "/articles/{doi:.+}", params = { "solrIndex" }, method = RequestMethod.POST) public ResponseEntity<?> updateSolrIndex(@PathVariable("doi") String doi, @ApiParam(value = "Enter 'lite' to perform a lite index. Any other value will perform a standard, full index") @RequestParam(value = "solrIndex", defaultValue = "standard") String solrIndexMode) { ArticleIdentifier identifier = ArticleIdentifier.create(DoiEscaping.unescape(doi)); solrIndexService.updateSolrIndex(identifier, solrIndexMode.equals("lite")); return new ResponseEntity<>(HttpStatus.OK); } @RequestMapping(value = "/articles/{doi:.+}", params = { "solrIndex" }, method = RequestMethod.DELETE) @ApiImplicitParam(name = "solrIndex", value = "solrIndex flag (any value)", required = true, defaultValue = "solrIndex", paramType = "query", dataType = "string") public ResponseEntity<?> removeSolrIndex(@PathVariable("doi") String doi) { ArticleIdentifier identifier = ArticleIdentifier.create(DoiEscaping.unescape(doi)); solrIndexService.removeSolrIndex(identifier); return new ResponseEntity<>(HttpStatus.OK); } @RequestMapping(value = "/articles/{doi}/revisions/{number}/syndications", method = RequestMethod.GET) public ResponseEntity<?> readSyndications(@PathVariable("doi") String doi, @PathVariable("number") int revisionNumber) throws IOException { ArticleRevisionIdentifier revisionId = ArticleRevisionIdentifier.create(DoiEscaping.unescape(doi), revisionNumber); List<Syndication> syndications = syndicationCrudService.getSyndications(revisionId); // TODO: If revision does not exist, need to respond with 404 instead of empty list? List<SyndicationView> views = syndications.stream().map(SyndicationView::new).collect(Collectors.toList()); return ServiceResponse.serveView(views).asJsonResponse(entityGson); } @RequestMapping(value = "/articles/{doi}/revisions/{number}/syndications", method = RequestMethod.POST) @ApiImplicitParam(name = "body", paramType = "body", dataType = "SyndicationInputView", value = "example: {\"targetQueue\": \"activemq:plos.pmc\"}") public ResponseEntity<?> createSyndication(HttpServletRequest request, @PathVariable("doi") String doi, @PathVariable("number") int revisionNumber) throws IOException { ArticleRevisionIdentifier revisionId = ArticleRevisionIdentifier.create(DoiEscaping.unescape(doi), revisionNumber); SyndicationInputView input = readJsonFromRequest(request, SyndicationInputView.class); Syndication syndication = syndicationCrudService.createSyndication(revisionId, input.getTargetQueue()); return ServiceResponse.reportCreated(new SyndicationView(syndication)).asJsonResponse(entityGson); } @RequestMapping(value = "/articles/{doi}/revisions/{number}/syndications", // Fold into PATCH operation so we can get rid of "?syndicate"? method = RequestMethod.POST, params = "syndicate") @ApiOperation(value = "syndicate", notes = "Send a syndication message to the queue for processing. " + "Will create and add a syndication to the database if none exist for current article and target.") @ApiImplicitParams({ @ApiImplicitParam(name = "syndicate", value = "syndicate flag (any value)", required = true, defaultValue = "syndicate", paramType = "query", dataType = "string"), @ApiImplicitParam(name = "body", paramType = "body", dataType = "SyndicationInputView", value = "example: {\"targetQueue\": \"activemq:plos.pmc\"}") }) public ResponseEntity<?> syndicate(HttpServletRequest request, @PathVariable("doi") String doi, @PathVariable("number") int revisionNumber) throws IOException { ArticleRevisionIdentifier revisionId = ArticleRevisionIdentifier.create(DoiEscaping.unescape(doi), revisionNumber); SyndicationInputView input = readJsonFromRequest(request, SyndicationInputView.class); Syndication created = syndicationCrudService.syndicate(revisionId, input.getTargetQueue()); return ServiceResponse.reportCreated(new SyndicationView(created)).asJsonResponse(entityGson); } @RequestMapping(value = "/articles/{doi}/revisions/{number}/syndications", method = RequestMethod.PATCH) @ApiImplicitParam(name = "body", paramType = "body", dataType = "SyndicationInputView", value = "example: {\"targetQueue\": \"activemq:plos.pmc\", \"status\": \"FAILURE\", \"errorMessage\": \"failed\"}") public ResponseEntity<?> patchSyndication(HttpServletRequest request, @PathVariable("doi") String doi, @PathVariable("number") int revisionNumber) throws IOException { ArticleRevisionIdentifier revisionId = ArticleRevisionIdentifier.create(DoiEscaping.unescape(doi), revisionNumber); SyndicationInputView input = readJsonFromRequest(request, SyndicationInputView.class); Syndication patched = syndicationCrudService.updateSyndication(revisionId, input.getTargetQueue(), input.getStatus(), input.getErrorMessage()); return ServiceResponse.serveView(new SyndicationView(patched)).asJsonResponse(entityGson); } /** * The following two methods {@link #getDoisPublishedOn} and {@link #getDoisRevisedOn} provide two utility endpoints * for our publication workflow. Their main use-case is to ensure that all articles that are to be published on a * given date are picked up by the publication scripts. */ @Transactional(readOnly = true) @RequestMapping(value = "/articles", method = RequestMethod.GET, params = "published") @ApiImplicitParam(name = "published", value = "published flag (any value)", required = true, defaultValue = "published", paramType = "query", dataType = "string") public ResponseEntity<?> getDoisPublishedOn( @ApiParam(value = "Date Format: yyyy-MM-dd") @RequestParam(value = "fromDate") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate fromDate, @ApiParam(value = "Date Format: yyyy-MM-dd") @RequestParam(value = "toDate") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate toDate, @RequestParam(value = "bucketName", required = false) String bucketName) throws IOException { List<ArticleRevisionView> views = articleCrudService.getArticlesPublishedOn(fromDate, toDate, bucketName) .stream().map(ArticleRevisionView::getView).collect(Collectors.toList()); return ServiceResponse.serveView(views).asJsonResponse(entityGson); } @Transactional(readOnly = true) @RequestMapping(value = "/articles", method = RequestMethod.GET, params = "revised") @ApiImplicitParam(name = "revised", value = "revised flag (any value)", required = true, defaultValue = "revised", paramType = "query", dataType = "string") public ResponseEntity<?> getDoisRevisedOn( @ApiParam(value = "Date Format: yyyy-MM-dd") @RequestParam(value = "fromDate") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate fromDate, @ApiParam(value = "Date Format: yyyy-MM-dd") @RequestParam(value = "toDate") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate toDate, @RequestParam(value = "bucketName", required = false) String bucketName) throws IOException { List<ArticleRevisionView> views = articleCrudService.getArticlesRevisedOn(fromDate, toDate, bucketName) .stream().map(ArticleRevisionView::getView).collect(Collectors.toList()); return ServiceResponse.serveView(views).asJsonResponse(entityGson); } }