/*
* Copyright (C) 2013 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.common.object;
import com.sun.jersey.api.client.ClientResponse;
import com.yourmediashelf.fedora.generated.management.DatastreamProfile;
import cz.cas.lib.proarc.common.fedora.BinaryEditor;
import cz.cas.lib.proarc.common.fedora.DigitalObjectException;
import cz.cas.lib.proarc.common.fedora.DigitalObjectNotFoundException;
import cz.cas.lib.proarc.common.fedora.FedoraObject;
import cz.cas.lib.proarc.common.fedora.FoxmlUtils;
import cz.cas.lib.proarc.common.fedora.LocalStorage.LocalObject;
import cz.cas.lib.proarc.common.fedora.RemoteStorage;
import cz.cas.lib.proarc.common.fedora.RemoteStorage.RemoteObject;
import cz.cas.lib.proarc.common.fedora.relation.RelationEditor;
import java.io.File;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
/**
* Handles datastream contents in its raw form.
*
* <p>For {@link BinaryEditor#RAW_ID} it also updates thumb, ocr, rels-ext.
*
* @author Jan Pokorsky
*/
public class DefaultDisseminationHandler implements DisseminationHandler {
private static final Logger LOG = Logger.getLogger(DefaultDisseminationHandler.class.getName());
private final String dsId;
private final DigitalObjectHandler handler;
private final FedoraObject fobject;
public DefaultDisseminationHandler(String dsId, DigitalObjectHandler handler) {
this.dsId = dsId;
this.handler = handler;
fobject = handler.getFedoraObject();
}
public String getDsId() {
return dsId;
}
public DigitalObjectHandler getHandler() {
return handler;
}
@Override
public Response getDissemination(Request httpRequest) throws DigitalObjectException, DigitalObjectNotFoundException {
String pid = fobject.getPid();
if (dsId == null) {
return Response.ok(fobject.asText(), MediaType.TEXT_XML_TYPE)
.header("Content-Disposition", "inline; filename=\"" + pid + ".xml\"")
.build();
}
if (fobject instanceof LocalObject) {
LocalObject lobject = (LocalObject) fobject;
BinaryEditor loader = BinaryEditor.dissemination(lobject, dsId);
if (loader == null) {
throw new DigitalObjectNotFoundException(pid, null, dsId, null, null);
}
File entity = loader.read();
if (entity == null) {
throw new DigitalObjectNotFoundException(pid, null, dsId, "no content", null);
}
Date lastModification = new Date(loader.getLastModified());
ResponseBuilder evaluatePreconditions = httpRequest == null
? null : httpRequest.evaluatePreconditions(lastModification);
if (evaluatePreconditions != null) {
return evaluatePreconditions.build();
}
return Response.ok(entity, loader.getProfile().getDsMIME())
.header("Content-Disposition", "inline; filename=\"" + entity.getName() + '"')
.lastModified(lastModification)
// .cacheControl(null)
// .expires(new Date(2100, 1, 1))
.build();
} else if (fobject instanceof RemoteObject) {
RemoteObject remote = (RemoteObject) fobject;
return getResponse(remote, dsId);
}
throw new IllegalStateException("unsupported: " + fobject.getClass());
}
public static Response getResponse(RemoteObject remote, String dsId) throws DigitalObjectException {
// This should limit fedora calls to 1.
// XXX It works around FedoraClient.FedoraClient.getDatastreamDissemination that hides HTTP headers of the response.
// Unfortunattely fedora does not return modification date as HTTP header
// In case of large images it could be faster to ask datastream for modification date first.
String pid = remote.getPid();
String path = String.format("objects/%s/datastreams/%s/content", pid, dsId);
ClientResponse response = remote.getClient().resource().path(path).get(ClientResponse.class);
if (Status.fromStatusCode(response.getStatus()) != Status.OK) {
throw new DigitalObjectNotFoundException(pid, null, dsId, response.getEntity(String.class), null);
}
MultivaluedMap<String, String> headers = response.getHeaders();
String filename = headers.getFirst("Content-Disposition");
filename = filename != null ? filename : "inline; filename=" + pid + '-' + dsId;
return Response.ok(response.getEntity(InputStream.class), headers.getFirst("Content-Type"))
.header("Content-Disposition", filename)
.build();
}
// XXX add impl of other data streams (PREVIEW, THUMB)
@Override
public void setDissemination(DisseminationInput input, String message) throws DigitalObjectException {
if (BinaryEditor.RAW_ID.equals(dsId)) {
setRawDissemination(input.getFile(), input.getFilename(), input.getMime(), message);
} else {
throw new UnsupportedOperationException(dsId);
}
}
public void setRawDissemination(File contents, String filename, MediaType mime, String message) throws DigitalObjectException {
setDsDissemination(BinaryEditor.RAW_ID, contents, filename, mime, message);
RelationEditor relationEditor = handler.relations();
relationEditor.setImportFile(filename);
relationEditor.write(relationEditor.getLastModified(), message);
// generate preview, thumb, ocr if possible
}
public void setPreviewDissemination(File contents, String filename, MediaType mime, String message) throws DigitalObjectException {
setDsDissemination(BinaryEditor.PREVIEW_ID, contents, filename, mime, message);
}
public void setIconAsDissemination(MediaType origMime, String dsLabel, String message) throws DigitalObjectException {
setIconAsDissemination(dsId, origMime, dsLabel, message);
}
/**
* Writes an icon URI to represent the given MIME. It searches for {@code icon:MIME/dsId}
* or {@code icon:MIME/THUMBNAIL}. If there is no icon and stream found, nothing is written.
*
* @param dsId stream ID where to write an icon location
* @param origMime MIME to derive icon URI
* @param dsLabel
* @param message
* @throws DigitalObjectException failure
*/
public void setIconAsDissemination(String dsId, MediaType origMime, String dsLabel, String message) throws DigitalObjectException {
DatastreamProfile newProfile = FoxmlUtils.externalProfile(dsId, BinaryEditor.IMAGE_JPEG, dsLabel);
BinaryEditor editor = new BinaryEditor(fobject, newProfile);
DatastreamProfile profile = editor.getProfile();
// default icon datastream ID for givent MIME
final String defaultDsId = BinaryEditor.THUMB_ID;
String dsLocation = profile.getDsLocation();
URI newLocation = toIconUri(origMime, dsId);
URI newLocationDefault;
if (newLocation.toASCIIString().equals(dsLocation)) {
// ok
return ;
} else {
newLocationDefault = toIconUri(origMime, defaultDsId);
if (newLocationDefault.toASCIIString().equals(dsLocation)) {
// ok
return ;
}
}
// check icon:mime/DS exists
RemoteObject icon = RemoteStorage.getInstance().find(mime2iconPid(origMime));
List<DatastreamProfile> iconStreams;
try {
iconStreams = icon.getDatastreams();
} catch (DigitalObjectNotFoundException ex) {
// no icon
LOG.log(Level.WARNING, "Missing object ''{0}''! No datastream ''{1}'' created for ''{2}''.",
new Object[]{icon.getPid(), dsId, fobject.getPid()});
return ;
}
DatastreamProfile iconStream = findProfile(dsId, iconStreams);
if (iconStream == null) {
// check default icon:mime/THUMBNAIL exists
iconStream = findProfile(defaultDsId, iconStreams);
if (iconStream == null) {
// no icon
return ;
}
newLocation = newLocationDefault;
}
if (iconStream.getDsMIME() != null) {
newProfile.setDsMIME(iconStream.getDsMIME());
}
editor.setProfile(newProfile);
editor.write(newLocation, editor.getLastModified(), message);
}
private URI toIconUri(MediaType origMime, String dsId) throws DigitalObjectException {
URI newLocation;
try {
newLocation = FoxmlUtils.localFedoraUri(mime2iconPid(origMime), dsId);
} catch (URISyntaxException ex) {
throw new DigitalObjectException(fobject.getPid(), null, dsId, null, ex);
}
return newLocation;
}
public void setDsDissemination(String dsId, File contents, String filename, MediaType mime, String message) throws DigitalObjectException {
BinaryEditor editor = BinaryEditor.dissemination(fobject, dsId, mime);
DatastreamProfile profile = editor.getProfile();
profile.setDsMIME(mime.toString());
// fedora adds own extensions :-(
profile.setDsLabel(filename);
editor.setProfile(profile);
editor.write(contents, editor.getLastModified(), message);
}
static DatastreamProfile findProfile(String dsId, List<DatastreamProfile> profiles) {
for (DatastreamProfile p : profiles) {
if (dsId.equals(p.getDsID())) {
return p;
}
}
return null;
}
static String mime2iconPid(MediaType mime) {
return mime2iconPid(mime.toString());
}
static String mime2iconPid(String mime) {
// object-id syntax: ( [A-Z] / [a-z] / [0-9] / "-" / "." / "~" / "_" / escaped-octet ) 1+
return "icon:" + mime.replaceAll("[^A-Za-z0-9\\-\\.~_]", "_");
}
}