Java tutorial
/* * $HeadURL$ * $Id$ * * Copyright (c) 2006-$today.year 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.article.service; import org.ambraproject.ApplicationException; import org.ambraproject.journal.JournalService; import org.ambraproject.model.UserProfileInfo; import org.ambraproject.model.article.ArticleInfo; import org.ambraproject.model.article.ArticleType; import org.ambraproject.model.article.CitationInfo; import org.ambraproject.model.article.RelatedArticleInfo; import org.ambraproject.models.Article; import org.ambraproject.models.ArticleAuthor; import org.ambraproject.models.ArticleRelationship; import org.ambraproject.models.Category; import org.ambraproject.models.UserProfile; import org.ambraproject.permission.service.PermissionsService; import org.ambraproject.service.HibernateServiceImpl; import org.ambraproject.views.ArticleCategory; import org.hibernate.Criteria; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.criterion.Order; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Restrictions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Required; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.transaction.annotation.Transactional; import org.topazproject.ambra.models.Issue; import org.topazproject.ambra.models.Journal; import org.topazproject.ambra.models.Volume; import java.net.URI; import java.sql.SQLException; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; /** * @author Joe Osowski */ public class ArticleServiceImpl extends HibernateServiceImpl implements ArticleService { private static final Logger log = LoggerFactory.getLogger(ArticleServiceImpl.class); private PermissionsService permissionsService; private JournalService journalService; /** * Determines if the articleURI is of type researchArticle * * @param article The URI of the article * @param authId the authorization ID of the current user * @return True if the article is a research article * @throws org.ambraproject.ApplicationException * if there was a problem talking to the OTM * @throws NoSuchArticleIdException When the article does not exist */ public boolean isResearchArticle(final Article article, final String authId) throws ApplicationException, NoSuchArticleIdException { // resolve article type and supported properties ArticleType articleType = ArticleType.getDefaultArticleType(); for (String artTypeUri : article.getTypes()) { if (ArticleType.getKnownArticleTypeForURI(URI.create(artTypeUri)) != null) { articleType = ArticleType.getKnownArticleTypeForURI(URI.create(artTypeUri)); break; } } if (articleType == null) { throw new ApplicationException("Unable to resolve article type for: " + article.getDoi()); } return ArticleType.isResearchArticle(articleType); } /** * Determines if the articleURI is of type researchArticle * * @param articleInfo The URI of the article * @param authId the authorization ID of the current user * @return True if the article is a research article * @throws org.ambraproject.ApplicationException * if there was a problem talking to the OTM * @throws NoSuchArticleIdException When the article does not exist */ public boolean isResearchArticle(final ArticleInfo articleInfo, final String authId) throws ApplicationException, NoSuchArticleIdException { ArticleType articleType = ArticleType.getDefaultArticleType(); for (String artTypeUri : articleInfo.getTypes()) { if (ArticleType.getKnownArticleTypeForURI(URI.create(artTypeUri)) != null) { articleType = ArticleType.getKnownArticleTypeForURI(URI.create(artTypeUri)); break; } } if (articleType == null) { throw new ApplicationException("Unable to resolve article type for: " + articleInfo.getDoi()); } return ArticleType.isResearchArticle(articleType); } /** * Returns a Map of publishable articles in the order indicated. The key of each element is the DOI (URI) of the * Article. The value of each element is the Article itself. * * @param eIssn eIssn of the Journal that this Article belongs to (no filter if null or empty) * @param orderField field on which to sort the results (no sort if null or empty) Should be one of the * ORDER_BY_FIELD_ constants from this class * @param isOrderAscending controls the sort order (default is "false" so default order is descending) * @return Map of publishable articles sorted as indicated * @throws org.ambraproject.ApplicationException * */ public List<ArticleInfo> getPublishableArticles(String eIssn, String orderField, boolean isOrderAscending) throws ApplicationException { List<ArticleInfo> articlesInfo = new ArrayList<ArticleInfo>(); Order order = isOrderAscending ? Order.asc(orderField) : Order.desc(orderField); List<Object[]> results = hibernateTemplate.findByCriteria(DetachedCriteria.forClass(Article.class) .add(Restrictions.eq("eIssn", eIssn)).add(Restrictions.eq("state", Article.STATE_UNPUBLISHED)) .addOrder(order).setProjection(Projections.projectionList().add(Projections.property("doi")) .add(Projections.property("date")))); for (Object[] rows : results) { ArticleInfo articleInfo = new ArticleInfo(); articleInfo.setDoi(rows[0].toString()); articleInfo.setDate((Date) rows[1]); articlesInfo.add(articleInfo); } return articlesInfo; } /** * Change an articles state. * * @param articleDoi uri * @param authId the authorization ID of the current user * @param state state * * @throws NoSuchArticleIdException NoSuchArticleIdException */ @Transactional(rollbackFor = { Throwable.class }) public void setState(final String articleDoi, final String authId, final int state) throws NoSuchArticleIdException { permissionsService.checkRole(PermissionsService.ADMIN_ROLE, authId); List articles = hibernateTemplate .findByCriteria(DetachedCriteria.forClass(Article.class).add(Restrictions.eq("doi", articleDoi))); if (articles.size() == 0) { throw new NoSuchArticleIdException(articleDoi); } Article a = (Article) articles.get(0); a.setState(state); //Remove relationships if this article is being disabled //they will be created on re-ingest if necessary, but if both articles in a reciprocal relationship are //disabled and have the relationships removed from xml, we want to the relationships to be gone when both are reingested if (state == Article.STATE_DISABLED) { a.getRelatedArticles().clear(); } hibernateTemplate.update(a); //log whenever someone disables or unpublishes an article if ((state == Article.STATE_UNPUBLISHED || state == Article.STATE_DISABLED) && log.isInfoEnabled()) { DetachedCriteria criteria = DetachedCriteria.forClass(UserProfile.class) .setProjection(Projections.property("displayName")).add(Restrictions.eq("authId", authId)); String userName = (String) hibernateTemplate.findByCriteria(criteria, 0, 1).get(0); userName = userName == null ? "UNKNOWN" : userName; log.info("User '{}' {} the article {}", new String[] { userName, state == Article.STATE_DISABLED ? "disabled" : "unpublished", articleDoi }); } } /** * Get the ids of all articles satisfying the given criteria. * <p/> * This method calls <code>getArticles(...)</code> then parses the Article IDs from that List. * <p/> * * @param params * @return the (possibly empty) list of article ids. * @throws java.text.ParseException if any of the dates could not be parsed */ @Transactional(readOnly = true) @SuppressWarnings("unchecked") public List<String> getArticleDOIs(final ArticleServiceSearchParameters params) throws ParseException { List<Article> articles = getArticles(params); List<String> articleIds = new ArrayList<String>(articles.size()); for (Article article : articles) { articleIds.add(article.getDoi()); } return articleIds; } /** * Get all of the articles satisfying the given criteria. * @param params * * @return all of the articles satisfying the given criteria (possibly null) Key is the Article DOI. Value is the * Article itself. */ @Transactional(readOnly = true) @SuppressWarnings("unchecked") public List<Article> getArticles(final ArticleServiceSearchParameters params) { return (List<Article>) this.hibernateTemplate.execute(new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { Criteria query = session.createCriteria(Article.class); if (params.getStates() != null && params.getStates().length > 0) { List<Integer> statesList = new ArrayList<Integer>(params.getStates().length); for (int state : params.getStates()) { statesList.add(state); } query.add(Restrictions.in("state", statesList)); } if (params.geteIssn() != null) { query.add(Restrictions.eq("eIssn", params.geteIssn())); } if (params.getOrderField() != null) { if (params.isOrderAscending()) { query.addOrder(Order.asc(params.getOrderField())); } else { query.addOrder(Order.desc(params.getOrderField())); } } if (params.getStartDate() != null) { query.add(Restrictions.ge("date", params.getStartDate())); } if (params.getEndDate() != null) { query.add(Restrictions.le("date", params.getEndDate())); } if (params.getMaxResults() > 0) { query.setMaxResults(params.getMaxResults()); } //Filter the results post-db access since the kind of restriction we want isn't easily representable in sql - //we want only articles such that the list of full names of the author list contains ALL the names in the given //author array. This is NOT an 'in' restriction or an 'equals' restriction List<Article> queryResults = query.list(); List<Article> filteredResults = new ArrayList<Article>(queryResults.size()); for (Article article : queryResults) { if (matchesAuthorFilter(params.getAuthors(), article.getAuthors()) && matchesCategoriesFilter(params.getCategories(), article.getCategories())) { filteredResults.add(article); } } return filteredResults; } }); } /** * Get an Article by ID. * * @param articleID ID of Article to get. * @param authId the authorization ID of the current user * @return Article with specified URI or null if not found. * @throws NoSuchArticleIdException NoSuchArticleIdException */ @Transactional(readOnly = true, noRollbackFor = { SecurityException.class }) public Article getArticle(final Long articleID, final String authId) throws NoSuchArticleIdException { // sanity check parms if (articleID == null) throw new IllegalArgumentException("articleID == null"); Article article = (Article) hibernateTemplate.load(Article.class, articleID); if (article == null) { throw new NoSuchArticleIdException(String.valueOf(articleID)); } checkArticleState(article, authId); return article; } /** * Get an Article by URI. * * @param articleDoi URI of Article to get. * @param authId the authorization ID of the current user * @return Article with specified URI or null if not found. * @throws NoSuchArticleIdException NoSuchArticleIdException */ @Transactional(readOnly = true, noRollbackFor = { SecurityException.class }) public Article getArticle(final String articleDoi, final String authId) throws NoSuchArticleIdException { // sanity check parms if (articleDoi == null) throw new IllegalArgumentException("articleDoi == null"); List articles = hibernateTemplate .findByCriteria(DetachedCriteria.forClass(Article.class).add(Restrictions.eq("doi", articleDoi))); if (articles.size() == 0) { throw new NoSuchArticleIdException(articleDoi); } checkArticleState((Article) articles.get(0), authId); return (Article) articles.get(0); } private void checkArticleState(Article article, String authId) throws NoSuchArticleIdException { //If the article is unpublished, it should not be returned if the user is not an admin if (article.getState() == Article.STATE_UNPUBLISHED) { try { permissionsService.checkRole(PermissionsService.ADMIN_ROLE, authId); } catch (SecurityException se) { throw new NoSuchArticleIdException(article.getDoi()); } } //If the article is disabled, don't display it ever if (article.getState() == Article.STATE_DISABLED) { throw new NoSuchArticleIdException(article.getDoi()); } } /** * Get articles based on a list of Article id's. * * @param articleDois list of article id's * @param authId the authorization ID of the current user * @return <code>List<Article></code> of articles requested * @throws java.text.ParseException when article ids are invalid * @throws NoSuchArticleIdException NoSuchArticleIdException */ @Transactional(readOnly = true) public List<Article> getArticles(List<String> articleDois, final String authId) throws ParseException, NoSuchArticleIdException { List<Article> articleList = new ArrayList<Article>(); for (String articleDoi : articleDois) articleList.add(getArticle(articleDoi, authId)); return articleList; } /** * Get a List of all of the Journal/Volume/Issue combinations that contain the <code>articleURI</code> which was * passed in. Each primary List element contains a secondary List of six Strings which are, in order: <ul> * <li><strong>Element 0: </strong> Journal URI</li> <li><strong>Element 1: </strong> Journal key</li> * <li><strong>Element 2: </strong> Volume URI</li> <li><strong>Element 3: </strong> Volume name</li> * <li><strong>Element 4: </strong> Issue URI</li> <li><strong>Element 5: </strong> Issue name</li> </ul> A Journal * might have multiple Volumes, any of which might have multiple Issues that contain the <code>articleURI</code>. The * primary List will always contain one element for each Issue that contains the <code>articleURI</code>. * * @param articleDoi Article DOI that is contained in the Journal/Volume/Issue combinations which will be returned * @return All of the Journal/Volume/Issue combinations which contain the articleURI passed in */ @Transactional(readOnly = true) @SuppressWarnings("unchecked") public List<List<String>> getArticleIssues(final String articleDoi) { return (List<List<String>>) hibernateTemplate.execute(new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { List<Object[]> articleIssues = session.createSQLQuery("select {j.*}, {v.*}, {i.*} " + "from AggregationSimpleCollection ial, VolumeIssueList vil, JournalVolumeList jvl, Issue i, Volume v, " + "Journal j " + "where ial.uri = :articleURI " + "and vil.issueUri = ial.aggregationArticleUri " + "and jvl.volumeUri = vil.aggregationUri " + "and i.aggregationUri = ial.aggregationArticleUri " + "and v.aggregationUri = vil.aggregationUri " + "and j.aggregationUri = jvl.aggregationUri " + "order by i.created desc ") .addEntity("j", Journal.class).addEntity("v", Volume.class).addEntity("i", Issue.class) .setString("articleURI", articleDoi).list(); List<List<String>> finalResults = new ArrayList<List<String>>(articleIssues.size()); for (Object[] row : articleIssues) { Journal journal = (Journal) row[0]; Volume volume = (Volume) row[1]; Issue issue = (Issue) row[2]; List<String> secondaryList = new ArrayList<String>(); secondaryList.add(journal.getId().toString()); // Journal URI secondaryList.add(journal.getKey()); // Journal Key secondaryList.add(volume.getId().toString()); // Volume URI secondaryList.add(volume.getDisplayName()); // Volume name secondaryList.add(issue.getId().toString()); // Issue URI secondaryList.add(issue.getDisplayName()); // Issue name finalResults.add(secondaryList); } return finalResults; } }); } /** * Get the articleInfo object for an article * @param articleDoi the ID of the article * @param authId the authorization ID of the current user * @return articleInfo */ @Transactional(readOnly = true) @Override @SuppressWarnings("unchecked") public ArticleInfo getArticleInfo(final String articleDoi, final String authId) throws NoSuchArticleIdException { final Article article; article = getArticle(articleDoi, authId); final ArticleInfo articleInfo = new ArticleInfo(); articleInfo.setId(article.getID()); //Set properties from the dublin core articleInfo.setDoi(article.getDoi()); articleInfo.setDate(article.getDate()); articleInfo.setTitle(article.getTitle()); articleInfo.setVolume(article.getVolume()); articleInfo.setIssue(article.getIssue()); articleInfo.setJournal(article.getJournal()); articleInfo.setDescription(article.getDescription()); articleInfo.setRights(article.getRights()); articleInfo.setPublisher(article.getPublisherName()); articleInfo.seteIssn(article.geteIssn()); articleInfo.setTypes(article.getTypes()); articleInfo.setPages(article.getPages()); articleInfo.setIssue(article.getIssue()); articleInfo.setVolume(article.getVolume()); articleInfo.seteLocationId(article.geteLocationId()); //Set the citation info CitationInfo citationInfo = new CitationInfo(); citationInfo.setId(URI.create(article.getDoi())); citationInfo.setCollaborativeAuthors(article.getCollaborativeAuthors()); Set<Category> categories = article.getCategories(); Set<ArticleCategory> catViews = new HashSet<ArticleCategory>(categories.size()); for (Category cat : categories) { catViews.add(new ArticleCategory(cat.getMainCategory(), cat.getSubCategory())); } articleInfo.setCategories(catViews); //authors (list of UserProfileInfo) //TODO: Refactor ArticleInfo and CitationInfo objects //there's no reason why authors need to be attached to the citation List<UserProfileInfo> authors = new ArrayList<UserProfileInfo>(); for (ArticleAuthor ac : article.getAuthors()) { UserProfileInfo author = new UserProfileInfo(); author.setRealName(ac.getFullName()); authors.add(author); } citationInfo.setAuthors(authors); articleInfo.setCi(citationInfo); //set article type if (article.getTypes() != null) { articleInfo.setAt(article.getTypes()); } //set journals /*fixed for NHOPE-103 */ articleInfo.setJournals(journalService.getJournalNameForObject(URI.create(article.getDoi()))); //get related articles //this results in more queries than doing a join, but getArticle() already has security logic built in to it //and a very small percentage of articles even have related articles articleInfo.setRelatedArticles(new ArrayList<RelatedArticleInfo>(article.getRelatedArticles().size())); for (ArticleRelationship relationship : article.getRelatedArticles()) { if (relationship.getOtherArticleDoi() != null) { try { Article otherArticle = getArticle(relationship.getOtherArticleDoi(), authId); RelatedArticleInfo relatedArticleInfo = new RelatedArticleInfo(); relatedArticleInfo.setUri(URI.create(otherArticle.getDoi())); relatedArticleInfo.setTitle(otherArticle.getTitle()); if (!articleInfo.getRelatedArticles().contains(relatedArticleInfo)) { articleInfo.getRelatedArticles().add(relatedArticleInfo); } } catch (NoSuchArticleIdException e) { //exclude this article } } } log.debug( "loaded ArticleInfo: id={}, articleTypes={}, " + "date={}, title={}, authors={}, related-articles={}", new Object[] { articleInfo.getDoi(), articleInfo.getArticleTypeForDisplay(), articleInfo.getDate(), articleInfo.getTitle(), Arrays.toString(articleInfo.getAuthors().toArray()), Arrays.toString(articleInfo.getRelatedArticles().toArray()) }); return articleInfo; } @Override public ArticleInfo getBasicArticleView(Long articleID) throws NoSuchArticleIdException { if (articleID == null) { throw new NoSuchArticleIdException("Null id"); } log.debug("loading up title and doi for article: {}", articleID); Object[] results = new Object[0]; try { results = (Object[]) hibernateTemplate.findByCriteria(DetachedCriteria.forClass(Article.class) .add(Restrictions.eq("ID", articleID)).setProjection(Projections.projectionList() .add(Projections.property("doi")).add(Projections.property("title"))), 0, 1).get(0); } catch (IndexOutOfBoundsException e) { throw new NoSuchArticleIdException(articleID.toString()); } ArticleInfo articleInfo = new ArticleInfo(); articleInfo.setDoi((String) results[0]); articleInfo.setTitle((String) results[1]); articleInfo.setId(articleID); return articleInfo; } @Override public ArticleInfo getBasicArticleView(String articleDoi) throws NoSuchArticleIdException { if (articleDoi == null) { throw new NoSuchArticleIdException("Null doi"); } log.debug("loading up title and doi for article: {}", articleDoi); Object[] results = new Object[0]; try { results = (Object[]) hibernateTemplate.findByCriteria( DetachedCriteria.forClass(Article.class).add(Restrictions.eq("doi", articleDoi)).setProjection( Projections.projectionList().add(Projections.id()).add(Projections.property("title"))), 0, 1).get(0); } catch (IndexOutOfBoundsException e) { throw new NoSuchArticleIdException(articleDoi.toString()); } ArticleInfo articleInfo = new ArticleInfo(); articleInfo.setDoi(articleDoi); articleInfo.setId((Long) results[0]); articleInfo.setTitle((String) results[1]); return articleInfo; } /** * Help0er method for getArticleIds() since the restriction we want doesn't appear * * @param authorFilter * @param authors * @return */ private boolean matchesAuthorFilter(String[] authorFilter, List<ArticleAuthor> authors) { if (authorFilter != null && authorFilter.length > 0) { List<String> authorNames = new ArrayList<String>(authors.size()); for (ArticleAuthor author : authors) { authorNames.add(author.getFullName()); } for (String author : authorFilter) { if (!authorNames.contains(author)) { return false; } } } return true; } /** * Helper method for getArticleIds(), since the type of restriction needed doesn't seem to be do-able in HQL or with * criteria - we want articles where each of the Strings in the category array is the main category of one of the * article's categories * * @param filterCategories - the array of main categories that was passed in to getArticleIds() to use to filter * results * @param categorySet - the 'categories' property of an article * @return - true if the article passes the category filter, false otherwise */ private boolean matchesCategoriesFilter(String[] filterCategories, Set<Category> categorySet) { if (filterCategories != null && filterCategories.length > 0) { if (filterCategories.length > categorySet.size()) { return false; //can't possibly contain all the categories if there's more of them than you have } //Just get the main category Set<String> mainCategories = new HashSet<String>(categorySet.size()); for (Category category : categorySet) { mainCategories.add(category.getMainCategory()); } //check that all the filter categories are in their for (String cat : filterCategories) { if (!mainCategories.contains(cat)) { return false; } } } return true; } /** * @param permissionsService the permissions service to use */ @Required public void setPermissionsService(PermissionsService permissionsService) { this.permissionsService = permissionsService; } @Required public void setJournalService(JournalService journalService) { this.journalService = journalService; } }