/** * Copyright 2008 The University of North Carolina at Chapel Hill * * 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 edu.unc.lib.dl.ui.controller; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeoutException; import javax.servlet.http.HttpServletRequest; import org.jdom2.Document; import org.jdom2.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import edu.unc.lib.dl.acl.util.AccessGroupSet; import edu.unc.lib.dl.acl.util.GroupsThreadStore; import edu.unc.lib.dl.fedora.AuthorizationException; import edu.unc.lib.dl.fedora.FedoraDataService; import edu.unc.lib.dl.fedora.FedoraException; import edu.unc.lib.dl.fedora.NotFoundException; import edu.unc.lib.dl.fedora.ServiceException; import edu.unc.lib.dl.model.ContainerSettings; import edu.unc.lib.dl.model.ContainerSettings.ContainerView; import edu.unc.lib.dl.search.solr.model.BriefObjectMetadataBean; import edu.unc.lib.dl.search.solr.model.FacetFieldObject; import edu.unc.lib.dl.search.solr.model.SearchRequest; import edu.unc.lib.dl.search.solr.model.SearchResultResponse; import edu.unc.lib.dl.search.solr.model.SearchState; import edu.unc.lib.dl.search.solr.model.SimpleIdRequest; import edu.unc.lib.dl.search.solr.service.ObjectPathFactory; import edu.unc.lib.dl.search.solr.service.SearchStateFactory; import edu.unc.lib.dl.search.solr.util.SearchFieldKeys; import edu.unc.lib.dl.ui.exception.InvalidRecordRequestException; import edu.unc.lib.dl.ui.exception.RenderViewException; import edu.unc.lib.dl.ui.util.AccessUtil; import edu.unc.lib.dl.ui.view.XSLViewResolver; import edu.unc.lib.dl.util.ContentModelHelper; import edu.unc.lib.dl.util.ContentModelHelper.Datastream; import edu.unc.lib.dl.util.ResourceType; import edu.unc.lib.dl.xml.FOXMLJDOMUtil; import edu.unc.lib.dl.xml.JDOMNamespaceUtil; /** * Controller which retrieves data necessary for populating the full record page, retrieving supplemental information * according to the specifics of the object being retrieved. * * @author bbpennel */ @Controller @RequestMapping("/record") public class FullRecordController extends AbstractSolrSearchController { private static final Logger LOG = LoggerFactory.getLogger(FullRecordController.class); @Autowired(required = true) private XSLViewResolver xslViewResolver; @Autowired private FedoraDataService fedoraDataService; @Autowired private ObjectPathFactory pathFactory; @Autowired SearchStateFactory stateFactory; private static final int MAX_FOXML_TRIES = 2; @RequestMapping(value = "/{pid}", method = RequestMethod.GET) public String handleRequest(@PathVariable("pid") String pid, Model model, HttpServletRequest request) { return getFullRecord(pid, model, request); } @RequestMapping(method = RequestMethod.GET) public String handleOldRequest(@RequestParam("id") String id, Model model, HttpServletRequest request) { return getFullRecord(id, model, request); } public String getFullRecord(String pid, Model model, HttpServletRequest request) { AccessGroupSet accessGroups = GroupsThreadStore.getGroups(); // Retrieve the objects record from Solr SimpleIdRequest idRequest = new SimpleIdRequest(pid, accessGroups); BriefObjectMetadataBean briefObject = queryLayer.getObjectById(idRequest); if (briefObject == null) { throw new InvalidRecordRequestException(); } // Get path information. model.addAttribute("briefObject", briefObject); boolean listAccess = AccessUtil.hasListAccessOnly(accessGroups, briefObject); Date embargoUntil = briefObject.getActiveEmbargo(); if (embargoUntil != null) { model.addAttribute("embargoDate", embargoUntil); } // Retrieve the objects description from Fedora String fullObjectView = null; boolean containsContent = false; Document foxmlView = null; if (!listAccess) { try { int retries = MAX_FOXML_TRIES; do { foxmlView = fedoraDataService.getFoxmlViewXML(idRequest.getId()); containsContent = foxmlView.getRootElement().getContent().size() > 0; } while (--retries > 0 && !containsContent); if (containsContent) { Element foxml = foxmlView.getRootElement().getChild("digitalObject", JDOMNamespaceUtil.FOXML_NS); Element mods = FOXMLJDOMUtil.getMostRecentDatastream(Datastream.MD_DESCRIPTIVE, foxml); if (mods != null) { mods = mods.getChild("xmlContent", JDOMNamespaceUtil.FOXML_NS) .getChild("mods", JDOMNamespaceUtil.MODS_V3_NS); fullObjectView = xslViewResolver.renderView("external.xslView.fullRecord.url", mods); } } else { throw new InvalidRecordRequestException("Failed to retrieve FOXML for object " + idRequest.getId()); } } catch (AuthorizationException e) { LOG.debug("Access to the full record was denied, user has list only access"); listAccess = true; } catch (NotFoundException e) { throw new InvalidRecordRequestException(e); } catch (FedoraException e) { LOG.error("Failed to render full record view for " + idRequest.getId(), e); } catch (RenderViewException e) { LOG.error("Failed to render full record view for " + idRequest.getId(), e); } catch (ServiceException e) { if (e.getCause() instanceof TimeoutException) { LOG.warn("Maximum retrieval time exceeded while retrieving FOXML for full record of {}", idRequest.getId()); } else { LOG.error("Failed to retrieve FOXML for object {}" , idRequest.getId(), e); } } } // Get additional information depending on the type of object since the user has access if (!listAccess) { boolean retrieveChildrenCount = briefObject.getResourceType().equals(searchSettings.resourceTypeAggregate) || briefObject.getResourceType().equals(searchSettings.resourceTypeFolder); boolean retrieveFacets = briefObject.getResourceType().equals(searchSettings.resourceTypeCollection); if (retrieveChildrenCount) { briefObject.getCountMap().put("child", queryLayer.getChildrenCount(briefObject, accessGroups)); } if (retrieveFacets) { List<String> facetsToRetrieve = null; facetsToRetrieve = new ArrayList<String>(searchSettings.collectionBrowseFacetNames); LOG.debug("Retrieving supplemental information for container at path " + briefObject.getPath().toString()); SearchResultResponse resultResponse = queryLayer.getFullRecordSupplementalData(briefObject.getPath(), accessGroups, facetsToRetrieve); briefObject.getCountMap().put("child", resultResponse.getResultCount()); boolean hasFacets = false; for (FacetFieldObject facetField : resultResponse.getFacetFields()) { if (facetField.getValues().size() > 0) { hasFacets = true; break; } } model.addAttribute("hasFacetFields", hasFacets); model.addAttribute("facetFields", resultResponse.getFacetFields()); } model.addAttribute("fullObjectView", fullObjectView); } if (briefObject.getResourceType().equals(searchSettings.resourceTypeFile) || briefObject.getResourceType().equals(searchSettings.resourceTypeAggregate)) { List<BriefObjectMetadataBean> neighbors = queryLayer.getNeighboringItems(briefObject, searchSettings.maxNeighborResults, accessGroups); model.addAttribute("neighborList", neighbors); // Get previous and next record in the same folder if there are any Map<String, BriefObjectMetadataBean> previousNext = new HashMap<String, BriefObjectMetadataBean>(); int selectedRecord = -1; for (BriefObjectMetadataBean neighbor : neighbors) { if (neighbor.getId().equals(briefObject.getId())) { selectedRecord = neighbors.indexOf(neighbor); break; } } if (selectedRecord != -1) { if (selectedRecord > 0) { previousNext.put("previous", neighbors.get(selectedRecord - 1)); } if (selectedRecord + 1 < neighbors.size()) { previousNext.put("next", neighbors.get(selectedRecord + 1)); } } model.addAttribute("previousNext", previousNext); } if (briefObject.getResourceType().equals(searchSettings.resourceTypeCollection) || briefObject.getResourceType().equals(searchSettings.resourceTypeFolder)) { applyContainerSettings(pid, foxmlView, model, fullObjectView != null); } model.addAttribute("listAccess", listAccess); model.addAttribute("pageSubtitle", briefObject.getTitle()); return "fullRecord"; } // The default collection tab views which are retrieved if no settings are found private static List<String> defaultViews = Arrays.asList(ContainerView.STRUCTURE.name(), ContainerView.EXPORTS.name()); private static List<String> defaultViewsDescriptive = Arrays.asList(ContainerView.DESCRIPTION.name(), ContainerView.STRUCTURE.name(), ContainerView.EXPORTS.name()); private void applyContainerSettings(String pid, Document foxml, Model model, boolean hasDescription) { if (foxml == null) { return; } ContainerSettings settings = new ContainerSettings(foxml.getRootElement().getChildren().get(0)); if (settings.getViews().size() == 0) { // Only include the metadata tab by default if there is a descriptive record if (hasDescription) { settings.setViews(defaultViewsDescriptive); } else { settings.setViews(defaultViews); } } if (settings.getDefaultView() == null) { settings.setDefaultView(ContainerView.STRUCTURE.name()); } // Populate department list if (settings.getViews().contains(ContainerView.DEPARTMENTS.name())) { SearchResultResponse result = queryLayer.getDepartmentList(GroupsThreadStore.getGroups(), pid); model.addAttribute("departmentFacets", result.getFacetFields().get(0)); } // Populate file list if (settings.getViews().contains(ContainerView.LIST_CONTENTS.name())) { SearchState searchState = stateFactory.createSearchState(); searchState.setResourceTypes( Arrays.asList(ResourceType.Aggregate.name(), ResourceType.File.name())); SearchRequest listContentsRequest = new SearchRequest(); listContentsRequest.setSearchState(searchState); listContentsRequest.setRetrieveFacets(false); listContentsRequest.setApplyCutoffs(false); listContentsRequest.setRootPid(pid); listContentsRequest.getSearchState().setRollup(true); SearchResultResponse contentListResponse = queryLayer.performSearch(listContentsRequest); model.addAttribute("contentListResponse", contentListResponse); } model.addAttribute("containerSettings", settings); } @ResponseStatus(value = HttpStatus.FORBIDDEN) @ExceptionHandler(InvalidRecordRequestException.class) public String handleInvalidRecordRequest(HttpServletRequest request) { request.setAttribute("pageSubtitle", "Invalid record"); return "error/invalidRecord"; } public void setXslViewResolver(XSLViewResolver xslViewResolver) { this.xslViewResolver = xslViewResolver; } }