/*
* 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.action;
import org.ambraproject.service.article.ArticleService;
import org.ambraproject.service.article.MostViewedArticleService;
import org.ambraproject.service.journal.JournalService;
import org.ambraproject.service.search.SolrException;
import org.ambraproject.views.SearchHit;
import org.ambraproject.views.article.HomePageArticleInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import java.net.URI;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Set;
/**
* @author stevec
*/
@SuppressWarnings("serial")
public class HomePageAction extends BaseActionSupport {
private static final Logger log = LoggerFactory.getLogger(HomePageAction.class);
private ArticleService articleService;
private JournalService journalService;
private MostViewedArticleService mostViewedArticleService;
private List<SearchHit> recentArticles;
private List<HomePageArticleInfo> recentArticleInfo;
private int numDaysInPast;
private int numArticlesToShow;
private List<HomePageArticleInfo> mostViewedArticleInfo;
// the action returns from mostViewedStartIndex, and sets mostViewedNextIndex = mostViewedStartIndex + mostViewedLimit
private int mostViewedNextIndex = 0;
private int mostViewedStartIndex = 0;
private int recentNextIndex = 0;
private int recentStartIndex = 0;
private List<HomePageArticleInfo> newsArticleInfo;
/**
* This execute method always returns SUCCESS
*/
@Override
public String execute() {
//for PLOSONE
log.info("Current journal is " + getCurrentJournal());
if ("PLoSONE".equals(getCurrentJournal())) {
recentStartIndex = 0;
mostViewedStartIndex = 0;
executeNews();
executeRecent();
executeMostViewed();
}
//for other journals
else {
initRecentArticles();
}
return SUCCESS;
}
//only for PLOSONE
public String executeNews() {
initNewsArticleInfo();
return SUCCESS;
}
//only for PLOSONE
public String executeRecent() {
initRecentArticleInfo();
return SUCCESS;
}
//only for PLOSONE
public String executeMostViewed() {
initMostViewedArticleInfo();
return SUCCESS;
}
/**
* Get the URIs for the Article Types which can be displayed on the <i>Recent Articles</i> tab
* on the home page. If there is no list of acceptable Article Type URIs,
* then an empty List is returned.
* This method should never return null.
* <p/>
* As an example, this XML is used to create a List of two Strings for the PLoS One Journal:
* <pre>
* <PLoSONE>
* <recentArticles>
* <numDaysInPast>7</numDaysInPast>
* <numArticlesToShow>5</numArticlesToShow>
* <typeUriArticlesToShow>
* <articleTypeUri>http://rdf.plos.org/RDF/articleType/Research%20Article</articleTypeUri>
* <articleTypeUri>http://rdf.plos.org/RDF/articleType/History/Profile</articleTypeUri>
* </typeUriArticlesToShow>
* </recentArticles>
* </PLoSONE>
* </pre>
* The logic in this method was adapted from the ArticleType.configureArticleTypes(Configuration)
* method.
*
* @param basePath The location (including Journal Name) of the properties which will be used to
* populate the returned Set. An example is <i>ambra.virtualJournals.PLoSONE.recentArticles</i>
* @return The URIs for the Article Types which will be displayed on the "Recent Articles" tab on
* the home page
*/
private List<URI> getArticleTypesToShow(String basePath) {
String baseString = basePath + ".typeUriArticlesToShow";
List<URI> typeUriArticlesToShow;
/*
* Iterate through the defined article types. This is ugly since the index needs to be given
* in xpath format to access the element, so we calculate a base string like:
* ambra.virtualJournals.PLoSONE.recentArticles.typeUriArticlesToShow.articleTypeUri(x)
* and check if that element is non-null.
*/
typeUriArticlesToShow = new LinkedList<URI>();
int count = 0;
String articleTypeUri;
while (true) {
articleTypeUri = configuration.getString(baseString + ".articleTypeUri(" + count + ")");
if (articleTypeUri != null && articleTypeUri.trim().length() > 0) {
typeUriArticlesToShow.add(URI.create(articleTypeUri));
} else {
break;
}
count++;
}
return typeUriArticlesToShow;
}
/**
* Populate the <b>recentArticles</b> (global) variable with random recent articles of
* appropriate Article Type(s).
* <ul>
* <li>The number of articles set into the <b>recentArticles</b>
* (global) variable determined by the
* <i>ambra.virtualJournals.CURRENT_JOURNAL_NAME.recentArticles.numArticlesToShow</i>
* configuration property
* </li>
* <li>The type of articles set into the <b>recentArticles</b> variable is determined by
* the list in the
* <i>ambra.virtualJournals.CURRENT_JOURNAL_NAME.recentArticles.typeUriListArticlesToShow</i>
* configuration property.
* If this property is not defined, then <b>all</b> types of articles are shown
* </li>
* <li>The initial definition of "recent" is the number of days (before today) indicated by
* the <i>ambra.virtualJournals.CURRENT_JOURNAL_NAME.recentArticles.numDaysInPast</i>
* configuration property.
* If not enough articles of the appropriate type are found in that span of time,
* then a new query is made for a somewhat longer duration.
* </li>
* </ul>
* The CURRENT_JOURNAL_NAME is acquired from the {@link BaseActionSupport#getCurrentJournal()}
*/
private void initRecentArticles() {
String journalKey = getCurrentJournal();
String journal_eIssn = journalService.getJournal(journalKey).geteIssn();
String rootKey = "ambra.virtualJournals." + journalKey + ".recentArticles";
List<URI> typeUriArticlesToShow = getArticleTypesToShow(rootKey);
numDaysInPast = configuration.getInteger(rootKey + ".numDaysInPast", 7);
numArticlesToShow = configuration.getInteger(rootKey + ".numArticlesToShow", 5);
recentArticles = articleService.getRandomRecentArticles(journal_eIssn, typeUriArticlesToShow, numDaysInPast,numArticlesToShow);
}
/**
* Populate the <b>newsArticleInfo</b> (global) variable with articles
* for PLOSONE
*/
private void initNewsArticleInfo() {
String listKey = getCurrentJournal().toLowerCase() + "_news";
newsArticleInfo = mostViewedArticleService.getNewsArticleInfo(listKey, getAuthId());
}
/**
* Populate the <b>recenetArticleInfo</b> (global) variable with articles
* for PLOSONE
*/
private void initRecentArticleInfo() {
String recentKey = "ambra.virtualJournals." + getCurrentJournal() + ".recentArticles";
try {
String limitKey = (recentStartIndex == 0 ? ".limitFirstPage" : ".limit");
int limit = configuration.getInt(recentKey + limitKey);
List<URI> typeUriArticlesToShow = getArticleTypesToShow(recentKey);
recentArticleInfo = mostViewedArticleService.getRecentArticleInfo(getCurrentJournal(), recentStartIndex, limit, typeUriArticlesToShow);
recentNextIndex = recentStartIndex + recentArticleInfo.size();
} catch (SolrException e) {
log.error("Error querying solr for most viewed articles; returning empty list", e);
recentArticleInfo = new LinkedList<HomePageArticleInfo>();
recentNextIndex = recentStartIndex;
}
}
/**
* Populate the <b>mostViewedArticleInfo</b> (global) variable with articles
* for PLOSONE
*
*/
private void initMostViewedArticleInfo() {
String mostViewedKey = "ambra.virtualJournals." + getCurrentJournal() + ".mostViewedArticles";
try {
String limitKey = (mostViewedStartIndex == 0 ? ".limitFirstPage" : ".limit");
int limit = configuration.getInt(mostViewedKey + limitKey);
Integer days;
try {
days = configuration.getInt(mostViewedKey + ".timeFrame");
} catch (Exception e) {
days = null;
}
mostViewedArticleInfo = mostViewedArticleService.getMostViewedArticleInfo(getCurrentJournal(), mostViewedStartIndex, limit, days);
mostViewedNextIndex = mostViewedStartIndex + mostViewedArticleInfo.size();
} catch (SolrException e) {
log.error("Error querying solr for most viewed articles; returning empty list", e);
mostViewedArticleInfo = new LinkedList<HomePageArticleInfo>();
mostViewedNextIndex = mostViewedStartIndex;
}
}
public void setMostViewedArticleService(MostViewedArticleService mostViewedArticleService) {
this.mostViewedArticleService = mostViewedArticleService;
}
public List<HomePageArticleInfo> getMostViewedArticleInfo() {
return mostViewedArticleInfo;
}
/**
* Retrieves the most recently published articles in the last 7 days
*
* @return array of SearchHit objects
*/
public List<SearchHit> getRecentArticles() {
return recentArticles;
}
/**
* Retrieves the most recently published articles in the last 7 days
*
* @return array of SearchHit objects
*/
public List<HomePageArticleInfo> getRecentArticleInfo() {
return recentArticleInfo;
}
/**
* Returns an array of numValues ints which are randomly selected between 0 (inclusive) and
* maxValue(exclusive). If maxValue is less than numValues, will return maxValue items. Guarantees
* uniqueness of values.
*
* @param numValues Length of the array
* @param maxValue Maximum value of each element of the array
* @return array of random ints
*/
public int[] randomNumbers(int numValues, int maxValue) {
if (numValues > maxValue) {
numValues = maxValue;
}
Random rng = new Random(System.currentTimeMillis());
Set<Integer> intValues = new HashSet<Integer>();
while (intValues.size() < numValues) {
Integer oneNum = rng.nextInt(maxValue);
if (!intValues.contains(oneNum)) {
intValues.add(oneNum);
}
}
Iterator<Integer> iter = intValues.iterator();
int[] returnArray = new int[intValues.size()];
for (int i = 0; iter.hasNext(); i++) {
returnArray[i] = iter.next();
}
return returnArray;
}
/**
* @param articleService the ArticleService to set
*/
@Required
public void setArticleService(ArticleService articleService) {
this.articleService = articleService;
}
/*
* @param journalService the JournalService to use
*/
public void setJournalService(JournalService journalService) {
this.journalService = journalService;
}
public int getNumDaysInPast() {
return numDaysInPast;
}
public int getNumArticlesToShow() {
return numArticlesToShow;
}
public int getMostViewedNextIndex() {
return mostViewedNextIndex;
}
public void setMostViewedNextIndex(int mostViewedNextIndex) {
this.mostViewedNextIndex = mostViewedNextIndex;
}
public int getMostViewedStartIndex() {
return mostViewedStartIndex;
}
public void setMostViewedStartIndex(int mostViewedStartIndex) {
this.mostViewedStartIndex = mostViewedStartIndex;
}
public int getRecentNextIndex() {
return recentNextIndex;
}
public void setRecentNextIndex(int recentNextIndex) {
this.recentNextIndex = recentNextIndex;
}
public int getRecentStartIndex() {
return recentStartIndex;
}
public void setRecentStartIndex(int recentStartIndex) {
this.recentStartIndex = recentStartIndex;
}
public List<HomePageArticleInfo> getNewsArticleInfo() {
return newsArticleInfo;
}
}