/*
* Copyright (C) 2012 Jan Pokorsky
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cz.cas.lib.proarc.webapp.server.rest;
import cz.cas.lib.proarc.common.config.AppConfiguration;
import cz.cas.lib.proarc.common.config.AppConfigurationException;
import cz.cas.lib.proarc.common.config.AppConfigurationFactory;
import cz.cas.lib.proarc.common.export.DataStreamExport;
import cz.cas.lib.proarc.common.export.DesaExport;
import cz.cas.lib.proarc.common.export.DesaExport.Result;
import cz.cas.lib.proarc.common.export.ExportException;
import cz.cas.lib.proarc.common.export.ExportResultLog;
import cz.cas.lib.proarc.common.export.ExportResultLog.ResultError;
import cz.cas.lib.proarc.common.export.Kramerius4Export;
import cz.cas.lib.proarc.common.export.NdkExport;
import cz.cas.lib.proarc.common.export.archive.ArchiveProducer;
import cz.cas.lib.proarc.common.export.cejsh.CejshConfig;
import cz.cas.lib.proarc.common.export.cejsh.CejshExport;
import cz.cas.lib.proarc.common.export.cejsh.CejshStatusHandler;
import cz.cas.lib.proarc.common.export.crossref.CrossrefExport;
import cz.cas.lib.proarc.common.export.mets.MetsExportException.MetsExportExceptionElement;
import cz.cas.lib.proarc.common.fedora.RemoteStorage;
import cz.cas.lib.proarc.common.object.DigitalObjectManager;
import cz.cas.lib.proarc.common.object.model.MetaModelRepository;
import cz.cas.lib.proarc.common.user.UserProfile;
import cz.cas.lib.proarc.webapp.shared.rest.ExportResourceApi;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.SecurityContext;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.apache.commons.io.FileUtils;
import org.glassfish.jersey.server.CloseableService;
/**
* REST resource to export data from the system.
*
* @author Jan Pokorsky
*/
@Path(ExportResourceApi.PATH)
public class ExportResource {
private final AppConfiguration appConfig;
private final UserProfile user;
private final SessionContext session;
public ExportResource(
@Context SecurityContext securityCtx,
@Context HttpServletRequest httpRequest
) throws AppConfigurationException {
this.appConfig = AppConfigurationFactory.getInstance().defaultInstance();
session = SessionContext.from(httpRequest);
user = session.getUser();
}
@POST
@Path(ExportResourceApi.DATASTREAM_PATH)
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<ExportResult> datastream(
@FormParam(ExportResourceApi.DATASTREAM_PID_PARAM) List<String> pids,
@FormParam(ExportResourceApi.DATASTREAM_DSID_PARAM) List<String> dsIds,
@FormParam(ExportResourceApi.DATASTREAM_HIERARCHY_PARAM) @DefaultValue("true") boolean hierarchy
) throws IOException, ExportException {
if (pids.isEmpty()) {
throw RestException.plainText(Status.BAD_REQUEST, "Missing " + ExportResourceApi.DATASTREAM_PID_PARAM);
}
if (dsIds.isEmpty()) {
throw RestException.plainText(Status.BAD_REQUEST, "Missing " + ExportResourceApi.DATASTREAM_DSID_PARAM);
}
DataStreamExport export = new DataStreamExport(RemoteStorage.getInstance(appConfig));
URI exportUri = user.getExportFolder();
File exportFolder = new File(exportUri);
File target = export.export(exportFolder, hierarchy, pids, dsIds);
URI targetPath = user.getUserHomeUri().relativize(target.toURI());
return new SmartGwtResponse<ExportResult>(new ExportResult(targetPath));
}
@POST
@Path(ExportResourceApi.KRAMERIUS4_PATH)
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<ExportResult> kramerius4(
@FormParam(ExportResourceApi.KRAMERIUS4_PID_PARAM) List<String> pids,
@FormParam(ExportResourceApi.KRAMERIUS4_HIERARCHY_PARAM) @DefaultValue("true") boolean hierarchy
) throws IOException {
if (pids.isEmpty()) {
throw RestException.plainText(Status.BAD_REQUEST, "Missing " + ExportResourceApi.KRAMERIUS4_PID_PARAM);
}
Kramerius4Export export = new Kramerius4Export(
RemoteStorage.getInstance(appConfig), appConfig.getKramerius4Export());
URI exportUri = user.getExportFolder();
File exportFolder = new File(exportUri);
File target = export.export(exportFolder, hierarchy, session.asFedoraLog(), pids.toArray(new String[pids.size()]));
URI targetPath = user.getUserHomeUri().relativize(target.toURI());
return new SmartGwtResponse<ExportResult>(new ExportResult(targetPath));
}
/**
* Starts a new export to DESA repository.
*
* @param pids PIDs to export
* @param hierarchy export also children hierarchy of requested PIDs. Default is {@code false}.
* @param forDownload export to file system for later client download. If {@code true} dryRun is ignored.
* Default is {@code false}.
* @param dryRun use to build packages without sending to the repository. Default is {@code false}.
* @return the list of results for requested PIDs
* @throws IOException unexpected failure
* @throws ExportException unexpected failure
*/
@POST
@Path(ExportResourceApi.DESA_PATH)
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<ExportResult> newDesaExport(
@FormParam(ExportResourceApi.DESA_PID_PARAM) List<String> pids,
@FormParam(ExportResourceApi.DESA_HIERARCHY_PARAM) @DefaultValue("false") boolean hierarchy,
@FormParam(ExportResourceApi.DESA_FORDOWNLOAD_PARAM) @DefaultValue("false") boolean forDownload,
@FormParam(ExportResourceApi.DESA_DRYRUN_PARAM) @DefaultValue("false") boolean dryRun
) throws IOException, ExportException {
if (pids.isEmpty()) {
throw RestException.plainText(Status.BAD_REQUEST, "Missing " + ExportResourceApi.DESA_PID_PARAM);
}
DesaExport export = new DesaExport(RemoteStorage.getInstance(appConfig),
appConfig.getDesaServices(), MetaModelRepository.getInstance());
URI exportUri = user.getExportFolder();
File exportFolder = new File(exportUri);
List<ExportResult> result = new ArrayList<ExportResult>(pids.size());
if (forDownload) {
Result r = export.exportDownload(exportFolder, pids.get(0));
result.add(r.getValidationError() != null
? new ExportResult(r.getValidationError().getExceptions())
: new ExportResult(r.getDownloadToken()));
} else {
if (dryRun) {
for (String pid : pids) {
List<MetsExportExceptionElement> errors = export.validate(exportFolder, pid, hierarchy);
result.add(new ExportResult(errors));
}
} else {
for (String pid : pids) {
Result r = export.export(exportFolder, pid, null, false, hierarchy, false, session.asFedoraLog(), user);
if (r.getValidationError() != null) {
result.add(new ExportResult(r.getValidationError().getExceptions()));
} else {
// XXX not used for now
result.add(new ExportResult((Integer) null, "done"));
}
}
}
}
return new SmartGwtResponse<ExportResult>(result);
}
/**
* Gets the exported package built by {@link #newDesaExport() } with {@code forDownload=true}.
* The package data are removed after completion of the response.
*
* @param token token to identify the prepared package
* @return the package contents in ZIP format
*/
@GET
@Path(ExportResourceApi.DESA_PATH)
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response getDesaExport(
@QueryParam(ExportResourceApi.RESULT_TOKEN) String token,
@Context CloseableService finalizer
) {
URI exportUri = user.getExportFolder();
File exportFolder = new File(exportUri);
final File file = DesaExport.findExportedPackage(exportFolder, token);
if (file == null) {
return Response.status(Status.NOT_FOUND).type(MediaType.TEXT_PLAIN_TYPE)
.entity("The contents not found!").build();
}
finalizer.add(new Closeable() {
@Override
public void close() throws IOException {
FileUtils.deleteQuietly(file.getParentFile());
}
});
return Response.ok(file, MediaType.APPLICATION_OCTET_STREAM)
.header("Content-Disposition", "attachment; filename=\"" + file.getName() + '"')
.build();
}
@POST
@Path(ExportResourceApi.NDK_PATH)
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<ExportResult> newNdkExport(
@FormParam(ExportResourceApi.NDK_PID_PARAM) List<String> pids
// @FormParam(ExportResourceApi.DESA_HIERARCHY_PARAM) @DefaultValue("false") boolean hierarchy,
// @FormParam(ExportResourceApi.DESA_FORDOWNLOAD_PARAM) @DefaultValue("false") boolean forDownload,
// @FormParam(ExportResourceApi.DESA_DRYRUN_PARAM) @DefaultValue("false") boolean dryRun
) throws ExportException {
if (pids.isEmpty()) {
throw RestException.plainText(Status.BAD_REQUEST, "Missing " + ExportResourceApi.DESA_PID_PARAM);
}
URI exportUri = user.getExportFolder();
File exportFolder = new File(exportUri);
List<ExportResult> result = new ArrayList<ExportResult>(pids.size());
NdkExport export = new NdkExport(RemoteStorage.getInstance());
List<NdkExport.Result> ndkResults = export.export(exportFolder, pids, true, true, session.asFedoraLog());
for (NdkExport.Result r : ndkResults) {
if (r.getValidationError() != null) {
result.add(new ExportResult(r.getValidationError().getExceptions()));
} else {
// XXX not used for now
result.add(new ExportResult((Integer) null, "done"));
}
}
return new SmartGwtResponse<ExportResult>(result);
}
/**
* Starts a new CEJSH export.
* @param pids PIDs to export
* @return the export result
*/
@POST
@Path(ExportResourceApi.CEJSH_PATH)
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<ExportResult> newCejshExport(
@FormParam(ExportResourceApi.CEJSH_PID_PARAM) List<String> pids
) {
if (pids.isEmpty()) {
throw RestException.plainText(Status.BAD_REQUEST, "Missing " + ExportResourceApi.CEJSH_PID_PARAM);
}
URI exportUri = user.getExportFolder();
File exportFolder = new File(exportUri);
CejshConfig cejshConfig = CejshConfig.from(appConfig.getAuthenticators());
CejshExport export = new CejshExport(
DigitalObjectManager.getDefault(), RemoteStorage.getInstance(), cejshConfig);
CejshStatusHandler status = export.export(exportFolder, pids);
File targetFolder = status.getTargetFolder();
ExportResult result = new ExportResult();
if (targetFolder != null) {
result.setTarget(user.getUserHomeUri().relativize(targetFolder.toURI()).toASCIIString());
}
if (!status.isOk()) {
result.setErrors(new ArrayList<ExportError>());
for (ExportResultLog.ExportResult logResult : status.getReslog().getExports()) {
for (ResultError error : logResult.getError()) {
result.getErrors().add(new ExportError(
error.getPid(), error.getMessage(), false, error.getDetails()));
}
}
}
return new SmartGwtResponse<ExportResult>(result);
}
/**
* Starts a new Crossref export.
* @param pids PIDs to export
* @return the export result
*/
@POST
@Path(ExportResourceApi.CROSSREF_PATH)
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<ExportResult> newCrossrefExport(
@FormParam(ExportResourceApi.CROSSREF_PID_PARAM) List<String> pids
) {
if (pids.isEmpty()) {
throw RestException.plainText(Status.BAD_REQUEST, "Missing " + ExportResourceApi.CROSSREF_PID_PARAM);
}
URI exportUri = user.getExportFolder();
File exportFolder = new File(exportUri);
// CejshConfig cejshConfig = CejshConfig.from(appConfig.getAuthenticators());
CrossrefExport export = new CrossrefExport(
DigitalObjectManager.getDefault(), RemoteStorage.getInstance());
CejshStatusHandler status = new CejshStatusHandler();
export.export(exportFolder, pids, status);
File targetFolder = status.getTargetFolder();
ExportResult result = new ExportResult();
if (targetFolder != null) {
result.setTarget(user.getUserHomeUri().relativize(targetFolder.toURI()).toASCIIString());
}
if (!status.isOk()) {
result.setErrors(new ArrayList<ExportError>());
for (ExportResultLog.ExportResult logResult : status.getReslog().getExports()) {
for (ResultError error : logResult.getError()) {
result.getErrors().add(new ExportError(
error.getPid(), error.getMessage(), false, error.getDetails()));
}
}
}
return new SmartGwtResponse<ExportResult>(result);
}
/**
* Starts new archiving.
* @param pids PIDs to export
* @return the export result
*/
@POST
@Path(ExportResourceApi.ARCHIVE_PATH)
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<ExportResult> newArchive(
@FormParam(ExportResourceApi.ARCHIVE_PID_PARAM) List<String> pids
) {
if (pids.isEmpty()) {
throw RestException.plainText(Status.BAD_REQUEST, "Missing " + ExportResourceApi.ARCHIVE_PID_PARAM);
}
URI exportUri = user.getExportFolder();
File exportFolder = new File(exportUri);
ExportResult result = new ExportResult();
ArchiveProducer export = new ArchiveProducer();
try {
File targetFolder = export.archive(pids, exportFolder);
if (targetFolder != null) {
result.setTarget(user.getUserHomeUri().relativize(targetFolder.toURI()).toASCIIString());
}
} catch (Exception ex) {
Logger.getLogger(ExportResource.class.getName()).log(Level.SEVERE, null, ex);
}
ExportResultLog reslog = export.getResultLog();
result.setErrors(new ArrayList<ExportError>());
for (ExportResultLog.ExportResult logResult : reslog.getExports()) {
for (ResultError error : logResult.getError()) {
result.getErrors().add(new ExportError(
error.getPid(), error.getMessage(), false, error.getDetails()));
}
}
return new SmartGwtResponse<ExportResult>(result);
}
/**
* The export result.
*/
@XmlAccessorType(XmlAccessType.FIELD)
public static class ExportResult {
@XmlElement(name = ExportResourceApi.RESULT_ID)
private Integer exportId;
@XmlElement(name = ExportResourceApi.RESULT_TOKEN)
private String token;
/**
* The target folder path.
*/
@XmlElement(name = ExportResourceApi.RESULT_TARGET)
private String target;
@XmlElement(name = ExportResourceApi.RESULT_ERRORS)
private List<ExportError> errors;
public ExportResult() {
}
public ExportResult(URI targetPath) {
this.target = targetPath.toASCIIString();
}
public ExportResult(String token) {
this.token = token;
}
public ExportResult(Integer exportId, String target) {
this.exportId = exportId;
this.target = target;
}
public ExportResult(List<MetsExportExceptionElement> validations) {
if (validations != null) {
errors = new ArrayList<ExportError>();
for (MetsExportExceptionElement me : validations) {
errors.add(new ExportError(me));
}
}
}
public Integer getExportId() {
return exportId;
}
public String getTarget() {
return target;
}
public List<ExportError> getErrors() {
return errors;
}
public void setExportId(Integer exportId) {
this.exportId = exportId;
}
public void setTarget(String target) {
this.target = target;
}
public void setErrors(List<ExportError> errors) {
this.errors = errors;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
/**
* The export error.
*/
@XmlRootElement(name = ExportResourceApi.RESULT_ERROR)
public static class ExportError {
@XmlElement(name = ExportResourceApi.RESULT_ERROR_PID)
private String pid;
@XmlElement(name = ExportResourceApi.RESULT_ERROR_MESSAGE)
private String message;
@XmlElement(name = ExportResourceApi.RESULT_ERROR_WARNING)
private boolean warning;
@XmlElement(name = ExportResourceApi.RESULT_ERROR_LOG)
private String log;
public ExportError() {
}
public ExportError(String pid, String message, boolean warning, String log) {
this.pid = pid;
this.message = message;
this.warning = warning;
this.log = log;
}
public ExportError(MetsExportExceptionElement me) {
this.pid = me.getPid();
this.message = me.getMessage();
this.warning = me.isWarning();
List<String> validations = me.getValidationErrors();
Exception ex = me.getEx();
if (validations != null && !validations.isEmpty()) {
StringBuilder sb = new StringBuilder();
for (String validation : validations) {
if (message == null) {
message = validation;
}
sb.append(validation).append('\n');
}
this.log = sb.toString();
} else if (ex != null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ex.printStackTrace(pw);
pw.close();
this.log = sw.toString();
}
}
}
}