/* * Copyright (c) 2007-2014 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.service.search; import org.ambraproject.models.ArticleList; import org.ambraproject.service.article.ArticleService; import org.ambraproject.service.article.MostViewedArticleService; import org.ambraproject.service.article.NoSuchArticleIdException; import org.ambraproject.service.hibernate.HibernateServiceImpl; import org.ambraproject.util.Pair; import org.ambraproject.views.article.HomePageArticleInfo; import org.apache.commons.lang.StringUtils; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.criterion.Restrictions; import org.springframework.beans.factory.annotation.Required; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.transaction.annotation.Transactional; import org.w3c.dom.Document; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.net.URI; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * @author Alex Kudlick Date: 4/19/11 * <p/> * org.ambraproject.solr */ public class MostViewedArticleServiceImpl extends HibernateServiceImpl implements MostViewedArticleService { private SolrFieldConversion solrFieldConverter; private SolrHttpService solrHttpService; private ArticleService articleService; /** * Cache for the most viewed results. This is a one-off caching implementation, but since this is a spring-injected * bean, there will only be one cache per ambra instance. Also, the cache will contain 2 strings per article (doi and * title) for each of the journals, which is 100 (relatively small) strings if the limit for articles is 5 and there * are 10 journals */ private ConcurrentMap<String, MostViewedCache> cachedMostViewedResults = new ConcurrentHashMap<String, MostViewedCache>(); private static final String DOI_ATTR = "id"; private static final String TITLE_ATTR = "title_display"; private static final String STRIKING_ATTR = "striking_image"; private static final String AUTHORS_ATTR = "author_display"; private static final String ABSTRACT_ATTR = "abstract_primary_display"; @Override public List<Pair<String, String>> getMostViewedArticles(String journal, int limit, Integer numDays) throws SolrException { //check if we still have valid results in the cache MostViewedCache cache = cachedMostViewedResults.get(journal); if (cache != null && cache.isValid()) { return cache.getArticles(); } Map<String, String> params = new HashMap<String, String>(); params.put("fl", DOI_ATTR + "," + TITLE_ATTR); params.put("fq", "doc_type:full AND !article_type_facet:\"Issue Image\" AND cross_published_journal_key:" + journal); params.put("start", "0"); params.put("rows", String.valueOf(limit)); params.put("indent", "off"); String sortField = (numDays != null) ? solrFieldConverter.getViewCountingFieldName(numDays) : solrFieldConverter.getAllTimeViewsField(); params.put("sort", sortField + " desc"); Document doc = solrHttpService.makeSolrRequest(params); List<Pair<String, String>> articles = new ArrayList<Pair<String, String>>(limit); //get the children of the "result" node XPath xPath = XPathFactory.newInstance().newXPath(); try { Integer count = Integer.valueOf(xPath.evaluate("count(//result/doc)", doc)); for (int i = 1; i <= count; i++) { String doi = xPath.evaluate("//result/doc[" + i + "]/str[@name = '" + DOI_ATTR + "']/text()", doc); String title = xPath.evaluate("//result/doc[" + i + "]/str[@name = '" + TITLE_ATTR + "']/text()", doc); articles.add(new Pair<String, String>(doi, title)); } } catch (XPathExpressionException e) { throw new SolrException("Error parsing solr xml response", e); } //cache the results cachedMostViewedResults.put(journal, new MostViewedCache(articles)); return articles; } @Override public List<HomePageArticleInfo> getMostViewedArticleInfo(String journal, int offset, int limit, Integer numDays) throws SolrException { //check if we still have valid results in the cache String cacheIndex = journal + ":mostviewed" + String.valueOf(offset) + ":" + String.valueOf(limit); MostViewedCache cache = cachedMostViewedResults.get(cacheIndex); if (cache != null && cache.isValid()) { return cache.getArticleInfo(); } Map<String, String> params = new HashMap<String, String>(); params.put("fl", DOI_ATTR + "," + TITLE_ATTR + "," + STRIKING_ATTR + "," + AUTHORS_ATTR + "," + ABSTRACT_ATTR); params.put("fq", "doc_type:full AND !article_type_facet:\"Issue Image\" AND cross_published_journal_key:" + journal); params.put("start", String.valueOf(offset)); params.put("rows", String.valueOf(limit)); params.put("indent", "off"); String sortField = (numDays != null) ? solrFieldConverter.getViewCountingFieldName(numDays) : solrFieldConverter.getAllTimeViewsField(); params.put("sort", sortField + " desc"); Document doc = solrHttpService.makeSolrRequest(params); List<HomePageArticleInfo> articles = getArticleInfoFromSolrResponse(doc); //cache the results cachedMostViewedResults.put(cacheIndex, new MostViewedCache(articles)); return articles; } @Override public List<HomePageArticleInfo> getRecentArticleInfo(String journal, int offset, int limit, List<URI> articleTypes) throws SolrException { //check if we still have valid results in the cache String cacheIndex = journal + ":recent:" + String.valueOf(offset) + ":" + String.valueOf(limit); MostViewedCache cache = cachedMostViewedResults.get(cacheIndex); if (cache != null && cache.isValid()) { return cache.getArticleInfo(); } Map<String, String> params = new HashMap<String, String>(); params.put("fl", DOI_ATTR + "," + TITLE_ATTR + "," + STRIKING_ATTR + "," + AUTHORS_ATTR + "," + ABSTRACT_ATTR); params.put("fq", "doc_type:full AND !article_type_facet:\"Issue Image\" AND cross_published_journal_key:" + journal); params.put("start", String.valueOf(offset)); params.put("rows", String.valueOf(limit)); params.put("indent", "off"); params.put("sort", "publication_date desc"); Document doc = solrHttpService.makeSolrRequest(params); List<HomePageArticleInfo> articles = getArticleInfoFromSolrResponse(doc); //cache the results cachedMostViewedResults.put(cacheIndex, new MostViewedCache(articles)); return articles; } private List<HomePageArticleInfo> getArticleInfoFromSolrResponse(Document doc) throws SolrException { List<HomePageArticleInfo> articles = new ArrayList<HomePageArticleInfo>(); //get the children of the "result" node XPath xPath = XPathFactory.newInstance().newXPath(); try { Integer count = Integer.valueOf(xPath.evaluate("count(//result/doc)", doc)); for (int i = 1; i <= count; i++) { String doi = xPath.evaluate("//result/doc[" + i + "]/str[@name = '" + DOI_ATTR + "']/text()", doc); String title = xPath.evaluate("//result/doc[" + i + "]/str[@name = '" + TITLE_ATTR + "']/text()", doc); String strkImg = xPath.evaluate("//result/doc[" + i + "]/str[@name = '" + STRIKING_ATTR + "']/text()", doc); String description = xPath.evaluate("//result/doc[" + i + "]/str[@name = '" + ABSTRACT_ATTR + "']/text()", doc); Integer authors_count = Integer.valueOf(xPath.evaluate("count(//result/doc[" + i + "]/arr[@name = '" + AUTHORS_ATTR + "']/str)", doc)); List<String> authors = new ArrayList<String>(); for(int j = 1; j <= authors_count; ++j) { String authorName = xPath.evaluate("//result/doc[" + i + "]/arr[@name = '" + AUTHORS_ATTR + "']/str[" + j + "]/text()", doc); authors.add(authorName); } String author = StringUtils.join(authors, ", "); HomePageArticleInfo article = new HomePageArticleInfo(); article.setDoi(doi); article.setTitle(title); article.setStrkImgURI(strkImg); article.setAuthors(author); article.setDescription(description); articles.add(article); } } catch (XPathExpressionException e) { throw new SolrException("Error parsing solr xml response", e); } return articles; } /** * Returns a list of dois in a article list for the given Journal. * @return String of articleDois */ @Transactional(readOnly = true) public ArticleList getArticleList(final String listKey) { try { return (ArticleList) hibernateTemplate.findByCriteria( DetachedCriteria.forClass(ArticleList.class) .add(Restrictions.eq("listKey", listKey)) ).get(0); } catch (IndexOutOfBoundsException e) { return null; } } private static final String ARTICLE_LIST_TYPE = "admin"; // From migrate_ambra_1007.sql @Override @SuppressWarnings("unchecked") public List<HomePageArticleInfo> getNewsArticleInfo(final String listKey, String authId) { final List<Object[]> tempRes = (List<Object[]>)hibernateTemplate.execute(new HibernateCallback() { @Override public Object doInHibernate(Session session) throws HibernateException, SQLException { String sqlQuery = "select a.articleID, a.doi, a.title, a.strkImgURI, a.description from articleList al " + "join articleListJoinTable alj on al.articleListID = alj.articleListID " + "join article a on a.articleID = alj.articleID " + "where al.listKey = :listKey and al.listType = :listType " + "order by alj.sortOrder asc"; return session.createSQLQuery(sqlQuery) .setParameter("listKey", listKey) .setParameter("listType", ARTICLE_LIST_TYPE) .list(); } }); List<HomePageArticleInfo> articleList = new ArrayList<HomePageArticleInfo>(tempRes.size()); for(final Object row[] : tempRes) { try { articleService.checkArticleState((String)row[1], authId); HomePageArticleInfo articleInfo = new HomePageArticleInfo(); articleInfo.setDoi((String)row[1]); articleInfo.setTitle((String)row[2]); articleInfo.setStrkImgURI((String)row[3]); articleInfo.setDescription((String)row[4]); List<String> authorList = (List<String>)hibernateTemplate.execute(new HibernateCallback() { @Override public Object doInHibernate(Session session) throws HibernateException, SQLException { return session.createSQLQuery("select ap.fullName from " + "articlePerson ap join article a on ap.articleID = a.articleID " + "where a.articleID = :articleID and ap.type = 'author' order by ap.sortOrder asc") .setParameter("articleID", row[0]) .list(); } }); String authors = StringUtils.join(authorList, ", "); articleInfo.setAuthors(authors); articleList.add(articleInfo); } catch(NoSuchArticleIdException ex) { //Do nothing beyond not adding the article to the list } } return articleList; } @Required public void setSolrFieldConverter(SolrFieldConversion solrFieldConverter) { this.solrFieldConverter = solrFieldConverter; } @Required public void setSolrHttpService(SolrHttpService solrHttpService) { this.solrHttpService = solrHttpService; } @Required public void setArticleService(ArticleService articleService) { this.articleService = articleService; } }