Java tutorial
/* $HeadURL:: $ * $Id:: $ * * Copyright (c) 2006-2010 by Public Library of Science * http://plos.org * http://ambraproject.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.ambraproject.struts2; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.Result; import com.sun.syndication.feed.atom.Content; import com.sun.syndication.feed.atom.Entry; import com.sun.syndication.feed.atom.Feed; import com.sun.syndication.feed.atom.Link; import com.sun.syndication.feed.atom.Person; import com.sun.syndication.io.WireFeedOutput; import org.ambraproject.ApplicationException; import org.ambraproject.service.article.NoSuchObjectIdException; import org.ambraproject.service.feed.FeedSearchParameters; import org.ambraproject.service.feed.FeedService; import org.ambraproject.service.feed.FeedService.FEED_TYPES; import org.ambraproject.views.article.ArticleInfo; import org.ambraproject.views.article.ArticleType; import org.ambraproject.models.AnnotationType; import org.ambraproject.service.xml.XMLService; import org.ambraproject.util.TextUtils; import org.ambraproject.views.AnnotationView; import org.ambraproject.views.ArticleCategory; import org.ambraproject.views.LinkbackView; import org.ambraproject.web.VirtualJournalContext; import org.apache.commons.configuration.Configuration; import org.apache.commons.lang.StringUtils; import org.apache.struts2.ServletActionContext; import org.jdom.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Required; import org.springframework.transaction.annotation.Transactional; import org.ambraproject.configuration.ConfigurationStore; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.net.URI; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; /** * The <code>class AmbraFeedResult</code> creates and serializes the query results from the * <code>ArticleFeedAction</code> query. Values are accessed via the Struts value stack. * * @see org.ambraproject.service.feed.FeedSearchParameters * * @author jsuttor * @author russ * @author Joe Osowski */ public class AmbraFeedResult extends Feed implements Result { private FeedService feedService; private XMLService secondaryObjectService; private boolean includeformatting = false; private static final int MAX_ANNOTATION_BODY_LENGTH = 512; private static final Configuration CONF = ConfigurationStore.getInstance().getConfiguration(); private static final Logger log = LoggerFactory.getLogger(AmbraFeedResult.class); private static final String ATOM_NS = "http://www.w3.org/2005/Atom"; private final String fetchObjectAttachmentAction = "article/fetchObjectAttachment.action"; // TODO: Should be in a resource bundle private static final String URL_DEF = "http://ambraproject.org/"; private static final String PLATFORM_NAME_DEF = "Ambra"; private static final String FEED_TITLE_DEF = "Ambra"; private static final String TAGLINE_DEF = "Publishing science, accelerating research"; private static final String ICON_DEF = "images/favicon.ico"; private static final String FEED_ID_DEF = "info:doi/12.3456/feed.ambr"; private static final String FEED_NS_DEF = "http://www.plos.org/atom/ns#plos"; private static final String PREFIX_DEF = "plos"; private static final String EMAIL_DEF = "webmaster@example.org"; private static final String COPYRIGHT_DEF = "This work is licensed under a Creative Commons " + "Attribution-Share Alike 3.0 License, " + "http://creativecommons.org/licenses/by-sa/3.0/"; private String JRNL_URI() { StringBuilder uri = new StringBuilder(); HttpServletRequest request = ServletActionContext.getRequest(); String pathInfo = request.getContextPath(); uri.append(CONF.getString("ambra.virtualJournals." + getCurrentJournal() + ".url", CONF.getString("ambra.platform.webserver-url", URL_DEF))); if (pathInfo != null) { uri.append(pathInfo); } String journalURI = uri.toString(); return (journalURI.endsWith("/")) ? journalURI : journalURI + "/"; } private String JOURNAL_NAME() { return jrnlConfGetStr("ambra.platform.name", PLATFORM_NAME_DEF); } private String FEED_TITLE() { return jrnlConfGetStr("ambra.services.feed.title", FEED_TITLE_DEF); } private String FEED_TAGLINE() { return jrnlConfGetStr("ambra.services.feed.tagline", TAGLINE_DEF); } private String FEED_ICON() { return JRNL_URI() + jrnlConfGetStr("ambra.services.feed.icon", ICON_DEF); } private String FEED_ID() { return jrnlConfGetStr("ambra.services.feed.id", FEED_ID_DEF); } private String FEED_EXTENDED_NS() { return jrnlConfGetStr("ambra.services.feed.extended.namespace", FEED_NS_DEF); } private String FEED_EXTENDED_PREFIX() { return jrnlConfGetStr("ambra.services.feed.extended.prefix", PREFIX_DEF); } private String JOURNAL_EMAIL_GENERAL() { return jrnlConfGetStr("ambra.platform.email.general", EMAIL_DEF); } private String JOURNAL_COPYRIGHT() { return jrnlConfGetStr("ambra.platform.copyright", COPYRIGHT_DEF); } /** * AmbraFeedResult constructor. Creates a atom_1.0 wire feed with UTF-8 encoding. */ public AmbraFeedResult() { super("atom_1.0"); setEncoding("UTF-8"); } /** * Main entry point into the WireFeed result. Once the <code>ArticleFeedAction</code> has preformed the query and * provided access to the Article ID's on the value stack it is the Results responsibility to get the article * information and construct the actual Atom feed formatted output. The feed result is not currently cached. * * @param ai action invocation context * @throws Exception */ @Transactional(readOnly = true) @SuppressWarnings("unchecked") public void execute(ActionInvocation ai) throws Exception { HttpServletRequest request = ServletActionContext.getRequest(); String pathInfo = request.getPathInfo(); if (request.getParameter("includeformatting") != null) { if (request.getParameter("includeformatting").equals("true")) { includeformatting = true; } } // URI is either "/" or the pathInfo final URI uri = (pathInfo == null) ? URI.create("/") : URI.create(pathInfo); String JOURNAL_URI = JRNL_URI(); setXmlBase(JRNL_URI()); // Get the article IDs that were cached by the feed. FeedSearchParameters searchParams = (FeedSearchParameters) ai.getStack().findValue("searchParameters"); List<Link> otherLinks = new ArrayList<Link>(); // Add the self link otherLinks.add(newLink(searchParams, uri.toString())); setOtherLinks(otherLinks); setId(newFeedID(searchParams)); setTitle(newFeedTitle(searchParams)); Content tagline = new Content(); tagline.setValue(FEED_TAGLINE()); setTagline(tagline); setUpdated(new Date()); setIcon(FEED_ICON()); setLogo(FEED_ICON()); setCopyright(JOURNAL_COPYRIGHT()); List<Person> feedAuthors = new ArrayList<Person>(); feedAuthors.add(plosPerson()); setAuthors(feedAuthors); String xmlBase = (searchParams.getRelativeLinks() ? "" : JOURNAL_URI); FEED_TYPES t = searchParams.feedType(); List<ArticleInfo> articles = (List<ArticleInfo>) ai.getStack().findValue("Articles"); // Add each Article or Annotations as a Feed Entry List<Entry> entries = null; List<LinkbackView> trackbacks = null; switch (t) { case Annotation: trackbacks = (List<LinkbackView>) ai.getStack().findValue("trackbacks"); case Comment: case Reply: List<AnnotationView> annotations = (List<AnnotationView>) ai.getStack().findValue("annotations"); entries = buildAnnotationFeed(xmlBase, annotations, trackbacks, searchParams.getMaxResults(), searchParams.getFormatting()); break; case Trackback: trackbacks = (List<LinkbackView>) ai.getStack().findValue("trackbacks"); entries = buildAnnotationFeed(xmlBase, null, trackbacks, searchParams.getMaxResults(), searchParams.getFormatting()); break; case Article: Document solrResult = (Document) ai.getStack().findValue("ResultFromSolr"); entries = buildArticleFeed(searchParams, xmlBase, solrResult); break; case Issue: //I assume here the feed is anonymous and unpublished articles will never be //included, If this isn't correct, a method needs to be added to fetch the //current user ID from the session entries = buildIssueFeed(searchParams, xmlBase, articles); break; } setEntries(entries); output(); } /** * Build a <code>List<Entry></code> from the Annotion Ids found by the query action. * * @param xmlBase xml base url * @param annotations list of web annotations * @param trackbacks list of trackbacks * @param maxResults maximum number of results to display * @param formatting if this parameter has the value FeedService.FEED_FORMATTING_COMPLETE, then display the entire * text of every available field * @return List of entries for the feed * @throws Exception Exception */ private List<Entry> buildAnnotationFeed(String xmlBase, List<AnnotationView> annotations, List<LinkbackView> trackbacks, int maxResults, String formatting) throws Exception { // Combine annotations and trackbacks sorted by date SortedMap<Date, Object> map = new TreeMap<Date, Object>(Collections.reverseOrder()); if (annotations != null) { for (AnnotationView annotation : annotations) { if (annotation.getType().equals(AnnotationType.REPLY)) { //AnnotationView rootAnnotation = getAnnotationRoot(annotation); map.put(annotation.getCreated(), annotation); } else { map.put(annotation.getCreated(), annotation); } } } if (trackbacks != null) { for (LinkbackView trackback : trackbacks) { map.put(trackback.getCreated(), trackback); } } // Add each Article as a Feed Entry List<Entry> entries = new ArrayList<Entry>(); int i = 0; for (Object view : map.values()) { entries.add(newEntry(view, xmlBase, formatting)); // i starts with 1, if maxResults=0 this will not interrupt the loop if (++i == maxResults) break; } return entries; } /** * Build a <code>List<Entry></code> from the Article Ids found by the query action. * * @param searchParams cache/data model * @param xmlBase xml base url * @param articles list of articles * @return List of entries for feed. * @throws NoSuchObjectIdException When an article does not exist */ private List<Entry> buildIssueFeed(FeedSearchParameters searchParams, String xmlBase, List<ArticleInfo> articles) throws NoSuchObjectIdException { // Add each Article as a Feed Entry List<Entry> entries = new ArrayList<Entry>(); for (ArticleInfo article : articles) { /* * Article may be removed by the time * it a actually retrieved. A null * may be the result so skip. */ if (article == null) continue; Entry entry = newEntry(article); List<Link> altLinks = new ArrayList<Link>(); // Link to article via xmlbase Link selfLink = newArticleLink(article.getDoi(), article.getTitle(), xmlBase); altLinks.add(selfLink); //Assume here that each article has an XML and PDF representation altLinks.add(newAltLink(article, "XML", xmlBase)); altLinks.add(newAltLink(article, "PDF", xmlBase)); // Add alternative links to this entry entry.setAlternateLinks(altLinks); // List will be created by newAuthorsList List<Person> authors = new ArrayList<Person>(); // Returns Comma delimited string of author names and Adds People to the authors list. String authorNames = newAuthorsList(searchParams.isExtended(), article, authors); entry.setAuthors(authors); // Add foreign markup if (searchParams.isExtended()) { // All our foreign markup List<Element> foreignMarkup = newForeignMarkUp(article); if (foreignMarkup.size() > 0) { entry.setForeignMarkup(foreignMarkup); } } List<Content> contents = newContentsList(searchParams, article, authorNames); entry.setContents(contents); // Add completed Entry to List entries.add(entry); } return entries; } /** * Build a <code>List<Entry></code> from the articles found from solr * * @param searchParams data model * @param xmlBase xml base url * @param result list of articles * @return List of entries for the feed */ private List<Entry> buildArticleFeed(FeedSearchParameters searchParams, String xmlBase, Document result) { // Add each Article as a Feed Entry List<Entry> entries = new ArrayList<Entry>(); // default is 2pm local time int publishTime = CONF.getInt("ambra.services.feed.publishTime", 14); NodeList nodes = result.getElementsByTagName("result"); NodeList docs = null; // there should be only one if (nodes.getLength() == 1) { Node node = nodes.item(0); docs = node.getChildNodes(); } // looping through children of result element for (int i = 0; i < docs.getLength(); i++) { // doc element Node doc = docs.item(i); Entry entry = new Entry(); String volume = null; String issue = null; String articleType = null; String abstractText = null; String copyright = null; NodeList authorsForContent = null; Node subjectHierarchyNode = null; // children elements of doc element NodeList fields = doc.getChildNodes(); for (int j = 0; j < fields.getLength(); j++) { Node field = fields.item(j); NamedNodeMap nnm = field.getAttributes(); Node attrNameNode = nnm.getNamedItem("name"); String attrName = attrNameNode.getNodeValue(); if (attrName.equals("id")) { // id entry.setId("info:doi/" + field.getTextContent()); } else if (attrName.equals("copyright")) { // rights copyright = field.getTextContent(); } else if (attrName.equals("publication_date")) { // published and updated dates // the only values we care about are the month, day and year String date = field.getTextContent(); int year = Integer.valueOf(date.substring(0, 4)); int month = Integer.valueOf(date.substring(5, 7)); int day = Integer.valueOf(date.substring(8, 10)); // we want the local time zone Calendar cal = Calendar.getInstance(); cal.set(Calendar.YEAR, year); // month value is 0 based cal.set(Calendar.MONTH, month - 1); cal.set(Calendar.DAY_OF_MONTH, day); cal.set(Calendar.HOUR_OF_DAY, publishTime); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); entry.setPublished(cal.getTime()); entry.setUpdated(cal.getTime()); } else if (attrName.equals("title_display")) { // title if (includeformatting) { Content title = new Content(); title.setType("html"); title.setValue(field.getTextContent()); entry.setTitleEx(title); } else { entry.setTitle(TextUtils.simpleStripAllTags(field.getTextContent())); } } else if (attrName.equals("author_display")) { // display ALL the authors in the content element // authors and collab authors authorsForContent = field.getChildNodes(); } else if (attrName.equals("author_without_collab_display")) { // authors (without the collaborative authors) ArrayList<Person> authors = newAuthorsList(searchParams, field); entry.setAuthors(authors); } else if (attrName.equals("author_collab_only_display")) { // contributors (collaborative authors) List<Person> contributors = new ArrayList<Person>(); NodeList children = field.getChildNodes(); for (int k = 0; k < children.getLength(); k++) { Node child = children.item(k); Person contributor = new Person(); contributor.setName(child.getTextContent()); contributors.add(contributor); } entry.setContributors(contributors); } else if (attrName.equals("volume")) { // volume (used for ForeignMarkup) volume = field.getTextContent(); } else if (attrName.equals("issue")) { // issue (used for ForeignMarkup) issue = field.getTextContent(); } else if (attrName.equals("article_type")) { // article type (used for ForeignMarkup) articleType = field.getTextContent(); } else if (attrName.equals("subject_hierarchy")) { subjectHierarchyNode = field; } else if (attrName.equals("abstract_primary_display")) { // abstract (used in Contents) abstractText = field.getTextContent(); } } // foreign markup if (searchParams.isExtended()) { List<Element> foreignMarkup = newForeignMarkUp(subjectHierarchyNode, volume, issue, articleType); if (foreignMarkup.size() > 0) { entry.setForeignMarkup(foreignMarkup); } } // alternative links List<Link> altLinks = newAltLinks(entry.getId(), xmlBase, entry.getTitle()); // Add alternative links to this entry entry.setAlternateLinks(altLinks); // contents List<Content> contents = newContentsList(searchParams, authorsForContent, abstractText); entry.setContents(contents); // rights if (copyright != null) { entry.setRights(copyright); } else { // Default is CC BY SA 3.0 entry.setRights(JOURNAL_COPYRIGHT()); } // Add completed Entry to List entries.add(entry); } return entries; } /** * Create a feed entry with Id, Rights, Title, Published and Updated set. * * @param object an AnnotationView or Trackback * @return Entry feed entry * @throws UnsupportedEncodingException */ private Entry newEntry(Object object, String xmlBase, String formatting) throws UnsupportedEncodingException, ApplicationException { Entry entry = new Entry(); List<Link> altLinks = new ArrayList<Link>(); List<Person> authors = new ArrayList<Person>(); String annotationType = null; Link selfLink = null; Link relLink = null; if (object instanceof AnnotationView) { AnnotationView view = (AnnotationView) object; entry.setId("info:doi/" + view.getAnnotationUri()); entry.setPublished(view.getCreated()); entry.setUpdated(view.getCreated()); selfLink = newSelfLink(view, xmlBase); relLink = newArticleLink(view.getArticleDoi(), view.getArticleTitle(), xmlBase); Person person = new Person(); person.setName(view.getCreatorFormattedName()); authors.add(person); annotationType = view.getType().toString(); entry.setTitle(view.getOriginalTitle()); } else if (object instanceof LinkbackView) { LinkbackView trackback = (LinkbackView) object; selfLink = newSelfLink(trackback, xmlBase); relLink = newArticleLink(trackback.getArticleDoi(), trackback.getArticleTitle(), xmlBase); Person person = new Person(); person.setName(trackback.getBlogName()); authors.add(person); entry.setId(trackback.getUrl()); entry.setPublished(trackback.getCreated()); entry.setUpdated(trackback.getCreated()); entry.setTitle(trackback.getTitle()); annotationType = "Trackback"; } else { throw new RuntimeException("Unandled class of " + object.getClass().toString() + " recieved."); } selfLink.setRel("alternate"); relLink.setRel("related"); // Add alternative links to this entry altLinks.add(selfLink); altLinks.add(relLink); entry.setAlternateLinks(altLinks); entry.setAuthors(authors); List<Content> contents = newAnnotationsList(relLink, annotationType, authors, getBody(object, FeedService.FEED_FORMATTING_COMPLETE.equals(formatting)), formatting); entry.setContents(contents); List<com.sun.syndication.feed.atom.Category> categories = newCategoryList(annotationType); entry.setCategories(categories); entry.setRights(JOURNAL_COPYRIGHT()); entry.setAlternateLinks(altLinks); return entry; } /** * Creates a <code>List<Element></code> that consist of foreign markup elements. In this case the elements * created consist of volume, issue category information. * * @param article the article * @return <code>List<Elements></code> of foreign markup elements with issue, volume and category information */ private List<Element> newForeignMarkUp(ArticleInfo article) { // All our foreign markup List<Element> foreignMarkup = new ArrayList<Element>(); // Add volume if (article.getVolume() != null) { Element volume = new Element("volume", FEED_EXTENDED_PREFIX(), FEED_EXTENDED_NS()); volume.setText(article.getVolume()); foreignMarkup.add(volume); } // Add issue if (article.getIssue() != null) { Element issue = new Element("issue", FEED_EXTENDED_PREFIX(), FEED_EXTENDED_NS()); issue.setText(article.getIssue()); foreignMarkup.add(issue); } //Add the article type to the extended feed element. for (ArticleType ar : article.getArticleTypes()) { if (ar != null) { Element articleType = new Element("article-type", FEED_EXTENDED_PREFIX(), FEED_EXTENDED_NS()); articleType.setText(ar.getHeading()); foreignMarkup.add(articleType); } } Set<ArticleCategory> categories = article.getCategories(); if (categories != null) { for (ArticleCategory category : categories) { Element feedCategory = new Element("category", ATOM_NS); feedCategory.setAttribute("term", category.getMainCategory()); feedCategory.setAttribute("label", category.getMainCategory()); String subCategory = category.getSubCategory(); if (subCategory != null) { Element feedSubCategory = new Element("category", ATOM_NS); feedSubCategory.setAttribute("term", subCategory); feedSubCategory.setAttribute("label", subCategory); feedCategory.addContent(feedSubCategory); } foreignMarkup.add(feedCategory); } } return foreignMarkup; } private String getBody(Object view, boolean includeCompetingInterest) throws UnsupportedEncodingException { String body = ""; if (view instanceof AnnotationView) { AnnotationView annot = (AnnotationView) view; body = TextUtils.makeHtmlLineBreaks(annot.getBody()); if (includeCompetingInterest && annot.getBody() != null && annot.getCompetingInterestStatement() != null && annot.getCompetingInterestStatement().trim().length() > 0) { body += "<p/><strong>Competing Interest Statement</strong>: " + TextUtils.makeHtmlLineBreaks(annot.getCompetingInterestStatement()); } } else if (view instanceof LinkbackView) { LinkbackView trackback = (LinkbackView) view; if (trackback.getExcerpt() != null) { body = trackback.getExcerpt(); } } return body; } /** * Creates a <code>List<Element></code> that consist of foreign markup elements. In this case the elements * created consist of volume, issue category information. * * @param subject categories * @param volume volume * @param issue issue * @param articleType article type * @return <code>List<Elements></code> of foreign markup elements with issue, volume and category information */ private List<Element> newForeignMarkUp(Node subject, String volume, String issue, String articleType) { List<Element> foreignMarkup = new ArrayList<Element>(); if (subject != null) { // subject, category NodeList children = subject.getChildNodes(); for (int k = 0; k < children.getLength(); k++) { Node node = children.item(k); String category = node.getTextContent(); int index = category.indexOf("/"); Element feedCategory = new Element("category", ATOM_NS); // existing logic assumes that subject hierarchy is two level deep if (index == -1) { feedCategory.setAttribute("term", category); feedCategory.setAttribute("label", category); } else { // main category and subcategory String subCategory = category.substring(index + 1); category = category.substring(0, index); feedCategory.setAttribute("term", category); feedCategory.setAttribute("label", category); Element feedSubCategory = new Element("category", ATOM_NS); feedSubCategory.setAttribute("term", subCategory); feedSubCategory.setAttribute("label", subCategory); feedCategory.addContent(feedSubCategory); } foreignMarkup.add(feedCategory); } } // volume if (volume != null) { Element elemVolume = new Element("volume", FEED_EXTENDED_PREFIX(), FEED_EXTENDED_NS()); elemVolume.setText(volume); foreignMarkup.add(elemVolume); } // issue if (issue != null) { Element elemIssue = new Element("issue", FEED_EXTENDED_PREFIX(), FEED_EXTENDED_NS()); elemIssue.setText(issue); foreignMarkup.add(elemIssue); } //Add the article type to the extended feed element. if (articleType != null) { Element elemArticleType = new Element("article-type", FEED_EXTENDED_PREFIX(), FEED_EXTENDED_NS()); elemArticleType.setText(articleType); foreignMarkup.add(elemArticleType); } return foreignMarkup; } /** * Creates a description of article contents in HTML format. Currently the description consists of the Author (or * Authors if extended format) and the DublinCore description of the article. * * @param searchParams input parameters * @param article the article * @param authorNames string concatenated list of author names * @return List<Content> consisting of HTML descriptions of the article and author */ private List<Content> newContentsList(FeedSearchParameters searchParams, ArticleInfo article, String authorNames) { List<Content> contents = new ArrayList<Content>(); Content description = new Content(); description.setType("html"); try { StringBuilder text = new StringBuilder(); if (!searchParams.isExtended()) { text.append("<p>by ").append(authorNames).append("</p>\n"); } if (article.getDescription() != null) { String content = secondaryObjectService.getTransformedDescription(article.getDescription()); content = TextUtils.simpleStripAllTags(content); text.append(content); } description.setValue(text.toString()); } catch (ApplicationException e) { log.error("Error getting description", e); description.setValue("<p>Internal server error.</p>"); } contents.add(description); return contents; } /** * Creates a description of article contents in HTML format. It contains abstract and authors * * @param searchParams input parameters * @param authorsForContent list of authors * @param abstractText abstract * @return List<Content> consisting of HTML descriptions of the article and author */ private List<Content> newContentsList(FeedSearchParameters searchParams, NodeList authorsForContent, String abstractText) { // contents List<Content> contents = new ArrayList<Content>(); Content description = new Content(); description.setType("html"); StringBuilder text = new StringBuilder(); // If this is a normal feed (not extended), add to content if (authorsForContent != null) { if (!searchParams.isExtended()) { StringBuilder authorNames = new StringBuilder(); for (int k = 0; k < authorsForContent.getLength(); k++) { if (authorNames.length() > 0) { authorNames.append(", "); } Node node = authorsForContent.item(k); authorNames.append(node.getTextContent()); } text.append("<p>by ").append(authorNames.toString()).append("</p>\n"); } } if (abstractText != null) { text.append(abstractText); } description.setValue(text.toString()); contents.add(description); return contents; } /** * Creates a description of annotation contents in HTML format. Currently the description consists of the Author (or * Authors if extended format) and the DublinCore description of the article. * * @param link Link to the article * @param entryTypeDisplay Text to describe type of entry (e.g., FormalCorrection, Reply, etc) * @param authors All of the authors for this annotation. At present, there should be only one author per * annotation, but this method supports the display of multiple authors * @param comment The main body of text that will be displayed by the feed * @return List<Content> containing HTML-formatted descriptions of the article, author, etc * @throws ApplicationException ApplicationException */ private List<Content> newAnnotationsList(Link link, String entryTypeDisplay, List<Person> authors, String comment, String formatting) throws ApplicationException { List<Content> contents = new ArrayList<Content>(); Content description = new Content(); description.setType("html"); StringBuilder text = new StringBuilder(); text.append("<p>"); if (entryTypeDisplay != null) text.append(entryTypeDisplay).append(" on "); String body = null; if (comment != null) { if (feedService.FEED_FORMATTING_COMPLETE.equals(formatting)) { body = TextUtils.makeHtmlLineBreaks(comment); } else { if (comment.length() > MAX_ANNOTATION_BODY_LENGTH) { body = comment.substring(0, MAX_ANNOTATION_BODY_LENGTH) + " ..."; } } } text.append(" <a href=").append(link.getHref()).append('>').append(link.getTitle()).append("</a></p>") .append("<p>"); if (authors != null && authors.size() > 0) { StringBuilder authorNames = new StringBuilder(); for (Person author : authors) { authorNames.append(", ").append(author.getName()); } text.append(" By ").append(authorNames.substring(2, authorNames.length())).append(": "); } text.append(body).append("</p>"); description.setValue(text.toString()); contents.add(description); return contents; } /** * This routine creates and returns a List<Person> authors listed in DublinCore for the article. * * @param extended Is this an extended? * @param article the article * @param authors modified and returned <code>List<Person></code> of article authors. * @return String of authors names. */ private String newAuthorsList(boolean extended, ArticleInfo article, List<Person> authors) { StringBuilder authorNames = new StringBuilder(); List<String> sourceAuthors = article.getAuthors(); if (extended) { /* If extended then create a list of persons * containing all the authors. */ for (String name : sourceAuthors) { Person person = new Person(); person.setName(name); authors.add(person); if (authorNames.length() > 0) authorNames.append(", "); authorNames.append(name); } } else if (sourceAuthors.size() >= 1) { // Not extended therefore there will only be one author. Person person = new Person(); String author = sourceAuthors.get(0); person.setName(author); authors.add(person); // Build a comma delimited list of author names for (String name : sourceAuthors) { if (authorNames.length() > 0) authorNames.append(", "); authorNames.append(name); } if (sourceAuthors.size() > 1) person.setName(author + " et al."); } return authorNames.toString(); } /** * Get the list of authors for an entry * * @param searchParams input parameters * @param field author node * @return list of authors */ private ArrayList<Person> newAuthorsList(FeedSearchParameters searchParams, Node field) { ArrayList<Person> authors = new ArrayList<Person>(); NodeList children = field.getChildNodes(); if (searchParams.isExtended()) { // If extended then create a list of persons containing all the authors. for (int i = 0; i < children.getLength(); i++) { Node node = children.item(i); Person person = new Person(); person.setName(node.getTextContent()); authors.add(person); } } else if (children.getLength() >= 1) { // Not extended therefore there will only be one author. Person person = new Person(); String author = children.item(0).getTextContent(); if (children.getLength() > 1) { author = author + " et al."; } person.setName(author); authors.add(person); } return authors; } /** * Create alternate link for the different representaions of the article. * * @param article the article * @param representation a respresentation of the article * @param xmlBase XML base * @return Link an alternate link to the article */ private Link newAltLink(ArticleInfo article, String representation, String xmlBase) { Link altLink = new Link(); altLink.setHref(xmlBase + fetchObjectAttachmentAction + "?uri=" + article.getDoi() + "&representation=" + representation); altLink.setRel("related"); if (includeformatting) { altLink.setTitle("(" + representation + ") " + article.getTitle()); } else { altLink.setTitle("(" + representation + ") " + TextUtils.simpleStripAllTags(article.getTitle())); } altLink.setType(representation); return altLink; } /** * Create a link to the annotation itself. * * @param view the annotationView * @param xmlBase xml base of article * @return link to the article */ private Link newSelfLink(Object view, String xmlBase) { if (view instanceof AnnotationView) { StringBuilder href = new StringBuilder(); href.append(xmlBase); AnnotationView annot = (AnnotationView) view; String id; if (annot.getType() == AnnotationType.REPLY) { id = annot.getParentID().toString(); } else { id = annot.getID().toString(); } href.append("annotation/listThread.action?inReplyTo=").append(id).append("&root=").append(id); // add anchor if (annot.getType() == AnnotationType.REPLY) { href.append('#').append(annot.getID().toString()); } Link link = new Link(); link.setRel("alternate"); link.setHref(href.toString()); link.setTitle(annot.getTitle()); return link; } else if (view instanceof LinkbackView) { StringBuilder href = new StringBuilder(); LinkbackView trackback = (LinkbackView) view; href.append(trackback.getUrl()); Link link = new Link(); link.setRel("alternate"); link.setHref(href.toString()); link.setTitle(trackback.getTitle()); return link; } else { throw new RuntimeException("Invalid type of " + view.getClass().toString() + " received."); } } /** * Create a link to the article itself. * * @param doi the doi of the article * @param title the title of the article * @param xmlBase xml base of article * @return link to the article */ private Link newArticleLink(String doi, String title, String xmlBase) { Link link = new Link(); String url = doiToUrl(doi); link.setRel("alternate"); link.setHref(xmlBase + "article/" + url); if (includeformatting) { link.setTitle(title); } else { link.setTitle(TextUtils.simpleStripAllTags(title)); } return link; } /** * Create all the alternative links * * @param id article id * @param xmlBase xml base of the article * @param title title of the article * @return list of alternative links */ private List<Link> newAltLinks(String id, String xmlBase, String title) { // alternative links List<Link> altLinks = new ArrayList<Link>(); if ((id != null && id.length() > 0) && (xmlBase != null) && (title != null && title.length() > 0)) { // Link to article via xmlbase Link selfLink = new Link(); String url = doiToUrl(id); selfLink.setRel("alternate"); selfLink.setHref(xmlBase + "article/" + url); selfLink.setTitle(title); altLinks.add(selfLink); // alternate links // pdf Link altLink = new Link(); altLink.setHref(xmlBase + fetchObjectAttachmentAction + "?uri=" + id + "&representation=PDF"); altLink.setRel("related"); altLink.setTitle("(PDF) " + title); altLink.setType("application/pdf"); altLinks.add(altLink); // xml altLink = new Link(); altLink.setHref(xmlBase + fetchObjectAttachmentAction + "?uri=" + id + "&representation=XML"); altLink.setRel("related"); altLink.setTitle("(XML) " + title); altLink.setType("text/xml"); altLinks.add(altLink); } return altLinks; } /** * Create a feed entry with Id, Rights, Title, Published and Updated set. * * @param article the article * @return Entry feed entry */ private Entry newEntry(ArticleInfo article) { Entry entry = new Entry(); entry.setId(article.getDoi()); // Respect Article specific rights String rights = article.getRights(); if (rights != null) { entry.setRights(rights); } else { // Default is CC BY SA 3.0 entry.setRights(JOURNAL_COPYRIGHT()); } if (includeformatting) { entry.setTitle(article.getTitle()); } else { entry.setTitle(TextUtils.simpleStripAllTags(article.getTitle())); } entry.setPublished(article.getDate()); entry.setUpdated(article.getDate()); return entry; } /** * Create a default Plos person element. * * @return Person with journal email, journal name, journal URI. */ private Person plosPerson() { Person plos = new Person(); plos.setEmail(JOURNAL_EMAIL_GENERAL()); plos.setName(JOURNAL_NAME()); plos.setUri(JRNL_URI()); return plos; } /** * If a self link was provided by the user create a <code>Link</code> based on the user input information contained in * the searchParams. * * @param searchParams data model * @param uri uri of regquest * @return <code>Link</code> user provide link. */ private Link newLink(FeedSearchParameters searchParams, String uri) { if (searchParams.getSelfLink() == null || searchParams.getSelfLink().equals("")) { if (uri.startsWith("/")) { searchParams.setSelfLink(JRNL_URI().substring(0, JRNL_URI().length() - 1) + uri); } else { searchParams.setSelfLink(JRNL_URI() + uri); } } Link newLink = new Link(); newLink.setRel("self"); newLink.setHref(searchParams.getSelfLink()); newLink.setTitle(FEED_TITLE()); return newLink; } /** * Build an atom feed categroy list for for the WebAnnotation. * * @param displayName Name of the entry. * @return List of atom categories */ public List<com.sun.syndication.feed.atom.Category> newCategoryList(String displayName) { List<com.sun.syndication.feed.atom.Category> categories = new ArrayList<com.sun.syndication.feed.atom.Category>(); com.sun.syndication.feed.atom.Category cat = new com.sun.syndication.feed.atom.Category(); cat.setTerm(displayName); cat.setLabel(displayName); categories.add(cat); return categories; } /** * Creates a Feed ID from the Config File value + key.Category + key.Author * * @param searchParams input parameters * @return String identifier generated for this feed */ private String newFeedID(FeedSearchParameters searchParams) { String id = FEED_ID(); String[] categories = searchParams.getCategories(); if (categories != null && categories.length > 0) { String categoryId = ""; for (String category : categories) { categoryId += "categories=" + category + "&"; } id += "?" + categoryId.substring(0, categoryId.length() - 1); } if (searchParams.getAuthor() != null) id += "?author=" + searchParams.getAuthor(); return id; } /** * Create a feed Title string from the the key.Category and key.Author fields * * @param searchParams input parameters * @return String feed title. */ private String newFeedTitle(FeedSearchParameters searchParams) { String feedTitle = searchParams.getTitle(); if (feedTitle == null) { feedTitle = FEED_TITLE(); String[] categories = searchParams.getCategories(); if (categories != null && categories.length > 0) { String categoryTitle = StringUtils.join(categories, ", "); feedTitle += " - Category " + categoryTitle; } if (searchParams.getAuthor() != null) feedTitle += " - Author " + searchParams.getAuthor(); } return feedTitle; } /** * Serialize and output the feed information. * * @throws IOException if the ouput fails to write */ private void output() throws IOException { // work w/HTTP directly, avoid WebWorks interactions HttpServletResponse httpResp = ServletActionContext.getResponse(); httpResp.setContentType("application/atom+xml"); httpResp.setCharacterEncoding(this.getEncoding()); Writer respStrm = httpResp.getWriter(); WireFeedOutput respOut = new WireFeedOutput(); try { respOut.output(this, respStrm); } catch (Exception e) { throw new RuntimeException(e); } finally { // respOut.output throws an exception if respStrm is null respStrm.close(); } } /** * Convert an doi to Trl encoding if possible. * * @param doi the doi to URL encode * @return URl encoded doi or the original doi if not UTF-8. */ private String doiToUrl(String doi) { String url = doi; try { url = URLEncoder.encode(url, "UTF-8"); } catch (UnsupportedEncodingException uee) { log.error("UTF-8 not supported?", uee); } return url; } /** * Get a String from the Configuration looking first for a Journal override. * * @param key to lookup. * @param defaultValue if key is not found. * @return value for key. */ private String jrnlConfGetStr(String key, String defaultValue) { String path = "ambra.virtualJournals." + getCurrentJournal() + "." + key; return CONF.getString(path, CONF.getString(key, defaultValue)); } /** * Get the journal name. * * @return the name of the current journal */ private String getCurrentJournal() { return ((VirtualJournalContext) ServletActionContext.getRequest() .getAttribute(VirtualJournalContext.PUB_VIRTUALJOURNAL_CONTEXT)).getJournal(); } /** * articleXmlUtils provide methods to manipulate the XML of the articles (transformations etc) * * @param secondaryObjectService a set of XML transformation utilities */ @Required public void setSecondaryObjectService(XMLService secondaryObjectService) { this.secondaryObjectService = secondaryObjectService; } /** * @param feedService Article Feed Service */ @Required public void setFeedService(FeedService feedService) { this.feedService = feedService; } }