/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/citations/trunk/citations-impl/impl/src/java/org/sakaiproject/citation/impl/CitationListAccessServlet.java $ * $Id: CitationListAccessServlet.java 124219 2013-05-17 21:10:51Z a.fish@lancaster.ac.uk $ *********************************************************************************** * * Copyright (c) 2006, 2007, 2008 The Sakai Foundation * * Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.sakaiproject.citation.impl; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.citation.api.Citation; import org.sakaiproject.citation.api.CitationCollection; import org.sakaiproject.citation.api.Schema; import org.sakaiproject.citation.api.Schema.Field; import org.sakaiproject.citation.cover.CitationService; import org.sakaiproject.citation.cover.ConfigurationService; import org.sakaiproject.content.api.ContentResource; import org.sakaiproject.content.cover.ContentHostingService; import org.sakaiproject.entity.api.EntityAccessOverloadException; import org.sakaiproject.entity.api.EntityCopyrightException; import org.sakaiproject.entity.api.EntityNotDefinedException; import org.sakaiproject.entity.api.EntityPermissionException; import org.sakaiproject.entity.api.HttpAccess; import org.sakaiproject.entity.api.Reference; import org.sakaiproject.entity.api.ResourceProperties; import org.sakaiproject.event.api.Event; import org.sakaiproject.event.cover.EventTrackingService; import org.sakaiproject.exception.IdUnusedException; import org.sakaiproject.exception.PermissionException; import org.sakaiproject.exception.ServerOverloadException; import org.sakaiproject.exception.TypeException; import org.sakaiproject.util.ResourceLoader; import org.sakaiproject.util.Validator; /** * */ public class CitationListAccessServlet implements HttpAccess { public static final String LIST_TEMPLATE = "/vm/citationList.vm"; /** Messages, for the http access. */ protected static ResourceLoader rb = new ResourceLoader("citations"); /** Our logger. */ private static Log m_log = LogFactory.getLog(CitationListAccessServlet.class); /** * Handle an HTTP request for access. The request and response objects are provider.<br /> * The access is for the referenced entity.<br /> * Use the response object to send the headers, length, content type, and bytes of the response in whatever manner needed.<br /> * Make the response ONLY if it is permitted and exists and otherwise valid. Use the exceptions for any error handling. * * @param req * The request object. * @param res * The response object. * @param ref * The entity reference * @param copyrightAcceptedRefs * The collection (entity reference String) of entities that the end user in this session have already accepted the copyright for. * @throws EntityPermissionException * Throw this if the current user does not have permission for the access. * @throws EntityNotDefinedException * Throw this if the ref is not supported or the entity is not available for access in any way. * @throws EntityAccessOverloadException * Throw this if you are rejecting an otherwise valid request because of some sort of server resource shortage or limit. * @throws EntityCopyrightException * Throw this if you are rejecting an otherwise valid request because the user needs to agree to the copyright and has not yet done so. */ public void handleAccess(HttpServletRequest req, HttpServletResponse res, Reference ref, Collection copyrightAcceptedRefs) throws EntityPermissionException, EntityNotDefinedException, EntityAccessOverloadException, EntityCopyrightException { String subtype = ref.getSubType(); if(org.sakaiproject.citation.api.CitationService.REF_TYPE_EXPORT_RIS_SEL.equals(subtype) || org.sakaiproject.citation.api.CitationService.REF_TYPE_EXPORT_RIS_ALL.equals(subtype)) { handleExportRequest(req, res, ref, org.sakaiproject.citation.api.CitationService.RIS_FORMAT, subtype); } else if(org.sakaiproject.citation.api.CitationService.REF_TYPE_VIEW_LIST.equals(subtype)) { handleViewRequest(req, res, ref); } else { throw new EntityNotDefinedException(ref.getReference()); } // SAK-22299. Build a pseudo content hosting event so that sitestats picks it up in its special resource area. Event e = EventTrackingService.newEvent(ContentHostingService.EVENT_RESOURCE_READ, "/content" + ref.getId(), false); EventTrackingService.post(e); } // handleAccess protected void handleExportRequest(HttpServletRequest req, HttpServletResponse res, Reference ref, String format, String subtype) throws EntityNotDefinedException, EntityAccessOverloadException, EntityPermissionException { if(! ContentHostingService.allowGetResource(req.getParameter("resourceId"))) { String url = (req.getRequestURL()).toString(); String user = ""; if(req.getUserPrincipal() != null) { user = req.getUserPrincipal().getName(); } throw new EntityPermissionException(user, ContentHostingService.EVENT_RESOURCE_READ, ref.getReference()); } String fileName = req.getParameter("resourceDisplayName"); if(fileName == null || fileName.trim().equals("")) { fileName = rb.getString("export.default.filename"); } if(org.sakaiproject.citation.api.CitationService.RIS_FORMAT.equals(format)) { String citationCollectionId = req.getParameter("citationCollectionId"); List<String> citationIds = new java.util.ArrayList<String>(); CitationCollection collection = null; try { collection = CitationService.getCollection(citationCollectionId); } catch (IdUnusedException e) { throw new EntityNotDefinedException(ref.getReference()); } // decide whether to export selected or entire list if( org.sakaiproject.citation.api.CitationService.REF_TYPE_EXPORT_RIS_SEL.equals(subtype) ) { // export selected String[] paramCitationIds = req.getParameterValues("citationId"); if( paramCitationIds == null || paramCitationIds.length < 1 ) { // none selected - do not continue try { res.sendError(HttpServletResponse.SC_BAD_REQUEST, rb.getString("export.none_selected")); } catch (IOException e) { m_log.warn("export-selected request received with not citations selected. citationCollectionId: " + citationCollectionId); } return; } citationIds.addAll(Arrays.asList(paramCitationIds)); fileName = rb.getFormattedMessage("export.filename.selected.ris", fileName); } else { // export all // iterate through ids List<Citation> citations = collection.getCitations(); if( citations == null || citations.size() < 1 ) { // no citations to export - do not continue try { res.sendError(HttpServletResponse.SC_NO_CONTENT, rb.getString("export.empty_collection")); } catch (IOException e) { m_log.warn("export-all request received for empty citation collection. citationCollectionId: " + citationCollectionId); } return; } for( Citation citation : citations ) { citationIds.add( citation.getId() ); } fileName = rb.getFormattedMessage("export.filename.all.ris", fileName); } // We need to write to a temporary stream for better speed, plus // so we can get a byte count. Internet Explorer has problems // if we don't make the setContentLength() call. StringBuilder buffer = new StringBuilder(4096); // StringBuilder contentType = new StringBuilder(); try { collection.exportRis(buffer, citationIds); } catch (IOException e) { throw new EntityAccessOverloadException(ref.getReference()); } // Set the mime type for a RIS file res.addHeader("Content-disposition", "attachment; filename=\"" + fileName + "\""); //res.addHeader("Content-Disposition", "attachment; filename=\"citations.RIS\""); res.setContentType("application/x-Research-Info-Systems"); res.setContentLength(buffer.length()); if (buffer.length() > 0) { // Increase the buffer size for more speed. res.setBufferSize(buffer.length()); } OutputStream out = null; try { out = res.getOutputStream(); if (buffer.length() > 0) { out.write(buffer.toString().getBytes()); } out.flush(); out.close(); } catch (Throwable ignore) { } finally { if (out != null) { try { out.close(); } catch (Throwable ignore) { } } } } } // handleExportRequest protected void handleViewRequest(HttpServletRequest req, HttpServletResponse res, Reference ref) throws EntityPermissionException, EntityAccessOverloadException, EntityNotDefinedException { try { // We get the resource as this checks permissions and throws exceptions if the user doesn't have access ContentResource resource = ContentHostingService.getResource(ref.getId()); if (!CitationService.CITATION_LIST_ID.equals(resource.getResourceType())) { // Don't do anything unless it's a citation list throw new EntityNotDefinedException("Couldn't find citation list"); } if (resource.getContentLength() > 1024) { // Only convert small byte arrays to string. throw new EntityAccessOverloadException(ref.getId()); } ResourceProperties properties = resource.getProperties(); String title = properties.getProperty(ResourceProperties.PROP_DISPLAY_NAME); String description = properties.getProperty( ResourceProperties.PROP_DESCRIPTION ); String citationCollectionId = new String( resource.getContent() ); CitationCollection collection = CitationService.getCollection(citationCollectionId); res.setContentType("text/html; charset=UTF-8"); PrintWriter out = res.getWriter(); out.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" + "<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\">\n" + "<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n" + "<title>" + rb.getString("list.title") + ": " + Validator.escapeHtml(title) + "</title>\n" + "<link href=\"/library/skin/tool_base.css\" type=\"text/css\" rel=\"stylesheet\" media=\"all\" />\n" + "<link href=\"/library/skin/default/tool.css\" type=\"text/css\" rel=\"stylesheet\" media=\"all\" />\n" + "<script type=\"text/javascript\" src=\"/library/js/jquery.js\"></script>\n" + "<script type=\"text/javascript\" src=\"/sakai-citations-tool/js/citationscript.js\"></script>\n" + "</head>\n<body>" ); List<Citation> citations = collection.getCitations(); out.println("<div class=\"portletBody\">\n\t<div class=\"indnt1\">"); out.println("\t<h3>" + rb.getString("list.title") + ": " + Validator.escapeHtml(title) + "</h3>"); if( description != null && !description.trim().equals("") ) { out.println("\t<p>" + description + "</p>"); } if( citations.size() > 0 ) { Object[] args = { ConfigurationService.getSiteConfigOpenUrlLabel() }; out.println("\t<p class=\"instruction\">" + rb.getFormattedMessage("cite.subtitle", args) + "</p>"); } out.println("\t<table class=\"listHier lines nolines\" summary=\"citations table\" cellpadding=\"0\" cellspacing=\"0\">"); out.println("\t<tbody>"); out.println("\t<tr><th colspan=\"2\">"); out.println("\t\t<div class=\"viewNav\" style=\"padding: 0pt;\"><strong>" + rb.getString("listing.title") + "</strong> (" + collection.size() + ")" ); out.println("\t\t</div>"); out.println("\t</th></tr>"); if( citations.size() > 0 ) { out.println("\t<tr class=\"exclude\"><td colspan=\"2\">"); out.println("\t\t<div class=\"itemAction\">"); out.println("\t\t\t<a href=\"#\" onclick=\"showAllDetails( '" + rb.getString("link.hide.results") + "' ); return false;\">" + rb.getString("link.show.readonly") + "</a> |" ); out.println("\t\t\t<a href=\"#\" onclick=\"hideAllDetails( '" + rb.getString("link.show.readonly") + "' ); return false;\">" + rb.getString("link.hide.results") + "</a>" ); out.println("\t\t</div>\n\t</td></tr>"); } for( Citation citation : citations ) { String escapedId = citation.getId().replace( '-', 'x' ); // toggle image out.println("\t\t<tr>"); out.println("\t\t\t<td class=\"attach\">"); out.println("\t\t\t\t<img onclick=\"toggleDetails( '" + escapedId + "', '" + rb.getString("link.show.readonly") + "', '" + rb.getString("link.hide.results") + "' );\"" ); out.println("\t\t\t\tid=\"toggle_" + escapedId + "\" class=\"toggleIcon\"" ); out.println("\t\t\t\tstyle=\"cursor: pointer;\" src=\"/library/image/sakai/expand.gif?panel=Main\""); out.println("\t\t\t\talt=\"" + rb.getString("link.show.readonly") + "\" align=\"top\"" ); out.println("\t\t\t\tborder=\"0\" height=\"13\" width=\"13\" />" ); out.println("\t\t\t</td>"); // preferred URL? String href = citation.hasPreferredUrl() ? citation.getCustomUrl(citation.getPreferredUrlId()) : citation.getOpenurl(); out.println("\t\t<td headers=\"details\">"); out.println("\t\t\t<a href=\"" + Validator.escapeHtml(href) + "\" target=\"_blank\">" + Validator.escapeHtml( (String)citation.getCitationProperty( Schema.TITLE, true ) ) + "</a><br />"); out.println("\t\t\t\t" + Validator.escapeHtml( citation.getCreator() ) ); out.println("\t\t\t\t" + Validator.escapeHtml( citation.getSource() ) ); out.println("\t\t\t<div class=\"itemAction\">"); if( citation.hasCustomUrls() ) { List<String> customUrlIds = citation.getCustomUrlIds(); for( String urlId : customUrlIds ) { if (!citation.hasPreferredUrl() || (citation.hasPreferredUrl() && (!citation.getPreferredUrlId().equals(urlId)))) { String urlLabel = ( citation.getCustomUrlLabel( urlId ) == null || citation.getCustomUrlLabel( urlId ).trim().equals("") ) ? rb.getString( "nullUrlLabel.view" ) : Validator.escapeHtml( citation.getCustomUrlLabel( urlId ) ); out.println("\t\t\t\t<a href=\"" + Validator.escapeHtml(citation.getCustomUrl( urlId )) + "\" target=\"_blank\">" + urlLabel + "</a>"); out.println("\t\t\t\t |"); } } } else { // We only want to show the open url if no custom urls have been specified. out.println("\t\t\t\t<a href=\"" + citation.getOpenurl() + "\" target=\"_blank\">" + ConfigurationService.getSiteConfigOpenUrlLabel() + "</a>"); } /* not using view citation link - using toggle triangle out.println("\t\t\t\t<a id=\"link_" + escapedId + "\" href=\"#\" onclick=\"viewFullCitation('" + escapedId + "'); return false;\">" + rb.getString( "action.view" ) + "</a>" ); */ // TODO This doesn't need any Inline HTTP Transport. out.println("\t\t\t\t<span class=\"Z3988\" title=\""+ citation.getOpenurlParameters().substring(1).replace("&", "&")+ "\"></span>"); out.println("\t\t\t</div>"); // show detailed info out.println("\t\t<div id=\"details_" + escapedId + "\" class=\"citationDetails\" style=\"display: none;\">"); out.println("\t\t\t<table class=\"listHier lines nolines\" style=\"margin-left: 2em;\" cellpadding=\"0\" cellspacing=\"0\">"); Schema schema = citation.getSchema(); if(schema == null) { m_log.warn("CLAS.handleViewRequest() Schema is null: " + citation); continue; } List fields = schema.getFields(); Iterator fieldIt = fields.iterator(); while(fieldIt.hasNext()) { Field field = (Field) fieldIt.next(); if(field.isMultivalued()) { // don't want to repeat authors if( !Schema.CREATOR.equals(field.getIdentifier()) ) { List values = (List) citation.getCitationProperty(field.getIdentifier(), false); Iterator valueIt = values.iterator(); boolean first = true; while(valueIt.hasNext()) { String value = (String) valueIt.next(); if( value != null && !value.trim().equals("") ) { if(first) { String label = rb.getString(schema.getIdentifier() + "." + field.getIdentifier(), field.getIdentifier()); out.println("\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"attach\"><strong>" + label + "</strong></td>\n\t\t\t\t\t<td>" + Validator.escapeHtml(value) + "</td>\n\t\t\t\t</tr>"); } else { out.println("\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"attach\"> </td>\n\t\t\t\t\t<td>" + Validator.escapeHtml(value) + "</td>\n\t\t\t\t</tr>\n"); } } first = false; } } } else { String value = (String) citation.getCitationProperty(field.getIdentifier(), true); if(value != null && ! value.trim().equals("")) { String label = rb.getString(schema.getIdentifier() + "." + field.getIdentifier(), field.getIdentifier()); /* leaving out "Find It!" link for now as we're not using it anywhere else anymore if(Schema.TITLE.equals(field.getIdentifier())) { value += " [<a href=\"" + citation.getOpenurl() + "\" target=\"_blank\">" + openUrlLabel + "</a>]"; } */ // don't want to repeat titles if( !Schema.TITLE.equals(field.getIdentifier()) ) { out.println("\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"attach\"><strong>" + label + "</strong></td>\n\t\t\t\t\t<td>" + Validator.escapeHtml(value) + "</td>\n\t\t\t\t</tr>"); } } } } out.println("\t\t\t</table>"); out.println("\t\t</div>"); out.println("\t\t</td>"); out.println("\t\t</tr>"); } if( citations.size() > 0 ) { out.println("\t<tr class=\"exclude\"><td colspan=\"2\">"); out.println("\t\t<div class=\"itemAction\">"); out.println("\t\t\t<a href=\"#\" onclick=\"showAllDetails( '" + rb.getString("link.hide.results") + "' ); return false;\">" + rb.getString("link.show.readonly") + "</a> |" ); out.println("\t\t\t<a href=\"#\" onclick=\"hideAllDetails( '" + rb.getString("link.show.readonly") + "' ); return false;\">" + rb.getString("link.hide.results") + "</a>" ); out.println("\t\t</div>\n\t</td></tr>"); out.println("\t<tr><th colspan=\"2\">"); out.println("\t\t<div class=\"viewNav\" style=\"padding: 0pt;\"><strong>" + rb.getString("listing.title") + "</strong> (" + collection.size() + ")" ); out.println("\t\t</div>"); out.println("\t</th></tr>"); out.println("\t</tbody>"); out.println("\t</table>"); out.println("</div></div>"); out.println("</body></html>"); } } catch (IOException e) { throw new EntityAccessOverloadException(ref.getReference()); } catch (ServerOverloadException e) { throw new EntityAccessOverloadException(ref.getReference()); } catch (IdUnusedException e) { throw new EntityNotDefinedException(ref.getReference()); } catch (PermissionException e) { throw new EntityPermissionException(e.getUser(), ContentHostingService.EVENT_RESOURCE_READ, ref.getReference()); } catch (TypeException e) { // This doesn't seem right but it's probably the best fit. throw new EntityNotDefinedException(ref.getReference()); } } }