/* $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.action.article; import com.opensymphony.xwork2.validator.annotations.RequiredStringValidator; import org.ambraproject.action.BaseSessionAwareActionSupport; import org.ambraproject.filestore.FileStoreService; import org.ambraproject.models.ArticleAsset; import org.ambraproject.models.ArticleView; import org.ambraproject.models.UserProfile; import org.ambraproject.service.article.ArticleAssetService; import org.ambraproject.service.article.NoSuchObjectIdException; import org.ambraproject.service.user.UserService; import org.ambraproject.service.xml.XMLService; import org.ambraproject.util.FileUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Required; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Date; /** * Fetch the object is primarily used to fetch assets associated with a particular DOI. These assets (tif's pdf's, * supplementary data etc) are either used in the rendered html version of the article or supplied to the user for * downloading. * <p/> * All transactions will be spanning over to results. */ public class FetchObjectAction extends BaseSessionAwareActionSupport { private static final Logger log = LoggerFactory.getLogger(FetchObjectAction.class); private ArticleAssetService articleAssetService; private FileStoreService fileStoreService; private XMLService xmlService; private UserService userService; private boolean fullDOI = false; private String uri; private String representation; private String contentDisposition; private String contentType; private String xReproxyList; private String reproxyCacheSettings; private InputStream inputStream; private Long contentLength; private Date lastModified; /** * Return the object for a given uri and representation * * @return webwork status code * @throws Exception Exception */ public String fetchObjectAction() { ArticleAsset articleAsset; try { if (StringUtils.isEmpty(representation)) { addActionMessage("No representation specified"); return ERROR; } articleAsset = articleAssetService.getArticleAsset(uri, representation, getAuthId()); setResponseParams(articleAsset); // Using the uri (doi) and representation (the file type) // construct a fsid to get the Inputstream String fsid = fileStoreService.objectIDMapper().doiTofsid(uri, representation); // If x-reproxy is available and they are not requesting a transformed // document then get the redirect urls. Boolean needTransformedXML = (fullDOI && "XML".equals(representation)); if (fileStoreService.hasXReproxy() && !needTransformedXML) { StringBuilder str = new StringBuilder(); URL[] urls = fileStoreService.getRedirectURL(fsid); for (URL url : urls) { str.append(url).append(" "); } xReproxyList = str.toString(); reproxyCacheSettings = fileStoreService.getReproxyCacheSettings(); } else { // x-reproxy not available so return the xml document stream // or a transformed document if requested. InputStream fsStream = fileStoreService.getFileInStream(fsid); inputStream = needTransformedXML ? xmlService.getTransformedInputStream(fsStream) : fsStream; } } catch (NoSuchObjectIdException e) { log.info("Object not found: " + uri, e); return ERROR; } catch (Exception e) { log.error("Error retrieving object: " + uri, e); return ERROR; } //If the user is logged in, record this as an xml or pdf download UserProfile user = getCurrentUser(); if (user != null) { try { if ("XML".equalsIgnoreCase(representation) && !fullDOI) { //downloading xml userService.recordArticleView( user.getID(), articleAssetService.getArticleID(articleAsset), ArticleView.Type.XML_DOWNLOAD); } else if ("PDF".equalsIgnoreCase(representation)) { //downloading pdf userService.recordArticleView (user.getID(), articleAssetService.getArticleID(articleAsset), ArticleView.Type.PDF_DOWNLOAD); } } catch (Exception e) { log.error("Error recording an object download for user: {} and uri: {}", user.getID(), uri); log.error(e.getMessage(), e); } } return SUCCESS; } /** * Fetch the only representation of the object. If the object does not exist or it has no representations then an * error is returned; if there are more than 1 representations a random representation is chosen. * <p/> * WTO: From the WTF department - Look into who uses this and why. Seems really stupid. * * @return webwork status code * @throws Exception Exception */ public String fetchSingleRepresentation() { try { final ArticleAsset articleAsset = articleAssetService.getSuppInfoAsset(uri, getAuthId()); if (null == articleAsset) { addActionMessage("No object found for uri: " + uri); return ERROR; } setResponseParams(articleAsset); // Using the uri (doi) and representation (the file type) // construct the fsid to get the Inputstream. String fsid = fileStoreService.objectIDMapper().doiTofsid(uri, articleAsset.getExtension()); if (fileStoreService.hasXReproxy()) { StringBuilder str = new StringBuilder(); URL[] urls = fileStoreService.getRedirectURL(fsid); for (URL url : urls) { str.append(url).append(" "); } xReproxyList = str.toString(); reproxyCacheSettings = fileStoreService.getReproxyCacheSettings(); } else { // x-reproxy not available so return the xml document stream inputStream = fileStoreService.getFileInStream(fsid); } } catch (NoSuchObjectIdException e) { log.info("Object not found: " + uri, e); return ERROR; } catch (Exception e) { log.error("Error retrieving object: " + uri, e); return ERROR; } return SUCCESS; } /* * Set up the various feilds that will be needed by the results handler * to properly form a response. */ private void setResponseParams(ArticleAsset articleAsset) throws IOException { contentType = articleAsset.getContentType(); contentLength = articleAsset.getSize(); if (contentType == null) contentType = "application/octet-stream"; lastModified = articleAsset.getLastModified(); if (lastModified == null) log.warn("Missing modification date for " + uri); if ("PDF".equalsIgnoreCase(representation)) { contentType = "application/pdf"; contentDisposition = getContentDisposition("pdf"); } else { contentDisposition = getContentDisposition(getFileExtension(contentType)); } } private String getContentDisposition(final String fileExt) { return "filename=\"" + FileUtils.getFileName(uri) + "." + fileExt + "\""; } private String getFileExtension(final String contentType) throws IOException { return FileUtils.getDefaultFileExtByMimeType(contentType); } @RequiredStringValidator(message = "Object URI is required.") public String getUri() { return uri; } /** * @param uri set uri */ public void setUri(final String uri) { this.uri = uri; } /** * @return the representation of the object */ public String getRepresentation() { return representation; } /** * @param representation set the representation of the article ex: XML, PDF, etc */ public void setRepresentation(final String representation) { this.representation = representation; } /** * Returns the InputStream of the File store * * @return */ public InputStream getInputStream() { return inputStream; } public Long getContentLength() { return contentLength; } /** * @return the filename that the file will be saved as of the client browser */ public String getContentDisposition() { return contentDisposition; } /** * @return Return the content type for the object */ public String getContentType() { return contentType; } /** * @return Return the last modified time for the object */ public Date getLastModified() { return lastModified; } /** * @return Return the list of x-reproxy url's that are available for this request. */ public String getXReproxyList() { return xReproxyList; } /** * @return Return the reproxy cache settings. */ public String getReproxyCacheSettings() { return this.reproxyCacheSettings; } /** * Set wether we should run the XSL transformation for the full DOI or not. * * @return */ public boolean isFullDOI() { return fullDOI; } /** * Wether we should run the XSL transformation for the full DOI or not. * * @param fullDOI */ public void setFullDOI(boolean fullDOI) { this.fullDOI = fullDOI; } /** * Set the XMLService * * @param xmlService */ @Required public void setXmlService(XMLService xmlService) { this.xmlService = xmlService; } /** * Set the FileStoreService * * @param fileStoreService */ @Required public void setFileStoreService(FileStoreService fileStoreService) { this.fileStoreService = fileStoreService; } /** * Spring setter method to inject articleAssetService * * @param articleAssetService articleAssetService */ @Required public void setArticleAssetService(final ArticleAssetService articleAssetService) { this.articleAssetService = articleAssetService; } @Required public void setUserService(UserService userService) { this.userService = userService; } }