/*
* Copyright (C) 2011 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 com.yourmediashelf.fedora.client.FedoraClientException;
import com.yourmediashelf.fedora.generated.management.DatastreamProfile;
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.dao.Batch;
import cz.cas.lib.proarc.common.dublincore.DcStreamEditor;
import cz.cas.lib.proarc.common.dublincore.DcStreamEditor.DublinCoreRecord;
import cz.cas.lib.proarc.common.fedora.AtmEditor;
import cz.cas.lib.proarc.common.fedora.AtmEditor.AtmItem;
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.DigitalObjectValidationException;
import cz.cas.lib.proarc.common.fedora.DigitalObjectValidationException.ValidationResult;
import cz.cas.lib.proarc.common.fedora.FedoraObject;
import cz.cas.lib.proarc.common.fedora.FoxmlUtils;
import cz.cas.lib.proarc.common.fedora.LocalStorage;
import cz.cas.lib.proarc.common.fedora.LocalStorage.LocalObject;
import cz.cas.lib.proarc.common.fedora.PurgeFedoraObject;
import cz.cas.lib.proarc.common.fedora.PurgeFedoraObject.PurgeException;
import cz.cas.lib.proarc.common.fedora.RemoteStorage;
import cz.cas.lib.proarc.common.fedora.SearchView;
import cz.cas.lib.proarc.common.fedora.SearchView.Item;
import cz.cas.lib.proarc.common.fedora.SearchView.Query;
import cz.cas.lib.proarc.common.fedora.StringEditor;
import cz.cas.lib.proarc.common.fedora.StringEditor.StringRecord;
import cz.cas.lib.proarc.common.fedora.relation.RelationEditor;
import cz.cas.lib.proarc.common.imports.ImportBatchManager;
import cz.cas.lib.proarc.common.imports.ImportBatchManager.BatchItemObject;
import cz.cas.lib.proarc.common.object.DescriptionMetadata;
import cz.cas.lib.proarc.common.object.DigitalObjectExistException;
import cz.cas.lib.proarc.common.object.DigitalObjectHandler;
import cz.cas.lib.proarc.common.object.DigitalObjectManager;
import cz.cas.lib.proarc.common.object.DisseminationHandler;
import cz.cas.lib.proarc.common.object.DisseminationInput;
import cz.cas.lib.proarc.common.object.MetadataHandler;
import cz.cas.lib.proarc.common.object.model.MetaModel;
import cz.cas.lib.proarc.common.object.model.MetaModelRepository;
import cz.cas.lib.proarc.common.urnnbn.UrnNbnConfiguration;
import cz.cas.lib.proarc.common.urnnbn.UrnNbnConfiguration.ResolverConfiguration;
import cz.cas.lib.proarc.common.urnnbn.UrnNbnService;
import cz.cas.lib.proarc.common.urnnbn.UrnNbnStatusHandler;
import cz.cas.lib.proarc.common.urnnbn.UrnNbnStatusHandler.PidResult;
import cz.cas.lib.proarc.common.urnnbn.UrnNbnStatusHandler.StatusEntry;
import cz.cas.lib.proarc.common.user.Group;
import cz.cas.lib.proarc.common.user.Permissions;
import cz.cas.lib.proarc.common.user.UserManager;
import cz.cas.lib.proarc.common.user.UserProfile;
import cz.cas.lib.proarc.common.user.UserUtil;
import cz.cas.lib.proarc.urnnbn.ResolverClient;
import cz.cas.lib.proarc.webapp.server.ServerMessages;
import cz.cas.lib.proarc.webapp.server.rest.SmartGwtResponse.ErrorBuilder;
import cz.cas.lib.proarc.webapp.shared.rest.DigitalObjectResourceApi;
import cz.cas.lib.proarc.webapp.shared.rest.DigitalObjectResourceApi.SearchType;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import org.apache.commons.io.FileUtils;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
/**
* Resource to manage digital objects.
*
* /object/{pid}/ GET - read DigObjDesc:{pid, displayname, date, owner};
* /object/ GET - lists all DigObjDesc
* /object/{pid}/foxml
* /object/{pid}/scan
* /object/{pid}/preview
* /object/{pid}/thumb
* /object/{pid}/ocr
* /object/{pid}/metadata
* /object/{pid}/relations
* /object/metamodel/ GET - lists model:{pid, displayname, type:(TOP|LEAF)}
*
* @author Jan Pokorsky
*/
@Path(DigitalObjectResourceApi.PATH)
public class DigitalObjectResource {
private static final Logger LOG = Logger.getLogger(DigitalObjectResource.class.getName());
private final AppConfiguration appConfig;
private final MetaModelRepository metamodels = MetaModelRepository.getInstance();
private final ImportBatchManager importManager;
private final Request httpRequest;
private final HttpHeaders httpHeaders;
private final UserProfile user;
private final SessionContext session;
public DigitalObjectResource(
@Context Request request,
@Context SecurityContext securityCtx,
@Context HttpHeaders httpHeaders,
@Context UriInfo uriInfo,
@Context HttpServletRequest httpRequest
) throws AppConfigurationException {
this.httpRequest = request;
this.httpHeaders = httpHeaders;
this.appConfig = AppConfigurationFactory.getInstance().defaultInstance();
this.importManager = ImportBatchManager.getInstance(appConfig);
session = SessionContext.from(httpRequest);
user = session.getUser();
LOG.fine(user.toString());
}
/**
* Creates a new digital object
*
* @param modelId model ID (model:page, ...) of the digital object; required
* @param pid PID of the digital object from external Kramerius. PID must not be already assigned. Optional
* @param parentPid optional PID of parent object to link the newly created object
* @param xmlMetadata XML used to create new object; optional
* @return
* @throws URISyntaxException
* @throws IOException
*/
@POST
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<Item> newObject(
@FormParam(DigitalObjectResourceApi.DIGITALOBJECT_MODEL) String modelId,
@FormParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) String pid,
@FormParam(DigitalObjectResourceApi.MEMBERS_ITEM_PARENT) String parentPid,
@FormParam(DigitalObjectResourceApi.NEWOBJECT_XML_PARAM) String xmlMetadata
) throws DigitalObjectException {
if (modelId == null) {
// XXX validate modelId values
throw RestException.plainNotFound(DigitalObjectResourceApi.DIGITALOBJECT_MODEL, modelId);
}
if (pid != null) {
boolean invalid = pid.length() < 5;
try {
if (!invalid) {
UUID uuid = UUID.fromString(FoxmlUtils.pidAsUuid(pid));
pid = FoxmlUtils.pidFromUuid(uuid.toString());
}
} catch (IllegalArgumentException e) {
invalid = true;
}
if (invalid) {
return SmartGwtResponse.<Item>asError().error(
DigitalObjectResourceApi.DIGITALOBJECT_PID, "Invalid PID!").build();
}
}
xmlMetadata = (xmlMetadata == null || xmlMetadata.isEmpty() || "null".equals(xmlMetadata)) ? null : xmlMetadata;
LOG.log(Level.FINE, "model: {0}, pid: {3}, parent: {2}, XML: {1}",
new Object[] {modelId, xmlMetadata, parentPid, pid});
DigitalObjectManager dom = DigitalObjectManager.getDefault();
try {
Item item = dom.createDigitalObject(modelId, pid, parentPid, user, xmlMetadata, session.asFedoraLog());
return new SmartGwtResponse<Item>(item);
} catch (DigitalObjectExistException ex) {
return SmartGwtResponse.<Item>asError().error("pid", "Object already exists!").build();
}
}
/**
* @see PurgeFedoraObject
*/
@DELETE
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<DigitalObject> deleteObject(
@QueryParam(DigitalObjectResourceApi.DELETE_PID_PARAM) List<String> pids,
@QueryParam(DigitalObjectResourceApi.DELETE_HIERARCHY_PARAM)
@DefaultValue("true") boolean hierarchy,
@QueryParam(DigitalObjectResourceApi.DELETE_PURGE_PARAM)
@DefaultValue("false") boolean purge
) throws IOException, PurgeException {
RemoteStorage fedora = RemoteStorage.getInstance(appConfig);
ArrayList<DigitalObject> result = new ArrayList<DigitalObject>(pids.size());
PurgeFedoraObject service = new PurgeFedoraObject(fedora);
if (purge) {
session.requirePermission(Permissions.ADMIN);
service.purge(pids, hierarchy, session.asFedoraLog());
} else {
service.delete(pids, hierarchy, session.asFedoraLog());
}
for (String pid : pids) {
result.add(new DigitalObject(pid, null));
}
return new SmartGwtResponse<DigitalObject>(result);
}
@GET
@Path(DigitalObjectResourceApi.SEARCH_PATH)
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<Item> search(
@QueryParam(DigitalObjectResourceApi.SEARCH_OWNER_PARAM) String owner,
@DefaultValue(SearchType.DEFAULT)
@QueryParam(DigitalObjectResourceApi.SEARCH_TYPE_PARAM) SearchType type,
@QueryParam(DigitalObjectResourceApi.SEARCH_PID_PARAM) List<String> pids,
@QueryParam(DigitalObjectResourceApi.SEARCH_BATCHID_PARAM) Integer batchId,
@QueryParam(DigitalObjectResourceApi.SEARCH_PHRASE_PARAM) String phrase,
@QueryParam(DigitalObjectResourceApi.SEARCH_QUERY_CREATOR_PARAM) String queryCreator,
@QueryParam(DigitalObjectResourceApi.SEARCH_QUERY_IDENTIFIER_PARAM) String queryIdentifier,
@QueryParam(DigitalObjectResourceApi.SEARCH_QUERY_LABEL_PARAM) String queryLabel,
@QueryParam(DigitalObjectResourceApi.SEARCH_QUERY_MODEL_PARAM) String queryModel,
@QueryParam(DigitalObjectResourceApi.SEARCH_QUERY_TITLE_PARAM) String queryTitle,
@QueryParam(DigitalObjectResourceApi.SEARCH_START_ROW_PARAM) int startRow
) throws FedoraClientException, IOException {
Locale locale = session.getLocale(httpHeaders);
SearchView search = RemoteStorage.getInstance(appConfig).getSearch(locale);
List<Item> items;
int page = 20;
switch (type) {
case LAST_MODIFIED:
items = search.findLastModified(startRow, queryModel, filterOwnObjects(user), 100);
break;
case QUERY:
items = search.findQuery(new Query().setTitle(queryTitle)
.setLabel(queryLabel).setIdentifier(queryIdentifier)
.setOwner(owner).setModel(queryModel).setCreator(queryCreator)
.setHasOwners(filterGroups(user)));
page = 1;
break;
case PIDS:
items = search.find(pids);
page = 1;
break;
case PHRASE:
if (session.checkPermission(Permissions.REPO_SEARCH_GROUPOWNER)) {
// unsupported type
throw new WebApplicationException(Status.FORBIDDEN);
}
items = search.findPhrase(phrase);
page = 1;
break;
case PARENT:
items = searchParent(batchId, pids, search);
page = 1;
break;
default:
items = search.findLastCreated(startRow, queryModel, filterOwnObjects(user));
}
int count = items.size();
int endRow = startRow + count - 1;
int total = count == 0 ? startRow : endRow + page;
return new SmartGwtResponse<Item>(SmartGwtResponse.STATUS_SUCCESS, startRow, endRow, total, items);
}
private String filterOwnObjects(UserProfile user) {
boolean checkPermission = session.checkPermission(Permissions.REPO_SEARCH_GROUPOWNER);
return checkPermission ? user.getUserNameAsPid() : null;
}
private Collection<String> filterGroups(UserProfile user) {
boolean checkPermission = session.checkPermission(Permissions.REPO_SEARCH_GROUPOWNER);
if (checkPermission && user.getDefaultGroup() != null) {
UserManager userManager = UserUtil.getDefaultManger();
// FedoraClient.findObjects() does not support OR operator!
// Filter just the default group.
Group defGroup = userManager.findGroup(user.getDefaultGroup());
if (defGroup != null) {
return Collections.singletonList(UserUtil.toGroupPid(defGroup));
}
// List<Group> groups = userManager.findUserGroups(user.getId());
// return UserUtil.toGroupPid(groups);
}
return Collections.emptyList();
}
private List<Item> searchParent(Integer batchId, List<String> pids, SearchView search)
throws IOException, FedoraClientException {
if (batchId != null) {
Batch batch = importManager.get(batchId);
String parentPid = batch == null ? null : batch.getParentPid();
if (parentPid == null) {
return Collections.emptyList();
} else {
return search.find(parentPid);
}
} else {
if (pids == null || pids.size() != 1) {
throw RestException.plainText(Status.BAD_REQUEST, "parent search requires single pid parameter");
}
return search.findReferrers(pids.get(0));
}
}
/**
* Gets members of a digital object.
* @param parent PID of digital object to query its members. {@code root} parameter is ignored.
* @param root PID of digital object to return itself as a member with {@link Item#parent} as {@code null}.
* Useful to show root of the member hierarchy.
* @return ordered list of members
*/
@GET
@Path(DigitalObjectResourceApi.MEMBERS_PATH)
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<Item> findMembers(
@QueryParam(DigitalObjectResourceApi.MEMBERS_ITEM_PARENT) String parent,
@QueryParam(DigitalObjectResourceApi.MEMBERS_ROOT_PARAM) String root
) throws FedoraClientException, IOException, DigitalObjectException {
SearchView search = RemoteStorage.getInstance(appConfig).getSearch(session.getLocale(httpHeaders));
List<Item> items;
String parentPid;
if (parent == null || "null".equals(parent)) {
items = search.find(root);
parentPid = null;
} else {
items = search.findSortedChildren(parent);
parentPid = parent;
}
for (Item item : items) {
item.setParentPid(parentPid);
}
return new SmartGwtResponse<Item>(items);
}
/**
* {@link #setMembers(SetMemberRequest)} request body.
*/
@XmlAccessorType(XmlAccessType.FIELD)
public static class SetMemberRequest {
@XmlElement(name = DigitalObjectResourceApi.MEMBERS_ITEM_PARENT)
String parentPid;
@XmlElement(name = DigitalObjectResourceApi.MEMBERS_ITEM_BATCHID)
Integer batchId;
@XmlElement(name = DigitalObjectResourceApi.MEMBERS_ITEM_PID)
List<String> toSetPids;
}
/**
* Sets new member sequence of given parent digital object.
*
* @param request params posted inside the request body
*/
@PUT
@Path(DigitalObjectResourceApi.MEMBERS_PATH)
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<Item> setMembers(
SetMemberRequest request
) throws IOException, FedoraClientException, DigitalObjectException {
return setMembers(request.parentPid, request.batchId, request.toSetPids);
}
/**
* Sets new member sequence of given parent digital object.
*
* @param parentPid parent PID
* @param batchId batch import ID
* @param toSetPids list of member PIDS
* @return ordered list of members
* @throws RestException
*/
@PUT
@Path(DigitalObjectResourceApi.MEMBERS_PATH)
@Consumes({MediaType.APPLICATION_FORM_URLENCODED})
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<Item> setMembers(
@FormParam(DigitalObjectResourceApi.MEMBERS_ITEM_PARENT) String parentPid,
@FormParam(DigitalObjectResourceApi.MEMBERS_ITEM_BATCHID) Integer batchId,
@FormParam(DigitalObjectResourceApi.MEMBERS_ITEM_PID) List<String> toSetPids
// XXX long timestamp
) throws IOException, FedoraClientException, DigitalObjectException {
// LOG.log(Level.INFO, "parentPid: {0}, batchId: {1}, toSetPids: {2}",
// new Object[]{parentPid, batchId, toSetPids});
if (batchId == null && parentPid == null) {
throw RestException.plainNotFound(DigitalObjectResourceApi.MEMBERS_ITEM_PARENT, null);
}
boolean batchImportMembers = batchId != null;
if (toSetPids == null || toSetPids.isEmpty()) {
throw RestException.plainNotFound(DigitalObjectResourceApi.MEMBERS_ITEM_PID, null);
}
if (!batchImportMembers && toSetPids.contains(parentPid)) {
throw RestException.plainText(Status.BAD_REQUEST, "parent and pid are same!");
}
HashSet<String> toSetPidSet = new HashSet<String>(toSetPids);
if (toSetPidSet.size() != toSetPids.size()) {
throw RestException.plainText(Status.BAD_REQUEST, "duplicates in PIDs to set!\n" + toSetPids.toString());
}
Batch batch = batchId == null ? null : importManager.get(batchId);
// fetch PID[] -> Item[]
Map<String, Item> memberSearchMap;
if (batchImportMembers) {
memberSearchMap = loadLocalSearchItems(batch);
checkSearchedMembers(toSetPidSet, memberSearchMap);
} else {
memberSearchMap = loadSearchItems(toSetPidSet);
}
// load current members
DigitalObjectHandler doHandler = findHandler(parentPid, batch, false);
RelationEditor editor = doHandler.relations();
List<String> members = editor.getMembers();
members.clear();
// add new members
ArrayList<Item> added = new ArrayList<Item>();
for (String addPid : toSetPids) {
if (!members.contains(addPid)) {
members.add(addPid);
Item item = memberSearchMap.get(addPid);
if (item == null) {
throw RestException.plainNotFound(DigitalObjectResourceApi.MEMBERS_ITEM_PID,
toSetPids.toString());
}
item.setParentPid(parentPid);
added.add(item);
}
}
editor.setMembers(members);
editor.write(editor.getLastModified(), session.asFedoraLog());
doHandler.commit();
return new SmartGwtResponse<Item>(added);
}
/**
* Fetches object descriptions from the index. Useful to check whether object exists.
* @param searchIndex index
* @param pids object IDs to search
* @return the map of found PIDs and descriptions
*/
private Map<String, Item> loadSearchItems(Set<String> pids) throws IOException, FedoraClientException {
RemoteStorage storage = RemoteStorage.getInstance(appConfig);
SearchView search = storage.getSearch(session.getLocale(httpHeaders));
List<Item> memberSearch = search.find(new ArrayList<String>(pids));
HashMap<String, Item> memberSearchMap = new HashMap<String, Item>(memberSearch.size());
for (Item item : memberSearch) {
memberSearchMap.put(item.getPid(), item);
}
checkSearchedMembers(pids, memberSearchMap);
return memberSearchMap;
}
private Map<String, Item> loadLocalSearchItems(Batch batch) throws IOException, DigitalObjectException {
if (batch == null) {
throw new NullPointerException();
}
HashMap<String, Item> memberSearchMap = new HashMap<String, Item>();
List<BatchItemObject> batchObjects = importManager.findLoadedObjects(batch);
for (BatchItemObject batchObject : batchObjects) {
DigitalObjectHandler doh = findHandler(batchObject.getPid(), batch);
LocalObject lfo = (LocalObject) doh.getFedoraObject();
Item item = new Item(batchObject.getPid());
item.setBatchId(batch.getId());
item.setLabel(lfo.getLabel());
item.setOwner(lfo.getOwner());
RelationEditor relationEditor = doh.relations();
item.setModel(relationEditor.getModel());
memberSearchMap.put(batchObject.getPid(), item);
}
return memberSearchMap;
}
private void checkSearchedMembers(Set<String> pids, Map<String, Item> memberSearchMap) throws RestException {
if (!pids.equals(memberSearchMap.keySet())) {
HashSet<String> notMembers = new HashSet<String>(pids);
notMembers.removeAll(memberSearchMap.keySet());
HashSet<String> missingPids = new HashSet<String>(memberSearchMap.keySet());
missingPids.removeAll(pids);
throw RestException.plainNotFound(DigitalObjectResourceApi.MEMBERS_ITEM_PID,
"Not member PIDs: " + notMembers.toString()
+ "\nMissing PIDs: " + missingPids.toString());
}
}
/**
* Adds new object members. Members that already exists remain untouched.
*
* @param parentPid PID of parent object
* @param toAddPids list of PIDs to add; cannot contain parent PID
* @return list of added members
*/
@POST
@Path(DigitalObjectResourceApi.MEMBERS_PATH)
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<Item> addMembers(
@FormParam(DigitalObjectResourceApi.MEMBERS_ITEM_PARENT) String parentPid,
@FormParam(DigitalObjectResourceApi.MEMBERS_ITEM_PID) List<String> toAddPids,
@FormParam(DigitalObjectResourceApi.MEMBERS_ITEM_BATCHID) Integer batchId
) throws IOException, FedoraClientException, DigitalObjectException {
if (parentPid == null) {
throw RestException.plainNotFound(DigitalObjectResourceApi.MEMBERS_ITEM_PARENT, null);
}
if (toAddPids == null || toAddPids.isEmpty()) {
throw RestException.plainNotFound(DigitalObjectResourceApi.MEMBERS_ITEM_PID, null);
}
if (toAddPids.contains(parentPid)) {
throw RestException.plainText(Status.BAD_REQUEST, "parent and pid are same!");
}
HashSet<String> addPidSet = new HashSet<String>(toAddPids);
if (addPidSet.size() != toAddPids.size()) {
throw RestException.plainText(Status.BAD_REQUEST, "Duplicate children in the request!");
}
// XXX loadLocalSearchItems
Map<String, Item> memberSearchMap = loadSearchItems(addPidSet);
DigitalObjectHandler handler = findHandler(parentPid, batchId, false);
List<Item> added = addMembers(handler, toAddPids, memberSearchMap);
handler.commit();
return new SmartGwtResponse<Item>(added);
}
private List<Item> addMembers(DigitalObjectHandler parent,
List<String> toAddPids,
Map<String, Item> memberSearchMap
) throws DigitalObjectException {
String parentPid = parent.getFedoraObject().getPid();
HashSet<String> toAddPidSet = new HashSet<String>(toAddPids);
ArrayList<Item> added = new ArrayList<Item>(toAddPidSet.size());
if (toAddPidSet.isEmpty()) {
return added;
}
RelationEditor editor = parent.relations();
List<String> members = editor.getMembers();
// add new members
for (String addPid : toAddPids) {
if (!members.contains(addPid)) {
members.add(addPid);
Item item = memberSearchMap.get(addPid);
if (item == null) {
throw RestException.plainNotFound("pid", toAddPidSet.toString());
}
item.setParentPid(parentPid);
added.add(item);
} else {
throw RestException.plainText(Status.BAD_REQUEST,
parentPid + " already contains: " + addPid);
}
}
// write if any change
if (!added.isEmpty()) {
editor.setMembers(members);
editor.write(editor.getLastModified(), session.asFedoraLog());
}
return added;
}
/**
* Deletes object members from digital object.
* @param parentPid digital object ID
* @param toRemovePids member IDs to remove
* @param batchId optional batch import ID
* @return list of removed IDs
*/
@DELETE
@Path(DigitalObjectResourceApi.MEMBERS_PATH)
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<Item> deleteMembers(
@QueryParam(DigitalObjectResourceApi.MEMBERS_ITEM_PARENT) String parentPid,
@QueryParam(DigitalObjectResourceApi.MEMBERS_ITEM_PID) List<String> toRemovePids,
@QueryParam(DigitalObjectResourceApi.MEMBERS_ITEM_BATCHID) Integer batchId
) throws IOException, DigitalObjectException {
if (parentPid == null) {
throw RestException.plainText(Status.BAD_REQUEST, "Missing parent parameter!");
}
if (toRemovePids == null || toRemovePids.isEmpty()) {
throw RestException.plainText(Status.BAD_REQUEST, "Missing pid parameter!");
}
if (toRemovePids.contains(parentPid)) {
throw RestException.plainText(Status.BAD_REQUEST, "parent and pid are same!");
}
HashSet<String> toRemovePidSet = new HashSet<String>(toRemovePids);
if (toRemovePidSet.isEmpty()) {
return new SmartGwtResponse<Item>(Collections.<Item>emptyList());
}
DigitalObjectHandler parent = findHandler(parentPid, batchId, false);
deleteMembers(parent, toRemovePidSet);
parent.commit();
ArrayList<Item> removed = new ArrayList<Item>(toRemovePidSet.size());
for (String removePid : toRemovePidSet) {
Item item = new Item(removePid);
item.setParentPid(parentPid);
removed.add(item);
}
return new SmartGwtResponse<Item>(removed);
}
/**
* Removes given children from a parent.
* <p><b>Requires handler commit!</b>
* @param parent parent PID
* @param toRemovePidSet PIDs of children to remove
*/
private void deleteMembers(DigitalObjectHandler parent, Set<String> toRemovePidSet) throws DigitalObjectException {
RelationEditor editor = parent.relations();
List<String> members = editor.getMembers();
// check that PIDs being removed are members of parent object
HashSet<String> toRemovePidSetCopy = new HashSet<String>(toRemovePidSet);
toRemovePidSetCopy.removeAll(members);
if (!toRemovePidSetCopy.isEmpty()) {
String msg = String.format("Parent: %s does not contain members: %s",
parent.getFedoraObject().getPid(), toRemovePidSetCopy.toString());
throw RestException.plainText(Status.BAD_REQUEST, msg);
}
// remove
if (members.removeAll(toRemovePidSet)) {
editor.setMembers(members);
editor.write(editor.getLastModified(), session.asFedoraLog());
}
}
/**
* The helper for {@link #moveMembers(MoveMembersRequest) } request body.
*/
@XmlAccessorType(XmlAccessType.FIELD)
public static class MoveMembersRequest {
@XmlElement(name = DigitalObjectResourceApi.MEMBERS_MOVE_SRCPID)
String srcParentPid;
@XmlElement(name = DigitalObjectResourceApi.MEMBERS_MOVE_DSTPID)
String dstParentPid;
@XmlElement(name = DigitalObjectResourceApi.MEMBERS_ITEM_BATCHID)
Integer batchId;
@XmlElement(name = DigitalObjectResourceApi.MEMBERS_ITEM_PID)
List<String> pids;
}
@PUT
@Path(DigitalObjectResourceApi.MEMBERS_PATH + '/' + DigitalObjectResourceApi.MEMBERS_MOVE_PATH)
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<Item> moveMembers(
MoveMembersRequest request
) throws IOException, DigitalObjectException, FedoraClientException {
return moveMembers(request.srcParentPid, request.dstParentPid, request.batchId, request.pids);
}
/**
* Moves members from a source object to a destination object.
* @param srcParentPid PID of source
* @param dstParentPid PID of destination
* @param batchId optional batch import ID
* @param movePids member PIDs to move
* @return the list of updated members
*/
@PUT
@Path(DigitalObjectResourceApi.MEMBERS_PATH + '/' + DigitalObjectResourceApi.MEMBERS_MOVE_PATH)
@Consumes({MediaType.APPLICATION_FORM_URLENCODED})
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<Item> moveMembers(
@FormParam(DigitalObjectResourceApi.MEMBERS_MOVE_SRCPID) String srcParentPid,
@FormParam(DigitalObjectResourceApi.MEMBERS_MOVE_DSTPID) String dstParentPid,
@FormParam(DigitalObjectResourceApi.MEMBERS_ITEM_BATCHID) Integer batchId,
@FormParam(DigitalObjectResourceApi.MEMBERS_ITEM_PID) List<String> movePids
) throws IOException, DigitalObjectException, FedoraClientException {
if (srcParentPid == null) {
throw RestException.plainText(Status.BAD_REQUEST, "Missing source PID!");
}
if (dstParentPid == null) {
throw RestException.plainText(Status.BAD_REQUEST, "Missing target PID!");
}
if (srcParentPid.equals(dstParentPid)) {
throw RestException.plainText(Status.BAD_REQUEST, "src == dst!");
}
if (movePids == null || movePids.isEmpty()) {
throw RestException.plainText(Status.BAD_REQUEST, "Missing children PIDs!");
}
HashSet<String> movePidSet = new HashSet<String>(movePids);
if (movePidSet.isEmpty()) {
return new SmartGwtResponse<Item>(Collections.<Item>emptyList());
} else if (movePidSet.size() != movePids.size()) {
throw RestException.plainText(Status.BAD_REQUEST, "Duplicate children in the request!");
}
if (movePidSet.contains(dstParentPid)) {
throw RestException.plainText(Status.BAD_REQUEST, "The target parent listed as child!");
}
Batch batch = batchId == null ? null : importManager.get(batchId);
DigitalObjectHandler srcHandler = findHandler(srcParentPid, batch, false);
deleteMembers(srcHandler, movePidSet);
// XXX loadLocalSearchItems
Map<String, Item> memberSearchMap = loadSearchItems(movePidSet);
DigitalObjectHandler dstHandler = findHandler(dstParentPid, batch, false);
List<Item> added = addMembers(dstHandler, movePids, memberSearchMap);
srcHandler.commit();
dstHandler.commit();
SmartGwtResponse<Item> result = new SmartGwtResponse<Item>(added);
return result;
}
@GET
@Path(DigitalObjectResourceApi.DC_PATH)
@Produces(MediaType.APPLICATION_XML)
public DublinCoreRecord getDublinCore(
@QueryParam(DigitalObjectResourceApi.DUBLINCORERECORD_PID) String pid,
@QueryParam(DigitalObjectResourceApi.DUBLINCORERECORD_BATCHID) Integer batchId
) throws IOException, DigitalObjectException {
FedoraObject fobject = findFedoraObject(pid, batchId);
DcStreamEditor dcEditor = new DcStreamEditor(fobject);
try {
DublinCoreRecord dc = dcEditor.read();
dc.setBatchId(batchId);
return dc;
} catch (DigitalObjectNotFoundException ex) {
throw RestException.plainNotFound(DigitalObjectResourceApi.DIGITALOBJECT_PID, pid);
}
}
@GET
@Path(DigitalObjectResourceApi.DC_PATH)
@Produces(MediaType.APPLICATION_JSON)
public DublinCoreRecord getDublinCoreJson(
@QueryParam(DigitalObjectResourceApi.DUBLINCORERECORD_PID) String pid,
@QueryParam(DigitalObjectResourceApi.DUBLINCORERECORD_BATCHID) Integer batchId
) throws IOException, DigitalObjectException {
FedoraObject fobject = findFedoraObject(pid, batchId);
DcStreamEditor dcEditor = new DcStreamEditor(fobject);
try {
DublinCoreRecord dc = dcEditor.read();
dc.setBatchId(batchId);
return dc;
} catch (DigitalObjectNotFoundException ex) {
throw RestException.plainNotFound(DigitalObjectResourceApi.DIGITALOBJECT_PID, pid);
}
}
@PUT
@Path(DigitalObjectResourceApi.DC_PATH)
@Consumes({MediaType.TEXT_XML, MediaType.APPLICATION_XML})
@Produces(MediaType.APPLICATION_XML)
public DublinCoreRecord updateDublinCore(DublinCoreRecord update) throws IOException, DigitalObjectException {
if (update == null || update.getDc() == null) {
throw new IllegalArgumentException();
}
FedoraObject fobject = findFedoraObject(update.getPid(), update.getBatchId(), false);
DcStreamEditor dcEditor = new DcStreamEditor(fobject);
dcEditor.write(update, session.asFedoraLog());
fobject.flush();
DublinCoreRecord result = dcEditor.read();
result.setBatchId(update.getBatchId());
return result;
}
/**
* Gets subset of MODS properties in JSON.
*
* @param pid PID of requested digital object
* @param editorId view defining subset of MODS properties
*/
@GET
@Path(DigitalObjectResourceApi.MODS_PATH + '/' + DigitalObjectResourceApi.MODS_CUSTOM_PATH)
@Produces(MediaType.APPLICATION_JSON)
public SmartGwtResponse<DescriptionMetadata<Object>> getDescriptionMetadata(
@QueryParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) String pid,
@QueryParam(DigitalObjectResourceApi.BATCHID_PARAM) Integer batchId,
@QueryParam(DigitalObjectResourceApi.MODS_CUSTOM_EDITORID) String editorId
) throws IOException, DigitalObjectException {
if (pid == null || pid.isEmpty()) {
throw RestException.plainNotFound(DigitalObjectResourceApi.DIGITALOBJECT_PID, pid);
}
DigitalObjectHandler doHandler = findHandler(pid, batchId);
DescriptionMetadata<Object> metadata = doHandler.metadata().getMetadataAsJsonObject(editorId);
metadata.setBatchId(batchId);
return new SmartGwtResponse<DescriptionMetadata<Object>>(metadata);
}
@PUT
@Path(DigitalObjectResourceApi.MODS_PATH + '/' + DigitalObjectResourceApi.MODS_CUSTOM_PATH)
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<DescriptionMetadata<Object>> updateDescriptionMetadata(
@FormParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) String pid,
@FormParam(DigitalObjectResourceApi.BATCHID_PARAM) Integer batchId,
@FormParam(DigitalObjectResourceApi.MODS_CUSTOM_EDITORID) String editorId,
@FormParam(DigitalObjectResourceApi.TIMESTAMP_PARAM) Long timestamp,
@FormParam(DigitalObjectResourceApi.MODS_CUSTOM_CUSTOMJSONDATA) String jsonData,
@FormParam(DigitalObjectResourceApi.MODS_CUSTOM_CUSTOMXMLDATA) String xmlData,
@DefaultValue("false")
@FormParam(DigitalObjectResourceApi.MODS_CUSTOM_IGNOREVALIDATION) boolean ignoreValidation
) throws IOException, DigitalObjectException {
LOG.fine(String.format("pid: %s, editor: %s, timestamp: %s, ignoreValidation: %s, json: %s, xml: %s",
pid, editorId, timestamp, ignoreValidation, jsonData, xmlData));
if (pid == null || pid.isEmpty()) {
throw RestException.plainNotFound(DigitalObjectResourceApi.DIGITALOBJECT_PID, pid);
}
if (timestamp == null) {
throw RestException.plainNotFound(DigitalObjectResourceApi.TIMESTAMP_PARAM, pid);
}
final boolean isJsonData = xmlData == null;
String data = isJsonData ? jsonData : xmlData;
DigitalObjectHandler doHandler = findHandler(pid, batchId, false);
MetadataHandler<?> mHandler = doHandler.metadata();
DescriptionMetadata<String> dMetadata = new DescriptionMetadata<String>();
dMetadata.setPid(pid);
dMetadata.setBatchId(batchId);
dMetadata.setEditor(editorId);
dMetadata.setData(data);
dMetadata.setTimestamp(timestamp);
dMetadata.setIgnoreValidation(ignoreValidation);
try {
if (isJsonData) {
mHandler.setMetadataAsJson(dMetadata, session.asFedoraLog());
} else {
mHandler.setMetadataAsXml(dMetadata, session.asFedoraLog());
}
} catch (DigitalObjectValidationException ex) {
return toError(ex);
}
doHandler.commit();
return new SmartGwtResponse<DescriptionMetadata<Object>>(mHandler.getMetadataAsJsonObject(editorId));
}
<T> SmartGwtResponse<T> toError(DigitalObjectValidationException ex) {
if (ex.getValidations().isEmpty()) {
return SmartGwtResponse.asError(ex);
}
ErrorBuilder<T> error = SmartGwtResponse.asError();
Locale locale = session.getLocale(httpHeaders);
ServerMessages msgs = ServerMessages.get(locale);
for (ValidationResult validation : ex.getValidations()) {
String msg;
try {
msg = msgs.getFormattedMessage(validation.getBundleKey(), validation.getValues());
} catch (MissingResourceException mrex) {
LOG.log(Level.WARNING, validation.getBundleKey(), mrex);
msg = validation.getBundleKey();
}
error.error(validation.getName(), msg);
}
return error.build();
}
@GET
@Path(DigitalObjectResourceApi.METAMODEL_PATH)
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<AnnotatedMetaModel> listModels() {
Locale locale = session.getLocale(httpHeaders);
Collection<MetaModel> models = metamodels.find();
ArrayList<AnnotatedMetaModel> result = new ArrayList<AnnotatedMetaModel>(models.size());
for (MetaModel model : models) {
result.add(new AnnotatedMetaModel(model, locale));
}
return new SmartGwtResponse<AnnotatedMetaModel>(result);
}
/**
* Gets list of data profiles. Only with digitized contents.
* @param pid object ID
* @param batchId optional import ID
* @param dsId optional profile ID to filter result
* @return the list of profiles
*/
@GET
@Path(DigitalObjectResourceApi.STREAMPROFILE_PATH)
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<DatastreamResult> getStreamProfile(
@QueryParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) String pid,
@QueryParam(DigitalObjectResourceApi.BATCHID_PARAM) Integer batchId,
@QueryParam(DigitalObjectResourceApi.STREAMPROFILE_ID) String dsId
) throws IOException, DigitalObjectException {
if (pid == null || pid.isEmpty()) {
throw RestException.plainNotFound(DigitalObjectResourceApi.DIGITALOBJECT_PID, pid);
}
FedoraObject fo = findFedoraObject(pid, batchId, true);
List<DatastreamProfile> profiles = fo.getStreamProfile(dsId);
ArrayList<DatastreamResult> result = new ArrayList<DatastreamResult>(profiles.size());
for (DatastreamProfile profile : profiles) {
// filter digital contents
String profileDsId = profile.getDsID();
if (BinaryEditor.isMediaStream(profileDsId)) {
result.add(DatastreamResult.from(profile));
}
}
return new SmartGwtResponse<DatastreamResult>(result);
}
@XmlAccessorType(XmlAccessType.FIELD)
public static class DatastreamResult {
@XmlElement(name = DigitalObjectResourceApi.STREAMPROFILE_ID)
private String id;
@XmlElement(name = DigitalObjectResourceApi.STREAMPROFILE_MIME)
private String mime;
public static DatastreamResult from(DatastreamProfile profile) {
DatastreamResult d = new DatastreamResult();
d.id = profile.getDsID();
d.mime = profile.getDsMIME();
return d;
}
public DatastreamResult(String id, String mime) {
this.id = id;
this.mime = mime;
}
public DatastreamResult() {
}
}
@GET
@Path(DigitalObjectResourceApi.PREVIEW_PATH)
@Produces("*/*")
public Response getPreview(
@QueryParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) String pid,
@QueryParam(DigitalObjectResourceApi.BATCHID_PARAM) Integer batchId
) throws IOException, DigitalObjectException {
return getDissemination(pid, batchId, BinaryEditor.PREVIEW_ID);
}
/**
* Default alias for FULL dissemination.
*
* @param pid digital object PID (required)
* @param batchId import batch ID (optional)
* @return raw version of the archived object
*/
@GET
@Path(DigitalObjectResourceApi.FULL_PATH)
@Produces("*/*")
public Response getFull(
@QueryParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) String pid,
@QueryParam(DigitalObjectResourceApi.BATCHID_PARAM) Integer batchId
) throws IOException, DigitalObjectException {
return getDissemination(pid, batchId, BinaryEditor.FULL_ID);
}
/**
* Default alias for raw dissemination.
*
* @param pid digital object PID (required)
* @param batchId import batch ID (optional)
* @return raw version of the archived object
*/
@GET
@Path(DigitalObjectResourceApi.RAW_PATH)
@Produces("*/*")
public Response getRaw(
@QueryParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) String pid,
@QueryParam(DigitalObjectResourceApi.BATCHID_PARAM) Integer batchId
) throws IOException, DigitalObjectException {
return getDissemination(pid, batchId, BinaryEditor.RAW_ID);
}
/**
* Gets digital object dissemination.
*
* @param pid PID (required)
* @param batchId import batch ID (optional)
* @param dsId data stream ID. If missing the whole digital object is returned as XML.
* @return digital object dissemination
* @throws IOException
*/
@GET
@Path(DigitalObjectResourceApi.DISSEMINATION_PATH)
@Produces("*/*")
public Response getDissemination(
@QueryParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) String pid,
@QueryParam(DigitalObjectResourceApi.BATCHID_PARAM) Integer batchId,
@QueryParam(DigitalObjectResourceApi.DISSEMINATION_DATASTREAM) String dsId
) throws DigitalObjectException {
DigitalObjectHandler doHandler = findHandler(pid, batchId);
DisseminationHandler dissemination = doHandler.dissemination(dsId);
return dissemination.getDissemination(httpRequest);
}
/**
* Updates dissemination of digital object with binary data sent as
* {@link MediaType#MULTIPART_FORM_DATA}. It allows to upload file from
* client.
* <p>For now only RAW stream is supported.
*
* @param pid PID (required)
* @param batchId import batch ID (optional)
* @param dsId data stream ID.
* @param file contents
* @param fileInfo contents description metadata (injected by the server)
* @param mimeType MIME type of the sent contents (optional)
* @param jsonErrors include error in JSON response with HTTP status 200
* @return JSON response with process ID (needs process API)
*/
@POST
@Path(DigitalObjectResourceApi.DISSEMINATION_PATH)
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces({MediaType.APPLICATION_JSON})
public SmartGwtResponse<Map<String, Object>> updateDissemination(
@FormDataParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) String pid,
@FormDataParam(DigitalObjectResourceApi.BATCHID_PARAM) Integer batchId,
@FormDataParam(DigitalObjectResourceApi.DISSEMINATION_DATASTREAM) String dsId,
@FormDataParam(DigitalObjectResourceApi.DISSEMINATION_FILE) InputStream file,
@FormDataParam(DigitalObjectResourceApi.DISSEMINATION_FILE) FormDataContentDisposition fileInfo,
@FormDataParam(DigitalObjectResourceApi.DISSEMINATION_FILE) FormDataBodyPart fileBodyPart,
@FormDataParam(DigitalObjectResourceApi.DISSEMINATION_MIME) String mimeType,
@FormDataParam(DigitalObjectResourceApi.DISSEMINATION_ERROR) @DefaultValue("false") boolean jsonErrors
) {
try {
return updateDisseminationImpl(pid, batchId, dsId, file, fileInfo, fileBodyPart, mimeType);
} catch (Throwable ex) {
if (jsonErrors) {
return SmartGwtResponse.<Map<String,Object>>asError(ex);
} else {
if (!(ex instanceof WebApplicationException)) {
ex = new WebApplicationException(ex);
}
throw (WebApplicationException) ex;
}
}
}
private SmartGwtResponse<Map<String, Object>> updateDisseminationImpl(
@FormDataParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) String pid,
@FormDataParam(DigitalObjectResourceApi.BATCHID_PARAM) Integer batchId,
@FormDataParam(DigitalObjectResourceApi.DISSEMINATION_DATASTREAM) String dsId,
@FormDataParam(DigitalObjectResourceApi.DISSEMINATION_FILE) InputStream fileContent,
@FormDataParam(DigitalObjectResourceApi.DISSEMINATION_FILE) FormDataContentDisposition fileInfo,
@FormDataParam(DigitalObjectResourceApi.DISSEMINATION_FILE) FormDataBodyPart fileBodyPart,
@FormDataParam(DigitalObjectResourceApi.DISSEMINATION_MIME) String mimeType
) throws IOException, DigitalObjectException {
if (pid == null) {
return SmartGwtResponse.<Map<String,Object>>asError(DigitalObjectResourceApi.DIGITALOBJECT_PID, "Missing PID!");
}
if (fileContent == null) {
return SmartGwtResponse.<Map<String,Object>>asError(DigitalObjectResourceApi.DISSEMINATION_FILE, "Missing file!");
}
if (dsId != null && !dsId.equals(BinaryEditor.RAW_ID)) {
throw RestException.plainText(Status.BAD_REQUEST, "Missing or unsupported datastream ID: " + dsId);
}
String filename = getFilename(fileInfo.getFileName());
File file = File.createTempFile("proarc_", null);
try {
FileUtils.copyToFile(fileContent, file);
// XXX add config property or user permission
if (file.length() > 1*1024 * 1024 * 1024) { // 1GB
throw RestException.plainText(Status.BAD_REQUEST, "File contents too large!");
}
MediaType mime;
try {
mime = mimeType != null ? MediaType.valueOf(mimeType) : fileBodyPart.getMediaType();
} catch (IllegalArgumentException ex) {
return SmartGwtResponse.<Map<String,Object>>asError(
DigitalObjectResourceApi.DISSEMINATION_MIME, "Invalid MIME type! " + mimeType);
}
LOG.log(Level.FINE, "filename: {0}, user mime: {1}, resolved mime: {2}, {3}/{4}", new Object[]{filename, mimeType, mime, pid, dsId});
DigitalObjectHandler doHandler = findHandler(pid, batchId);
DisseminationHandler dissemination = doHandler.dissemination(BinaryEditor.RAW_ID);
DisseminationInput input = new DisseminationInput(file, filename, mime);
dissemination.setDissemination(input, session.asFedoraLog());
doHandler.commit();
} finally {
file.delete();
}
return new SmartGwtResponse<Map<String,Object>>(Collections.singletonMap("processId", (Object) 0L));
}
@GET
@Path(DigitalObjectResourceApi.THUMB_PATH)
@Produces("image/*")
public Response getThumbnail(
@QueryParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) String pid,
@QueryParam(DigitalObjectResourceApi.BATCHID_PARAM) Integer batchId
) throws IOException, DigitalObjectException {
return getDissemination(pid, batchId, BinaryEditor.THUMB_ID);
}
@GET
@Path(DigitalObjectResourceApi.MODS_PATH + '/' + DigitalObjectResourceApi.MODS_PLAIN_PATH)
@Produces(MediaType.APPLICATION_JSON)
public StringRecord getDescriptionMetadataTxt(
@QueryParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) String pid,
@QueryParam(DigitalObjectResourceApi.BATCHID_PARAM) Integer batchId
) throws DigitalObjectException {
DigitalObjectHandler handler = findHandler(pid, batchId, false);
MetadataHandler<?> metadataHandler = handler.metadata();
DescriptionMetadata<String> metadataAsXml = metadataHandler.getMetadataAsXml();
StringRecord result = new StringRecord(
metadataAsXml.getData(), metadataAsXml.getTimestamp(), metadataAsXml.getPid());
result.setBatchId(batchId);
return result;
}
@GET
@Path(DigitalObjectResourceApi.OCR_PATH)
@Produces(MediaType.APPLICATION_JSON)
public StringRecord getOcr(
@QueryParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) String pid,
@QueryParam(DigitalObjectResourceApi.BATCHID_PARAM) Integer batchId
) throws IOException, DigitalObjectException {
FedoraObject fobject = findFedoraObject(pid, batchId);
StringEditor ocrEditor = StringEditor.ocr(fobject);
try {
StringRecord ocr = ocrEditor.readRecord();
ocr.setBatchId(batchId);
return ocr;
} catch (DigitalObjectNotFoundException ex) {
throw RestException.plainNotFound(DigitalObjectResourceApi.DIGITALOBJECT_PID, pid);
}
}
@PUT
@Path(DigitalObjectResourceApi.OCR_PATH)
@Produces(MediaType.APPLICATION_JSON)
public StringRecord updateOcr(
@FormParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) String pid,
@FormParam(DigitalObjectResourceApi.BATCHID_PARAM) Integer batchId,
@FormParam(DigitalObjectResourceApi.TIMESTAMP_PARAM) Long timestamp,
@FormParam(DigitalObjectResourceApi.STRINGRECORD_CONTENT) String content
) throws IOException, DigitalObjectException {
if (timestamp == null) {
throw RestException.plainText(Status.BAD_REQUEST, "Missing timestamp!");
}
FedoraObject fobject = findFedoraObject(pid, batchId, false);
StringEditor ocrEditor = StringEditor.ocr(fobject);
try {
ocrEditor.write(content, timestamp, session.asFedoraLog());
fobject.flush();
StringRecord result = ocrEditor.readRecord();
result.setBatchId(batchId);
return result;
} catch (DigitalObjectNotFoundException ex) {
throw RestException.plainNotFound(DigitalObjectResourceApi.DIGITALOBJECT_PID, pid);
}
}
@GET
@Path(DigitalObjectResourceApi.PRIVATENOTE_PATH)
@Produces(MediaType.APPLICATION_JSON)
public StringRecord getPrivateNote(
@QueryParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) String pid,
@QueryParam(DigitalObjectResourceApi.BATCHID_PARAM) Integer batchId
) throws IOException, DigitalObjectException {
FedoraObject fobject = findFedoraObject(pid, batchId);
StringEditor editor = StringEditor.privateNote(fobject);
try {
StringRecord content = editor.readRecord();
content.setBatchId(batchId);
return content;
} catch (DigitalObjectNotFoundException ex) {
throw RestException.plainNotFound(DigitalObjectResourceApi.DIGITALOBJECT_PID, pid);
}
}
@PUT
@Path(DigitalObjectResourceApi.PRIVATENOTE_PATH)
@Produces(MediaType.APPLICATION_JSON)
public StringRecord updatePrivateNote(
@FormParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) String pid,
@FormParam(DigitalObjectResourceApi.BATCHID_PARAM) Integer batchId,
@FormParam(DigitalObjectResourceApi.TIMESTAMP_PARAM) Long timestamp,
@FormParam(DigitalObjectResourceApi.STRINGRECORD_CONTENT) String content
) throws IOException, DigitalObjectException {
if (timestamp == null) {
throw RestException.plainText(Status.BAD_REQUEST, "Missing timestamp!");
}
FedoraObject fobject = findFedoraObject(pid, batchId, false);
StringEditor editor = StringEditor.privateNote(fobject);
editor.write(content, timestamp, session.asFedoraLog());
fobject.flush();
StringRecord result = editor.readRecord();
result.setBatchId(batchId);
return result;
}
/**
* Gets digital object administration and technical data.
*
* @param pid PID (required)
* @param batchId import batch ID (optional)
* @return digital object dissemination
* @throws IOException
*/
@GET
@Path(DigitalObjectResourceApi.ATM_PATH)
@Produces(MediaType.APPLICATION_JSON)
public SmartGwtResponse<AtmItem> getAtm(
@QueryParam(DigitalObjectResourceApi.ATM_ITEM_PID) String pid,
@QueryParam(DigitalObjectResourceApi.ATM_ITEM_BATCHID) Integer batchId
) throws IOException, DigitalObjectException, FedoraClientException {
if (pid == null) {
return new SmartGwtResponse<AtmItem>();
}
FedoraObject fobject = findFedoraObject(pid, batchId);
Locale locale = session.getLocale(httpHeaders);
RemoteStorage storage = RemoteStorage.getInstance(appConfig);
AtmEditor editor = new AtmEditor(fobject, storage.getSearch(locale));
AtmItem atm = editor.read();
atm.setBatchId(batchId);
return new SmartGwtResponse<AtmItem>(atm);
}
@PUT
@Path(DigitalObjectResourceApi.ATM_PATH)
@Produces(MediaType.APPLICATION_JSON)
public SmartGwtResponse<AtmItem> updateAtm(
@FormParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) Set<String> pids,
@FormParam(DigitalObjectResourceApi.BATCHID_PARAM) Integer batchId,
@FormParam(DigitalObjectResourceApi.MEMBERS_ITEM_OWNER) String owner,
@FormParam(DigitalObjectResourceApi.ATM_ITEM_DEVICE) String deviceId
) throws IOException, DigitalObjectException {
ArrayList<AtmItem> result = new ArrayList<AtmItem>(pids.size());
Locale locale = session.getLocale(httpHeaders);
RemoteStorage storage = RemoteStorage.getInstance(appConfig);
SearchView search = storage.getSearch(locale);
for (String pid : pids) {
FedoraObject fobject = findFedoraObject(pid, batchId);
AtmEditor editor = new AtmEditor(fobject, search);
editor.write(deviceId, session.asFedoraLog());
fobject.flush();
AtmItem atm = editor.read();
atm.setBatchId(batchId);
result.add(atm);
}
return new SmartGwtResponse<AtmItem>(result);
}
@POST
@Path(DigitalObjectResourceApi.URNNBN_PATH)
@Produces(MediaType.APPLICATION_JSON)
public SmartGwtResponse<UrnNbnResult> registerUrnNbn(
@FormParam(DigitalObjectResourceApi.DIGITALOBJECT_PID) List<String> pids,
@FormParam(DigitalObjectResourceApi.URNNBN_RESOLVER) String resolverId,
@FormParam(DigitalObjectResourceApi.URNNBN_HIERARCHY) @DefaultValue("true") boolean hierarchy
) {
List<UrnNbnResult> result = new LinkedList<UrnNbnResult>();
if (!pids.isEmpty()) {
UrnNbnConfiguration config = appConfig.getUrnNbnConfiguration();
ResolverConfiguration resolverConfig = null;
if (resolverId == null) {
// no resolver passed, try the first registered
List<ResolverConfiguration> confs = config.getResolverConfigurations();
if (!confs.isEmpty()) {
resolverConfig = confs.get(0);
}
} else {
resolverConfig = config.findResolverConfiguration(resolverId);
}
if (resolverConfig == null) {
throw RestException.plainText(Status.BAD_REQUEST,
String.format("Unknown property '%s' = '%s'. Check server configuration!",
DigitalObjectResourceApi.URNNBN_RESOLVER, resolverId));
}
ResolverClient resolverClient = config.getClient(resolverConfig);
UrnNbnService service = new UrnNbnService(resolverClient);
UrnNbnStatusHandler status = service.register(pids, hierarchy);
for (Entry<String, PidResult> entry : status.getPids().entrySet()) {
PidResult pidResult = entry.getValue();
String entryPid = entry.getKey();
for (StatusEntry statusEntry : pidResult.getErrors()) {
result.add(new UrnNbnResult(entryPid, statusEntry, false, pidResult.getPid()));
}
for (StatusEntry statusEntry : pidResult.getWarnings()) {
result.add(new UrnNbnResult(entryPid, statusEntry, true, pidResult.getPid()));
}
if (pidResult.getUrnNbn() != null) {
result.add(new UrnNbnResult(entryPid, pidResult.getUrnNbn(), pidResult.getPid()));
}
}
}
return new SmartGwtResponse<UrnNbnResult>(result);
}
@XmlAccessorType(XmlAccessType.FIELD)
public static class UrnNbnResult {
@XmlElement(name = DigitalObjectResourceApi.DIGITALOBJECT_PID)
private String pid;
@XmlElement(name = DigitalObjectResourceApi.DIGITALOBJECT_MODEL)
private String modelId;
@XmlElement(name = DigitalObjectResourceApi.MEMBERS_ITEM_LABEL)
private String label;
@XmlElement(name = DigitalObjectResourceApi.URNNBN_ITEM_URNNBN)
private String urnnbn;
@XmlElement(name = DigitalObjectResourceApi.URNNBN_ITEM_MESSAGE)
private String message;
@XmlElement(name = DigitalObjectResourceApi.URNNBN_ITEM_STATUSTYPE)
private String type;
@XmlElement(name = DigitalObjectResourceApi.URNNBN_ITEM_WARNING)
private Boolean warning;
@XmlElement(name = DigitalObjectResourceApi.URNNBN_ITEM_LOG)
private String log;
public UrnNbnResult() {
}
public UrnNbnResult(String pid, String urnnbn, Item elm) {
this.pid = pid;
this.urnnbn = urnnbn;
if (elm != null) {
this.modelId = elm.getModel();
this.label = elm.getLabel();
}
}
public UrnNbnResult(String pid, StatusEntry me, boolean warning, Item elm) {
this.pid = pid;
this.message = me.getMessage();
this.type = me.getStatus().name();
this.warning = warning;
if (elm != null) {
this.modelId = elm.getModel();
this.label = elm.getLabel();
}
}
}
private DigitalObjectHandler findHandler(String pid, Integer batchId) throws DigitalObjectNotFoundException {
return findHandler(pid, batchId, true);
}
private DigitalObjectHandler findHandler(String pid, Batch batch) throws DigitalObjectNotFoundException {
return findHandler(pid, batch, true);
}
private DigitalObjectHandler findHandler(String pid, Integer batchId, boolean readonly)
throws DigitalObjectNotFoundException {
Batch batch = null;
if (batchId != null) {
batch = importManager.get(batchId);
}
return findHandler(pid, batch, readonly);
}
private DigitalObjectHandler findHandler(String pid, Batch batch, boolean readonly)
throws DigitalObjectNotFoundException {
DigitalObjectManager dom = DigitalObjectManager.getDefault();
FedoraObject fobject = dom.find2(pid, batch);
if (!readonly && fobject instanceof LocalObject) {
ImportResource.checkBatchState(batch);
}
return dom.createHandler(fobject);
}
@Deprecated
private FedoraObject findFedoraObject(String pid, Integer batchId) throws IOException {
return findFedoraObject(pid, batchId, true);
}
@Deprecated
private FedoraObject findFedoraObject(String pid, Integer batchId, boolean readonly) throws IOException {
FedoraObject fobject;
if (batchId != null) {
Batch batch = importManager.get(batchId);
if (batch == null) {
throw RestException.plainNotFound(DigitalObjectResourceApi.MEMBERS_ITEM_BATCHID, String.valueOf(batchId));
}
if (!readonly) {
ImportResource.checkBatchState(batch);
}
if (pid == null || ImportBatchManager.ROOT_ITEM_PID.equals(pid)) {
fobject = importManager.getRootObject(batch);
} else {
BatchItemObject item = importManager.findBatchObject(batchId, pid);
if (item == null) {
throw RestException.plainNotFound(DigitalObjectResourceApi.DIGITALOBJECT_PID, pid);
}
fobject = new LocalStorage().load(pid, item.getFile());
}
} else {
if (pid == null) {
throw new NullPointerException("pid");
}
fobject = RemoteStorage.getInstance(appConfig).find(pid);
}
return fobject;
}
/**
* Removes extension from the file name.
* @param filename file name
* @return name without extension
*/
private static String getBareFilename(String filename) {
int index = filename.lastIndexOf('.');
return index <= 0 ? filename : filename.substring(0, index);
}
/**
* Remove path from file name sent by client. It searches for platform path
* delimiters.
* @param filepath file path
* @return the file name
*/
private static String getFilename(String filepath) {
int slashIndex = filepath.lastIndexOf('/');
int backslashIndex = filepath.lastIndexOf('\\');
int index = Math.max(slashIndex, backslashIndex);
if (index > 0) {
filepath = filepath.substring(index);
}
return filepath;
}
@XmlAccessorType(XmlAccessType.FIELD)
public static class DigitalObject {
@XmlElement(name = DigitalObjectResourceApi.DIGITALOBJECT_PID)
private String pid;
@XmlElement(name = DigitalObjectResourceApi.DIGITALOBJECT_MODEL)
private String model;
public DigitalObject(String pid, String model) {
this.pid = pid;
this.model = model;
}
public DigitalObject() {
}
}
}