New REST api

New REST api with CRUD operations over community, collections, items,
bitstreams.
This commit is contained in:
ctu-developers
2014-09-29 19:36:12 +02:00
parent 27fe6808c9
commit eb399686c5
17 changed files with 4269 additions and 508 deletions

View File

@@ -7,143 +7,684 @@
*/ */
package org.dspace.rest; package org.dspace.rest;
import org.apache.log4j.Logger; import java.io.IOException;
import org.dspace.authorize.AuthorizeException; import java.io.InputStream;
import org.dspace.authorize.AuthorizeManager; import java.net.URLConnection;
import org.dspace.content.DSpaceObject; import java.sql.SQLException;
import org.dspace.core.ConfigurationManager; import java.util.ArrayList;
import org.dspace.core.Constants; import java.util.List;
import org.dspace.rest.common.Bitstream;
import org.dspace.usage.UsageEvent;
import org.dspace.utils.DSpace;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*; import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
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.Context;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.io.IOException; import javax.ws.rs.core.Response.Status;
import java.sql.SQLException;
import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.AuthorizeManager;
import org.dspace.content.BitstreamFormat;
import org.dspace.content.Bundle;
import org.dspace.core.Constants;
import org.dspace.eperson.Group;
import org.dspace.rest.common.Bitstream;
import org.dspace.rest.common.ResourcePolicy;
import org.dspace.rest.exceptions.ContextException;
import org.dspace.storage.bitstore.BitstreamStorageManager;
import org.dspace.storage.rdbms.DatabaseManager;
import org.dspace.storage.rdbms.TableRow;
import org.dspace.usage.UsageEvent;
/** /**
* Created with IntelliJ IDEA. * @author Rostislav Novak (Computing and Information Centre, CTU in Prague)
* User: peterdietz
* Date: 10/2/13
* Time: 5:56 PM
* To change this template use File | Settings | File Templates.
*/ */
//Every DSpace class used without namespace is from package org.dspace.rest.common.*. Otherwise namespace is defined.
@Path("/bitstreams") @Path("/bitstreams")
public class BitstreamResource { public class BitstreamResource extends Resource{
Logger log = Logger.getLogger(BitstreamResource.class);
private static final boolean writeStatistics; private static Logger log = Logger.getLogger(BitstreamResource.class);
static{
writeStatistics=ConfigurationManager.getBooleanProperty("rest","stats",false);
}
//BitstreamList - Not Implemented
/**
* Return bitstream properties without file data. It can throws
* WebApplicationException with three response codes. Response code
* NOT_FOUND(404) or UNAUTHORIZED(401) or INTERNAL_SERVER_ERROR(500). Bad
* request is when id of bitstream does not exist. UNAUTHORIZED is if logged
* user into DSpace context have not permission to access bitstream. Server
* error when something went wrong.
*
* @param bitstreamId
* Id of bitstream in DSpace.
* @param expand
* This string defined, which additional options will be added
* into bitstream. Single options are separated by commas without
* space. Options are: "all", "parent".
* @param headers
* If you want to access to item under logged user into context.
* In headers must be set header "rest-dspace-token" with passed
* token from login method.
* @return If user is allowed to read bitstream, it returns instance of
* bitstream. Otherwise, it throws WebApplicationException with
* response code UNAUTHORIZED.
* @throws WebApplicationException
* It can happen on: Bad request, unauthorized, SQL exception
* and context exception(could not create context).
*/
@GET @GET
@Path("/{bitstream_id}") @Path("/{bitstream_id}")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Bitstream getBitstream(@PathParam("bitstream_id") Integer bitstream_id, @QueryParam("expand") String expand) { public Bitstream getBitstream(
org.dspace.core.Context context = null; @PathParam("bitstream_id") Integer bitstreamId, @QueryParam("expand") String expand,
try { @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent,
context = new org.dspace.core.Context(); @QueryParam("xforwarderfor") String xforwarderfor, @Context HttpHeaders headers,
@Context HttpServletRequest request) throws WebApplicationException {
org.dspace.content.Bitstream bitstream = org.dspace.content.Bitstream.find(context, bitstream_id);
log.info("Reading bitstream(id=" + bitstreamId + ") metadata.");
if(AuthorizeManager.authorizeActionBoolean(context, bitstream, org.dspace.core.Constants.READ)) { org.dspace.core.Context context = null;
return new org.dspace.rest.common.Bitstream(bitstream, expand); Bitstream bitstream = null;
} else {
throw new WebApplicationException(Response.Status.UNAUTHORIZED); try {
} context = createContext(getUser(headers));
} catch(SQLException e) { org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, org.dspace.core.Constants.READ);
log.error(e.getMessage());
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); writeStats(Constants.BITSTREAM, dspaceBitstream, UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor, headers, request);
} finally {
if(context != null) { bitstream = new Bitstream(dspaceBitstream, expand);
try { context.complete();
context.complete(); log.trace("Bitsream(id=" + bitstreamId + ") was successfully read.");
} catch (SQLException e) {
log.error(e.getMessage() + " occurred while trying to close"); } catch (SQLException e) {
} processException("Someting went wrong while reading bitstream(id=" + bitstreamId + ") from database! Message: " + e,context);
} } catch (ContextException e) {
} processException("Someting went wrong while reading bitstream(id=" + bitstreamId + "), ContextException. Message: " + e.getMessage(), context);
} }
@GET return bitstream;
@Path("/{bitstream_id}/retrieve")
public javax.ws.rs.core.Response getFile(@PathParam("bitstream_id") final Integer bitstream_id,
@QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, @QueryParam("xforwarderfor") String xforwarderfor,
@Context HttpHeaders headers, @Context HttpServletRequest request) {
org.dspace.core.Context context = null;
try {
context = new org.dspace.core.Context();
org.dspace.content.Bitstream bitstream = org.dspace.content.Bitstream.find(context, bitstream_id);
if(AuthorizeManager.authorizeActionBoolean(context, bitstream, org.dspace.core.Constants.READ)) {
if(writeStatistics){
writeStats(context, bitstream_id, user_ip, user_agent, xforwarderfor, headers, request);
}
return Response.ok(bitstream.retrieve()).type(bitstream.getFormat().getMIMEType()).build();
} else {
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
} catch (IOException e) {
log.error(e.getMessage());
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
} catch (SQLException e) {
log.error(e.getMessage());
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
} catch (AuthorizeException e) {
log.error(e.getMessage());
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
} finally {
if(context != null) {
try {
context.complete();
} catch (SQLException e) {
log.error(e.getMessage() + " occurred while trying to close");
}
}
}
} }
private void writeStats(org.dspace.core.Context context, Integer bitstream_id, String user_ip, String user_agent, /**
String xforwarderfor, HttpHeaders headers, * Return all bitstream resource policies from all bundles, in which
HttpServletRequest request) { * bitstream is.
*
try{ * @param bitstreamId
DSpaceObject bitstream = DSpaceObject.find(context, Constants.BITSTREAM, bitstream_id); * Id of bitstream in DSpace.
* @param headers
if(user_ip==null || user_ip.length()==0){ * If you want to access to item under logged user into context.
new DSpace().getEventService().fireEvent( * In headers must be set header "rest-dspace-token" with passed
new UsageEvent( * token from login method.
UsageEvent.Action.VIEW, * @return It returns array of ResourcePolicy.
request, */
context, @GET
bitstream)); @Path("/{bitstream_id}/policy")
} else{ @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
new DSpace().getEventService().fireEvent( public ResourcePolicy[] getBitstreamPolicies(@PathParam("bitstream_id") Integer bitstreamId, @Context HttpHeaders headers) {
new UsageEvent(
UsageEvent.Action.VIEW, log.info("Reading bitstream(id=" + bitstreamId + ") policies.");
user_ip, org.dspace.core.Context context = null;
user_agent, List<ResourcePolicy> policies = new ArrayList<ResourcePolicy>();
xforwarderfor,
context, try {
bitstream)); context = createContext(getUser(headers));
} org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, org.dspace.core.Constants.READ);
log.debug("fired event");
Bundle[] bundles = dspaceBitstream.getBundles();
} catch(SQLException ex){ for (Bundle bundle : bundles) {
log.error("SQL exception can't write usageEvent \n" + ex); List<org.dspace.authorize.ResourcePolicy> bitstreamsPolicies = bundle.getBitstreamPolicies();
for (org.dspace.authorize.ResourcePolicy policy : bitstreamsPolicies) {
if (policy.getResourceID() == bitstreamId) {
policies.add(new ResourcePolicy(policy));
}
}
}
context.complete();
log.trace("Policies for bitsream(id=" + bitstreamId + ") was successfully read.");
} catch (SQLException e) {
processException("Someting went wrong while reading policies of bitstream(id=" + bitstreamId + "), SQLException! Message: " + e, context);
} catch (ContextException e) {
processException("Someting went wrong while reading policies of bitstream(id=" + bitstreamId + "), ContextException. Message: " + e.getMessage(), context);
} }
return policies.toArray(new ResourcePolicy[0]);
} }
/**
* Read list of bitstreams. It throws WebApplicationException with response
* code INTERNAL_SERVER_ERROR(500), if there was problem while reading
* bitstreams from database.
*
* @param limit
* How much bitstreams in list will be. Default value is 100.
* @param offset
* On which index will list starts. Default values is 0.
* @param headers
* If you want to access to item under logged user into context.
* In headers must be set header "rest-dspace-token" with passed
* token from login method.
* @return It returns array of bistreams. In array are not bitstreams on
* which user has not permission to read.
* @throws WebApplicationException
* It is thrown when was problem with database reading or with
* creating context.
*/
@GET
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Bitstream[] getBitstreams(
@QueryParam("expand") String expand,
@QueryParam("limit") @DefaultValue("100") Integer limit,
@QueryParam("offset") @DefaultValue("0") Integer offset,
@QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent,
@QueryParam("xforwarderfor") String xforwarderfor, @Context HttpHeaders headers,
@Context HttpServletRequest request) throws WebApplicationException {
log.info("Reading bitstreams.(offset=" + offset + ",limit=" + limit + ")");
org.dspace.core.Context context = null;
List<Bitstream> bitstreams = new ArrayList<Bitstream>();
try {
context = createContext(getUser(headers));
org.dspace.content.Bitstream[] dspaceBitstreams = org.dspace.content.Bitstream.findAll(context);
if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) {
log.warn("Pagging was badly set.");
limit = 100;
offset = 0;
}
// TODO If bitsream doesnt not exist it throw exception.
for (int i = offset; (i < (offset+limit)) && (i < dspaceBitstreams.length); i++) {
if (AuthorizeManager.authorizeActionBoolean(context, dspaceBitstreams[i], org.dspace.core.Constants.READ)) {
if (dspaceBitstreams[i].getParentObject() != null) { // To eliminate bitstreams which cause exception, because reading under administrator permissions
bitstreams.add(new Bitstream(dspaceBitstreams[i], expand));
writeStats(Constants.BITSTREAM, dspaceBitstreams[i], UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor, headers, request);
}
}
}
context.complete();
log.trace("Bitstreams were successfully read.");
} catch (SQLException e) {
processException("Something get wrong while reading bitstreams from database!. Message: " + e, context);
} catch (ContextException e) {
processException("Something get wrong while reading bitstreams, ContextException. Message: " + e.getMessage(), context);
}
return bitstreams.toArray(new Bitstream[0]);
}
/**
* Read bitstream data. It can throw WebApplicationException with code
* INTERNAL_SERVER_ERROR(500). Caused by three exceptions: IOException if
* there was problem with reading bitstream file. SQLException if there was
* problem while reading from database. And AuthorizeException if there was
* problem with authorization of user logged to DSpace context.
*
* @param bitstreamId
* Id of bitstream, of which will be read data.
* @param headers
* If you want to access to item under logged user into context.
* In headers must be set header "rest-dspace-token" with passed
* token from login method.
* @return Returns response with data with content type of file. It can
* return response code NOT_FOUND(404) if there was bad id of
* bitstream. Or response code UNAUTHORIZED(401) if user is not
* allowed to read bitstream.
* @throws WebApplicationException
* It is throw in this cases: When was problem with reading file
* data. Or was problem with database reading. Or was problem
* with creating context. Or problem with authorization.
*/
@POST
@Path("/{bitstream_id}/retrieve")
public javax.ws.rs.core.Response getBitstreamData(
@PathParam("bitstream_id") Integer bitstreamId,
@QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent,
@QueryParam("xforwarderfor") String xforwarderfor, @Context HttpHeaders headers,
@Context HttpServletRequest request) throws WebApplicationException {
log.info("Reading data of bitstream(id=" + bitstreamId + ").");
org.dspace.core.Context context = null;
try {
context = createContext(getUser(headers));
org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, org.dspace.core.Constants.READ);
writeStats(Constants.BITSTREAM, dspaceBitstream, UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor, headers, request);
log.trace("Bitsream(id=" + bitstreamId + ") data was successfully read.");
InputStream inputStream = dspaceBitstream.retrieve();
String type = dspaceBitstream.getFormat().getMIMEType();
context.complete();
return Response.ok(inputStream).type(type).build();
} catch (IOException e) {
processException("Could not read file of bitstream(id="+ bitstreamId + ")! Message: " + e, context);
} catch (SQLException e) {
processException("Something get wrong while reading bitsream(id=" + bitstreamId + ") from database! Message: " + e, context);
} catch (AuthorizeException e) {
processException("Could not retrieve file of bitstream(id=" + bitstreamId + "), AuthorizeException! Message: " + e, context);
} catch (ContextException e) {
processException("Could not retrieve file of bitstream(id=" + bitstreamId + "), ContextException! Message: " + e.getMessage(), context);
}
return null;
}
/**
* Add bitstream policy to all bundles in which bitstream is.
*
* @param bitstreamId
* Id of bitstream in DSpace.
* @param policy
* Policy which will be added. But this atributes does not be
* applied: epersonId,
* @param headers
* If you want to access to item under logged user into context.
* In headers must be set header "rest-dspace-token" with passed
* token from login method.
* @return Returns ok, if was all ok. Otherwise status code 500.
*/
@POST
@Path("/{bitstream_id}/policy")
public javax.ws.rs.core.Response addBitstreamPolicy(@PathParam("bitstream_id") Integer bitstreamId, ResourcePolicy policy, @Context HttpHeaders headers) {
log.info("Adding bitstream(id=" + bitstreamId + ") READ policy with permission for group(id=" + policy.getGroupId() + ").");
org.dspace.core.Context context = null;
try {
context = createContext(getUser(headers));
org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, org.dspace.core.Constants.READ);
Bundle[] bundles = dspaceBitstream.getBundles();
for (Bundle bundle : bundles) {
List<org.dspace.authorize.ResourcePolicy> bitstreamsPolicies = bundle.getBitstreamPolicies();
org.dspace.authorize.ResourcePolicy dspacePolicy = org.dspace.authorize.ResourcePolicy.create(context);
dspacePolicy.setAction(policy.getActionInt());
dspacePolicy.setGroup(Group.find(context, policy.getGroupId()));
dspacePolicy.setResourceID(dspaceBitstream.getID());
dspacePolicy.setResource(dspaceBitstream);
dspacePolicy.setResourceType(org.dspace.core.Constants.BITSTREAM);
dspacePolicy.setStartDate(policy.getStartDate());
dspacePolicy.setEndDate(policy.getEndDate());
dspacePolicy.setRpDescription(policy.getRpDescription());
dspacePolicy.setRpName(policy.getRpName());
dspacePolicy.update();
//dspacePolicy.setRpType(org.dspace.authorize.ResourcePolicy.TYPE_CUSTOM);
bitstreamsPolicies.add(dspacePolicy);
bundle.replaceAllBitstreamPolicies(bitstreamsPolicies);
bundle.update();
}
context.complete();
log.trace("Policy for bitsream(id=" + bitstreamId + ") was successfully added.");
} catch (SQLException e) {
processException("Someting went wrong while adding policy to bitstream(id=" + bitstreamId + "), SQLException! Message: " + e, context);
} catch (ContextException e) {
processException("Someting went wrong while adding policy to bitstream(id=" + bitstreamId + "), ContextException. Message: " + e.getMessage(), context);
} catch (AuthorizeException e) {
processException("Someting went wrong while adding policy to bitstream(id=" + bitstreamId + "), AuthorizeException! Message: " + e, context);
}
return Response.status(Status.OK).build();
}
/**
* Update bitstream metadata. It replace everything on targeted bitstream.
* It can throws WebApplicationException caused by two exceptions:
* SQLException, if there was problem with database. AuthorizeException if
* there was problem with authorization to edit bitstream metadata.
*
* @param bitstreamId
* Id of bistream, wich will be updated.
* @param bitstream
* Bitstream with will be placed. It muset have filled user
* creditials.
* @param headers
* If you want to access to item under logged user into context.
* In headers must be set header "rest-dspace-token" with passed
* token from login method.
* @return Return response codes: OK(200), NOT_FOUND(404) if bitstream
* does not exist and UNAUTHORIZED(401) if user is not allowed to
* write to bitstream.
* @throws WebApplicationException
* It can be thrown by: Error in reading from database. Or
* creating context or with authorization to bitstream.
*/
@PUT
@Path("/{bitstream_id}")
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response updateBitstream(
@PathParam("bitstream_id") Integer bitstreamId, Bitstream bitstream,
@QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent,
@QueryParam("xforwarderfor") String xforwarderfor, @Context HttpHeaders headers,
@Context HttpServletRequest request) throws WebApplicationException {
log.info("Updating bitstream(id=" + bitstreamId + ") metadata.");
org.dspace.core.Context context = null;
try {
context = createContext(getUser(headers));
org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, org.dspace.core.Constants.WRITE);
writeStats(Constants.BITSTREAM, dspaceBitstream, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwarderfor, headers, request);
log.trace("Updating bitstream metadata.");
dspaceBitstream.setDescription(bitstream.getDescription());
if (getMimeType(bitstream.getName()) == null) {
dspaceBitstream.setFormat(BitstreamFormat.findUnknown(context));
} else {
dspaceBitstream.setFormat(BitstreamFormat.findByMIMEType(context, getMimeType(bitstream.getName())));
}
dspaceBitstream.setName(bitstream.getName());
Integer sequenceId = bitstream.getSequenceId();
if(sequenceId != null && sequenceId.intValue() != -1){
dspaceBitstream.setSequenceID(sequenceId);
}
dspaceBitstream.update();
if (bitstream.getPolicies() != null) {
Bundle[] bundles = dspaceBitstream.getBundles();
ResourcePolicy[] policies = bitstream.getPolicies();
for (Bundle bundle : bundles) {
List<org.dspace.authorize.ResourcePolicy> bitstreamsPolicies = bundle.getBitstreamPolicies();
// Remove old bitstream policies
List<org.dspace.authorize.ResourcePolicy> policiesToRemove = new ArrayList<org.dspace.authorize.ResourcePolicy>();
for (org.dspace.authorize.ResourcePolicy policy : bitstreamsPolicies) {
if (policy.getResourceID() == dspaceBitstream.getID()) {
policiesToRemove.add(policy);
}
}
for (org.dspace.authorize.ResourcePolicy policy : policiesToRemove) {
bitstreamsPolicies.remove(policy);
}
// Add all new bitstream policies
for (ResourcePolicy policy : policies) {
org.dspace.authorize.ResourcePolicy dspacePolicy = org.dspace.authorize.ResourcePolicy.create(context);
dspacePolicy.setAction(policy.getActionInt());
dspacePolicy.setGroup(Group.find(context, policy.getGroupId()));
dspacePolicy.setResourceID(dspaceBitstream.getID());
dspacePolicy.setResource(dspaceBitstream);
dspacePolicy.setResourceType(org.dspace.core.Constants.BITSTREAM);
dspacePolicy.setStartDate(policy.getStartDate());
dspacePolicy.setEndDate(policy.getEndDate());
dspacePolicy.setRpDescription(policy.getRpDescription());
dspacePolicy.setRpName(policy.getRpName());
dspacePolicy.update();
bitstreamsPolicies.add(dspacePolicy);
}
bundle.replaceAllBitstreamPolicies(bitstreamsPolicies);
bundle.update();
}
}
context.complete();
} catch(SQLException e) {
processException("Could not update bitstream(id=" + bitstreamId + ") metadata, SQLException. Message: " + e, context);
} catch (AuthorizeException e) {
processException("Could not update bitstream(id=" + bitstreamId + ") metadata, AuthorizeException. Message: " + e, context);
} catch (ContextException e) {
processException("Could not update bitstream(id=" + bitstreamId + ") metadata, ContextException. Message: " + e.getMessage(), context);
}
log.info("Bitstream metadata(id=" + bitstreamId + ") were successfully updated.");
return Response.ok().build();
}
/**
* Update bitstream data. It change bitstream data by editing database rows.
* It can throw WebApplicationException caused by: SQLException if there was
* problem with database editing or reading, IOException if there was
* problem with reading from inputstream, Exception if there was another
* problem.
*
* @param bitstreamId
* Id of bistream, which will be updated.
* @param is
* Inputstream filled with new data.
* @param headers
* If you want to access to item under logged user into context.
* In headers must be set header "rest-dspace-token" with passed
* token from login method.
* @return Return response if bitstream was updated. Response codes:
* OK(200), NOT_FOUND(404) if id of bitstream was bad. And
* UNAUTHORIZED(401) if user is not allowed to update bitstream.
* @throws WebApplicationException
* This exception can be thrown in this cases: Problem with
* reading or writing to database. Or problem with reading from
* inputstream.
*/
// TODO Change to better logic, without editing database.
@PUT
@Path("/{bitstream_id}/data")
public Response updateBitstreamData(
@PathParam("bitstream_id") Integer bitstreamId, InputStream is,
@QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent,
@QueryParam("xforwarderfor") String xforwarderfor, @Context HttpHeaders headers,
@Context HttpServletRequest request) throws WebApplicationException {
log.info("Updating bitstream(id=" + bitstreamId + ") data.");
org.dspace.core.Context context = null;
try{
context = createContext(getUser(headers));
org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, org.dspace.core.Constants.WRITE);
writeStats(Constants.BITSTREAM, dspaceBitstream, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwarderfor, headers, request);
log.trace("Creating new bitstream.");
int newBitstreamId = BitstreamStorageManager.store(context, is);
log.trace("Looking for table rows of bitstreams.");
TableRow originalBitstreamRow = DatabaseManager.find(context, "Bitstream", bitstreamId);
TableRow bitstream = DatabaseManager.find(context, "Bitstream", newBitstreamId);
log.trace("Changing new internal id with old internal id.");
String internal_id = originalBitstreamRow.getStringColumn("internal_id");
Long size_bytes = originalBitstreamRow.getLongColumn("size_bytes");
originalBitstreamRow.setColumn("internal_id", bitstream.getStringColumn("internal_id"));
originalBitstreamRow.setColumn("size_bytes", bitstream.getLongColumn("size_bytes"));
bitstream.setColumn("internal_id", internal_id);
bitstream.setColumn("size_bytes", size_bytes);
DatabaseManager.update(context, originalBitstreamRow);
BitstreamStorageManager.delete(context, newBitstreamId);
context.complete();
} catch(SQLException e) {
processException("Could not update bitstream(id=" + bitstreamId + ") data, SQLException. Message: " + e, context);
} catch (IOException e) {
processException("Could not update bitstream(id=" + bitstreamId + ") data, IOException. Message: " + e, context);
} catch (ContextException e) {
processException("Could not update bitstream(id=" + bitstreamId + ") data, ContextException. Message: " + e.getMessage(), context);
}
log.info("Bitstream(id=" + bitstreamId + ") data was successfully updated.");
return Response.ok().build();
}
/**
* Delete bitstream from all bundles in dspace. It can throw
* WebApplicationException, which can be caused by three exceptions.
* SQLException if there was problem with reading from database or removing
* from database. AuthorizeException, if user has not permission to delete
* bitstream or file. IOException, if there was problem with file deleting.
*
* @param bitstreamId
* Id of bitsream, which will be deleted.
* @param headers
* If you want to access to item under logged user into context.
* In headers must be set header "rest-dspace-token" with passed
* token from login method.
* @return Return response codes: OK(200), NOT_FOUND(404) if bitstream of
* that id does not exist and UNAUTHORIZED(401) if user is not
* allowed to delete bitstream.
* @throws WebApplicationException
* It can be throw when was problem with reading or editting
* database. Or problem with file deleting. Or problem with
* authorization to bitstream and bundles. Or problem with
* creating context.
*/
@DELETE
@Path("/{bitstream_id}")
public Response deleteBitstream(
@PathParam("bitstream_id") Integer bitstreamId,
@QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent,
@QueryParam("xforwarderfor") String xforwarderfor, @Context HttpHeaders headers,
@Context HttpServletRequest request) throws WebApplicationException {
log.info("Deleting bitstream(id=" + bitstreamId + ").");
org.dspace.core.Context context = null;
try {
context = createContext(getUser(headers));
org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, org.dspace.core.Constants.DELETE);
writeStats(Constants.BITSTREAM, dspaceBitstream, UsageEvent.Action.DELETE, user_ip, user_agent, xforwarderfor, headers, request);
log.trace("Deleting bitstream from all bundles.");
for (org.dspace.content.Bundle bundle : dspaceBitstream.getBundles()) {
org.dspace.content.Bundle.find(context, bundle.getID()).removeBitstream(dspaceBitstream);
}
context.complete();
} catch(SQLException e) {
processException("Could not delete bitstream(id=" + bitstreamId + "), SQLException. Message: " + e, context);
} catch (AuthorizeException e) {
processException("Could not delete bitstream(id=" + bitstreamId + "), AuthorizeException. Message: " + e, context);
} catch (IOException e) {
processException("Could not delete bitstream(id=" + bitstreamId + "), IOException. Message: " + e, context);
} catch (ContextException e) {
processException("Could not delete bitstream(id=" + bitstreamId + "), ContextException. Message:" + e.getMessage(), context);
}
log.info("Bitstream(id=" + bitstreamId + ") was successfully deleted.");
return Response.ok().build();
}
/**
* Delete policy.
*
* @param bitstreamId
* Id of bitstream in dspace, which policy will be deleted.
* @param policyId
* Id of policy which will be deleted.
* @param headers
* If you want to access to item under logged user into context.
* In headers must be set header "rest-dspace-token" with passed
* token from login method.
* @return It returns Ok, if was all ok. Otherwise status code 500.
*/
@DELETE
@Path("/{bitstream_id}/policy/{policy_id}")
public javax.ws.rs.core.Response deleteBitstreamPolicy(@PathParam("bitstream_id") Integer bitstreamId, @PathParam("policy_id") Integer policyId, @Context HttpHeaders headers) {
log.info("Deleting bitstream(id=" + bitstreamId + ") READ policy(id=" + policyId + ").");
org.dspace.core.Context context = null;
try {
context = createContext(getUser(headers));
org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, org.dspace.core.Constants.READ);
Bundle[] bundles = dspaceBitstream.getBundles();
for (Bundle bundle : bundles) {
List<org.dspace.authorize.ResourcePolicy> bitstreamsPolicies = bundle.getBitstreamPolicies();
for (org.dspace.authorize.ResourcePolicy policy : bitstreamsPolicies) {
if (policy.getID() == policyId.intValue()) {
bitstreamsPolicies.remove(policy);
break;
}
}
bundle.replaceAllBitstreamPolicies(bitstreamsPolicies);
bundle.update();
}
context.complete();
log.trace("Policy for bitsream(id=" + bitstreamId + ") was successfully added.");
} catch (SQLException e) {
processException("Someting went wrong while deleting READ policy(id=" + policyId + ") to bitstream(id=" + bitstreamId + "), SQLException! Message: " + e, context);
} catch (ContextException e) {
processException("Someting went wrong while deleting READ policy(id=" + policyId + ") to bitstream(id=" + bitstreamId + "), ContextException. Message: " + e.getMessage(), context);
} catch (AuthorizeException e) {
processException("Someting went wrong while deleting READ policy(id=" + policyId + ") to bitstream(id=" + bitstreamId + "), AuthorizeException! Message: " + e, context);
}
return Response.status(Status.OK).build();
}
/**
* Return type of file in MIME, by file extension.
*
* @param name
* Name of file.
* @return String filled with type of file in MIME style.
*/
static String getMimeType(String name) {
return URLConnection.guessContentTypeFromName(name);
}
/**
* Find bitstream from DSpace database. It is encapsulation of method
* org.dspace.content.Bitstream.find with checking if item exist and if user
* logged into context has permission to do passed action.
*
* @param context
* Context of actual logged user.
* @param id
* Id of bitstream in DSpace.
* @param action
* Constant from org.dspace.core.Constants.
* @return It returns DSpace bitstream.
* @throws WebApplicationException
* Is thrown when item with passed id is not exists and if user
* has no permission to do passed action.
*/
private org.dspace.content.Bitstream findBitstream(org.dspace.core.Context context, int id, int action) throws WebApplicationException {
org.dspace.content.Bitstream bitstream = null;
try {
bitstream = org.dspace.content.Bitstream.find(context, id);
if ((bitstream == null) || (bitstream.getParentObject() == null)) {
context.abort();
log.warn("Bitstream(id=" + id + ") was not found!");
throw new WebApplicationException(Response.Status.NOT_FOUND);
} else if (!AuthorizeManager.authorizeActionBoolean(context, bitstream, action)) {
context.abort();
if (context.getCurrentUser() != null ) {
log.error("User(" + context.getCurrentUser().getEmail() + ") has not permission to " + getActionString(action) + " bitstream!");
} else {
log.error("User(anonymous) has not permission to " + getActionString(action) + " bitsteam!");
}
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
} catch (SQLException e) {
processException("Something get wrong while finding bitstream. SQLException, Message:" + e, context);
}
return bitstream;
}
} }

View File

@@ -7,149 +7,729 @@
*/ */
package org.dspace.rest; package org.dspace.rest;
import java.io.IOException;
import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeManager;
import org.dspace.content.DSpaceObject;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Constants;
import org.dspace.rest.common.Collection;
import org.dspace.usage.UsageEvent;
import org.dspace.utils.DSpace;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletContext;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
/* import javax.servlet.http.HttpServletRequest;
The "Path" annotation indicates the URI this class will be available at relative to your base URL. For import javax.ws.rs.Consumes;
example, if this web-app is launched at localhost using a context of "hello" and no URL pattern is defined import javax.ws.rs.DELETE;
in the web.xml servlet mapping section, then the web service will be available at: import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
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.Response;
http://localhost:8080/<webapp>/collections import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.AuthorizeManager;
import org.dspace.browse.BrowseException;
import org.dspace.core.Constants;
import org.dspace.rest.common.Collection;
import org.dspace.rest.common.Item;
import org.dspace.rest.common.MetadataEntry;
import org.dspace.rest.exceptions.ContextException;
import org.dspace.usage.UsageEvent;
/**
* This class provides all CRUD operation over collections.
*
* @author Rostislav Novak (Computing and Information Centre, CTU in Prague)
*/ */
@Path("/collections") @Path("/collections")
public class CollectionsResource { public class CollectionsResource extends Resource
{
private static Logger log = Logger.getLogger(CollectionsResource.class); private static Logger log = Logger.getLogger(CollectionsResource.class);
@javax.ws.rs.core.Context ServletContext servletContext; /**
* Return instance of collection with passed id. You can add more properties
private static final boolean writeStatistics; * through expand parameter.
*
static{ * @param collectionId
writeStatistics=ConfigurationManager.getBooleanProperty("rest","stats",false); * Id of collection in DSpace.
} * @param expand
* String in which is what you want to add to returned instance
@GET * of collection. Options are: "all", "parentCommunityList",
@Path("/") * "parentCommunity", "items", "license" and "logo". If you want
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) * to use multiple options, it must be separated by commas.
public org.dspace.rest.common.Collection[] list(@QueryParam("expand") String expand, @QueryParam("limit") @DefaultValue("100") Integer limit, @QueryParam("offset") @DefaultValue("0") Integer offset) { * @param limit
org.dspace.core.Context context = null; * Limit value for items in list in collection. Default value is
try { * 100.
context = new org.dspace.core.Context(); * @param offset
* Offset of start index in list of items of collection. Default
org.dspace.content.Collection[] collections; * value is 0.
* @param headers
//Only support paging if limit/offset are 0 or positive values. * If you want to access to collection under logged user into
if(limit != null && limit >= 0 && offset != null && offset >= 0) { * context. In headers must be set header "rest-dspace-token"
collections = org.dspace.content.Collection.findAll(context, limit, offset); * with passed token from login method.
} else { * @return Return instance of collection. It can also return status code
collections = org.dspace.content.Collection.findAll(context); * NOT_FOUND(404) if id of collection is incorrect or status code
} * UNATHORIZED(401) if user has no permission to read collection.
* @throws WebApplicationException
ArrayList<org.dspace.rest.common.Collection> collectionArrayList = new ArrayList<org.dspace.rest.common.Collection>(); * It is thrown when was problem with database reading
for(org.dspace.content.Collection collection : collections) { * (SQLException) or problem with creating
if(AuthorizeManager.authorizeActionBoolean(context, collection, org.dspace.core.Constants.READ)) { * context(ContextException). It is thrown by NOT_FOUND and
org.dspace.rest.common.Collection restCollection = new org.dspace.rest.common.Collection(collection, null, context, limit, offset); * UNATHORIZED status codes, too.
collectionArrayList.add(restCollection); */
} // Not showing restricted-access collections
}
return collectionArrayList.toArray(new org.dspace.rest.common.Collection[0]);
} catch (SQLException e) {
log.error(e.getMessage());
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
} finally {
if(context != null) {
try {
context.complete();
} catch (SQLException e) {
log.error(e.getMessage() + " occurred while trying to close");
}
}
}
}
@GET @GET
@Path("/{collection_id}") @Path("/{collection_id}")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public org.dspace.rest.common.Collection getCollection(@PathParam("collection_id") Integer collection_id, @QueryParam("expand") String expand, public org.dspace.rest.common.Collection getCollection(@PathParam("collection_id") Integer collectionId,
@QueryParam("limit") @DefaultValue("100") Integer limit, @QueryParam("offset") @DefaultValue("0") Integer offset, @QueryParam("expand") String expand, @QueryParam("limit") @DefaultValue("100") Integer limit,
@QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, @QueryParam("xforwarderfor") String xforwarderfor, @QueryParam("offset") @DefaultValue("0") Integer offset, @QueryParam("userIP") String user_ip,
@Context HttpHeaders headers, @Context HttpServletRequest request) { @QueryParam("userAgent") String user_agent, @QueryParam("xforwarderfor") String xforwarderfor,
org.dspace.core.Context context = null; @Context HttpHeaders headers, @Context HttpServletRequest request) throws WebApplicationException
try { {
context = new org.dspace.core.Context();
org.dspace.content.Collection collection = org.dspace.content.Collection.find(context, collection_id); log.info("Reading collection(id=" + collectionId + ").");
if(AuthorizeManager.authorizeActionBoolean(context, collection, org.dspace.core.Constants.READ)) { org.dspace.core.Context context = null;
if(writeStatistics){ Collection collection = null;
writeStats(context, collection_id, user_ip, user_agent, xforwarderfor, headers, request);
} try
return new org.dspace.rest.common.Collection(collection, expand, context, limit, offset); {
} else { context = createContext(getUser(headers));
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId, org.dspace.core.Constants.READ);
writeStats(Constants.COLLECTION, dspaceCollection, UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor,
headers, request);
collection = new Collection(dspaceCollection, expand, context, limit, offset);
context.complete();
}
catch (SQLException e)
{
processException("Could not read collection(id=" + collectionId + "), SQLException. Message: " + e, context);
}
catch (ContextException e)
{
processException("Could not read collection(id=" + collectionId + "), ContextException. Message: " + e.getMessage(),
context);
}
log.trace("Collection(id=" + collectionId + ") has been successfully read.");
return collection;
}
/**
* Return array of all collections in DSpace. You can add more properties
* through expand parameter.
*
* @param expand
* String in which is what you want to add to returned instance
* of collection. Options are: "all", "parentCommunityList",
* "parentCommunity", "items", "license" and "logo". If you want
* to use multiple options, it must be separated by commas.
* @param limit
* Limit value for items in list in collection. Default value is
* 100.
* @param offset
* Offset of start index in list of items of collection. Default
* value is 0.
* @param headers
* If you want to access to collections under logged user into
* context. In headers must be set header "rest-dspace-token"
* with passed token from login method.
* @return Return array of collection, on which has logged user permission
* to view.
* @throws WebApplicationException
* It is thrown when was problem with database reading
* (SQLException) or problem with creating
* context(ContextException).
*/
@GET
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public org.dspace.rest.common.Collection[] getCollections(@QueryParam("expand") String expand,
@QueryParam("limit") @DefaultValue("100") Integer limit, @QueryParam("offset") @DefaultValue("0") Integer offset,
@QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent,
@QueryParam("xforwarderfor") String xforwarderfor, @Context HttpHeaders headers, @Context HttpServletRequest request)
throws WebApplicationException
{
log.info("Reading all collections.(offset=" + offset + ",limit=" + limit + ")");
org.dspace.core.Context context = null;
List<Collection> collections = new ArrayList<Collection>();
try
{
context = createContext(getUser(headers));
org.dspace.content.Collection[] dspaceCollections;
if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0)))
{
log.warn("Pagging was badly set.");
limit = 100;
offset = 0;
} }
} catch (SQLException e) {
log.error(e.getMessage()); // dspaceCollections =
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); // org.dspace.content.Collection.findAll(context, limit, offset); //
} finally { // Message:java.sql.SQLSyntaxErrorException: ORA-00933: SQL command
if(context != null) { // not properly ended
try { dspaceCollections = org.dspace.content.Collection.findAll(context);
context.complete();
} catch (SQLException e) { for (int i = offset; i < limit + offset; i++)
log.error(e.getMessage() + " occurred while trying to close"); {
if (AuthorizeManager.authorizeActionBoolean(context, dspaceCollections[i], org.dspace.core.Constants.READ))
{
Collection collection = new org.dspace.rest.common.Collection(dspaceCollections[i], null, context, limit,
offset);
collections.add(collection);
writeStats(Constants.COLLECTION, dspaceCollections[i], UsageEvent.Action.VIEW, user_ip, user_agent,
xforwarderfor, headers, request);
} }
} }
context.complete();
} }
catch (SQLException e)
{
processException("Something went wrong while reading collections from database. Message: " + e, context);
}
catch (ContextException e)
{
processException("Something went wrong while reading collections, ContextError. Message: " + e.getMessage(), context);
}
log.trace("All collections were successfully read.");
return collections.toArray(new org.dspace.rest.common.Collection[0]);
}
/**
* Return array of items in collection. You can add more properties to items
* with expand parameter.
*
* @param collectionId
* Id of collection in DSpace.
* @param expand
* String which define, what additional properties will be in
* returned item. Options are separeted by commas and are: "all",
* "metadata", "parentCollection", "parentCollectionList",
* "parentCommunityList" and "bitstreams".
* @param limit
* Limit value for items in array. Default value is 100.
* @param offset
* Offset of start index in array of items of collection. Default
* value is 0.
* @param headers
* If you want to access to collection under logged user into
* context. In headers must be set header "rest-dspace-token"
* with passed token from login method.
* @return Return array of items, on which has logged user permission to
* read. It can also return status code NOT_FOUND(404) if id of
* collection is incorrect or status code UNATHORIZED(401) if user
* has no permission to read collection.
* @throws WebApplicationException
* It is thrown when was problem with database reading
* (SQLException) or problem with creating
* context(ContextException). It is thrown by NOT_FOUND and
* UNATHORIZED status codes, too.
*/
@GET
@Path("/{collection_id}/items")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public org.dspace.rest.common.Item[] getCollectionItems(@PathParam("collection_id") Integer collectionId,
@QueryParam("expand") String expand, @QueryParam("limit") @DefaultValue("100") Integer limit,
@QueryParam("offset") @DefaultValue("0") Integer offset, @QueryParam("userIP") String user_ip,
@QueryParam("userAgent") String user_agent, @QueryParam("xforwarderfor") String xforwarderfor,
@Context HttpHeaders headers, @Context HttpServletRequest request) throws WebApplicationException
{
log.info("Reading collection(id=" + collectionId + ") items.");
org.dspace.core.Context context = null;
try
{
context = createContext(getUser(headers));
org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId, org.dspace.core.Constants.READ);
writeStats(Constants.COLLECTION, dspaceCollection, UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor,
headers, request);
List<Item> items = new ArrayList<Item>();
org.dspace.content.ItemIterator dspaceItems = dspaceCollection.getItems();
for (int i = 0; (dspaceItems.hasNext()) && (i < (limit + offset)); i++)
{
if (i >= offset)
{
org.dspace.content.Item dspaceItem = dspaceItems.next();
if (AuthorizeManager.authorizeActionBoolean(context, dspaceItem, org.dspace.core.Constants.READ))
{
items.add(new Item(dspaceItem, expand, context));
writeStats(Constants.ITEM, dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor,
headers, request);
}
}
}
context.complete();
return items.toArray(new Item[0]);
}
catch (SQLException e)
{
processException("Could not read collection items, SQLException. Message: " + e, context);
}
catch (ContextException e)
{
processException("Could not read collection items, ContextException. Message: " + e.getMessage(), context);
}
log.trace("All items in collection(id=" + collectionId + ") were successfully read.");
return null;
}
/**
* Create item in collection. Item can be without filled metadata.
*
* @param collectionId
* Id of collection in which will be item created.
* @param item
* Item filled only with metadata, other variables are ignored.
* @param headers
* If you want to access to collection under logged user into
* context. In headers must be set header "rest-dspace-token"
* with passed token from login method.
* @return Return status code with item. Return status
* (OK)200 if item was created. NOT_FOUND(404) if id of collection
* does not exists. UNAUTHORIZED(401) if user have not permission to
* write items in collection.
* @throws WebApplicationException
* It is thrown when was problem with database reading or
* writing (SQLException) or problem with creating
* context(ContextException) or problem with authorization to
* collection or IOException or problem with index item into
* browse index. It is thrown by NOT_FOUND and UNATHORIZED
* status codes, too.
*
*/
@POST
@Path("/{collection_id}/items")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Item addCollectionItem(@PathParam("collection_id") Integer collectionId, Item item,
@QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent,
@QueryParam("xforwarderfor") String xforwarderfor, @Context HttpHeaders headers, @Context HttpServletRequest request)
throws WebApplicationException
{
log.info("Create item in collection(id=" + collectionId + ").");
org.dspace.core.Context context = null;
Item returnItem = null;
try
{
context = createContext(getUser(headers));
org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId,
org.dspace.core.Constants.WRITE);
writeStats(Constants.COLLECTION, dspaceCollection, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwarderfor,
headers, request);
log.trace("Creating item in collection(id=" + collectionId + ").");
org.dspace.content.WorkspaceItem workspaceItem = org.dspace.content.WorkspaceItem.create(context, dspaceCollection,
false);
org.dspace.content.Item dspaceItem = workspaceItem.getItem();
log.trace("Adding metadata to item(id=" + dspaceItem.getID() + ").");
if (item.getMetadata() != null)
{
for (MetadataEntry entry : item.getMetadata())
{
String data[] = mySplit(entry.getKey());
dspaceItem.addMetadata(data[0], data[1], data[2], entry.getLanguage(), entry.getValue());
}
}
workspaceItem.update();
// Index item to browse.
org.dspace.browse.IndexBrowse browse = new org.dspace.browse.IndexBrowse();
browse.indexItem(dspaceItem);
log.trace("Installing item to collection(id=" + collectionId + ").");
dspaceItem = org.dspace.content.InstallItem.installItem(context, workspaceItem);
returnItem = new Item(dspaceItem, "", context);
context.complete();
}
catch (SQLException e)
{
processException("Could not add item into collection(id=" + collectionId + "), SQLException. Message: " + e, context);
}
catch (AuthorizeException e)
{
processException("Could not add item into collection(id=" + collectionId + "), AuthorizeException. Message: " + e,
context);
}
catch (IOException e)
{
processException("Could not add item into collection(id=" + collectionId + "), IOException. Message: " + e, context);
}
catch (BrowseException e)
{
processException("Could not add item into browse index, BrowseException. Message: " + e, context);
}
catch (ContextException e)
{
processException(
"Could not add item into collection(id=" + collectionId + "), ContextException. Message: " + e.getMessage(),
context);
}
log.info("Item successfully created in collection(id=" + collectionId + "). Item handle=" + returnItem.getHandle());
return returnItem;
}
/**
* Update collection. It replace all properties.
*
* @param collectionId
* Id of collection in DSpace.
* @param collection
* Collection which will replace properties of actual collection.
* @param headers
* If you want to access to collection under logged user into
* context. In headers must be set header "rest-dspace-token"
* with passed token from login method.
* @return Return response 200 if was everything all right. Otherwise 400
* when id of community was incorrect or 401 if was problem with
* permission to write into collection.
* @throws WebApplicationException
* It is thrown when was problem with database reading or
* writing. Or problem with authorization to collection. Or
* problem with creating context.
*/
@PUT
@Path("/{collection_id}")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response updateCollection(@PathParam("collection_id") Integer collectionId,
org.dspace.rest.common.Collection collection, @QueryParam("userIP") String user_ip,
@QueryParam("userAgent") String user_agent, @QueryParam("xforwarderfor") String xforwarderfor,
@Context HttpHeaders headers, @Context HttpServletRequest request) throws WebApplicationException
{
log.info("Updating collection(id=" + collectionId + ").");
org.dspace.core.Context context = null;
try
{
context = createContext(getUser(headers));
org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId,
org.dspace.core.Constants.WRITE);
writeStats(Constants.COLLECTION, dspaceCollection, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwarderfor,
headers, request);
dspaceCollection.setMetadata("name", collection.getName());
dspaceCollection.setLicense(collection.getLicense());
// dspaceCollection.setLogo(collection.getLogo()); // TODO Add this
// option.
dspaceCollection.setMetadata(org.dspace.content.Collection.COPYRIGHT_TEXT, collection.getCopyrightText());
dspaceCollection.setMetadata(org.dspace.content.Collection.INTRODUCTORY_TEXT, collection.getIntroductoryText());
dspaceCollection.setMetadata(org.dspace.content.Collection.SHORT_DESCRIPTION, collection.getShortDescription());
dspaceCollection.setMetadata(org.dspace.content.Collection.SIDEBAR_TEXT, collection.getSidebarText());
dspaceCollection.update();
context.complete();
}
catch (ContextException e)
{
processException("Could not update collection(id=" + collectionId + "), ContextEception. Message: " + e.getMessage(),
context);
}
catch (SQLException e)
{
processException("Could not update collection(id=" + collectionId + "), SQLException. Message: " + e, context);
}
catch (AuthorizeException e)
{
processException("Could not update collection(id=" + collectionId + "), AuthorizeException. Message: " + e, context);
}
log.info("Collection(id=" + collectionId + ") successfully updated.");
return Response.ok().build();
}
/**
* Delete collection.
*
* @param collectionId
* Id of collection which will be deleted.
* @param headers
* If you want to access to collection under logged user into
* context. In headers must be set header "rest-dspace-token"
* with passed token from login method.
* @return Return response code OK(200) if was everything all right.
* Otherwise return NOT_FOUND(404) if was id of community or
* collection incorrect. Or (UNAUTHORIZED)401 if was problem with
* permission to community or collection.
* @throws WebApplicationException
* It is throw when was problem with creating context or problem
* with database reading or writing. Or problem with deleting
* collection caused by IOException or authorization.
*/
@DELETE
@Path("/{collection_id}")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response deleteCollection(@PathParam("collection_id") Integer collectionId, @QueryParam("userIP") String user_ip,
@QueryParam("userAgent") String user_agent, @QueryParam("xforwarderfor") String xforwarderfor,
@Context HttpHeaders headers, @Context HttpServletRequest request) throws WebApplicationException
{
log.info("Delete collection(id=" + collectionId + ").");
org.dspace.core.Context context = null;
try
{
context = createContext(getUser(headers));
org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId,
org.dspace.core.Constants.DELETE);
writeStats(Constants.COLLECTION, dspaceCollection, UsageEvent.Action.REMOVE, user_ip, user_agent, xforwarderfor,
headers, request);
org.dspace.content.Community community = (org.dspace.content.Community) dspaceCollection.getParentObject();
community.removeCollection(dspaceCollection);
context.complete();
}
catch (ContextException e)
{
processException(
"Could not delete collection(id=" + collectionId + "), ContextExcpetion. Message: " + e.getMessage(), context);
}
catch (SQLException e)
{
processException("Could not delete collection(id=" + collectionId + "), SQLException. Message: " + e, context);
}
catch (AuthorizeException e)
{
processException("Could not delete collection(id=" + collectionId + "), AuthorizeException. Message: " + e, context);
}
catch (IOException e)
{
processException("Could not delete collection(id=" + collectionId + "), IOException. Message: " + e, context);
}
log.info("Collection(id=" + collectionId + ") was successfully deleted.");
return Response.ok().build();
}
/**
* Delete item in collection.
*
* @param collectionId
* Id of collection which will be deleted.
*
* @param itemId
* Id of item in colletion.
* @return It returns status code: OK(200). NOT_FOUND(404) if item or
* collection was not found, UNAUTHORIZED(401) if user is not
* allowed to delete item or permission to write into collection.
* @throws WebApplicationException
* It can be thrown by: SQLException, when was problem with
* database reading or writting. AuthorizeException, when was
* problem with authorization to item or collection.
* IOException, when was problem with removing item.
* ContextException, when was problem with creating context of
* DSpace.
*/
@DELETE
@Path("/{collection_id}/items/{item_id}")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response deleteCollectionItem(@PathParam("collection_id") Integer collectionId, @PathParam("item_id") Integer itemId,
@QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent,
@QueryParam("xforwarderfor") String xforwarderfor, @Context HttpHeaders headers, @Context HttpServletRequest request)
throws WebApplicationException
{
log.info("Delete item(id=" + itemId + ") in collection(id=" + collectionId + ").");
org.dspace.core.Context context = null;
try
{
context = createContext(getUser(headers));
org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId,
org.dspace.core.Constants.WRITE);
org.dspace.content.Item item = null;
org.dspace.content.ItemIterator dspaceItems = dspaceCollection.getItems();
while (dspaceItems.hasNext())
{
org.dspace.content.Item dspaceItem = dspaceItems.next();
if (dspaceItem.getID() == itemId)
{
item = dspaceItem;
}
}
if (item == null)
{
context.abort();
log.warn("Item(id=" + itemId + ") was not found!");
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
else if (!AuthorizeManager.authorizeActionBoolean(context, item, org.dspace.core.Constants.REMOVE))
{
context.abort();
if (context.getCurrentUser() != null)
{
log.error("User(" + context.getCurrentUser().getEmail() + ") has not permission to delete item!");
}
else
{
log.error("User(anonymous) has not permission to delete item!");
}
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
writeStats(Constants.COLLECTION, dspaceCollection, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwarderfor,
headers, request);
writeStats(Constants.ITEM, item, UsageEvent.Action.REMOVE, user_ip, user_agent, xforwarderfor, headers, request);
dspaceCollection.removeItem(item);
context.complete();
}
catch (ContextException e)
{
processException("Could not delete item(id=" + itemId + ") in collection(id=" + collectionId
+ "), ContextException. Message: " + e.getMessage(), context);
}
catch (SQLException e)
{
processException("Could not delete item(id=" + itemId + ") in collection(id=" + collectionId
+ "), SQLException. Message: " + e, context);
}
catch (AuthorizeException e)
{
processException("Could not delete item(id=" + itemId + ") in collection(id=" + collectionId
+ "), AuthorizeException. Message: " + e, context);
}
catch (IOException e)
{
processException("Could not delete item(id=" + itemId + ") in collection(id=" + collectionId
+ "), IOException. Message: " + e, context);
}
log.info("Item(id=" + itemId + ") in collection(id=" + collectionId + ") was successfully deleted.");
return Response.ok().build();
} }
private void writeStats(org.dspace.core.Context context, Integer collection_id, String user_ip, String user_agent, /**
String xforwarderfor, HttpHeaders headers, * Search for first collection with passed name.
HttpServletRequest request) { * @param name Name of collection.
* @param headers
try{ * If you want to access to collection under logged user into
DSpaceObject collection = DSpaceObject.find(context, Constants.COLLECTION, collection_id); * context. In headers must be set header "rest-dspace-token"
* with passed token from login method.
if(user_ip==null || user_ip.length()==0){ * @return It returns null if collection was not found. Otherwise returns first founded collection.
new DSpace().getEventService().fireEvent( * @throws WebApplicationException
new UsageEvent( */
UsageEvent.Action.VIEW, @POST
request, @Path("/find-collection")
context, @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
collection)); @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
} else{ public Collection findCollectionByName(String name, @Context HttpHeaders headers) throws WebApplicationException {
new DSpace().getEventService().fireEvent( log.info("Searching for first collection with name=" + name + ".");
new UsageEvent( org.dspace.core.Context context = null;
UsageEvent.Action.VIEW, Collection collection = null;
user_ip,
user_agent, try {
xforwarderfor, context = createContext(getUser(headers));
context, org.dspace.content.Collection[] dspaceCollections;
collection));
} dspaceCollections = org.dspace.content.Collection.findAll(context);
log.debug("fired event");
for (org.dspace.content.Collection dspaceCollection : dspaceCollections) {
} catch(SQLException ex){ if (AuthorizeManager.authorizeActionBoolean(context, dspaceCollection, org.dspace.core.Constants.READ)) {
log.error("SQL exception can't write usageEvent \n" + ex); if (dspaceCollection.getName().equals(name)) {
collection = new Collection(dspaceCollection, "", context, 100, 0);
break;
}
}
}
context.complete();
} catch (SQLException e) {
processException("Something went wrong while searching for collection(name=" + name + ") from database. Message: " + e, context);
} catch (ContextException e) {
processException("Something went wrong while searching for collection(name=" + name + "), ContextError. Message: " + e.getMessage(), context);
} }
if (collection == null) {
log.info("Collection was not found.");
} else {
log.info("Collection was found with id(" + collection.getId() + ").");
}
return collection;
} }
/**
* Find collection from DSpace database. It is encapsulation of method
* org.dspace.content.Collection.find with checking if item exist and if
* user logged into context has permission to do passed action.
*
* @param context
* Context of actual logged user.
* @param id
* Id of collection in DSpace.
* @param action
* Constant from org.dspace.core.Constants.
* @return It returns DSpace collection.
* @throws WebApplicationException
* Is thrown when item with passed id is not exists and if user
* has no permission to do passed action.
*/
private org.dspace.content.Collection findCollection(org.dspace.core.Context context, int id, int action)
throws WebApplicationException
{
org.dspace.content.Collection collection = null;
try
{
collection = org.dspace.content.Collection.find(context, id);
if (collection == null)
{
context.abort();
log.warn("Collection(id=" + id + ") was not found!");
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
else if (!AuthorizeManager.authorizeActionBoolean(context, collection, action))
{
context.abort();
if (context.getCurrentUser() != null)
{
log.error("User(" + context.getCurrentUser().getEmail() + ") has not permission to "
+ getActionString(action) + " collection!");
}
else
{
log.error("User(anonymous) has not permission to " + getActionString(action) + " collection!");
}
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
}
catch (SQLException e)
{
processException("Something get wrong while finding collection(id=" + id + "). SQLException, Message: " + e, context);
}
return collection;
}
} }

View File

@@ -32,14 +32,18 @@ import java.sql.SQLException;
@Path("/handle") @Path("/handle")
public class HandleResource { public class HandleResource {
private static Logger log = Logger.getLogger(HandleResource.class); private static Logger log = Logger.getLogger(HandleResource.class);
private static org.dspace.core.Context context;
@GET @GET
@Path("/{prefix}/{suffix}") @Path("/{prefix}/{suffix}")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public org.dspace.rest.common.DSpaceObject getObject(@PathParam("prefix") String prefix, @PathParam("suffix") String suffix, @QueryParam("expand") String expand) { public org.dspace.rest.common.DSpaceObject getObject(@PathParam("prefix") String prefix, @PathParam("suffix") String suffix, @QueryParam("expand") String expand) {
org.dspace.core.Context context = null;
try { try {
context = new org.dspace.core.Context(); if(context == null || !context.isValid() ) {
context = new Context();
//Failed SQL is ignored as a failed SQL statement, prevent: current transaction is aborted, commands ignored until end of transaction block
context.getDBConnection().setAutoCommit(true);
}
org.dspace.content.DSpaceObject dso = HandleManager.resolveToObject(context, prefix + "/" + suffix); org.dspace.content.DSpaceObject dso = HandleManager.resolveToObject(context, prefix + "/" + suffix);
if(dso == null) { if(dso == null) {
@@ -64,14 +68,6 @@ public class HandleResource {
} catch (SQLException e) { } catch (SQLException e) {
log.error(e.getMessage()); log.error(e.getMessage());
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
} finally {
if(context != null) {
try {
context.complete();
} catch (SQLException e) {
log.error(e.getMessage() + " occurred while trying to close");
}
}
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,250 @@
package org.dspace.rest;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import org.apache.log4j.Logger;
import org.dspace.core.ConfigurationManager;
import org.dspace.eperson.EPerson;
import org.dspace.rest.exceptions.ContextException;
import org.dspace.usage.UsageEvent;
import org.dspace.utils.DSpace;
/**
* Superclass of all resource class in REST api. It has methods for creating
* context, write statistics, process exception, splitting key of metadata,
* string representation of action and method for getting user from header.
*
* @author Rostislav Novak (Computing and Information Centre, CTU in Prague)
*
*/
public class Resource
{
private static Logger log = Logger.getLogger(Resource.class);
private static final boolean writeStatistics;
static
{
writeStatistics = ConfigurationManager.getBooleanProperty("rest", "stats", false);
}
/**
* Create context to work with DSpace database. It can create context
* without logged user (parameter user is null) or with. It can throws
* WebApplicationException caused by: SQLException, if there was problem
* with reading from database. AuthorizeException, if there was problem with
* authorization to read form database. And Exception, if there was some
* problem with creating context.
*
* @param user
* User which will be logged in context.
* @return New created context with logged user if user was not null.
* Otherwise, without logged user.
* @throws ContextException
* Throw if was problem to create context. It can be caused by
* SQLException, error in creating context or find user to log
* in. Or can be caused by AuthorizeException if was problem to
* authorize to find user.
*/
protected org.dspace.core.Context createContext(EPerson person) throws ContextException
{
org.dspace.core.Context context = null;
try
{
context = new org.dspace.core.Context();
context.getDBConnection().setAutoCommit(false); // Disable autocommit.
if (person != null)
{
context.setCurrentUser(person);
}
return context;
}
catch (SQLException e)
{
context.abort();
throw new ContextException("Could not create context, SQLException. Message: " + e, e);
}
}
/**
* It write statistic about using REST api.
*
* @param user
* User, which is used by actual context.
* @param typeOfObject
* Type of which object is performed. From class
* org.dspace.core.Constants. For example: bitstream, item and so
* on.
* @param dspaceObject
* Object of DSpace which is performed.
* @param action
* What action is performed with object.
* @param user_ip
* @param user_agent
* @param xforwarderfor
* @param headers
* @param request
*/
protected void writeStats(int typeOfObject, org.dspace.content.DSpaceObject dspaceObject, UsageEvent.Action action,
String user_ip, String user_agent, String xforwarderfor, HttpHeaders headers, HttpServletRequest request)
{
if (!writeStatistics)
{
return;
}
org.dspace.core.Context context = null;
try
{
context = createContext(getUser(headers));
if ((user_ip == null) || (user_ip.length() == 0))
{
new DSpace().getEventService().fireEvent(new UsageEvent(action, request, context, dspaceObject));
}
else
{
new DSpace().getEventService().fireEvent(
new UsageEvent(action, user_ip, user_agent, xforwarderfor, context, dspaceObject));
}
log.debug("fired event");
context.complete();
}
catch (SQLException e)
{
processException("Could not write usageEvent, SQLException. Message: " + e, context);
}
catch (ContextException e)
{
processException("Could not write usageEvent, ContextException. Message: " + e.getMessage(), context);
}
}
/**
* Process exception, print message to logger error stream and abort DSpace
* context.
*
* @param message
* Message, which will be printed to error stream.
* @param context
* Context which must be aborted.
* @throws WebApplicationException
* This exception is throw for user of REST api.
*/
protected void processException(String message, org.dspace.core.Context context) throws WebApplicationException
{
if ((context != null) && (context.isValid()))
{
context.abort();
}
log.error(message);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
/**
* Split string with regex ".".
*
* @param key
* String which will be splitted.
* @return String array filed with separated string.
*/
protected String[] mySplit(String key)
{
ArrayList<String> list = new ArrayList<String>();
int prev = 0;
for (int i = 0; i < key.length(); i++)
{
if (key.charAt(i) == '.')
{
list.add(key.substring(prev, i));
prev = i + 1;
}
else if (i + 1 == key.length())
{
list.add(key.substring(prev, i + 1));
}
}
if (list.size() == 2)
{
list.add(null);
}
return list.toArray(new String[0]);
}
/**
* Return string representation of values
* org.dspace.core.Constants.{READ,WRITE,DELETE}.
*
* @param action
* Constant from org.dspace.core.Constants.*
* @return String representation. read or write or delete.
*/
protected String getActionString(int action)
{
String actionStr;
switch (action)
{
case org.dspace.core.Constants.READ:
actionStr = "read";
break;
case org.dspace.core.Constants.WRITE:
actionStr = "write";
break;
case org.dspace.core.Constants.DELETE:
actionStr = "delete";
break;
case org.dspace.core.Constants.REMOVE:
actionStr = "remove";
break;
case org.dspace.core.Constants.ADD:
actionStr = "add";
break;
default:
actionStr = "(?action?)";
break;
}
return actionStr;
}
/**
* Return EPerson based on stored token in headers under
* "rest-dspace-token".
*
* @param headers
* Only must have "rest-api-token" for successfull return of
* user.
* @return Return EPerson logged under token in headers. If token was wrong
* or header rest-dspace-token was missing, returns null.
*/
protected EPerson getUser(HttpHeaders headers)
{
List<String> list = headers.getRequestHeader(TokenHolder.TOKEN_HEADER);
String token = null;
if ((list != null) && (list.size() > 0))
{
token = list.get(0);
return TokenHolder.getEPerson(token);
}
return null;
}
}

View File

@@ -7,39 +7,165 @@
*/ */
package org.dspace.rest; package org.dspace.rest;
import java.util.List;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/* import org.dspace.rest.common.User;
Root of API, should have documentation on where to find the other resources.
/**
* Root of RESTful api. It provides login and logout. Also have method for
* printing every method which is provides by RESTful api.
*
* @author Rostislav Novak (Computing and Information Centre, CTU in Prague)
*
*/ */
@Path("/") @Path("/")
public class RestIndex { public class RestIndex {
@javax.ws.rs.core.Context public static ServletContext servletContext; @javax.ws.rs.core.Context public static ServletContext servletContext;
/* /**
The "GET" annotation indicates this method will respond to HTTP Get requests. * Return html page with information about REST api. It contains methods all
The "Produces" annotation indicates the MIME response the method will return. * methods provide by REST api.
*
* @return HTML page which has information about all methods of REST api.
*/ */
@GET @GET
@Produces(MediaType.TEXT_HTML) @Produces(MediaType.TEXT_HTML)
public String sayHtmlHello() { public String sayHtmlHello() {
return "<html><title>DSpace REST</title>" + // TODO Better graphics, add arguments to all methods. (limit, offset, item and so on)
"<body><h1>DSpace REST API</h1>" + return "<html><title>DSpace REST - index</title>" +
"<ul>" + "<body>"
"<li><a href='" + servletContext.getContextPath() + "/communities'>/communities</a></li>" + + "<h1>DSpace REST API</h1>" +
"<li><a href='" + servletContext.getContextPath() + "/communities/1'>/communities/1</a></li>" + "Server path: " + servletContext.getContextPath() +
"<li><a href='" + servletContext.getContextPath() + "/collections'>/collections</a></li>" + "<h2>Index</h2>" +
"<li><a href='" + servletContext.getContextPath() + "/collections/1'>/collections/1</a></li>" + "<ul>" +
"<li><a href='" + servletContext.getContextPath() + "/items'>/items</a></li>" + "<li>GET / - It returns this page.</li>" +
"<li><a href='" + servletContext.getContextPath() + "/items/1'>/items/1</a></li>" + "<li>GET /test - Return string \"REST api is running\". It is method for testing.</li>" +
"<li><a href='" + servletContext.getContextPath() + "/bitstreams'>/bitstreams</a></li>" + "<li>POST /login - Method for login into DSpace RESTful api. You must post User class. Example: {\"email\":\"test@dspace\",\"password\":\"pass\"}. It returns token under which will must sending requests. In header \"rest-dspace-token\"</li>" +
"<li><a href='" + servletContext.getContextPath() + "/bitstreams/1'>/bitstreams/1</a></li>" + "<li>POST /logout - Method for logout from DSpace RESTful api. You must post request with header \"rest-dspace-token\" token</li>" +
"<li><a href='" + servletContext.getContextPath() + "/bitstreams/1/retrieve'>/bitstreams/1/retrieve</a></li>" + "</ul>" +
"</ul>" + "<h2>Communities</h2>" +
"<ul>" +
"<li>GET /communities - Returns array of all communities in DSpace.</li>" +
"<li>GET /communities/top-communities - Returns array of all top communities in DSpace.</li>" +
"<li>GET /communities/{communityId} - Returns community.</li>" +
"<li>GET /communities/{communityId}/collections - Returns array of collections of community.</li>" +
"<li>GET /communities/{communityId}/communities - Returns array of subcommunities of community.</li>" +
"<li>POST /communities - Create new community at top level. You must post community.</li>" +
"<li>POST /communities/{communityId}/collections - Create new collections in community. You must post collection.</li>" +
"<li>POST /communities/{communityId}/communities - Create new subcommunity in community. You must post community.</li>" +
"<li>PUT /communities/{communityId} - Update community.</li>" +
"<li>DELETE /communities/{communityId} - Delete community.</li>" +
"<li>DELETE /communities/{communityId}/collections/{collectionId} - Delete collection in community.</li>" +
"<li>DELETE /communities/{communityId}/communities/{communityId2} - Delete subcommunity in community.</li>" +
"</ul>" +
"<h2>Collections</h2>" +
"<ul>" +
"<li>GET /collections - Return all collections of DSpace in array.</li>" +
"<li>GET /collections/{collectionId} - Return collection with id.</li>" +
"<li>GET /collections/{collectionId}/items - Return all items of collection.</li>" +
"<li>POST /collections/{collectionId}/items - Create posted item in collection.</li>" +
"<li>PUT /collections/{collectionId} </li> - Update collection. You muset post collection." +
"<li>DELETE /collections/{collectionId} - Delete collection from DSpace.</li>" +
"<li>DELETE /collections/{collectionId}/items/{itemId} - Delete item in collection. </li>" +
"</ul>" +
"<h2>Items</h2>" +
"<ul>" +
"<li>GET /items - Return list of items.</li>" +
"<li>GET /items/{item id} - Return item.</li>" +
"<li>GET /items/{item id}/metadata - Return item metadata.</li>" +
"<li>GET /items/{item id}/bitstreams - Return item bitstreams.</li>" +
"<li>POST /items/find-by-metadata-field - Find items by metadata entry.</li>" +
"<li>POST /items/{item id}/metadata - Add metadata to item.</li>" +
"<li>POST /items/{item id}/bitstreams - Add bitstream to item.</li>" +
"<li>PUT /items/{item id}/metadata - Update metadata in item.</li>" +
"<li>DELETE /items/{item id} - Delete item.</li>" +
"<li>DELETE /items/{item id}/metadata - Clear item metadata.</li>" +
"<li>DELETE /items/{item id}/bitstreams/{bitstream id} - Delete item bitstream.</li>" +
"</ul>" +
"<h2>Bitstreams</h2>" +
"<ul>" +
"<li>GET /bitstreams - Return all bitstreams in DSpace.</li>" +
"<li>GET /bitstreams/{bitstream id} - Return bitstream.</li>" +
"<li>POST /bitstreams/{bitstream id}/retrieve - Return data of bitstream.</li>" +
"<li>PUT /bitstreams/{bitstream id}/data - Update data of bitstream.</li>" +
"<li>PUT /bitstreams/{bitstream id} - Update metadata of bitstream.</li>" +
"<li>DELETE /bitstreams/{bitstream id} - Delete bitstream from DSpace.</li>" +
"</ul>" +
"</body></html> "; "</body></html> ";
} }
/**
* Method for only test if rest api is running.
*
* @return String "REST api is running."
*/
@GET
@Path("/test")
public String test()
{
return "REST api is running.";
}
/**
* Method for login user into REST api.
*
* @param user
* User which will be logged into REST api.
* @return Returns response code OK with token. Otherwise returns response
* code FORBIDDEN(403).
*/
@POST
@Path("/login")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response login(User user)
{
String token = TokenHolder.login(user);
if (token == null)
{
return Response.status(Response.Status.FORBIDDEN).build();
}
return Response.ok(token, "text/plain").build();
}
/**
* Method for logout from DSpace REST api. It removes token and user from
* TokenHolder.
*
* @param headers
* Request header which contains header with key
* "rest-dspace-token" and value of token.
* @return Return response OK, otherwise BAD_REQUEST, if was problem with
* logout or token is incorrect.
*/
@POST
@Path("/logout")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response logout(@Context HttpHeaders headers)
{
List<String> list = headers.getRequestHeader(TokenHolder.TOKEN_HEADER);
String token = null;
boolean logout = false;
if (list != null)
{
token = list.get(0);
logout = TokenHolder.logout(token);
}
if ((token == null) || (!logout))
{
return Response.status(Response.Status.BAD_REQUEST).build();
}
return Response.ok().build();
}
} }

View File

@@ -0,0 +1,146 @@
/**
* 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.rest;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.eperson.EPerson;
import org.dspace.rest.common.User;
/**
* This class provide token generation, token holding and logging user into rest
* api. For login use method login with class org.dspace.rest.common.User. If
* you want to be deleted from holder, use method for logout.
*
* @author Rostislav Novak (Computing and Information Centre, CTU in Prague)
*/
public class TokenHolder
{
private static final Logger log = Logger.getLogger(TokenHolder.class);
public static String TOKEN_HEADER = "rest-dspace-token";
private static Map<String, String> tokens = new HashMap<String, String>(); // Map with pair Email,token
private static Map<String, EPerson> persons = new HashMap<String, EPerson>(); // Map with pair token,Eperson
/**
* Login user into rest api. It check user credentials if they are okay.
*
* @param user
* User which will be logged into rest api.
* @return Returns generated token, which must be used in request header
* under rest-api-token. If password is bad or user does not exist,
* it returns NULL.
* @throws WebApplicationException
* It is thrown by SQLException if user could not be read from
* database. And by Authorization exception if context has not
* permission to read eperson.
*/
public static String login(User user) throws WebApplicationException
{
org.dspace.core.Context context = null;
String token = null;
try
{
context = new org.dspace.core.Context();
EPerson dspaceUser = EPerson.findByEmail(context, user.getEmail());
if ((dspaceUser == null) || (!dspaceUser.checkPassword(user.getPassword())))
{
token = null;
}
else if (tokens.containsKey(user.getEmail()))
{
token = tokens.get(user.getEmail());
}
else
{
token = generateToken();
persons.put(token, dspaceUser);
tokens.put(user.getEmail(), token);
dspaceUser.setPassword(user.getPassword());
}
log.trace("User(" + user.getEmail() + ") has been logged.");
context.complete();
}
catch (SQLException e)
{
context.abort();
log.error("Could not read user from database. Message:" + e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
catch (AuthorizeException e)
{
context.abort();
log.error("Could not find user, AuthorizeException. Message:" + e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
return token;
}
/**
* Return EPerson for log into context.
*
* @param token
* Token under which is stored eperson.
* @return Return instance of EPerson if is token right, otherwise it
* returns NULL.
*/
public static EPerson getEPerson(String token)
{
return persons.get(token);
}
/**
* Logout user from rest api. It delete token and EPerson from TokenHolder.
*
* @param token
* Token under which is stored eperson.
* @return Return true if was all okay, otherwise return false.
*/
public static boolean logout(String token)
{
if ((token == null) || (persons.get(token) == null))
{
return false;
}
String email = persons.get(token).getEmail();
EPerson person = persons.remove(token);
if (person == null)
{
return false;
}
tokens.remove(email);
return true;
}
/**
* It generates unique token.
*
* @return String filled with unique token.
*/
private static String generateToken()
{
return UUID.randomUUID().toString();
}
}

View File

@@ -7,15 +7,17 @@
*/ */
package org.dspace.rest.common; package org.dspace.rest.common;
import org.apache.log4j.Logger;
import org.dspace.core.Constants;
import javax.xml.bind.annotation.XmlRootElement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import org.apache.log4j.Logger;
import org.dspace.content.Bundle;
import org.dspace.core.Constants;
/** /**
* Created with IntelliJ IDEA. * Created with IntelliJ IDEA.
* User: peterdietz * User: peterdietz
@@ -36,7 +38,9 @@ public class Bitstream extends DSpaceObject {
private String retrieveLink; private String retrieveLink;
private CheckSum checkSum; private CheckSum checkSum;
private Integer sequenceId; private Integer sequenceId;
private ResourcePolicy[] policies = null;
public Bitstream() { public Bitstream() {
} }
@@ -72,8 +76,22 @@ public class Bitstream extends DSpaceObject {
if(expandFields.contains("parent") || expandFields.contains("all")) { if(expandFields.contains("parent") || expandFields.contains("all")) {
parentObject = new DSpaceObject(bitstream.getParentObject()); parentObject = new DSpaceObject(bitstream.getParentObject());
} else if(expandFields.contains("policies") || expandFields.contains("all")) {
List<ResourcePolicy> tempPolicies = new ArrayList<ResourcePolicy>();
Bundle[] bundles = bitstream.getBundles();
for (Bundle bundle : bundles) {
List<org.dspace.authorize.ResourcePolicy> bitstreamsPolicies = bundle.getBitstreamPolicies();
for (org.dspace.authorize.ResourcePolicy policy : bitstreamsPolicies) {
if (policy.getResourceID() == this.getId()) {
tempPolicies.add(new ResourcePolicy(policy));
}
}
}
policies = tempPolicies.toArray(new ResourcePolicy[0]);
} else { } else {
this.addExpand("parent"); this.addExpand("parent");
this.addExpand("policies");
} }
if(!expandFields.contains("all")) { if(!expandFields.contains("all")) {
@@ -153,4 +171,13 @@ public class Bitstream extends DSpaceObject {
this.checkSum = checkSum; this.checkSum = checkSum;
} }
public ResourcePolicy[] getPolicies() {
return policies;
}
public void setPolicies(ResourcePolicy[] policies) {
this.policies = policies;
}
} }

View File

@@ -13,7 +13,6 @@ import org.dspace.content.ItemIterator;
import org.dspace.core.Context; import org.dspace.core.Context;
import javax.ws.rs.WebApplicationException; import javax.ws.rs.WebApplicationException;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;

View File

@@ -38,8 +38,9 @@ public class Community extends DSpaceObject{
private String copyrightText, introductoryText, shortDescription, sidebarText; private String copyrightText, introductoryText, shortDescription, sidebarText;
private Integer countItems; private Integer countItems;
@XmlElement(name = "subcommunities", required = true) // Renamed because of xml annotation exception with this attribute and getSubCommunities.
private List<Community> subCommunities = new ArrayList<Community>(); @XmlElement(name = "subcommunities2", required = true)
private List<Community> subCommunities2 = new ArrayList<Community>();
private List<Collection> collections = new ArrayList<Collection>(); private List<Collection> collections = new ArrayList<Collection>();
@@ -87,10 +88,10 @@ public class Community extends DSpaceObject{
if(expandFields.contains("subCommunities") || expandFields.contains("all")) { if(expandFields.contains("subCommunities") || expandFields.contains("all")) {
org.dspace.content.Community[] communityArray = community.getSubcommunities(); org.dspace.content.Community[] communityArray = community.getSubcommunities();
subCommunities = new ArrayList<Community>(); subCommunities2 = new ArrayList<Community>();
for(org.dspace.content.Community subCommunity : communityArray) { for(org.dspace.content.Community subCommunity : communityArray) {
if(AuthorizeManager.authorizeActionBoolean(context, subCommunity, org.dspace.core.Constants.READ)) { if(AuthorizeManager.authorizeActionBoolean(context, subCommunity, org.dspace.core.Constants.READ)) {
subCommunities.add(new Community(subCommunity, null, context)); subCommunities2.add(new Community(subCommunity, null, context));
} else { } else {
log.info("Omitted restricted subCommunity: " + subCommunity.getID() + " _ " + subCommunity.getName()); log.info("Omitted restricted subCommunity: " + subCommunity.getID() + " _ " + subCommunity.getName());
} }
@@ -171,4 +172,13 @@ public class Community extends DSpaceObject{
public Bitstream getLogo() { public Bitstream getLogo() {
return logo; return logo;
} }
public List<Community> getSubCommunities() {
return subCommunities2;
}
public void setSubCommunities(List<Community> subCommunities) {
this.subCommunities2 = subCommunities;
}
} }

View File

@@ -11,6 +11,7 @@ import org.atteo.evo.inflector.English;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -26,9 +27,7 @@ public class DSpaceObject {
private Integer id; private Integer id;
private String name; private String name;
private String handle; private String handle;
private String type; private String type;
@XmlElement(name = "link", required = true) @XmlElement(name = "link", required = true)
@@ -42,17 +41,17 @@ public class DSpaceObject {
} }
public DSpaceObject(org.dspace.content.DSpaceObject dso) { public DSpaceObject(org.dspace.content.DSpaceObject dso) {
setID(dso.getID()); setId(dso.getID());
setName(dso.getName()); setName(dso.getName());
setHandle(dso.getHandle()); setHandle(dso.getHandle());
setType(dso.getTypeText().toLowerCase()); setType(dso.getTypeText().toLowerCase());
} }
public Integer getID() { public Integer getId() {
return id; return id;
} }
public void setID(Integer id) { public void setId(Integer id) {
this.id = id; this.id = id;
} }
@@ -74,7 +73,7 @@ public class DSpaceObject {
public String getLink() { public String getLink() {
//TODO, get actual contextPath of /rest/ //TODO, get actual contextPath of /rest/
return "/rest/" + English.plural(getType()) + "/" + getID(); return "/RESTapi/" + English.plural(getType()) + "/" + getId();
} }
public String getType() { public String getType() {

View File

@@ -17,6 +17,7 @@ import org.dspace.core.Context;
import javax.ws.rs.WebApplicationException; import javax.ws.rs.WebApplicationException;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -29,6 +30,7 @@ import java.util.List;
* Time: 4:50 PM * Time: 4:50 PM
* To change this template use File | Settings | File Templates. * To change this template use File | Settings | File Templates.
*/ */
@SuppressWarnings("deprecation")
@XmlRootElement(name = "item") @XmlRootElement(name = "item")
public class Item extends DSpaceObject { public class Item extends DSpaceObject {
Logger log = Logger.getLogger(Item.class); Logger log = Logger.getLogger(Item.class);
@@ -39,11 +41,8 @@ public class Item extends DSpaceObject {
Collection parentCollection; Collection parentCollection;
List<Collection> parentCollectionList; List<Collection> parentCollectionList;
List<Community> parentCommunityList; List<Community> parentCommunityList;
List<MetadataEntry> metadata; List<MetadataEntry> metadata;
List<Bitstream> bitstreams; List<Bitstream> bitstreams;
public Item(){} public Item(){}
@@ -63,9 +62,8 @@ public class Item extends DSpaceObject {
metadata = new ArrayList<MetadataEntry>(); metadata = new ArrayList<MetadataEntry>();
DCValue[] dcvs = item.getMetadata(org.dspace.content.Item.ANY, org.dspace.content.Item.ANY, org.dspace.content.Item.ANY, org.dspace.content.Item.ANY); DCValue[] dcvs = item.getMetadata(org.dspace.content.Item.ANY, org.dspace.content.Item.ANY, org.dspace.content.Item.ANY, org.dspace.content.Item.ANY);
for (DCValue dcv : dcvs) { for (DCValue dcv : dcvs) {
if (!MetadataExposure.isHidden(context, dcv.schema, dcv.element, dcv.qualifier)) { if (!MetadataExposure.isHidden(context, dcv.schema, dcv.element, dcv.qualifier))
metadata.add(new MetadataEntry(dcv.getField(), dcv.value)); metadata.add(new MetadataEntry(dcv.getField(), dcv.value, dcv.language));
}
} }
} else { } else {
this.addExpand("metadata"); this.addExpand("metadata");

View File

@@ -10,37 +10,58 @@ package org.dspace.rest.common;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
/** /**
* Created with IntelliJ IDEA. * @author peterdietz, Rostislav Novak (Computing and Information Centre, CTU in
* User: peterdietz * Prague)
* Date: 9/20/13 *
* Time: 5:51 PM
* To change this template use File | Settings | File Templates.
*/ */
@XmlRootElement(name = "metadataentry") @XmlRootElement(name = "metadataentry")
public class MetadataEntry { public class MetadataEntry
{
String key; String key;
String value; String value;
public MetadataEntry() {} String language;
public MetadataEntry(String key, String value) { public MetadataEntry()
this.key = key; {
this.value = value;
} }
public String getValue() { public MetadataEntry(String key, String value, String language)
{
this.key = key;
this.value = value;
this.language = language;
}
public String getValue()
{
return value; return value;
} }
public void setValue(String value) { public void setValue(String value)
{
this.value = value; this.value = value;
} }
public String getKey() { public String getKey()
{
return key; return key;
} }
public void setKey(String key) { public void setKey(String key)
{
this.key = key; this.key = key;
} }
public String getLanguage()
{
return language;
}
public void setLanguage(String language)
{
this.language = language;
}
} }

View File

@@ -0,0 +1,174 @@
package org.dspace.rest.common;
import java.util.Date;
import org.codehaus.jackson.annotate.JsonIgnore;
public class ResourcePolicy{
public enum Action {
READ, WRITE, DELETE;
}
private Integer id;
private Action action;
private Integer epersonId;
private Integer groupId;
private Integer resourceId;
private String resourceType;
private String rpDescription;
private String rpName;
private String rpType;
private Date startDate;
private Date endDate;
public ResourcePolicy() {}
public ResourcePolicy(org.dspace.authorize.ResourcePolicy dspacePolicy) {
this.id = dspacePolicy.getID();
switch(dspacePolicy.getAction()) {
case org.dspace.core.Constants.READ:
this.action = Action.READ;
break;
case org.dspace.core.Constants.WRITE:
this.action = Action.WRITE;
break;
case org.dspace.core.Constants.DELETE:
this.action = Action.DELETE;
break;
}
this.epersonId = dspacePolicy.getEPersonID();
this.groupId = dspacePolicy.getGroupID();
this.resourceId = dspacePolicy.getResourceID();
this.rpDescription = dspacePolicy.getRpDescription();
this.rpName = dspacePolicy.getRpName();
this.rpType = dspacePolicy.getRpType();
this.startDate = dspacePolicy.getStartDate();
this.endDate = dspacePolicy.getEndDate();
switch(dspacePolicy.getResourceType()) {
case org.dspace.core.Constants.BITSTREAM:
this.resourceType = "bitstream";
break;
case org.dspace.core.Constants.ITEM:
this.resourceType = "item";
break;
case org.dspace.core.Constants.COLLECTION:
this.resourceType = "collection";
break;
case org.dspace.core.Constants.COMMUNITY:
this.resourceType = "community";
break;
case org.dspace.core.Constants.BUNDLE:
this.resourceType = "bundle";
break;
default:
this.resourceType = "";
break;
}
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Action getAction() {
return action;
}
@JsonIgnore
public int getActionInt(){
switch(action) {
case READ:
return org.dspace.core.Constants.READ;
case WRITE:
return org.dspace.core.Constants.WRITE;
case DELETE:
return org.dspace.core.Constants.DELETE;
}
return org.dspace.core.Constants.READ;
}
public void setAction(Action action) {
this.action = action;
}
public Integer getEpersonId() {
return epersonId;
}
public void setEpersonId(Integer epersonId) {
this.epersonId = epersonId;
}
public Integer getGroupId() {
return groupId;
}
public void setGroupId(Integer groupId) {
this.groupId = groupId;
}
public Integer getResourceId() {
return resourceId;
}
public void setResourceId(Integer resourceId) {
this.resourceId = resourceId;
}
public String getResourceType() {
return resourceType;
}
public void setResourceType(String resourceType) {
this.resourceType = resourceType;
}
public String getRpDescription() {
return rpDescription;
}
public void setRpDescription(String rpDescription) {
this.rpDescription = rpDescription;
}
public String getRpName() {
return rpName;
}
public void setRpName(String rpName) {
this.rpName = rpName;
}
public String getRpType() {
return rpType;
}
public void setRpType(String rpType) {
this.rpType = rpType;
}
public Date getStartDate() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public Date getEndDate() {
return endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
}

View File

@@ -0,0 +1,49 @@
package org.dspace.rest.common;
import javax.xml.bind.annotation.XmlRootElement;
/**
* Class for handle login information for POST request.
*
* @author Rostislav Novak (Computing and Information Centre, CTU in Prague)
*
*/
@XmlRootElement(name = "user")
public class User
{
private String email;
private String password;
public User()
{
}
public User(String email, String password)
{
this.email = email;
this.password = password;
}
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
}

View File

@@ -0,0 +1,28 @@
package org.dspace.rest.exceptions;
/**
* Simple exception which only encapsulate classic exception. This exception is
* only for exceptions caused by creating context.
*
* @author Rostislav Novak (Computing and Information Centre, CTU in Prague)
*
*/
public class ContextException extends Exception
{
private static final long serialVersionUID = 1L;
Exception causedBy;
public ContextException(String message, Exception causedBy)
{
super(message);
this.causedBy = causedBy;
}
public Exception getCausedBy()
{
return causedBy;
}
}