/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.sword2;
import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.factory.AuthorizeServiceFactory;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.Item;
import org.dspace.content.WorkspaceItem;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.WorkspaceItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.LogManager;
import org.dspace.workflow.WorkflowItem;
import org.dspace.workflow.WorkflowItemService;
import org.dspace.workflow.factory.WorkflowServiceFactory;
import org.swordapp.server.*;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class ContainerManagerDSpace extends DSpaceSwordAPI
implements ContainerManager
{
private static Logger log = Logger.getLogger(ContainerManagerDSpace.class);
protected AuthorizeService authorizeService =
AuthorizeServiceFactory.getInstance().getAuthorizeService();
protected WorkflowItemService workflowItemService =
WorkflowServiceFactory.getInstance().getWorkflowItemService();
protected WorkspaceItemService workspaceItemService =
ContentServiceFactory.getInstance().getWorkspaceItemService();
private VerboseDescription verboseDescription = new VerboseDescription();
public boolean isStatementRequest(String editIRI,
Map<String, String> accept, AuthCredentials authCredentials,
SwordConfiguration swordConfig)
throws SwordError, SwordServerException, SwordAuthException
{
SwordContext sc = null;
try
{
sc = this.noAuthContext();
SwordConfigurationDSpace config = (SwordConfigurationDSpace) swordConfig;
String acceptContentType = this.getHeader(accept, "Accept", null);
TreeMap<Float, List<String>> analysed = this
.analyseAccept(acceptContentType);
// a request is for a statement if the content negotiation asks for a format that the
// Statement disseminator supports
SwordStatementDisseminator disseminator = null;
try
{
SwordDisseminatorFactory
.getStatementInstance(analysed);
}
catch (SwordError swordError)
{
// in this case, it means that no relevant disseminator could be found, which means
// this is not a statement request
return false;
}
return true;
}
catch (DSpaceSwordException e)
{
log.error("caught exception:", e);
throw new SwordServerException(
"There was a problem determining the request type", e);
}
finally
{
// this is a read operation only, so there's never any need to commit the context
if (sc != null)
{
sc.abort();
}
}
}
public DepositReceipt getEntry(String editIRI, Map<String, String> accept,
AuthCredentials authCredentials, SwordConfiguration swordConfig)
throws SwordServerException, SwordError, SwordAuthException
{
SwordContext sc = null;
try
{
SwordConfigurationDSpace config = (SwordConfigurationDSpace) swordConfig;
sc = this.doAuth(authCredentials);
Context context = sc.getContext();
SwordUrlManager urlManager = config.getUrlManager(context, config);
Item item = urlManager.getItem(context, editIRI);
if (item == null)
{
throw new SwordError(404);
}
// we can't give back an entry unless the user is authorised to retrieve it
authorizeService.authorizeAction(context, item, Constants.READ);
ReceiptGenerator genny = new ReceiptGenerator();
DepositReceipt receipt = genny.createReceipt(context, item, config);
sc.abort();
return receipt;
}
catch (AuthorizeException e)
{
throw new SwordAuthException();
}
catch (SQLException | DSpaceSwordException e)
{
throw new SwordServerException(e);
}
finally
{
// this is a read operation only, so there's never any need to commit the context
if (sc != null)
{
sc.abort();
}
}
}
public DepositReceipt replaceMetadata(String editIRI, Deposit deposit,
AuthCredentials authCredentials, SwordConfiguration swordConfig)
throws SwordError, SwordServerException, SwordAuthException
{
// start the timer
Date start = new Date();
// store up the verbose description, which we can then give back at the end if necessary
this.verboseDescription.append(
"Initialising verbose replace of metadata");
SwordContext sc = null;
SwordConfigurationDSpace config = (SwordConfigurationDSpace) swordConfig;
try
{
sc = this.doAuth(authCredentials);
Context context = sc.getContext();
if (log.isDebugEnabled())
{
log.debug(LogManager.getHeader(context, "sword_replace", ""));
}
// get the deposit target
Item item = this.getDSpaceTarget(context, editIRI, config);
if (item == null)
{
throw new SwordError(404);
}
// now we have the deposit target, we can determine whether this operation is allowed
// at all
WorkflowManager wfm = WorkflowManagerFactory.getInstance();
wfm.replaceMetadata(context, item);
// find out if the supplied SWORDContext can submit to the given
// dspace object
SwordAuthenticator auth = new SwordAuthenticator();
if (!auth.canSubmit(sc, item, this.verboseDescription))
{
// throw an exception if the deposit can't be made
String oboEmail = "none";
if (sc.getOnBehalfOf() != null)
{
oboEmail = sc.getOnBehalfOf().getEmail();
}
log.info(LogManager.getHeader(
context, "replace_failed_authorisation",
"user=" + sc.getAuthenticated().getEmail() +
",on_behalf_of=" + oboEmail));
throw new SwordAuthException(
"Cannot replace the given item with this context");
}
// make a note of the authentication in the verbose string
this.verboseDescription.append(
"Authenticated user: " + sc.getAuthenticated().getEmail());
if (sc.getOnBehalfOf() != null)
{
this.verboseDescription.append("Depositing on behalf of: " +
sc.getOnBehalfOf().getEmail());
}
DepositResult result = null;
try
{
result = this.doReplaceMetadata(
sc, item, deposit, authCredentials, config);
}
catch (DSpaceSwordException | SwordError e)
{
if (config.isKeepPackageOnFailedIngest())
{
try
{
this.storeEntryAsFile(deposit, authCredentials, config);
}
catch (IOException e2)
{
log.warn("Unable to store SWORD entry as file: " + e);
}
}
throw e;
}
// now we've produced a deposit, we need to decide on its workflow state
wfm.resolveState(context, deposit, result, this.verboseDescription);
ReceiptGenerator genny = new ReceiptGenerator();
DepositReceipt receipt = genny.createReceipt(
context, result, config);
Date finish = new Date();
long delta = finish.getTime() - start.getTime();
this.verboseDescription.append(
"Total time for deposit processing: " + delta + " ms");
this.addVerboseDescription(receipt, this.verboseDescription);
// if something hasn't killed it already (allowed), then complete the transaction
sc.commit();
return receipt;
}
catch (DSpaceSwordException e)
{
log.error("caught exception:", e);
throw new SwordServerException(
"There was a problem depositing the item", e);
}
finally
{
// this is a read operation only, so there's never any need to commit the context
if (sc != null)
{
sc.abort();
}
}
}
public DepositReceipt replaceMetadataAndMediaResource(String editIRI,
Deposit deposit, AuthCredentials authCredentials,
SwordConfiguration swordConfig)
throws SwordError, SwordServerException, SwordAuthException
{
// start the timer
Date start = new Date();
// store up the verbose description, which we can then give back at the end if necessary
this.verboseDescription.append(
"Initialising verbose multipart replace");
SwordContext sc = null;
SwordConfigurationDSpace config = (SwordConfigurationDSpace) swordConfig;
try
{
// first authenticate the request
// note: this will build our various DSpace contexts for us
sc = this.doAuth(authCredentials);
Context context = sc.getContext();
if (log.isDebugEnabled())
{
log.debug(LogManager.getHeader(
context, "sword_create_new", ""));
}
// get the deposit target
Item item = this.getDSpaceTarget(context, editIRI, config);
if (item == null)
{
throw new SwordError(404);
}
// Ensure that this method is allowed
WorkflowManager wfm = WorkflowManagerFactory.getInstance();
wfm.replaceMetadataAndMediaResource(context, item);
// find out if the supplied SWORDContext can submit to the given
// dspace object
SwordAuthenticator auth = new SwordAuthenticator();
if (!auth.canSubmit(sc, item, this.verboseDescription))
{
// throw an exception if the deposit can't be made
String oboEmail = "none";
if (sc.getOnBehalfOf() != null)
{
oboEmail = sc.getOnBehalfOf().getEmail();
}
log.info(LogManager.getHeader(context,
"deposit_failed_authorisation",
"user=" + sc.getAuthenticated().getEmail() +
",on_behalf_of=" + oboEmail));
throw new SwordAuthException(
"Cannot submit to the given collection with this context");
}
// make a note of the authentication in the verbose string
this.verboseDescription.append(
"Authenticated user: " + sc.getAuthenticated().getEmail());
if (sc.getOnBehalfOf() != null)
{
this.verboseDescription.append("Depositing on behalf of: " +
sc.getOnBehalfOf().getEmail());
}
DepositResult result = null;
try
{
result = this.replaceFromMultipart(
sc, item, deposit, authCredentials, config);
}
catch (DSpaceSwordException | SwordError e)
{
if (config.isKeepPackageOnFailedIngest())
{
try
{
this.storePackageAsFile(deposit, authCredentials,
config);
this.storeEntryAsFile(deposit, authCredentials, config);
}
catch (IOException e2)
{
log.warn("Unable to store SWORD package as file: " + e);
}
}
throw e;
}
// now we've produced a deposit, we need to decide on its workflow state
wfm.resolveState(context, deposit, result, this.verboseDescription);
ReceiptGenerator genny = new ReceiptGenerator();
DepositReceipt receipt = genny.createReceipt(
context, result, config);
Date finish = new Date();
long delta = finish.getTime() - start.getTime();
this.verboseDescription.append(
"Total time for deposit processing: " + delta + " ms");
this.addVerboseDescription(receipt, this.verboseDescription);
// if something hasn't killed it already (allowed), then complete the transaction
sc.commit();
return receipt;
}
catch (DSpaceSwordException e)
{
log.error("caught exception:", e);
throw new SwordServerException(
"There was a problem depositing the item", e);
}
finally
{
// this is a read operation only, so there's never any need to commit the context
if (sc != null)
{
sc.abort();
}
}
}
public DepositReceipt addMetadataAndResources(String s, Deposit deposit,
AuthCredentials authCredentials, SwordConfiguration config)
throws SwordError, SwordServerException
{
return null;
}
public DepositReceipt addMetadata(String editIRI, Deposit deposit,
AuthCredentials authCredentials, SwordConfiguration swordConfig)
throws SwordError, SwordServerException, SwordAuthException
{
// start the timer
Date start = new Date();
// store up the verbose description, which we can then give back at the end if necessary
this.verboseDescription.append(
"Initialising verbose replace of metadata");
SwordContext sc = null;
SwordConfigurationDSpace config = (SwordConfigurationDSpace) swordConfig;
try
{
sc = this.doAuth(authCredentials);
Context context = sc.getContext();
if (log.isDebugEnabled())
{
log.debug(LogManager.getHeader(context, "sword_replace", ""));
}
// get the deposit target
Item item = this.getDSpaceTarget(context, editIRI, config);
if (item == null)
{
throw new SwordError(404);
}
// now we have the deposit target, we can determine whether this operation is allowed
// at all
WorkflowManager wfm = WorkflowManagerFactory.getInstance();
wfm.addMetadata(context, item);
// find out if the supplied SWORDContext can submit to the given
// dspace object
SwordAuthenticator auth = new SwordAuthenticator();
if (!auth.canSubmit(sc, item, this.verboseDescription))
{
// throw an exception if the deposit can't be made
String oboEmail = "none";
if (sc.getOnBehalfOf() != null)
{
oboEmail = sc.getOnBehalfOf().getEmail();
}
log.info(LogManager.getHeader(
context, "replace_failed_authorisation",
"user=" + sc.getAuthenticated().getEmail() +
",on_behalf_of=" + oboEmail));
throw new SwordAuthException(
"Cannot replace the given item with this context");
}
// make a note of the authentication in the verbose string
this.verboseDescription.append("Authenticated user: " +
sc.getAuthenticated().getEmail());
if (sc.getOnBehalfOf() != null)
{
this.verboseDescription.append("Depositing on behalf of: " +
sc.getOnBehalfOf().getEmail());
}
DepositResult result = null;
try
{
result = this.doAddMetadata(
sc, item, deposit, authCredentials, config);
}
catch (DSpaceSwordException | SwordError e)
{
if (config.isKeepPackageOnFailedIngest())
{
try
{
this.storeEntryAsFile(deposit, authCredentials, config);
}
catch (IOException e2)
{
log.warn("Unable to store SWORD entry as file: " + e);
}
}
throw e;
}
// now we've produced a deposit, we need to decide on its workflow state
wfm.resolveState(context, deposit, result, this.verboseDescription);
ReceiptGenerator genny = new ReceiptGenerator();
DepositReceipt receipt = genny.createReceipt(
context, result, config);
Date finish = new Date();
long delta = finish.getTime() - start.getTime();
this.verboseDescription.append(
"Total time for deposit processing: " + delta + " ms");
this.addVerboseDescription(receipt, this.verboseDescription);
// if something hasn't killed it already (allowed), then complete the transaction
sc.commit();
return receipt;
}
catch (DSpaceSwordException e)
{
log.error("caught exception:", e);
throw new SwordServerException(
"There was a problem depositing the item", e);
}
finally
{
// this is a read operation only, so there's never any need to commit the context
if (sc != null)
{
sc.abort();
}
}
}
public DepositReceipt addResources(String s, Deposit deposit,
AuthCredentials authCredentials, SwordConfiguration config)
throws SwordError, SwordServerException
{
return null;
}
public void deleteContainer(String editIRI, AuthCredentials authCredentials,
SwordConfiguration swordConfig)
throws SwordError, SwordServerException, SwordAuthException
{
// start the timer
Date start = new Date();
// store up the verbose description, which we can then give back at the end if necessary
this.verboseDescription.append("Initialising verbose container delete");
SwordContext sc = null;
SwordConfigurationDSpace config = (SwordConfigurationDSpace) swordConfig;
try
{
sc = this.doAuth(authCredentials);
Context context = sc.getContext();
if (log.isDebugEnabled())
{
log.debug(LogManager.getHeader(context, "sword_delete", ""));
}
// get the deposit target
Item item = this.getDSpaceTarget(context, editIRI, config);
if (item == null)
{
throw new SwordError(404);
}
// now we have the deposit target, we can determine whether this operation is allowed
// at all
WorkflowManager wfm = WorkflowManagerFactory.getInstance();
wfm.deleteItem(context, item);
// find out if the supplied SWORDContext can submit to the given
// dspace object
SwordAuthenticator auth = new SwordAuthenticator();
if (!auth.canSubmit(sc, item, this.verboseDescription))
{
// throw an exception if the deposit can't be made
String oboEmail = "none";
if (sc.getOnBehalfOf() != null)
{
oboEmail = sc.getOnBehalfOf().getEmail();
}
log.info(LogManager.getHeader(context,
"replace_failed_authorisation",
"user=" + sc.getAuthenticated().getEmail() +
",on_behalf_of=" + oboEmail));
throw new SwordAuthException(
"Cannot delete the given item with this context");
}
// make a note of the authentication in the verbose string
this.verboseDescription.append("Authenticated user: " +
sc.getAuthenticated().getEmail());
if (sc.getOnBehalfOf() != null)
{
this.verboseDescription.append("Depositing on behalf of: " +
sc.getOnBehalfOf().getEmail());
}
this.doContainerDelete(sc, item, authCredentials, config);
Date finish = new Date();
long delta = finish.getTime() - start.getTime();
this.verboseDescription.append(
"Total time for deposit processing: " + delta + " ms");
// if something hasn't killed it already (allowed), then complete the transaction
sc.commit();
}
catch (DSpaceSwordException e)
{
log.error("caught exception:", e);
throw new SwordServerException(
"There was a problem depositing the item", e);
}
finally
{
// this is a read operation only, so there's never any need to commit the context
if (sc != null)
{
sc.abort();
}
}
}
public DepositReceipt useHeaders(String editIRI, Deposit deposit,
AuthCredentials authCredentials, SwordConfiguration swordConfig)
throws SwordError, SwordServerException, SwordAuthException
{
// start the timer
Date start = new Date();
// store up the verbose description, which we can then give back at the end if necessary
this.verboseDescription.append(
"Initialising verbose empty request (headers only)");
SwordContext sc = null;
SwordConfigurationDSpace config = (SwordConfigurationDSpace) swordConfig;
try
{
sc = this.doAuth(authCredentials);
Context context = sc.getContext();
if (log.isDebugEnabled())
{
log.debug(LogManager.getHeader(
context, "sword_modify_by_headers", ""));
}
// get the deposit target
Item item = this.getDSpaceTarget(context, editIRI, config);
if (item == null)
{
throw new SwordError(404);
}
// now we have the deposit target, we can determine whether this operation is allowed
// at all
WorkflowManager wfm = WorkflowManagerFactory.getInstance();
wfm.modifyState(context, item);
// find out if the supplied SWORDContext can submit to the given
// dspace object
SwordAuthenticator auth = new SwordAuthenticator();
if (!auth.canSubmit(sc, item, this.verboseDescription))
{
// throw an exception if the deposit can't be made
String oboEmail = "none";
if (sc.getOnBehalfOf() != null)
{
oboEmail = sc.getOnBehalfOf().getEmail();
}
log.info(LogManager.getHeader(context,
"modify_failed_authorisation",
"user=" + sc.getAuthenticated().getEmail() +
",on_behalf_of=" + oboEmail));
throw new SwordAuthException(
"Cannot modify the given item with this context");
}
// make a note of the authentication in the verbose string
this.verboseDescription.append("Authenticated user: " +
sc.getAuthenticated().getEmail());
if (sc.getOnBehalfOf() != null)
{
this.verboseDescription.append("Modifying on behalf of: " +
sc.getOnBehalfOf().getEmail());
}
DepositResult result = new DepositResult();
result.setItem(item);
// the main objective here is just to resolve the state
wfm.resolveState(context, deposit, result, this.verboseDescription);
// now return the usual deposit receipt
ReceiptGenerator genny = new ReceiptGenerator();
DepositReceipt receipt = genny.createReceipt(context, item, config);
Date finish = new Date();
long delta = finish.getTime() - start.getTime();
this.verboseDescription.append(
"Total time for modify processing: " + delta + " ms");
this.addVerboseDescription(receipt, this.verboseDescription);
// if something hasn't killed it already (allowed), then complete the transaction
sc.commit();
return receipt;
}
catch (DSpaceSwordException e)
{
log.error("caught exception:", e);
throw new SwordServerException(
"There was a problem depositing the item", e);
}
finally
{
// this is a read operation only, so there's never any need to commit the context
if (sc != null)
{
sc.abort();
}
}
}
private DepositResult replaceFromMultipart(SwordContext swordContext,
Item item, Deposit deposit, AuthCredentials authCredentials,
SwordConfigurationDSpace swordConfig)
throws DSpaceSwordException, SwordError, SwordAuthException,
SwordServerException
{
// get the things out of the service that we need
Context context = swordContext.getContext();
// is the content acceptable? If not, this will throw an error
this.isAcceptable(swordConfig, context, deposit, item);
// Obtain the relevant content ingester from the factory
SwordContentIngester sci = SwordIngesterFactory
.getContentInstance(context, deposit, item);
this.verboseDescription
.append("Loaded content ingester: " + sci.getClass().getName());
// obtain the relevant entry intester from the factory
SwordEntryIngester sei = SwordIngesterFactory.getEntryInstance(
context, deposit, item);
this.verboseDescription.append(
"Loaded entry ingester: " + sei.getClass().getName());
try
{
// delegate the to the version manager to get rid of any existing content and to version
// if if necessary
VersionManager vm = new VersionManager();
vm.removeBundle(context, item, "ORIGINAL");
}
catch (SQLException | IOException e)
{
throw new DSpaceSwordException(e);
}
catch (AuthorizeException e)
{
throw new SwordAuthException(e);
}
DepositResult result;
if (swordConfig.isEntryFirst())
{
// do the entry deposit
result = sei.ingest(
context, deposit, item, this.verboseDescription, null, true);
// do the content deposit
result = sci.ingest(
context, deposit, item, this.verboseDescription, result);
this.verboseDescription.append(
"Archive ingest completed successfully");
}
else
{
// do the content deposit
result = sci.ingest(
context, deposit, item, this.verboseDescription, null);
// do the entry deposit
result = sei.ingest(
context, deposit, item, this.verboseDescription, result, true);
this.verboseDescription.append(
"Archive ingest completed successfully");
}
// store the originals (this code deals with the possibility that that's not required)
this.storeOriginals(
swordConfig, context, this.verboseDescription, deposit, result);
return result;
}
private DepositResult doReplaceMetadata(SwordContext swordContext,
Item item, Deposit deposit, AuthCredentials authCredentials,
SwordConfigurationDSpace swordConfig)
throws DSpaceSwordException, SwordError, SwordAuthException,
SwordServerException
{
// get the things out of the service that we need
Context context = swordContext.getContext();
// Obtain the relevant ingester from the factory
SwordEntryIngester si = SwordIngesterFactory.getEntryInstance(
context, deposit, null);
this.verboseDescription.append(
"Loaded ingester: " + si.getClass().getName());
// do the deposit
DepositResult result = si.ingest(
context, deposit, item, this.verboseDescription, null, true);
this.verboseDescription.append("Replace completed successfully");
// store the originals (this code deals with the possibility that that's not required)
this.storeOriginals(
swordConfig, context, this.verboseDescription, deposit, result);
return result;
}
protected DepositResult doAddMetadata(SwordContext swordContext, Item item,
Deposit deposit, AuthCredentials authCredentials,
SwordConfigurationDSpace swordConfig)
throws DSpaceSwordException, SwordError, SwordAuthException,
SwordServerException
{
return this.doAddMetadata(
swordContext, item, deposit, authCredentials, swordConfig, null);
}
protected DepositResult doAddMetadata(SwordContext swordContext, Item item,
Deposit deposit, AuthCredentials authCredentials,
SwordConfigurationDSpace swordConfig, DepositResult result)
throws DSpaceSwordException, SwordError, SwordAuthException,
SwordServerException
{
if (result == null)
{
result = new DepositResult();
}
// get the things out of the service that we need
Context context = swordContext.getContext();
// Obtain the relevant ingester from the factory
SwordEntryIngester si = SwordIngesterFactory.getEntryInstance(
context, deposit, null);
this.verboseDescription.append(
"Loaded ingester: " + si.getClass().getName());
// do the deposit
result = si.ingest(
context, deposit, item, this.verboseDescription, result, false);
this.verboseDescription.append("Replace completed successfully");
// store the originals (this code deals with the possibility that that's not required)
this.storeOriginals(
swordConfig, context, this.verboseDescription, deposit, result);
return result;
}
protected void doContainerDelete(SwordContext swordContext, Item item,
AuthCredentials authCredentials,
SwordConfigurationDSpace swordConfig)
throws DSpaceSwordException, SwordAuthException
{
try
{
Context context = swordContext.getContext();
// first figure out if there's anything we need to do about the workflow/workspace state
WorkflowTools wft = new WorkflowTools();
if (wft.isItemInWorkspace(swordContext.getContext(), item))
{
WorkspaceItem wsi = wft.getWorkspaceItem(context, item);
workspaceItemService.deleteAll(context, wsi);
}
else if (wft.isItemInWorkflow(context, item))
{
WorkflowItem wfi = wft.getWorkflowItem(context, item);
workflowItemService.deleteWrapper(context, wfi);
}
// then delete the item
itemService.delete(context, item);
}
catch (SQLException | IOException e)
{
throw new DSpaceSwordException(e);
}
catch (AuthorizeException e)
{
throw new SwordAuthException(e);
}
}
private Item getDSpaceTarget(Context context, String editUrl,
SwordConfigurationDSpace config)
throws DSpaceSwordException, SwordError
{
SwordUrlManager urlManager = config.getUrlManager(context, config);
// get the target collection
Item item = urlManager.getItem(context, editUrl);
if (item == null)
{
throw new SwordError(404);
}
this.verboseDescription.append(
"Performing replace using edit-media URL: " + editUrl);
this.verboseDescription.append(
"Location resolves to item with handle: " + item.getHandle());
return item;
}
}