/**
* 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.admin.controller;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.annotation.Resource;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.servlet.http.HttpServletRequest;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import edu.unc.lib.dl.acl.service.AccessControlService;
import edu.unc.lib.dl.acl.util.AccessGroupSet;
import edu.unc.lib.dl.acl.util.GroupsThreadStore;
import edu.unc.lib.dl.acl.util.Permission;
import edu.unc.lib.dl.fedora.AccessClient;
import edu.unc.lib.dl.fedora.DatastreamDocument;
import edu.unc.lib.dl.fedora.FedoraException;
import edu.unc.lib.dl.fedora.ManagementClient;
import edu.unc.lib.dl.fedora.PID;
import edu.unc.lib.dl.search.solr.model.BriefObjectMetadata;
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.service.SearchStateFactory;
import edu.unc.lib.dl.search.solr.util.SearchFieldKeys;
import edu.unc.lib.dl.ui.service.SolrQueryLayerService;
import edu.unc.lib.dl.util.ContentModelHelper.Datastream;
/**
* Responds to requests to generate an XML document containing metadata for objects in the selected set of objects,
* and sends the document to the provided email address.
*
* @author sreenug
* @author bbpennel
* @date Jul 7, 2015
*/
@Controller
public class ExportXMLController {
private static final Logger log = LoggerFactory.getLogger(ExportXMLController.class);
@Autowired
private SearchStateFactory searchStateFactory;
@Autowired
private SolrQueryLayerService queryLayer;
@Autowired
private JavaMailSender mailSender;
@Resource
@Qualifier("forwardedAccessClient")
private AccessClient client;
@Resource(name="forwardedManagementClient")
private ManagementClient managementClient;
@Autowired
private AccessControlService aclService;
private final List<String> resultFields = Arrays.asList(SearchFieldKeys.ID.name());
private final int BUFFER_SIZE = 2048;
private final Charset utf8 = Charset.forName("UTF-8");
private final String separator = System.getProperty("line.separator");
private final byte[] separatorBytes = System.getProperty("line.separator").getBytes();
private final byte[] exportHeaderBytes = ("<?xml version=\"1.0\" encoding=\"utf-8\"?>" + separator
+ "<bulkMetadata>" + separator).getBytes(utf8);
/**
* Exports an XML document containing metadata for all objects specified plus all of their children
*
* @param exportRequest
* @param request
* @return
* @throws IOException
* @throws FedoraException
*/
@RequestMapping(value = "exportContainerXML", method = RequestMethod.POST)
public @ResponseBody
Object exportFolder(@RequestBody XMLExportRequest exportRequest,
HttpServletRequest request) throws IOException, FedoraException {
List<String> pids = new ArrayList<>();
for (String pid : exportRequest.getPids()) {
SearchState searchState = searchStateFactory.createSearchState();
searchState.setResultFields(resultFields);
searchState.setSortType("export");
searchState.setRowsPerPage(Integer.MAX_VALUE);
SearchRequest searchRequest = new SearchRequest(searchState, GroupsThreadStore.getGroups());
BriefObjectMetadata container = queryLayer.addSelectedContainer(pid, searchState, false);
SearchResultResponse resultResponse = queryLayer.getSearchResults(searchRequest);
List<BriefObjectMetadata> objects = resultResponse.getResultList();
objects.add(0, container);
for (BriefObjectMetadata object : objects) {
pids.add(object.getPid().getPid());
}
}
XMLExportRunnable runnable = new XMLExportRunnable(new XMLExportRequest(pids, exportRequest.getEmail()),
GroupsThreadStore.getUsername(), GroupsThreadStore.getGroups());
Thread thread = new Thread(runnable);
thread.start();
Map <String, String> response = new HashMap<>();
response.put("message", "Metadata export for " + pids.size()
+ " objects has begun, you will receive the data via email soon");
return response;
}
/**
* Generates an XML document containing metadata for all objects in the provided list of PIDs.
*
* @param exportRequest
* @return
* @throws IOException
* @throws FedoraException
*/
@RequestMapping(value = "exportXML", method = RequestMethod.POST)
public @ResponseBody
Object exportSet(@RequestBody XMLExportRequest exportRequest) throws IOException, FedoraException {
XMLExportRunnable runnable = new XMLExportRunnable(
exportRequest, GroupsThreadStore.getUsername(), GroupsThreadStore.getGroups());
Thread thread = new Thread(runnable);
thread.start();
Map <String, String> response = new HashMap<>();
response.put("message", "Metadata export for " + exportRequest.getPids().size()
+ " has begun, you will receive the data via email soon");
return response;
}
public static class XMLExportRequest {
private List<String> pids;
private String email;
public XMLExportRequest() {
}
public XMLExportRequest(List<String> pids, String email) {
this.pids = pids;
this.email = email;
}
public List<String> getPids() {
return pids;
}
public void setPids(List<String> pids) {
this.pids = pids;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
/**
* Runnable which performs the work of retrieving metadata documents and compiling them into the export document.
*
* @author bbpennel
* @date Jul 7, 2015
*/
public class XMLExportRunnable implements Runnable {
private final String user;
private final AccessGroupSet groups;
private final XMLExportRequest request;
public XMLExportRunnable(XMLExportRequest request, String user, AccessGroupSet groups) {
this.user = user;
this.groups = groups;
this.request = request;
}
@Override
public void run() {
long startTime = System.currentTimeMillis();
try {
GroupsThreadStore.storeGroups(groups);
GroupsThreadStore.storeUsername(user);
File mdExportFile = File.createTempFile("xml_export", ".xml");
try (FileOutputStream xfop = new FileOutputStream(mdExportFile)) {
xfop.write(exportHeaderBytes);
XMLOutputter xmlOutput = new XMLOutputter(Format.getRawFormat());
for (String pidString : request.getPids()) {
PID pid = new PID(pidString);
if (!aclService.hasAccess(pid, groups, Permission.editDescription)) {
log.debug("User {} does not have permission to export metadata for {}", user, pid);
continue;
}
try{
Document objectDoc = new Document();
Element objectEl = new Element("object");
objectEl.setAttribute("pid", pid.getPid());
objectDoc.addContent(objectEl);
DatastreamDocument modsDS = managementClient
.getXMLDatastreamIfExists(pid, Datastream.MD_DESCRIPTIVE.getName());
if (modsDS != null) {
objectEl.addContent(separator);
Element modsUpdateEl = new Element("update");
modsUpdateEl.setAttribute("type", "MODS");
modsUpdateEl.setAttribute("lastModified", modsDS.getLastModified());
modsUpdateEl.addContent(separator);
modsUpdateEl.addContent(modsDS.getDocument().detachRootElement());
modsUpdateEl.addContent(separator);
objectEl.addContent(modsUpdateEl);
objectEl.addContent(separator);
}
xmlOutput.output(objectEl, xfop);
xfop.write(separatorBytes);
xfop.flush();
} catch(Exception e) {
log.error("Failed to export XML for object {}", pid, e);
}
}
xfop.write("</bulkMetadata>".getBytes(utf8));
}
sendEmail(zipit(mdExportFile), request.getEmail());
} catch(Exception e) {
log.error("Failed to export metadata for user {}", user, e);
} finally {
log.info("Finished metadata export for {} objects in {}ms for user {}",
new Object[] {request.getPids().size(), System.currentTimeMillis() - startTime, user});
GroupsThreadStore.clearStore();
}
}
private File zipit(File mdExportFile) throws IOException {
File mdExportZip = File.createTempFile("xml_export", ".zip");
FileOutputStream dest = new FileOutputStream(mdExportZip);
try (ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest))) {
FileInputStream fi = new FileInputStream(mdExportFile);
try (BufferedInputStream origin = new BufferedInputStream(fi, BUFFER_SIZE)) {
byte data[] = new byte[BUFFER_SIZE];
ZipEntry entry = new ZipEntry("export.xml");
out.putNextEntry(entry);
int count;
while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) {
out.write(data, 0, count);
}
}
}
return mdExportZip;
}
public void sendEmail(File mdExportFile, String toEmail) {
MimeMessage mimeMessage = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, MimeMessageHelper.MULTIPART_MODE_MIXED);
helper.setSubject("CDR Metadata Export");
helper.setFrom("cdr@listserv.unc.edu");
helper.setText("The XML metadata for " + request.getPids().size() + " object(s) requested for export by "
+ this.user + " is attached.\n");
helper.setTo(toEmail);
helper.addAttachment("xml_export.zip", mdExportFile);
mailSender.send(mimeMessage);
log.debug("Sending XML export email to {}", toEmail);
} catch (MessagingException e) {
log.error("Cannot send notification email", e);
}
}
}
}