diff --git a/dspace-rest/src/main/java/org/dspace/rest/BitstreamResource.java b/dspace-rest/src/main/java/org/dspace/rest/BitstreamResource.java index fd6baff1cc..4074dd81b3 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/BitstreamResource.java +++ b/dspace-rest/src/main/java/org/dspace/rest/BitstreamResource.java @@ -7,143 +7,849 @@ */ package org.dspace.rest; -import org.apache.log4j.Logger; -import org.dspace.authorize.AuthorizeException; -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.Bitstream; -import org.dspace.usage.UsageEvent; -import org.dspace.utils.DSpace; +import java.io.IOException; +import java.io.InputStream; +import java.net.URLConnection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; 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.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import java.io.IOException; -import java.sql.SQLException; +import javax.ws.rs.core.Response.Status; + +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.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. - * User: peterdietz - * Date: 10/2/13 - * Time: 5:56 PM - * To change this template use File | Settings | File Templates. + * @author Rostislav Novak (Computing and Information Centre, CTU in Prague) */ +// Every DSpace class used without namespace is from package +// org.dspace.rest.common.*. Otherwise namespace is defined. @Path("/bitstreams") -public class BitstreamResource { - Logger log = Logger.getLogger(BitstreamResource.class); - - private static final boolean writeStatistics; - - static{ - writeStatistics=ConfigurationManager.getBooleanProperty("rest","stats",false); - } +public class BitstreamResource extends Resource +{ - //BitstreamList - Not Implemented + private static Logger log = Logger.getLogger(BitstreamResource.class); + /** + * 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 @Path("/{bitstream_id}") - @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public Bitstream getBitstream(@PathParam("bitstream_id") Integer bitstream_id, @QueryParam("expand") String expand) { + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Bitstream getBitstream(@PathParam("bitstream_id") Integer bitstreamId, @QueryParam("expand") String expand, + @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 bitstream(id=" + bitstreamId + ") metadata."); org.dspace.core.Context context = null; - try { - context = new org.dspace.core.Context(); + Bitstream bitstream = null; - org.dspace.content.Bitstream bitstream = org.dspace.content.Bitstream.find(context, bitstream_id); + try + { + context = createContext(getUser(headers)); + org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, org.dspace.core.Constants.READ); + + writeStats(dspaceBitstream, UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor, headers, + request); + + bitstream = new Bitstream(dspaceBitstream, expand); + context.complete(); + log.trace("Bitsream(id=" + bitstreamId + ") was successfully read."); - if(AuthorizeManager.authorizeActionBoolean(context, bitstream, org.dspace.core.Constants.READ)) { - return new org.dspace.rest.common.Bitstream(bitstream, expand); - } else { - throw new WebApplicationException(Response.Status.UNAUTHORIZED); - } - } 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"); - } - } } + 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); + } + finally + { + processFinally(context); + } + + return bitstream; } + /** + * Return all bitstream resource policies from all bundles, in which + * bitstream is. + * + * @param bitstreamId + * Id of bitstream in DSpace. + * @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 ResourcePolicy. + */ + @GET + @Path("/{bitstream_id}/policy") + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public ResourcePolicy[] getBitstreamPolicies(@PathParam("bitstream_id") Integer bitstreamId, @Context HttpHeaders headers) + { + + log.info("Reading bitstream(id=" + bitstreamId + ") policies."); + org.dspace.core.Context context = null; + List policies = new ArrayList(); + + 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 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); + } + finally + { + processFinally(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 bitstreams = new ArrayList(); + + 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 of + // reading under administrator permissions + bitstreams.add(new Bitstream(dspaceBitstreams[i], expand)); + writeStats(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); + } + finally + { + processFinally(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. + */ @GET @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(); + 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 + { - 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 { + log.info("Reading data of bitstream(id=" + bitstreamId + ")."); + org.dspace.core.Context context = null; + InputStream inputStream = null; + String type = null; + + try + { + context = createContext(getUser(headers)); + org.dspace.content.Bitstream dspaceBitstream = findBitstream(context, bitstreamId, org.dspace.core.Constants.READ); + + writeStats(dspaceBitstream, UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor, headers, + request); + + log.trace("Bitsream(id=" + bitstreamId + ") data was successfully read."); + inputStream = dspaceBitstream.retrieve(); + type = dspaceBitstream.getFormat().getMIMEType(); + + context.complete(); + } + 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); + } + finally + { + processFinally(context); + } + + return Response.ok(inputStream).type(type).build(); + } + + /** + * 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 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); + } + finally + { + processFinally(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(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 bitstreamsPolicies = bundle.getBitstreamPolicies(); + // Remove old bitstream policies + List policiesToRemove = new ArrayList(); + 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); + } + finally + { + processFinally(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(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); + } + finally + { + processFinally(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(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); + } + finally + { + processFinally(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 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); + } + finally + { + processFinally(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 (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"); - } - } } + catch (SQLException e) + { + processException("Something get wrong while finding bitstream. SQLException, Message:" + e, context); + } + return bitstream; } - - private void writeStats(org.dspace.core.Context context, Integer bitstream_id, String user_ip, String user_agent, - String xforwarderfor, HttpHeaders headers, - HttpServletRequest request) { - - try{ - DSpaceObject bitstream = DSpaceObject.find(context, Constants.BITSTREAM, bitstream_id); - - if(user_ip==null || user_ip.length()==0){ - new DSpace().getEventService().fireEvent( - new UsageEvent( - UsageEvent.Action.VIEW, - request, - context, - bitstream)); - } else{ - new DSpace().getEventService().fireEvent( - new UsageEvent( - UsageEvent.Action.VIEW, - user_ip, - user_agent, - xforwarderfor, - context, - bitstream)); - } - log.debug("fired event"); - - } catch(SQLException ex){ - log.error("SQL exception can't write usageEvent \n" + ex); - } - - } - } diff --git a/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java b/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java index 9d9b95339b..7fc6138e28 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java +++ b/dspace-rest/src/main/java/org/dspace/rest/CollectionsResource.java @@ -7,149 +7,767 @@ */ package org.dspace.rest; - -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.io.IOException; import java.sql.SQLException; import java.util.ArrayList; +import java.util.List; -/* -The "Path" annotation indicates the URI this class will be available at relative to your base URL. For -example, if this web-app is launched at localhost using a context of "hello" and no URL pattern is defined -in the web.xml servlet mapping section, then the web service will be available at: +import javax.servlet.http.HttpServletRequest; +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.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; -http://localhost:8080//collections +import org.apache.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.AuthorizeManager; +import org.dspace.browse.BrowseException; +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") -public class CollectionsResource { +public class CollectionsResource extends Resource +{ private static Logger log = Logger.getLogger(CollectionsResource.class); - @javax.ws.rs.core.Context ServletContext servletContext; - - private static final boolean writeStatistics; - - static{ - writeStatistics=ConfigurationManager.getBooleanProperty("rest","stats",false); - } - - @GET - @Path("/") - @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.Collection[] list(@QueryParam("expand") String expand, @QueryParam("limit") @DefaultValue("100") Integer limit, @QueryParam("offset") @DefaultValue("0") Integer offset) { - org.dspace.core.Context context = null; - try { - context = new org.dspace.core.Context(); - - org.dspace.content.Collection[] collections; - - //Only support paging if limit/offset are 0 or positive values. - if(limit != null && limit >= 0 && offset != null && offset >= 0) { - collections = org.dspace.content.Collection.findAll(context, limit, offset); - } else { - collections = org.dspace.content.Collection.findAll(context); - } - - ArrayList collectionArrayList = new ArrayList(); - for(org.dspace.content.Collection collection : collections) { - if(AuthorizeManager.authorizeActionBoolean(context, collection, org.dspace.core.Constants.READ)) { - org.dspace.rest.common.Collection restCollection = new org.dspace.rest.common.Collection(collection, null, context, limit, offset); - 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"); - } - } - } - } - + /** + * Return instance of collection with passed id. You can add more properties + * through expand parameter. + * + * @param collectionId + * Id of collection in DSpace. + * @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 collection under logged user into + * context. In headers must be set header "rest-dspace-token" + * with passed token from login method. + * @return Return instance of collection. 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}") - @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.Collection getCollection(@PathParam("collection_id") Integer collection_id, @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) { - org.dspace.core.Context context = null; - try { - context = new org.dspace.core.Context(); + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public org.dspace.rest.common.Collection getCollection(@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 + { - org.dspace.content.Collection collection = org.dspace.content.Collection.find(context, collection_id); - if(AuthorizeManager.authorizeActionBoolean(context, collection, org.dspace.core.Constants.READ)) { - if(writeStatistics){ - writeStats(context, collection_id, user_ip, user_agent, xforwarderfor, headers, request); - } - return new org.dspace.rest.common.Collection(collection, expand, context, limit, offset); - } else { - throw new WebApplicationException(Response.Status.UNAUTHORIZED); + log.info("Reading collection(id=" + collectionId + ")."); + org.dspace.core.Context context = null; + Collection collection = null; + + try + { + context = createContext(getUser(headers)); + + org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId, org.dspace.core.Constants.READ); + writeStats(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); + } + finally + { + processFinally(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 collections = new ArrayList(); + + try + { + context = createContext(getUser(headers)); + + if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) + { + log.warn("Paging was badly set."); + limit = 100; + offset = 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"); + + org.dspace.content.Collection[] dspaceCollections = org.dspace.content.Collection.findAll(context, limit, offset); + for(org.dspace.content.Collection dspaceCollection : dspaceCollections) + { + if (AuthorizeManager.authorizeActionBoolean(context, dspaceCollection, org.dspace.core.Constants.READ)) + { + Collection collection = new org.dspace.rest.common.Collection(dspaceCollection, null, context, limit, + offset); + collections.add(collection); + writeStats(dspaceCollection, 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); + } + finally + { + processFinally(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; + List items = null; + + try + { + context = createContext(getUser(headers)); + + org.dspace.content.Collection dspaceCollection = findCollection(context, collectionId, org.dspace.core.Constants.READ); + writeStats(dspaceCollection, UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor, + headers, request); + + items = new ArrayList(); + 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(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor, + headers, request); + } + } + } + + context.complete(); + } + 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); + } + finally + { + processFinally(context); + } + + log.trace("All items in collection(id=" + collectionId + ") were successfully read."); + return items.toArray(new Item[0]); + } + + /** + * 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(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); + } + finally + { + processFinally(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(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); + } + finally + { + processFinally(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(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); + } + finally + { + processFinally(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(dspaceCollection, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwarderfor, + headers, request); + writeStats(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); + } + finally + { + processFinally(context); + } + + log.info("Item(id=" + itemId + ") in collection(id=" + collectionId + ") was successfully deleted."); + return Response.ok().build(); + } + + /** + * Search for first collection with passed name. + * + * @param name + * Name of 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 It returns null if collection was not found. Otherwise returns + * first founded collection. + * @throws WebApplicationException + */ + @POST + @Path("/find-collection") + @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Collection findCollectionByName(String name, @Context HttpHeaders headers) throws WebApplicationException + { + log.info("Searching for first collection with name=" + name + "."); + org.dspace.core.Context context = null; + Collection collection = null; + + try + { + context = createContext(getUser(headers)); + org.dspace.content.Collection[] dspaceCollections; + + dspaceCollections = org.dspace.content.Collection.findAll(context); + + for (org.dspace.content.Collection dspaceCollection : dspaceCollections) + { + if (AuthorizeManager.authorizeActionBoolean(context, dspaceCollection, org.dspace.core.Constants.READ)) + { + 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); + } + finally + { + processFinally(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; } - - private void writeStats(org.dspace.core.Context context, Integer collection_id, String user_ip, String user_agent, - String xforwarderfor, HttpHeaders headers, - HttpServletRequest request) { - - try{ - DSpaceObject collection = DSpaceObject.find(context, Constants.COLLECTION, collection_id); - - if(user_ip==null || user_ip.length()==0){ - new DSpace().getEventService().fireEvent( - new UsageEvent( - UsageEvent.Action.VIEW, - request, - context, - collection)); - } else{ - new DSpace().getEventService().fireEvent( - new UsageEvent( - UsageEvent.Action.VIEW, - user_ip, - user_agent, - xforwarderfor, - context, - collection)); - } - log.debug("fired event"); - - } catch(SQLException ex){ - log.error("SQL exception can't write usageEvent \n" + ex); - } - - } } diff --git a/dspace-rest/src/main/java/org/dspace/rest/CommunitiesResource.java b/dspace-rest/src/main/java/org/dspace/rest/CommunitiesResource.java index 2e5b87f70b..8fb93c39d6 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/CommunitiesResource.java +++ b/dspace-rest/src/main/java/org/dspace/rest/CommunitiesResource.java @@ -7,137 +7,1087 @@ */ package org.dspace.rest; -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.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.io.IOException; import java.sql.SQLException; import java.util.ArrayList; -/* -The "Path" annotation indicates the URI this class will be available at relative to your base URL. For -example, if this web-app is launched at localhost using a context of "hello" and no URL pattern is defined -in the web.xml servlet mapping section, then the web service will be available at: +import javax.servlet.http.HttpServletRequest; +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.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; -http://localhost:8080//communities +import org.apache.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.AuthorizeManager; +import org.dspace.rest.common.Collection; +import org.dspace.rest.common.Community; +import org.dspace.rest.exceptions.ContextException; +import org.dspace.usage.UsageEvent; + +/** + * Class which provides CRUD methods over communities. + * + * @author Rostislav Novak (Computing and Information Centre, CTU in Prague) + * */ @Path("/communities") -public class CommunitiesResource { +public class CommunitiesResource extends Resource +{ private static Logger log = Logger.getLogger(CommunitiesResource.class); - private static final boolean writeStatistics; - - static{ - writeStatistics=ConfigurationManager.getBooleanProperty("rest","stats",false); - } - - @GET - @Path("/") - @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.Community[] list(@QueryParam("expand") String expand) { - org.dspace.core.Context context = null; - try { - context = new org.dspace.core.Context(); - - org.dspace.content.Community[] topCommunities = org.dspace.content.Community.findAllTop(context); - ArrayList communityArrayList = new ArrayList(); - for(org.dspace.content.Community community : topCommunities) { - if(AuthorizeManager.authorizeActionBoolean(context, community, org.dspace.core.Constants.READ)) { - //Only list communities that this user has access to. - org.dspace.rest.common.Community restCommunity = new org.dspace.rest.common.Community(community, expand, context); - communityArrayList.add(restCommunity); - } - } - - return communityArrayList.toArray(new org.dspace.rest.common.Community[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"); - } - } - } - } - + /** + * Returns community with basic properties. If you want more, use expand + * parameter or method for community collections or subcommunities. + * + * @param communityId + * Id of community in DSpace. + * @param expand + * String in which is what you want to add to returned instance + * of community. Options are: "all", "parentCommunity", + * "collections", "subCommunities" and "logo". If you want to use + * multiple options, it must be separated by commas. + * @param headers + * If you want to access to community under logged user into + * context. In headers must be set header "rest-dspace-token" + * with passed token from login method. + * @return Return instance of org.dspace.rest.common.Community. + * @throws WebApplicationException + * It is throw when was problem with creating context or problem + * with database reading. Also if id of community is incorrect + * or logged user into context has no permission to read. + */ @GET @Path("/{community_id}") - @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.Community getCommunity(@PathParam("community_id") Integer community_id, @QueryParam("expand") String expand, - @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(); + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Community getCommunity(@PathParam("community_id") Integer communityId, @QueryParam("expand") String expand, + @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, + @QueryParam("xforwarderfor") String xforwarderfor, @Context HttpHeaders headers, @Context HttpServletRequest request) + throws WebApplicationException + { - org.dspace.content.Community community = org.dspace.content.Community.find(context, community_id); - if(AuthorizeManager.authorizeActionBoolean(context, community, org.dspace.core.Constants.READ)) { - if(writeStatistics){ - writeStats(context, community_id, user_ip, user_agent, xforwarderfor, headers, request); - } - return new org.dspace.rest.common.Community(community, expand, context); - } else { - throw new WebApplicationException(Response.Status.UNAUTHORIZED); + log.info("Reading community(id=" + communityId + ")."); + org.dspace.core.Context context = null; + Community community = null; + + try + { + context = createContext(getUser(headers)); + + org.dspace.content.Community dspaceCommunity = findCommunity(context, communityId, org.dspace.core.Constants.READ); + writeStats(dspaceCommunity, UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor, headers, + request); + + community = new Community(dspaceCommunity, expand, context); + context.complete(); + + } + catch (SQLException e) + { + processException("Could not read community(id=" + communityId + "), SQLException. Message:" + e, context); + } + catch (ContextException e) + { + processException("Could not read community(id=" + communityId + "), ContextException. Message:" + e.getMessage(), + context); + } + finally + { + processFinally(context); + } + + + log.trace("Community(id=" + communityId + ") was successfully read."); + return community; + } + + /** + * Return all communities in DSpace. + * + * @param expand + * String in which is what you want to add to returned instance + * of community. Options are: "all", "parentCommunity", + * "collections", "subCommunities" and "logo". If you want to use + * multiple options, it must be separated by commas. + * + * @param limit + * Maximum communities in array. Default value is 100. + * @param offset + * Index from which will start array of communities. + * @param headers + * If you want to access to community under logged user into + * context. In headers must be set header "rest-dspace-token" + * with passed token from login method. + * @return Return array of communities. + * @throws WebApplicationException + * It can be caused by creating context or while was problem + * with reading community from database(SQLException). + */ + @GET + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Community[] getCommunities(@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 communities.(offset=" + offset + " ,limit=" + limit + ")."); + org.dspace.core.Context context = null; + ArrayList communities = null; + + try + { + context = createContext(getUser(headers)); + + org.dspace.content.Community[] dspaceCommunities = org.dspace.content.Community.findAll(context); + communities = new ArrayList(); + + if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) + { + log.warn("Paging was badly set, using default values."); + limit = 100; + offset = 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"); + + for (int i = offset; (i < (offset + limit)) && i < dspaceCommunities.length; i++) + { + if (AuthorizeManager.authorizeActionBoolean(context, dspaceCommunities[i], org.dspace.core.Constants.READ)) + { + Community community = new Community(dspaceCommunities[i], expand, context); + writeStats(dspaceCommunities[i], UsageEvent.Action.VIEW, user_ip, user_agent, + xforwarderfor, headers, request); + communities.add(community); } } + + context.complete(); } + catch (SQLException e) + { + processException("Could not read communities, SQLException. Message:" + e, context); + } + catch (ContextException e) + { + processException("Could not read communities, ContextException. Message:" + e.getMessage(), context); + } + finally + { + processFinally(context); + } + + log.trace("All communities successfully read."); + return communities.toArray(new Community[0]); + } + + /** + * Return all top communities in DSpace. Top communities are communities on + * the root of tree. + * + * @param expand + * String in which is what you want to add to returned instance + * of community. Options are: "all", "parentCommunity", + * "collections", "subCommunities" and "logo". If you want to use + * multiple options, it must be separated by commas. + * + * @param limit + * Maximum communities in array. Default value is 100. + * @param offset + * Index from which will start array of communities. Default + * value is 0. + * @param headers + * If you want to access to community under logged user into + * context. In headers must be set header "rest-dspace-token" + * with passed token from login method. + * @return Return array of top communities. + * @throws WebApplicationException + * It can be caused by creating context or while was problem + * with reading community from database(SQLException). + */ + @GET + @Path("/top-communities") + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Community[] getTopCommunities(@QueryParam("expand") String expand, + @QueryParam("limit") @DefaultValue("20") 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 top communities.(offset=" + offset + " ,limit=" + limit + ")."); + org.dspace.core.Context context = null; + ArrayList communities = null; + + try + { + context = createContext(getUser(headers)); + + org.dspace.content.Community[] dspaceCommunities = org.dspace.content.Community.findAllTop(context); + communities = new ArrayList(); + + if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) + { + log.warn("Pagging was badly set, using default values."); + limit = 100; + offset = 0; + } + + for (int i = offset; (i < (offset + limit)) && i < dspaceCommunities.length; i++) + { + if (AuthorizeManager.authorizeActionBoolean(context, dspaceCommunities[i], org.dspace.core.Constants.READ)) + { + Community community = new Community(dspaceCommunities[i], expand, context); + writeStats(dspaceCommunities[i], UsageEvent.Action.VIEW, user_ip, user_agent, + xforwarderfor, headers, request); + communities.add(community); + } + } + + context.complete(); + } + catch (SQLException e) + { + processException("Could not read top communities, SQLException. Message:" + e, context); + } + catch (ContextException e) + { + processException("Could not read top communities, ContextException. Message:" + e.getMessage(), context); + } + finally + { + processFinally(context); + } + + log.trace("All top communities successfully read."); + return communities.toArray(new Community[0]); + } + + /** + * Return all collections of community. + * + * @param communityId + * Id of community in DSpace. + * @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 + * Maximum collection in array. Default value is 100. + * @param offset + * Index from which will start array of collections. Default + * value is 0. + * @param headers + * If you want to access to community under logged user into + * context. In headers must be set header "rest-dspace-token" + * with passed token from login method. + * @return Return array of collections of community. + * @throws WebApplicationException + * It can be caused by creating context or while was problem + * with reading community from database(SQLException). + */ + @GET + @Path("/{community_id}/collections") + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Collection[] getCommunityCollections(@PathParam("community_id") Integer communityId, + @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 community(id=" + communityId + ") collections."); + org.dspace.core.Context context = null; + ArrayList collections = null; + + try + { + context = createContext(getUser(headers)); + + org.dspace.content.Community dspaceCommunity = findCommunity(context, communityId, org.dspace.core.Constants.READ); + writeStats(dspaceCommunity, UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor, headers, + request); + + if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) + { + log.warn("Pagging was badly set, using default values."); + limit = 100; + offset = 0; + } + + collections = new ArrayList(); + org.dspace.content.Collection[] dspaceCollections = dspaceCommunity.getCollections(); + for (int i = offset; (i < (offset + limit)) && (i < dspaceCollections.length); i++) + { + if (AuthorizeManager.authorizeActionBoolean(context, dspaceCollections[i], org.dspace.core.Constants.READ)) + { + collections.add(new Collection(dspaceCollections[i], expand, context, 20, 0)); + writeStats(dspaceCollections[i], UsageEvent.Action.VIEW, user_ip, user_agent, + xforwarderfor, headers, request); + } + } + + context.complete(); + } + catch (SQLException e) + { + processException("Could not read community(id=" + communityId + ") collections, SQLException. Message:" + e, context); + } + catch (ContextException e) + { + processException( + "Could not read community(id=" + communityId + ") collections, ContextException. Message:" + e.getMessage(), + context); + } + finally + { + processFinally(context); + } + + log.trace("Community(id=" + communityId + ") collections were successfully read."); + return collections.toArray(new Collection[0]); + } + + /** + * Return all subcommunities of community. + * + * @param communityId + * Id of community in DSpace. + * @param expand + * String in which is what you want to add to returned instance + * of community. Options are: "all", "parentCommunity", + * "collections", "subCommunities" and "logo". If you want to use + * multiple options, it must be separated by commas. + * @param limit + * Maximum communities in array. Default value is 20. + * @param offset + * Index from which will start array of communities. Default + * value is 0. + * @param headers + * If you want to access to community under logged user into + * context. In headers must be set header "rest-dspace-token" + * with passed token from login method. + * @return Return array of subcommunities of community. + * @throws WebApplicationException + * It can be caused by creating context or while was problem + * with reading community from database(SQLException). + */ + @GET + @Path("/{community_id}/communities") + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Community[] getCommunityCommunities(@PathParam("community_id") Integer communityId, + @QueryParam("expand") String expand, @QueryParam("limit") @DefaultValue("20") 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 community(id=" + communityId + ") subcommunities."); + org.dspace.core.Context context = null; + ArrayList communities = null; + + try + { + context = createContext(getUser(headers)); + + org.dspace.content.Community dspaceCommunity = findCommunity(context, communityId, org.dspace.core.Constants.READ); + writeStats(dspaceCommunity, UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor, headers, + request); + + if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) + { + log.warn("Pagging was badly set, using default values."); + limit = 100; + offset = 0; + } + + communities = new ArrayList(); + org.dspace.content.Community[] dspaceCommunities = dspaceCommunity.getSubcommunities(); + for (int i = offset; (i < (offset + limit)) && (i < dspaceCommunities.length); i++) + { + if (AuthorizeManager.authorizeActionBoolean(context, dspaceCommunities[i], org.dspace.core.Constants.READ)) + { + communities.add(new Community(dspaceCommunities[i], expand, context)); + writeStats(dspaceCommunities[i], UsageEvent.Action.VIEW, user_ip, user_agent, + xforwarderfor, headers, request); + } + } + + context.complete(); + } + catch (SQLException e) + { + processException("Could not read community(id=" + communityId + ") subcommunities, SQLException. Message:" + e, + context); + } + catch (ContextException e) + { + processException( + "Could not read community(id=" + communityId + ") subcommunities, ContextException. Message:" + + e.getMessage(), context); + } + finally + { + processFinally(context); + } + + log.trace("Community(id=" + communityId + ") subcommunities were successfully read."); + return communities.toArray(new Community[0]); + } + + /** + * Create community at top level. Creating community at top level has + * permission only admin. + * + * @param community + * Community which will be created at top level of communities. + * @param headers + * If you want to access to community under logged user into + * context. In headers must be set header "rest-dspace-token" + * with passed token from login method. + * @return Returns response with handle of community, if was all ok. + * @throws WebApplicationException + * It can be thrown by SQLException, AuthorizeException and + * ContextException. + */ + @POST + @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Community createCommunity(Community community, @QueryParam("userIP") String user_ip, + @QueryParam("userAgent") String user_agent, @QueryParam("xforwarderfor") String xforwarderfor, + @Context HttpHeaders headers, @Context HttpServletRequest request) throws WebApplicationException + { + + log.info("Creating community at top level."); + org.dspace.core.Context context = null; + Community retCommunity = null; + + try + { + context = createContext(getUser(headers)); + + if (!AuthorizeManager.isAdmin(context)) + { + context.abort(); + String user = "anonymous"; + if (context.getCurrentUser() != null) + { + user = context.getCurrentUser().getEmail(); + } + log.error("User(" + user + ") has not permission to create community!"); + throw new WebApplicationException(Response.Status.UNAUTHORIZED); + } + + org.dspace.content.Community dspaceCommunity = org.dspace.content.Community.create(null, context); + writeStats(dspaceCommunity, UsageEvent.Action.CREATE, user_ip, user_agent, xforwarderfor, + headers, request); + + dspaceCommunity.setMetadata("name", community.getName()); + dspaceCommunity.setMetadata(org.dspace.content.Community.COPYRIGHT_TEXT, community.getCopyrightText()); + dspaceCommunity.setMetadata(org.dspace.content.Community.INTRODUCTORY_TEXT, community.getIntroductoryText()); + dspaceCommunity.setMetadata(org.dspace.content.Community.SHORT_DESCRIPTION, community.getShortDescription()); + dspaceCommunity.setMetadata(org.dspace.content.Community.SIDEBAR_TEXT, community.getSidebarText()); + dspaceCommunity.update(); + + retCommunity = new Community(dspaceCommunity, "", context); + context.complete(); + + } + catch (SQLException e) + { + processException("Could not create new top community, SQLException. Message: " + e, context); + } + catch (ContextException e) + { + processException("Could not create new top community, ContextException. Message: " + e.getMessage(), context); + } + catch (AuthorizeException e) + { + processException("Could not create new top community, AuthorizeException. Message: " + e.getMessage(), context); + } + finally + { + processFinally(context); + } + + + log.info("Community at top level has been successfully created. Handle:" + retCommunity.getHandle()); + return retCommunity; + } + + /** + * Create collection in community. + * + * @param communityId + * Id of community in DSpace. + * @param collection + * Collection which will be added into community. + * @param headers + * If you want to access to community 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 community. Or + * problem with creating context. + */ + @POST + @Path("/{community_id}/collections") + @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Collection addCommunityCollection(@PathParam("community_id") Integer communityId, 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("Adding collection into community(id=" + communityId + ")."); + org.dspace.core.Context context = null; + Collection retCollection = null; + + try + { + context = createContext(getUser(headers)); + org.dspace.content.Community dspaceCommunity = findCommunity(context, communityId, org.dspace.core.Constants.WRITE); + + writeStats(dspaceCommunity, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwarderfor, + headers, request); + + org.dspace.content.Collection dspaceCollection = dspaceCommunity.createCollection(); + dspaceCollection.setLicense(collection.getLicense()); + // dspaceCollection.setLogo(collection.getLogo()); // TODO Add this option. + dspaceCollection.setMetadata("name", collection.getName()); + 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.setLicense(collection.getLicense()); + dspaceCollection.update(); + dspaceCommunity.update(); + + retCollection = new Collection(dspaceCollection, "", context, 100, 0); + context.complete(); + + } + catch (SQLException e) + { + processException("Could not add collection into community(id=" + communityId + "), SQLException. Message:" + e, + context); + } + catch (AuthorizeException e) + { + processException("Could not add collection into community(id=" + communityId + "), AuthorizeException. Message:" + e, + context); + } + catch (ContextException e) + { + processException( + "Could not add collection into community(id=" + communityId + "), ContextException. Message:" + + e.getMessage(), context); + } + finally + { + processFinally(context); + } + + + log.info("Collection was successfully added into community(id=" + communityId + "). Collection handle=" + + retCollection.getHandle()); + return retCollection; + } + + /** + * Create subcommunity in community. + * + * @param communityId + * Id of community in DSpace, in which will be created + * subcommunity. + * @param community + * Community which will be added into community. + * @param headers + * If you want to access to community 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 community. Or + * problem with creating context. + */ + @POST + @Path("/{community_id}/communities") + @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Community addCommunityCommunity(@PathParam("community_id") Integer communityId, Community community, + @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, + @QueryParam("xforwarderfor") String xforwarderfor, @Context HttpHeaders headers, @Context HttpServletRequest request) + throws WebApplicationException + { + + log.info("Add subcommunity into community(id=" + communityId + ")."); + org.dspace.core.Context context = null; + Community retCommunity = null; + + try + { + context = createContext(getUser(headers)); + org.dspace.content.Community dspaceParentCommunity = findCommunity(context, communityId, + org.dspace.core.Constants.WRITE); + + writeStats(dspaceParentCommunity, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwarderfor, + headers, request); + + org.dspace.content.Community dspaceCommunity = org.dspace.content.Community.create(dspaceParentCommunity, context); + dspaceCommunity.setMetadata("name", community.getName()); + dspaceCommunity.setMetadata(org.dspace.content.Community.COPYRIGHT_TEXT, community.getCopyrightText()); + dspaceCommunity.setMetadata(org.dspace.content.Community.INTRODUCTORY_TEXT, community.getIntroductoryText()); + dspaceCommunity.setMetadata(org.dspace.content.Community.SHORT_DESCRIPTION, community.getShortDescription()); + dspaceCommunity.setMetadata(org.dspace.content.Community.SIDEBAR_TEXT, community.getSidebarText()); + dspaceCommunity.update(); + dspaceParentCommunity.update(); + + retCommunity = new Community(dspaceCommunity, "", context); + context.complete(); + + } + catch (SQLException e) + { + processException("Could not add subcommunity into community(id=" + communityId + "), SQLException. Message:" + e, + context); + } + catch (AuthorizeException e) + { + processException("Could not add subcommunity into community(id=" + communityId + "), AuthorizeException. Message:" + + e, context); + } + catch (ContextException e) + { + processException("Could not add subcommunity into community(id=" + communityId + "), ContextException. Message:" + e, + context); + } + finally + { + processFinally(context); + } + + + log.info("Subcommunity was successfully added in community(id=" + communityId + ")."); + return retCommunity; + } + + /** + * Update community. Replace all information about community except: id, + * handle and expandle items. + * + * @param communityId + * Id of community in DSpace. + * @param community + * Instance of community which will replace actual community in + * DSpace. + * @param headers + * If you want to access to community under logged user into + * context. In headers must be set header "rest-dspace-token" + * with passed token from login method. + * @return Response 200 if was all ok. Otherwise 400 if was id incorrect or + * 401 if logged user has no permission to delete community. + * @throws WebApplicationException + * It is throw when was problem with creating context or problem + * with database reading or writing. Or problem with writing to + * community caused by authorization. + */ + @PUT + @Path("/{community_id}") + @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Response updateCommunity(@PathParam("community_id") Integer communityId, Community community, + @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 community(id=" + communityId + ")."); + org.dspace.core.Context context = null; + + try + { + context = createContext(getUser(headers)); + + org.dspace.content.Community dspaceCommunity = findCommunity(context, communityId, org.dspace.core.Constants.WRITE); + writeStats(dspaceCommunity, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwarderfor, + headers, request); + + // dspaceCommunity.setLogo(arg0); // TODO Add this option. + dspaceCommunity.setMetadata("name", community.getName()); + dspaceCommunity.setMetadata(org.dspace.content.Community.COPYRIGHT_TEXT, community.getCopyrightText()); + dspaceCommunity.setMetadata(org.dspace.content.Community.INTRODUCTORY_TEXT, community.getIntroductoryText()); + dspaceCommunity.setMetadata(org.dspace.content.Community.SHORT_DESCRIPTION, community.getShortDescription()); + dspaceCommunity.setMetadata(org.dspace.content.Community.SIDEBAR_TEXT, community.getSidebarText()); + dspaceCommunity.update(); + + context.complete(); + + } + catch (SQLException e) + { + processException("Could not update community(id=" + communityId + "), AuthorizeException. Message:" + e, context); + } + catch (ContextException e) + { + processException("Could not update community(id=" + communityId + "), ContextException Message:" + e, context); + } + catch (AuthorizeException e) + { + processException("Could not update community(id=" + communityId + "), AuthorizeException. Message:" + e, context); + } + finally + { + processFinally(context); + } + + log.info("Community(id=" + communityId + ") has been successfully updated."); + return Response.ok().build(); + } + + /** + * Delete community from DSpace. It delete it everything with community! + * + * @param communityId + * Id of community in DSpace. + * @param headers + * If you want to access to community 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 incorrect. + * Or (UNAUTHORIZED)401 if was problem with permission to community. + * @throws WebApplicationException + * It is throw when was problem with creating context or problem + * with database reading or deleting. Or problem with deleting + * community caused by IOException or authorization. + */ + @DELETE + @Path("/{community_id}") + public Response deleteCommunity(@PathParam("community_id") Integer communityId, @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 community(id=" + communityId + ")."); + org.dspace.core.Context context = null; + + try + { + context = createContext(getUser(headers)); + + org.dspace.content.Community community = findCommunity(context, communityId, org.dspace.core.Constants.DELETE); + writeStats(community, UsageEvent.Action.DELETE, user_ip, user_agent, xforwarderfor, headers, + request); + + community.delete(); + context.complete(); + + } + catch (SQLException e) + { + processException("Could not delete community(id=" + communityId + "), SQLException. Message:" + e, context); + } + catch (AuthorizeException e) + { + processException("Could not delete community(id=" + communityId + "), AuthorizeException. Message:" + e, context); + } + catch (IOException e) + { + processException("Could not delete community(id=" + communityId + "), IOException. Message:" + e, context); + } + catch (ContextException e) + { + processException("Could not delete community(id=" + communityId + "), ContextException. Message:" + e.getMessage(), + context); + } + finally + { + processFinally(context); + } + + + log.info("Community(id=" + communityId + ") was successfully deleted."); + return Response.status(Response.Status.OK).build(); + } + + /** + * Delete collection in community. + * + * @param communityId + * Id of community in DSpace. + * @param collectionId + * Id of collection which will be deleted. + * @param headers + * If you want to access to community 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 deleting. Or problem with deleting + * collection caused by IOException or authorization. + */ + @DELETE + @Path("/{community_id}/collections/{collection_id}") + public Response deleteCommunityCollection(@PathParam("community_id") Integer communityId, + @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("Deleting collection(id=" + collectionId + ") in community(id=" + communityId + ")."); + org.dspace.core.Context context = null; + + try + { + context = createContext(getUser(headers)); + + org.dspace.content.Community community = findCommunity(context, communityId, org.dspace.core.Constants.WRITE); + org.dspace.content.Collection collection = null; + for (org.dspace.content.Collection dspaceCollection : community.getAllCollections()) + { + if (dspaceCollection.getID() == collectionId) + { + collection = dspaceCollection; + break; + } + } + + if (collection == null) + { + context.abort(); + log.warn("Collection(id=" + collectionId + ") was not found!"); + throw new WebApplicationException(Response.Status.NOT_FOUND); + } + else if (!AuthorizeManager.authorizeActionBoolean(context, collection, org.dspace.core.Constants.REMOVE)) + { + context.abort(); + if (context.getCurrentUser() != null) + { + log.error("User(" + context.getCurrentUser().getEmail() + ") has not permission to delete collection!"); + } + else + { + log.error("User(anonymous) has not permission to delete collection!"); + } + throw new WebApplicationException(Response.Status.UNAUTHORIZED); + } + + writeStats(community, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwarderfor, headers, + request); + writeStats(collection, UsageEvent.Action.DELETE, user_ip, user_agent, xforwarderfor, headers, + request); + + community.removeCollection(collection); + + context.complete(); + + } + catch (SQLException e) + { + processException("Could not delete collection(id=" + collectionId + ") in community(id=" + communityId + + "), SQLException. Message:" + e, context); + } + catch (AuthorizeException e) + { + processException("Could not delete collection(id=" + collectionId + ") in community(id=" + communityId + + "), AuthorizeException. Message:" + e, context); + } + catch (IOException e) + { + processException("Could not delete collection(id=" + collectionId + ") in community(id=" + communityId + + "), IOException. Message:" + e, context); + } + catch (ContextException e) + { + processException("Could not delete collection(id=" + collectionId + ") in community(id=" + communityId + + "), ContextExcpetion. Message:" + e.getMessage(), context); + } + finally + { + processFinally(context); + } + + + log.info("Collection(id=" + collectionId + ") in community(id=" + communityId + ") was successfully deleted."); + return Response.status(Response.Status.OK).build(); + } + + /** + * Delete subcommunity in community. + * + * @param parentCommunityId + * Id of community in DSpace. + * @param subcommunityId + * Id of community which will be deleted. + * @param headers + * If you want to access to community 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 + * subcommunity incorrect. Or (UNAUTHORIZED)401 if was problem with + * permission to community or subcommunity. + * @throws WebApplicationException + * It is throw when was problem with creating context or problem + * with database reading or deleting. Or problem with deleting + * subcommunity caused by IOException or authorization. + */ + @DELETE + @Path("/{community_id}/communities/{community_id2}") + public Response deleteCommunityCommunity(@PathParam("community_id") Integer parentCommunityId, + @PathParam("community_id2") Integer subcommunityId, @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 community(id=" + parentCommunityId + ")."); + org.dspace.core.Context context = null; + + try + { + context = createContext(getUser(headers)); + + org.dspace.content.Community parentCommunity = findCommunity(context, parentCommunityId, + org.dspace.core.Constants.WRITE); + org.dspace.content.Community subcommunity = null; + for (org.dspace.content.Community dspaceCommunity : parentCommunity.getSubcommunities()) + { + if (dspaceCommunity.getID() == subcommunityId) + { + subcommunity = dspaceCommunity; + break; + } + } + + if (subcommunity == null) + { + context.abort(); + log.warn("Subcommunity(id=" + subcommunityId + ") in community(id=" + ") was not found!"); + throw new WebApplicationException(Response.Status.NOT_FOUND); + } + else if (!AuthorizeManager.authorizeActionBoolean(context, subcommunity, org.dspace.core.Constants.REMOVE)) + { + context.abort(); + if (context.getCurrentUser() != null) + { + log.error("User(" + context.getCurrentUser().getEmail() + ") has not permission to delete community!"); + } + else + { + log.error("User(anonymous) has not permission to delete community!"); + } + throw new WebApplicationException(Response.Status.UNAUTHORIZED); + } + + writeStats(parentCommunity, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwarderfor, + headers, request); + writeStats(subcommunity, UsageEvent.Action.DELETE, user_ip, user_agent, xforwarderfor, headers, + request); + + parentCommunity.removeSubcommunity(subcommunity); + context.complete(); + + } + catch (SQLException e) + { + processException("Could not delete subcommunity(id=" + subcommunityId + ") in community(id=" + parentCommunityId + + "), SQLException. Message:" + e, context); + } + catch (AuthorizeException e) + { + processException("Could not delete subcommunity(id=" + subcommunityId + ") in community(id=" + parentCommunityId + + "), AuthorizeException. Message:" + e, context); + } + catch (IOException e) + { + processException("Could not delete subcommunity(id=" + subcommunityId + ") in community(id=" + parentCommunityId + + "), IOException. Message:" + e, context); + } + catch (ContextException e) + { + processException("Could not delete subcommunity(id=" + subcommunityId + ") in community(id=" + parentCommunityId + + "), ContextExcpetion. Message:" + e.getMessage(), context); + } + finally + { + processFinally(context); + } + + + log.info("Subcommunity(id=" + subcommunityId + ") from community(id=" + parentCommunityId + ") was successfully deleted."); + return Response.status(Response.Status.OK).build(); + } + + /** + * Find community from DSpace database. It is encapsulation of method + * org.dspace.content.Community.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 community 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.Community findCommunity(org.dspace.core.Context context, int id, int action) + throws WebApplicationException + { + org.dspace.content.Community community = null; + try + { + community = org.dspace.content.Community.find(context, id); + + if (community == null) + { + context.abort(); + log.warn("Community(id=" + id + ") was not found!"); + throw new WebApplicationException(Response.Status.NOT_FOUND); + } + else if (!AuthorizeManager.authorizeActionBoolean(context, community, action)) + { + context.abort(); + if (context.getCurrentUser() != null) + { + log.error("User(" + context.getCurrentUser().getEmail() + ") has not permission to " + + getActionString(action) + " community!"); + } + else + { + log.error("User(anonymous) has not permission to " + getActionString(action) + " community!"); + } + throw new WebApplicationException(Response.Status.UNAUTHORIZED); + } + + } + catch (SQLException e) + { + processException("Something get wrong while finding community(id=" + id + "). SQLException, Message:" + e, context); + } + return community; } - - private void writeStats(org.dspace.core.Context context, Integer community_id, String user_ip, String user_agent, - String xforwarderfor, HttpHeaders headers, - HttpServletRequest request) { - - try{ - DSpaceObject community = DSpaceObject.find(context, Constants.COMMUNITY, community_id); - - if(user_ip==null || user_ip.length()==0){ - new DSpace().getEventService().fireEvent( - new UsageEvent( - UsageEvent.Action.VIEW, - request, - context, - community)); - } else{ - new DSpace().getEventService().fireEvent( - new UsageEvent( - UsageEvent.Action.VIEW, - user_ip, - user_agent, - xforwarderfor, - context, - community)); - } - log.debug("fired event"); - - } catch(SQLException ex){ - log.error("SQL exception can't write usageEvent \n" + ex); - } - - } } diff --git a/dspace-rest/src/main/java/org/dspace/rest/HandleResource.java b/dspace-rest/src/main/java/org/dspace/rest/HandleResource.java index 859e85affa..25270f301f 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/HandleResource.java +++ b/dspace-rest/src/main/java/org/dspace/rest/HandleResource.java @@ -32,14 +32,18 @@ import java.sql.SQLException; @Path("/handle") public class HandleResource { private static Logger log = Logger.getLogger(HandleResource.class); + private static org.dspace.core.Context context; @GET @Path("/{prefix}/{suffix}") @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) { - org.dspace.core.Context context = null; 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); if(dso == null) { @@ -64,14 +68,6 @@ public class HandleResource { } 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"); - } - } } } } diff --git a/dspace-rest/src/main/java/org/dspace/rest/ItemsResource.java b/dspace-rest/src/main/java/org/dspace/rest/ItemsResource.java index 3f8503d309..941f780e4c 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/ItemsResource.java +++ b/dspace-rest/src/main/java/org/dspace/rest/ItemsResource.java @@ -7,110 +7,1085 @@ */ package org.dspace.rest; -import org.apache.log4j.Logger; -import org.dspace.authorize.AuthorizeManager; -import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; -import javax.ws.rs.*; +import javax.servlet.http.HttpServletRequest; +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.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import java.sql.SQLException; +import javax.ws.rs.core.Response.Status; -import org.dspace.content.DSpaceObject; -import org.dspace.core.ConfigurationManager; -import org.dspace.core.Constants; +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.content.DCValue; +import org.dspace.content.ItemIterator; +import org.dspace.eperson.Group; +import org.dspace.rest.common.Bitstream; +import org.dspace.rest.common.Item; +import org.dspace.rest.common.MetadataEntry; +import org.dspace.rest.exceptions.ContextException; +import org.dspace.storage.rdbms.TableRow; +import org.dspace.storage.rdbms.TableRowIterator; import org.dspace.usage.UsageEvent; -import org.dspace.utils.DSpace; /** - * Created with IntelliJ IDEA. - * User: peterdietz - * Date: 9/19/13 - * Time: 4:54 PM - * To change this template use File | Settings | File Templates. + * Class which provide all CRUD methods over items. + * + * @author Rostislav Novak (Computing and Information Centre, CTU in Prague) + * */ +// Every DSpace class used without namespace is from package org.dspace.rest.common.*. Otherwise namespace is defined. +@SuppressWarnings("deprecation") @Path("/items") -public class ItemsResource { - - private static final boolean writeStatistics; - - static{ - writeStatistics=ConfigurationManager.getBooleanProperty("rest","stats",false); - } - - /** log4j category */ - private static final Logger log = Logger.getLogger(ItemsResource.class); - //ItemList - Not Implemented +public class ItemsResource extends Resource +{ + private static final Logger log = Logger.getLogger(ItemsResource.class); + + /** + * Return item properties without metadata and bitstreams. You can add + * additional properties by parameter expand. + * + * @param itemId + * Id of item 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 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 item, it returns item. Otherwise is + * thrown WebApplicationException with response status + * UNAUTHORIZED(401) or NOT_FOUND(404) if was id incorrect. + * @throws WebApplicationException + * This exception can be throw by NOT_FOUND(bad id of item), + * UNAUTHORIZED, SQLException if wasproblem with reading from + * database and ContextException, if there was problem with + * creating context of DSpace. + */ @GET @Path("/{item_id}") - @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) - public org.dspace.rest.common.Item getItem(@PathParam("item_id") Integer item_id, @QueryParam("expand") String expand, - @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, @QueryParam("xforwarderfor") String xforwarderfor, - @Context HttpHeaders headers, @Context HttpServletRequest request) throws WebApplicationException { + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Item getItem(@PathParam("item_id") Integer itemId, @QueryParam("expand") String expand, + @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 item(id=" + itemId + ")."); org.dspace.core.Context context = null; - try { - context = new org.dspace.core.Context(); + Item item = null; - org.dspace.content.Item item = org.dspace.content.Item.find(context, item_id); + try + { + context = createContext(getUser(headers)); + org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.READ); - if(AuthorizeManager.authorizeActionBoolean(context, item, org.dspace.core.Constants.READ)) { - if(writeStatistics){ - writeStats(context, item_id, user_ip, user_agent, xforwarderfor, headers, request); - } - return new org.dspace.rest.common.Item(item, expand, context); - } else { + writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor, headers, request); + + item = new Item(dspaceItem, expand, context); + context.complete(); + log.trace("Item(id=" + itemId + ") was successfully read."); + + } + catch (SQLException e) + { + processException("Could not read item(id=" + itemId + "), SQLException. Message: " + e, context); + } + catch (ContextException e) + { + processException("Could not read item(id=" + itemId + "), ContextException. Message: " + e.getMessage(), context); + } + finally + { + processFinally(context); + } + + return item; + } + + /** + * It returns an array of items in DSpace. You can define how many items in + * list will be and from which index will start. Items in list are sorted by + * handle, not by id. + * + * @param limit + * How many items in array will be. Default value is 100. + * @param offset + * On which index will array start. Default value 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 Return array of items, on which has logged user into context + * permission. + * @throws WebApplicationException + * It can be thrown by SQLException, when was problem with + * reading items from database or ContextException, when was + * problem with creating context of DSpace. + */ + @GET + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Item[] getItems(@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 items.(offset=" + offset + ",limit=" + limit + ")."); + org.dspace.core.Context context = null; + List items = null; + + try + { + context = createContext(getUser(headers)); + + ItemIterator dspaceItems = org.dspace.content.Item.findAllUnfiltered(context); + items = new ArrayList(); + + if (!((limit != null) && (limit >= 0) && (offset != null) && (offset >= 0))) + { + log.warn("Pagging was badly set, using default values."); + limit = 100; + offset = 0; + } + + for (int i = 0; (dspaceItems.hasNext()) && (i < (limit + offset)); i++) + { + org.dspace.content.Item dspaceItem = dspaceItems.next(); + if (i >= offset) + { + if (AuthorizeManager.authorizeActionBoolean(context, dspaceItem, org.dspace.core.Constants.READ)) + { + items.add(new Item(dspaceItem, expand, context)); + writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor, + headers, request); + } + } + } + context.complete(); + } + catch (SQLException e) + { + processException("Something went wrong while reading items from database. Message: " + e, context); + } + catch (ContextException e) + { + processException("Something went wrong while reading items, ContextException. Message: " + e.getMessage(), context); + } + finally + { + processFinally(context); + } + + log.trace("Items were successfully read."); + return items.toArray(new Item[0]); + } + + /** + * Returns item metadata in list. + * + * @param itemId + * Id of item in DSpace. + * @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 list of metadata fields if was everything ok. Otherwise it + * throw WebApplication exception with response code NOT_FOUND(404) + * or UNAUTHORIZED(401). + * @throws WebApplicationException + * It can be thrown by two exceptions: SQLException if was + * problem wtih reading item from database and ContextException, + * if was problem with creating context of DSpace. And can be + * thrown by NOT_FOUND and UNAUTHORIZED too. + */ + @GET + @Path("/{item_id}/metadata") + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public MetadataEntry[] getItemMetadata(@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("Reading item(id=" + itemId + ") metadata."); + org.dspace.core.Context context = null; + List metadata = null; + + try + { + context = createContext(getUser(headers)); + org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.READ); + + writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor, headers, request); + + metadata = new org.dspace.rest.common.Item(dspaceItem, "metadata", context).getMetadata(); + context.complete(); + } + catch (SQLException e) + { + processException("Could not read item(id=" + itemId + "), SQLException. Message: " + e, context); + } + catch (ContextException e) + { + processException("Could not read item(id=" + itemId + "), ContextException. Message: " + e.getMessage(), context); + } + finally + { + processFinally(context); + } + + log.trace("Item(id=" + itemId + ") metadata were successfully read."); + return metadata.toArray(new MetadataEntry[0]); + } + + /** + * Return array of bitstreams in item. It can be pagged. + * + * @param itemId + * Id of item in DSpace. + * @param limit + * How many items will be in array. + * @param offset + * On which index will start array. + * @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 pagged array of bitstreams in item. + * @throws WebApplicationException + * It can be throw by NOT_FOUND, UNAUTHORIZED, SQLException if + * was problem with reading from database and ContextException + * if was problem with creating context of DSpace. + */ + @GET + @Path("/{item_id}/bitstreams") + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Bitstream[] getItemBitstreams(@PathParam("item_id") Integer itemId, + @QueryParam("limit") @DefaultValue("20") 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 item(id=" + itemId + ") bitstreams.(offset=" + offset + ",limit=" + limit + ")"); + org.dspace.core.Context context = null; + List bitstreams = null; + try + { + context = createContext(getUser(headers)); + org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.READ); + + writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor, headers, request); + + List itemBitstreams = new Item(dspaceItem, "bitstreams", context).getBitstreams(); + + if ((offset + limit) > (itemBitstreams.size() - offset)) + { + bitstreams = itemBitstreams.subList(offset, itemBitstreams.size()); + } + else + { + bitstreams = itemBitstreams.subList(offset, offset + limit); + } + context.complete(); + } + catch (SQLException e) + { + processException("Could not read item(id=" + itemId + ") bitstreams, SQLExcpetion. Message: " + e, context); + } + catch (ContextException e) + { + processException("Could not read item(id=" + itemId + ") bitstreams, ContextException. Message: " + e.getMessage(), + context); + } + finally + { + processFinally(context); + } + + log.trace("Item(id=" + itemId + ") bitstreams were successfully read."); + return bitstreams.toArray(new Bitstream[0]); + } + + /** + * Adding metadata fields to item. If metadata key is in item, it will be + * added, NOT REPLACED! + * + * @param itemId + * Id of item in DSpace. + * @param metadata + * List of metadata fields, which will be added into item. + * @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 status code OK(200) if all was ok. UNAUTHORIZED(401) + * if user is not allowed to write to item. NOT_FOUND(404) if id of + * item is incorrect. + * @throws WebApplicationException + * It is throw by these exceptions: SQLException, if was problem + * with reading from database or writing to database. + * AuthorizeException, if was problem with authorization to item + * fields. ContextException, if was problem with creating + * context of DSpace. + */ + @POST + @Path("/{item_id}/metadata") + @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Response addItemMetadata(@PathParam("item_id") Integer itemId, List metadata, + @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, + @QueryParam("xforwarderfor") String xforwarderfor, @Context HttpHeaders headers, @Context HttpServletRequest request) + throws WebApplicationException + { + + log.info("Adding metadata to item(id=" + itemId + ")."); + org.dspace.core.Context context = null; + + try + { + context = createContext(getUser(headers)); + org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.WRITE); + + writeStats(dspaceItem, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwarderfor, headers, request); + + for (MetadataEntry entry : metadata) + { + // TODO Test with Java split + String data[] = mySplit(entry.getKey()); // Done by my split, because of java split was not function. + if ((data.length >= 2) && (data.length <= 3)) + { + dspaceItem.addMetadata(data[0], data[1], data[2], entry.getLanguage(), entry.getValue()); + } + } + dspaceItem.update(); + context.complete(); + + } + catch (SQLException e) + { + processException("Could not write metadata to item(id=" + itemId + "), SQLException. Message: " + e, context); + } + catch (AuthorizeException e) + { + processException("Could not write metadata to item(id=" + itemId + "), AuthorizeException. Message: " + e, context); + } + catch (ContextException e) + { + processException("Could not write metadata to item(id=" + itemId + "), ContextException. Message: " + e.getMessage(), + context); + } + finally + { + processFinally(context); + } + + log.info("Metadata to item(id=" + itemId + ") were successfully added."); + return Response.status(Status.OK).build(); + } + + /** + * Create bitstream in item. + * + * @param itemId + * Id of item in DSpace. + * @param inputStream + * Data of bitstream in inputStream. + * @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 bitstream with status code OK(200). If id of item is + * invalid , it returns status code NOT_FOUND(404). If user is not + * allowed to write to item, UNAUTHORIZED(401). + * @throws WebApplicationException + * It is thrown by these exceptions: SQLException, when was + * problem with reading/writing from/to database. + * AuthorizeException, when was problem with authorization to + * item and add bitstream to item. IOException, when was problem + * with creating file or reading from inpustream. + * ContextException. When was problem with creating context of + * DSpace. + */ + // TODO Add option to add bitsream by URI.(for very big files) + @POST + @Path("/{item_id}/bitstreams") + public Bitstream addItemBitstream(@PathParam("item_id") Integer itemId, InputStream inputStream, + @QueryParam("name") String name, @QueryParam("description") String description, + @QueryParam("groupId") Integer groupId, @QueryParam("year") Integer year, @QueryParam("month") Integer month, + @QueryParam("day") Integer day, @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, + @QueryParam("xforwarderfor") String xforwarderfor, @Context HttpHeaders headers, @Context HttpServletRequest request) + throws WebApplicationException + { + + log.info("Adding bitstream to item(id=" + itemId + ")."); + org.dspace.core.Context context = null; + Bitstream bitstream = null; + + try + { + context = createContext(getUser(headers)); + org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.WRITE); + + writeStats(dspaceItem, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwarderfor, headers, request); + + // Is better to add bitstream to ORIGINAL bundle or to item own? + log.trace("Creating bitstream in item."); + org.dspace.content.Bundle bundle = null; + org.dspace.content.Bitstream dspaceBitstream = null; + if ((dspaceItem.getBundles().length == 0) || (dspaceItem.getBundles() == null)) + { + log.trace("Creating bundle in item."); + dspaceBitstream = dspaceItem.createSingleBitstream(inputStream); + } + else + { + log.trace("Getting bundle from item."); + bundle = dspaceItem.getBundles()[0]; + dspaceBitstream = bundle.createBitstream(inputStream); + } + + dspaceBitstream.setSource("DSpace Rest api"); + + // Set bitstream name and description + if (name != null) + { + if (BitstreamResource.getMimeType(name) == null) + { + dspaceBitstream.setFormat(BitstreamFormat.findUnknown(context)); + } + else + { + dspaceBitstream.setFormat(BitstreamFormat.findByMIMEType(context, BitstreamResource.getMimeType(name))); + } + dspaceBitstream.setName(name); + } + if (description != null) + { + dspaceBitstream.setDescription(description); + } + + dspaceBitstream.update(); + + // Create policy for bitstream + if (groupId != null) + { + Bundle[] bundles = dspaceBitstream.getBundles(); + for (Bundle dspaceBundle : bundles) + { + List bitstreamsPolicies = dspaceBundle.getBitstreamPolicies(); + + // Remove default bitstream policies + List policiesToRemove = new ArrayList(); + 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); + } + + org.dspace.authorize.ResourcePolicy dspacePolicy = org.dspace.authorize.ResourcePolicy.create(context); + dspacePolicy.setAction(org.dspace.core.Constants.READ); + dspacePolicy.setGroup(Group.find(context, groupId)); + dspacePolicy.setResourceID(dspaceBitstream.getID()); + dspacePolicy.setResource(dspaceBitstream); + dspacePolicy.setResourceType(org.dspace.core.Constants.BITSTREAM); + if ((year != null) || (month != null) || (day != null)) + { + Date date = new Date(); + if (year != null) + { + date.setYear(year - 1900); + } + if (month != null) + { + date.setMonth(month - 1); + } + if (day != null) + { + date.setDate(day); + } + date.setHours(0); + date.setMinutes(0); + date.setSeconds(0); + dspacePolicy.setStartDate(date); + } + dspacePolicy.update(); + bitstreamsPolicies.add(dspacePolicy); + + dspaceBundle.replaceAllBitstreamPolicies(bitstreamsPolicies); + dspaceBundle.update(); + } + } + + dspaceBitstream = org.dspace.content.Bitstream.find(context, dspaceBitstream.getID()); + bitstream = new Bitstream(dspaceBitstream, ""); + + context.complete(); + + } + catch (SQLException e) + { + processException("Could not create bitstream in item(id=" + itemId + "), SQLException. Message: " + e, context); + } + catch (AuthorizeException e) + { + processException("Could not create bitstream in item(id=" + itemId + "), AuthorizeException. Message: " + e, context); + } + catch (IOException e) + { + processException("Could not create bitstream in item(id=" + itemId + "), IOException Message: " + e, context); + } + catch (ContextException e) + { + processException( + "Could not create bitstream in item(id=" + itemId + "), ContextException Message: " + e.getMessage(), context); + } + finally + { + processFinally(context); + } + + log.info("Bitstream(id=" + bitstream.getId() + ") was successfully added into item(id=" + itemId + ")."); + return bitstream; + } + + /** + * Replace all metadata in item with new passed metadata. + * + * @param itemId + * Id of item in DSpace. + * @param metadata + * List of metadata fields, which will replace old metadata in + * item. + * @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 status code: OK(200). NOT_FOUND(404) if item was not + * found, UNAUTHORIZED(401) if user is not allowed to write to item. + * @throws WebApplicationException + * It is thrown by: SQLException, when was problem with database + * reading or writting, AuthorizeException when was problem with + * authorization to item and metadata fields. And + * ContextException, when was problem with creating context of + * DSpace. + */ + @PUT + @Path("/{item_id}/metadata") + @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Response updateItemMetadata(@PathParam("item_id") Integer itemId, MetadataEntry[] metadata, + @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 metadata in item(id=" + itemId + ")."); + org.dspace.core.Context context = null; + + try + { + context = createContext(getUser(headers)); + org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.WRITE); + + writeStats(dspaceItem, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwarderfor, headers, request); + + log.trace("Deleting original metadata from item."); + for (MetadataEntry entry : metadata) + { + String data[] = mySplit(entry.getKey()); + if ((data.length >= 2) && (data.length <= 3)) + { + dspaceItem.clearMetadata(data[0], data[1], data[2], org.dspace.content.Item.ANY); + } + } + + log.trace("Adding new metadata to item."); + for (MetadataEntry entry : metadata) + { + String data[] = mySplit(entry.getKey()); + if ((data.length >= 2) && (data.length <= 3)) + { + dspaceItem.addMetadata(data[0], data[1], data[2], entry.getLanguage(), entry.getValue()); + } + } + + dspaceItem.update(); + context.complete(); + + } + catch (SQLException e) + { + processException("Could not update metadata in item(id=" + itemId + "), SQLException. Message: " + e, context); + } + catch (AuthorizeException e) + { + processException("Could not update metadata in item(id=" + itemId + "), AuthorizeException. Message: " + e, context); + } + catch (ContextException e) + { + processException( + "Could not update metadata in item(id=" + itemId + "), ContextException. Message: " + e.getMessage(), context); + } + finally + { + processFinally(context); + } + + log.info("Metadata of item(id=" + itemId + ") were successfully updated."); + return Response.status(Status.OK).build(); + } + + /** + * Delete item from DSpace. It delete bitstreams only from item bundle. + * + * @param itemId + * Id of item 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 status code: OK(200). NOT_FOUND(404) if item was not + * found, UNAUTHORIZED(401) if user is not allowed to delete item + * metadata. + * @throws WebApplicationException + * It can be thrown by: SQLException, when was problem with + * database reading. AuthorizeException, when was problem with + * authorization to item.(read and delete) IOException, when was + * problem with deleting bitstream file. ContextException, when + * was problem with creating context of DSpace. + */ + @DELETE + @Path("/{item_id}") + public Response deleteItem(@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("Deleting item(id=" + itemId + ")."); + org.dspace.core.Context context = null; + + try + { + context = createContext(getUser(headers)); + org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.DELETE); + + writeStats(dspaceItem, UsageEvent.Action.REMOVE, user_ip, user_agent, xforwarderfor, headers, request); + + log.trace("Deleting item."); + org.dspace.content.Collection collection = org.dspace.content.Collection.find(context, + dspaceItem.getCollections()[0].getID()); + collection.removeItem(dspaceItem); + context.complete(); + + } + catch (SQLException e) + { + processException("Could not delete item(id=" + itemId + "), SQLException. Message: " + e, context); + } + catch (AuthorizeException e) + { + processException("Could not delete item(id=" + itemId + "), AuthorizeException. Message: " + e, context); + throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); + } + catch (IOException e) + { + processException("Could not delete item(id=" + itemId + "), IOException. Message: " + e, context); + } + catch (ContextException e) + { + processException("Could not delete item(id=" + itemId + "), ContextException. Message: " + e.getMessage(), context); + } + finally + { + processFinally(context); + } + + log.info("Item(id=" + itemId + ") was successfully deleted."); + return Response.status(Status.OK).build(); + } + + /** + * Delete all item metadata. + * + * @param itemId + * Id of item in DSpace. + * @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 status code: OK(200). NOT_FOUND(404) if item was not + * found, UNAUTHORIZED(401) if user is not allowed to delete item + * metadata. + * @throws WebApplicationException + * It is thrown by three exceptions. SQLException, when was + * problem with reading item from database or editting metadata + * fields. AuthorizeException, when was problem with + * authorization to item. And ContextException, when was problem + * with creating context of DSpace. + */ + @DELETE + @Path("/{item_id}/metadata") + public Response deleteItemMetadata(@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("Deleting metadata in item(id=" + itemId + ")."); + org.dspace.core.Context context = null; + + try + { + context = createContext(getUser(headers)); + org.dspace.content.Item dspaceItem = findItem(context, itemId, org.dspace.core.Constants.WRITE); + + writeStats(dspaceItem, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwarderfor, headers, request); + + log.trace("Deleting metadata."); + // TODO Rewrite without deprecated object. Leave there only generated metadata. + DCValue[] value = dspaceItem.getMetadata("dc", "date", "accessioned", org.dspace.content.Item.ANY); + DCValue[] value2 = dspaceItem.getMetadata("dc", "date", "available", org.dspace.content.Item.ANY); + DCValue[] value3 = dspaceItem.getMetadata("dc", "identifier", "uri", org.dspace.content.Item.ANY); + DCValue[] value4 = dspaceItem.getMetadata("dc", "description", "provenance", org.dspace.content.Item.ANY); + + dspaceItem.clearMetadata(org.dspace.content.Item.ANY, org.dspace.content.Item.ANY, org.dspace.content.Item.ANY, + org.dspace.content.Item.ANY); + dspaceItem.update(); + + // Add there generated metadata + dspaceItem.addMetadata(value[0].schema, value[0].element, value[0].qualifier, null, value[0].value); + dspaceItem.addMetadata(value2[0].schema, value2[0].element, value2[0].qualifier, null, value2[0].value); + dspaceItem.addMetadata(value3[0].schema, value3[0].element, value3[0].qualifier, null, value3[0].value); + dspaceItem.addMetadata(value4[0].schema, value4[0].element, value4[0].qualifier, null, value4[0].value); + + dspaceItem.update(); + context.complete(); + + } + catch (SQLException e) + { + processException("Could not delete item(id=" + itemId + "), SQLException. Message: " + e, context); + } + catch (AuthorizeException e) + { + processException("Could not delete item(id=" + itemId + "), AuthorizeExcpetion. Message: " + e, context); + } + catch (ContextException e) + { + processException("Could not delete item(id=" + itemId + "), ContextException. Message:" + e.getMessage(), context); + } + finally + { + processFinally(context); + } + + log.info("Item(id=" + itemId + ") metadata were successfully deleted."); + return Response.status(Status.OK).build(); + } + + /** + * Delete bitstream from item bundle. + * + * @param itemId + * Id of item in DSpace. + * @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. + * @param bitstreamId + * Id of bitstream, which will be deleted from bundle. + * @return Return status code OK(200) if is all ok. NOT_FOUND(404) if item + * or bitstream was not found. UNAUTHORIZED(401) if user is not + * allowed to delete bitstream. + * @throws WebApplicationException + * It is thrown, when: Was problem with edditting database, + * SQLException. Or problem with authorization to item, bundle + * or bitstream, AuthorizeException. When was problem with + * deleting file IOException. Or problem with creating context + * of DSpace, ContextException. + */ + @DELETE + @Path("/{item_id}/bitstreams/{bitstream_id}") + public Response deleteItemBitstream(@PathParam("item_id") Integer itemId, @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 in item(id=" + itemId + ")."); + org.dspace.core.Context context = null; + + try + { + context = createContext(getUser(headers)); + org.dspace.content.Item item = findItem(context, itemId, org.dspace.core.Constants.WRITE); + + org.dspace.content.Bitstream bitstream = org.dspace.content.Bitstream.find(context, bitstreamId); + if (bitstream == null) + { + context.abort(); + log.warn("Bitstream(id=" + bitstreamId + ") was not found."); + return Response.status(Status.NOT_FOUND).build(); + } + else if (!AuthorizeManager.authorizeActionBoolean(context, bitstream, org.dspace.core.Constants.DELETE)) + { + context.abort(); + log.error("User(" + getUser(headers).getEmail() + ") is not allowed to delete bitstream(id=" + bitstreamId + ")."); + return Response.status(Status.UNAUTHORIZED).build(); + } + + writeStats(item, UsageEvent.Action.UPDATE, user_ip, user_agent, xforwarderfor, headers, request); + writeStats(bitstream, UsageEvent.Action.REMOVE, user_ip, user_agent, xforwarderfor, headers, + request); + + log.trace("Deleting bitstream..."); + for (org.dspace.content.Bundle bundle : item.getBundles()) + { + for (org.dspace.content.Bitstream bit : bundle.getBitstreams()) + { + if (bit == bitstream) + { + bundle.removeBitstream(bitstream); + } + } + } + + 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); + } + finally + { + processFinally(context); + } + + log.info("Bitstream(id=" + bitstreamId + ") from item(id=" + itemId + ") was successfuly deleted ."); + return Response.status(Status.OK).build(); + } + + /** + * Find items by one metadada field. + * + * @param metadataEntry + * Metadata field by which will be searched. + * @param scheme + * Scheme of metadata(key). + * @param value + * Value of metadata field. + * @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 array of founded items. + * @throws WebApplicationException + * It can be thrown: SQLException, when was problem with + * database reading. AuthorizeException. when was problem with + * authorization to item. IOException when was problem with + * reading from metadata field. ContextException, when was + * problem with creating context of DSpace. + */ + @POST + @Path("/find-by-metadata-field") + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Item[] findItemsByMetadataField(MetadataEntry metadataEntry, @QueryParam("expand") String expand, + @QueryParam("userIP") String user_ip, @QueryParam("userAgent") String user_agent, + @QueryParam("xforwarderfor") String xforwarderfor, @Context HttpHeaders headers, @Context HttpServletRequest request) + throws WebApplicationException + { + + log.info("Looking for item with metadata(key=" + metadataEntry.getKey() + ",value=" + metadataEntry.getValue() + + ", language=" + metadataEntry.getLanguage() + ")."); + org.dspace.core.Context context = null; + + List items = new ArrayList(); + String[] metadata = mySplit(metadataEntry.getKey()); + + try + { + context = createContext(getUser(headers)); + + // TODO Repair, it ends by error: + // "java.sql.SQLSyntaxErrorException: ORA-00932: inconsistent datatypes: expected - got CLOB" + /* + * if (metadata.length == 3){ + * itemIterator = org.dspace.content.Item.findByMetadataField(context, metadata[0], + * metadata[1], metadata[2], value); + * } else if (metadata.length == 2){ + * itemIterator = org.dspace.content.Item.findByMetadataField(context, metadata[0], + * metadata[1], null, value); + * } else { + * context.abort(); + * log.error("Finding failed, bad metadata key."); + * throw new WebApplicationException(Response.Status.NOT_FOUND); + * } + * + * if (itemIterator.hasNext()) { + * item = new Item(itemIterator.next(), "", context); + * } + */ + + // Must used own style. + if ((metadata.length < 2) || (metadata.length > 3)) + { + context.abort(); + log.error("Finding failed, bad metadata key."); + throw new WebApplicationException(Response.Status.NOT_FOUND); + } + + String sql = "SELECT ITEM_ID, TEXT_VALUE, TEXT_LANG, SHORT_ID, ELEMENT, QUALIFIER " + + "FROM METADATAVALUE " + + "JOIN METADATAFIELDREGISTRY ON METADATAVALUE.METADATA_FIELD_ID = METADATAFIELDREGISTRY.METADATA_FIELD_ID " + + "JOIN METADATASCHEMAREGISTRY ON METADATAFIELDREGISTRY.METADATA_SCHEMA_ID = METADATASCHEMAREGISTRY.METADATA_SCHEMA_ID " + + "WHERE " + + "SHORT_ID='" + metadata[0] + "' AND " + + "ELEMENT='" + metadata[1] + "' AND "; + if (metadata.length > 3) + { + sql += "QUALIFIER='" + metadata[2] + "' AND "; + } + sql += "dbms_lob.substr(TEXT_VALUE, 40)='" + metadataEntry.getValue() + "' AND "; + if (metadataEntry.getLanguage() != null) + { + sql += "TEXT_LANG='" + metadataEntry.getLanguage() + "'"; + } + else + { + sql += "TEXT_LANG is null"; + } + + TableRowIterator iterator = org.dspace.storage.rdbms.DatabaseManager.query(context, sql); + while (iterator.hasNext()) + { + TableRow row = iterator.next(); + org.dspace.content.Item dspaceItem = this.findItem(context, row.getIntColumn("ITEM_ID"), + org.dspace.core.Constants.READ); + Item item = new Item(dspaceItem, "", context); + writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwarderfor, headers, + request); + items.add(item); + } + + context.complete(); + + } + catch (SQLException e) + { + processException("Something get wrong while finding item. SQLException, Message: " + e, context); + } + catch (ContextException e) + { + processException("Context error:" + e.getMessage(), context); + } + finally + { + processFinally(context); + } + + if (items.size() == 0) + { + log.info("Items not found."); + } + else + { + log.info("Items were found."); + } + + return items.toArray(new Item[0]); + } + + /** + * Find item from DSpace database. It is encapsulation of method + * org.dspace.content.Item.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 item in DSpace. + * @param action + * Constant from org.dspace.core.Constants. + * @return It returns DSpace item. + * @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.Item findItem(org.dspace.core.Context context, int id, int action) throws WebApplicationException + { + org.dspace.content.Item item = null; + try + { + item = org.dspace.content.Item.find(context, id); + + if (item == null) + { + context.abort(); + log.warn("Item(id=" + id + ") was not found!"); + throw new WebApplicationException(Response.Status.NOT_FOUND); + } + else if (!AuthorizeManager.authorizeActionBoolean(context, item, action)) + { + context.abort(); + if (context.getCurrentUser() != null) + { + log.error("User(" + context.getCurrentUser().getEmail() + ") has not permission to " + + getActionString(action) + " item!"); + } + else + { + log.error("User(anonymous) has not permission to " + getActionString(action) + " item!"); + } throw new WebApplicationException(Response.Status.UNAUTHORIZED); } - } 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"); - } - } } + catch (SQLException e) + { + processException("Something get wrong while finding item(id=" + id + "). SQLException, Message: " + e, context); + } + return item; } - - - private void writeStats(org.dspace.core.Context context, Integer item_id, String user_ip, String user_agent, - String xforwarderfor, HttpHeaders headers, - HttpServletRequest request) { - - try{ - DSpaceObject item = DSpaceObject.find(context, Constants.ITEM, item_id); - - if(user_ip==null || user_ip.length()==0){ - new DSpace().getEventService().fireEvent( - new UsageEvent( - UsageEvent.Action.VIEW, - request, - context, - item)); - } else{ - new DSpace().getEventService().fireEvent( - new UsageEvent( - UsageEvent.Action.VIEW, - user_ip, - user_agent, - xforwarderfor, - context, - item)); - } - log.debug("fired event"); - - } catch(SQLException ex){ - log.error("SQL exception can't write usageEvent \n" + ex); - } - - } - } diff --git a/dspace-rest/src/main/java/org/dspace/rest/Resource.java b/dspace-rest/src/main/java/org/dspace/rest/Resource.java new file mode 100644 index 0000000000..f049b0582f --- /dev/null +++ b/dspace-rest/src/main/java/org/dspace/rest/Resource.java @@ -0,0 +1,292 @@ +/** + * 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.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.content.DSpaceObject; +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 person + * 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 static 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) + { + if ((context != null) && (context.isValid())) + { + context.abort(); + } + throw new ContextException("Could not create context, SQLException. Message: " + e, e); + } + } + + /** + * It write statistic about using REST api. + * @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(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) + { + if ((context != null) && (context.isValid())) + { + context.abort(); + } + log.error("Could not write usageEvent, SQLException. Message: " + e); + } + catch (ContextException e) + { + log.error("Could not write usageEvent, ContextException. Message: " + e.getMessage()); + } + finally + { + if ((context != null) && (context.isValid())) + { + context.abort(); + log.error("Something get wrong. Aborting context in finally statement."); + } + } + } + + /** + * 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 static 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); + } + + /** + * Process finally statement. It will print message to logger error stream + * and abort DSpace context, if was not properly ended. + * + * @param context + * Context which must be aborted. + * @throws WebApplicationException + * This exception is throw for user of REST api. + */ + protected void processFinally(org.dspace.core.Context context) throws WebApplicationException + { + if ((context != null) && (context.isValid())) + { + context.abort(); + log.error("Something get wrong. Aborting context in finally statement."); + 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 list = new ArrayList(); + 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 static EPerson getUser(HttpHeaders headers) + { + List 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; + } + + protected static String getToken(HttpHeaders headers) { + List list = headers.getRequestHeader(TokenHolder.TOKEN_HEADER); + String token = null; + if ((list != null) && (list.size() > 0)) + { + token = list.get(0); + return token; + } + return null; + } +} diff --git a/dspace-rest/src/main/java/org/dspace/rest/RestIndex.java b/dspace-rest/src/main/java/org/dspace/rest/RestIndex.java index 3c0188f676..48535c3cff 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/RestIndex.java +++ b/dspace-rest/src/main/java/org/dspace/rest/RestIndex.java @@ -7,39 +7,229 @@ */ package org.dspace.rest; +import java.io.UnsupportedEncodingException; +import java.sql.SQLException; +import java.util.List; + import javax.servlet.ServletContext; +import javax.ws.rs.Consumes; import javax.ws.rs.GET; +import javax.ws.rs.POST; import javax.ws.rs.Path; 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.Response; -/* -Root of API, should have documentation on where to find the other resources. +import org.apache.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.eperson.EPerson; +import org.dspace.rest.common.Status; +import org.dspace.rest.common.User; +import org.dspace.rest.exceptions.ContextException; + +/** + * 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("/") public class RestIndex { + private static Logger log = Logger.getLogger(RestIndex.class); + @javax.ws.rs.core.Context public static ServletContext servletContext; - /* - The "GET" annotation indicates this method will respond to HTTP Get requests. - The "Produces" annotation indicates the MIME response the method will return. + /** + * Return html page with information about REST api. It contains methods all + * methods provide by REST api. + * + * @return HTML page which has information about all methods of REST api. */ @GET @Produces(MediaType.TEXT_HTML) - public String sayHtmlHello() { - return "DSpace REST" + - "

DSpace REST API

" + - "" + + public String sayHtmlHello() { + // TODO Better graphics, add arguments to all methods. (limit, offset, item and so on) + return "DSpace REST - index" + + "" + + "

DSpace REST API

" + + "Server path: " + servletContext.getContextPath() + + "

Index

" + + "
    " + + "
  • GET / - It returns this page.
  • " + + "
  • GET /test - Return string \"REST api is running\". It is method for testing.
  • " + + "
  • 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\"
  • " + + "
  • POST /logout - Method for logout from DSpace RESTful api. You must post request with header \"rest-dspace-token\" token
  • " + + "
" + + "

Communities

" + + "
    " + + "
  • GET /communities - Returns array of all communities in DSpace.
  • " + + "
  • GET /communities/top-communities - Returns array of all top communities in DSpace.
  • " + + "
  • GET /communities/{communityId} - Returns community.
  • " + + "
  • GET /communities/{communityId}/collections - Returns array of collections of community.
  • " + + "
  • GET /communities/{communityId}/communities - Returns array of subcommunities of community.
  • " + + "
  • POST /communities - Create new community at top level. You must post community.
  • " + + "
  • POST /communities/{communityId}/collections - Create new collections in community. You must post collection.
  • " + + "
  • POST /communities/{communityId}/communities - Create new subcommunity in community. You must post community.
  • " + + "
  • PUT /communities/{communityId} - Update community.
  • " + + "
  • DELETE /communities/{communityId} - Delete community.
  • " + + "
  • DELETE /communities/{communityId}/collections/{collectionId} - Delete collection in community.
  • " + + "
  • DELETE /communities/{communityId}/communities/{communityId2} - Delete subcommunity in community.
  • " + + "
" + + "

Collections

" + + "
    " + + "
  • GET /collections - Return all collections of DSpace in array.
  • " + + "
  • GET /collections/{collectionId} - Return collection with id.
  • " + + "
  • GET /collections/{collectionId}/items - Return all items of collection.
  • " + + "
  • POST /collections/{collectionId}/items - Create posted item in collection.
  • " + + "
  • POST /collections/find-collection - Find collection by passed name.
  • " + + "
  • PUT /collections/{collectionId}
  • - Update collection. You muset post collection." + + "
  • DELETE /collections/{collectionId} - Delete collection from DSpace.
  • " + + "
  • DELETE /collections/{collectionId}/items/{itemId} - Delete item in collection.
  • " + + "
" + + "

Items

" + + "
    " + + "
  • GET /items - Return list of items.
  • " + + "
  • GET /items/{item id} - Return item.
  • " + + "
  • GET /items/{item id}/metadata - Return item metadata.
  • " + + "
  • GET /items/{item id}/bitstreams - Return item bitstreams.
  • " + + "
  • POST /items/find-by-metadata-field - Find items by metadata entry.
  • " + + "
  • POST /items/{item id}/metadata - Add metadata to item.
  • " + + "
  • POST /items/{item id}/bitstreams - Add bitstream to item.
  • " + + "
  • PUT /items/{item id}/metadata - Update metadata in item.
  • " + + "
  • DELETE /items/{item id} - Delete item.
  • " + + "
  • DELETE /items/{item id}/metadata - Clear item metadata.
  • " + + "
  • DELETE /items/{item id}/bitstreams/{bitstream id} - Delete item bitstream.
  • " + + "
" + + "

Bitstreams

" + + "
    " + + "
  • GET /bitstreams - Return all bitstreams in DSpace.
  • " + + "
  • GET /bitstreams/{bitstream id} - Return bitstream.
  • " + + "
  • GET /bitstreams/{bitstream id}/policy - Return bitstream policies.
  • " + + "
  • POST /bitstreams/{bitstream id}/retrieve - Return data of bitstream.
  • " + + "
  • POST /bitstreams/{bitstream id}/policy - Add policy to item.
  • " + + "
  • PUT /bitstreams/{bitstream id}/data - Update data of bitstream.
  • " + + "
  • PUT /bitstreams/{bitstream id} - Update metadata of bitstream.
  • " + + "
  • DELETE /bitstreams/{bitstream id} - Delete bitstream from DSpace.
  • " + + "
  • DELETE /bitstreams/{bitstream id}/policy/{policy_id} - Delete bitstream policy.
  • " + + "
" + " "; } + + /** + * 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) + { + log.info("REST Login Attempt failed for user: " + user.getEmail()); + return Response.status(Response.Status.FORBIDDEN).build(); + } else { + log.info("REST Login Success for user: " + user.getEmail()); + 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 list = headers.getRequestHeader(TokenHolder.TOKEN_HEADER); + String token = null; + boolean logout = false; + EPerson ePerson = null; + if (list != null) + { + token = list.get(0); + ePerson = TokenHolder.getEPerson(token); + logout = TokenHolder.logout(token); + } + if ((token == null) || (!logout)) + { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + + if(ePerson != null) { + log.info("REST Logout: " + ePerson.getEmail()); + } + return Response.ok().build(); + } + + /** + * ? status: OK + * authenticated: TRUE | FALSE + * epersonEMAIL: user@dspace.org + * epersonNAME: Joe User + * @param headers + * @return + */ + @GET + @Path("/status") + @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) + public Status status(@Context HttpHeaders headers) throws UnsupportedEncodingException { + org.dspace.core.Context context = null; + + try { + context = Resource.createContext(Resource.getUser(headers)); + EPerson ePerson = context.getCurrentUser(); + + if(ePerson != null) { + //DB EPerson needed since token won't have full info, need context + EPerson dbEPerson = EPerson.findByEmail(context, ePerson.getEmail()); + String token = Resource.getToken(headers); + Status status = new Status(dbEPerson.getEmail(), dbEPerson.getFullName(), token); + return status; + } + + } catch (ContextException e) + { + Resource.processException("Status context error: " + e.getMessage(), context); + } catch (SQLException e) { + Resource.processException("Status eperson db lookup error: " + e.getMessage(), context); + } catch (AuthorizeException e) { + Resource.processException("Status eperson authorize exception: " + e.getMessage(), context); + } finally { + context.abort(); + } + + //fallback status, unauth + return new Status(); + } + + } diff --git a/dspace-rest/src/main/java/org/dspace/rest/TokenHolder.java b/dspace-rest/src/main/java/org/dspace/rest/TokenHolder.java new file mode 100644 index 0000000000..75c1af77fa --- /dev/null +++ b/dspace-rest/src/main/java/org/dspace/rest/TokenHolder.java @@ -0,0 +1,154 @@ +/** + * 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 tokens = new HashMap(); // Map with pair Email,token + + private static Map persons = new HashMap(); // 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); + } + + 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); + } + finally + { + if ((context != null) && (context.isValid())) + { + context.abort(); + log.error("Something get wrong. Aborting context in finally statement."); + 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(); + } + +} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/Bitstream.java b/dspace-rest/src/main/java/org/dspace/rest/common/Bitstream.java index ffe28af1c0..87b99aecd2 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/common/Bitstream.java +++ b/dspace-rest/src/main/java/org/dspace/rest/common/Bitstream.java @@ -7,15 +7,17 @@ */ 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.util.ArrayList; import java.util.Arrays; 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. * User: peterdietz @@ -36,7 +38,9 @@ public class Bitstream extends DSpaceObject { private String retrieveLink; private CheckSum checkSum; private Integer sequenceId; - + + private ResourcePolicy[] policies = null; + public Bitstream() { } @@ -72,8 +76,22 @@ public class Bitstream extends DSpaceObject { if(expandFields.contains("parent") || expandFields.contains("all")) { parentObject = new DSpaceObject(bitstream.getParentObject()); + } else if(expandFields.contains("policies") || expandFields.contains("all")) { + List tempPolicies = new ArrayList(); + Bundle[] bundles = bitstream.getBundles(); + for (Bundle bundle : bundles) { + List 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 { this.addExpand("parent"); + this.addExpand("policies"); } if(!expandFields.contains("all")) { @@ -153,4 +171,13 @@ public class Bitstream extends DSpaceObject { this.checkSum = checkSum; } + public ResourcePolicy[] getPolicies() { + return policies; + } + + public void setPolicies(ResourcePolicy[] policies) { + this.policies = policies; + } + + } diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/Collection.java b/dspace-rest/src/main/java/org/dspace/rest/common/Collection.java index b8e32ab91d..45d8448ad2 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/common/Collection.java +++ b/dspace-rest/src/main/java/org/dspace/rest/common/Collection.java @@ -13,7 +13,6 @@ import org.dspace.content.ItemIterator; import org.dspace.core.Context; import javax.ws.rs.WebApplicationException; -import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import java.sql.SQLException; import java.util.ArrayList; diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/Community.java b/dspace-rest/src/main/java/org/dspace/rest/common/Community.java index 1c13561007..968f7132b9 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/common/Community.java +++ b/dspace-rest/src/main/java/org/dspace/rest/common/Community.java @@ -38,8 +38,9 @@ public class Community extends DSpaceObject{ private String copyrightText, introductoryText, shortDescription, sidebarText; private Integer countItems; + // Renamed because of xml annotation exception with this attribute and getSubCommunities. @XmlElement(name = "subcommunities", required = true) - private List subCommunities = new ArrayList(); + private List subcommunities = new ArrayList(); private List collections = new ArrayList(); @@ -87,10 +88,10 @@ public class Community extends DSpaceObject{ if(expandFields.contains("subCommunities") || expandFields.contains("all")) { org.dspace.content.Community[] communityArray = community.getSubcommunities(); - subCommunities = new ArrayList(); + subcommunities = new ArrayList(); for(org.dspace.content.Community subCommunity : communityArray) { if(AuthorizeManager.authorizeActionBoolean(context, subCommunity, org.dspace.core.Constants.READ)) { - subCommunities.add(new Community(subCommunity, null, context)); + subcommunities.add(new Community(subCommunity, null, context)); } else { log.info("Omitted restricted subCommunity: " + subCommunity.getID() + " _ " + subCommunity.getName()); } @@ -171,4 +172,13 @@ public class Community extends DSpaceObject{ public Bitstream getLogo() { return logo; } + + public List getSubcommunities() { + return subcommunities; + } + + public void setSubcommunities(List subcommunities) { + this.subcommunities = subcommunities; + } + } diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/DSpaceObject.java b/dspace-rest/src/main/java/org/dspace/rest/common/DSpaceObject.java index 864872d606..ee262eade5 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/common/DSpaceObject.java +++ b/dspace-rest/src/main/java/org/dspace/rest/common/DSpaceObject.java @@ -11,6 +11,7 @@ import org.atteo.evo.inflector.English; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; + import java.util.ArrayList; import java.util.List; @@ -26,9 +27,7 @@ public class DSpaceObject { private Integer id; private String name; - private String handle; - private String type; @XmlElement(name = "link", required = true) @@ -42,17 +41,17 @@ public class DSpaceObject { } public DSpaceObject(org.dspace.content.DSpaceObject dso) { - setID(dso.getID()); + setId(dso.getID()); setName(dso.getName()); setHandle(dso.getHandle()); setType(dso.getTypeText().toLowerCase()); } - public Integer getID() { + public Integer getId() { return id; } - public void setID(Integer id) { + public void setId(Integer id) { this.id = id; } @@ -74,7 +73,7 @@ public class DSpaceObject { public String getLink() { //TODO, get actual contextPath of /rest/ - return "/rest/" + English.plural(getType()) + "/" + getID(); + return "/RESTapi/" + English.plural(getType()) + "/" + getId(); } public String getType() { diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/Item.java b/dspace-rest/src/main/java/org/dspace/rest/common/Item.java index d21ba75cc8..199135c9a8 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/common/Item.java +++ b/dspace-rest/src/main/java/org/dspace/rest/common/Item.java @@ -17,6 +17,7 @@ import org.dspace.core.Context; import javax.ws.rs.WebApplicationException; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; + import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; @@ -29,6 +30,7 @@ import java.util.List; * Time: 4:50 PM * To change this template use File | Settings | File Templates. */ +@SuppressWarnings("deprecation") @XmlRootElement(name = "item") public class Item extends DSpaceObject { Logger log = Logger.getLogger(Item.class); @@ -39,11 +41,8 @@ public class Item extends DSpaceObject { Collection parentCollection; List parentCollectionList; - List parentCommunityList; - List metadata; - List bitstreams; public Item(){} @@ -63,9 +62,8 @@ public class Item extends DSpaceObject { metadata = new ArrayList(); 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) { - if (!MetadataExposure.isHidden(context, dcv.schema, dcv.element, dcv.qualifier)) { - metadata.add(new MetadataEntry(dcv.getField(), dcv.value)); - } + if (!MetadataExposure.isHidden(context, dcv.schema, dcv.element, dcv.qualifier)) + metadata.add(new MetadataEntry(dcv.getField(), dcv.value, dcv.language)); } } else { this.addExpand("metadata"); diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/MetadataEntry.java b/dspace-rest/src/main/java/org/dspace/rest/common/MetadataEntry.java index 18d28b0eab..b5e453a5c6 100644 --- a/dspace-rest/src/main/java/org/dspace/rest/common/MetadataEntry.java +++ b/dspace-rest/src/main/java/org/dspace/rest/common/MetadataEntry.java @@ -10,37 +10,58 @@ package org.dspace.rest.common; import javax.xml.bind.annotation.XmlRootElement; /** - * Created with IntelliJ IDEA. - * User: peterdietz - * Date: 9/20/13 - * Time: 5:51 PM - * To change this template use File | Settings | File Templates. + * @author peterdietz, Rostislav Novak (Computing and Information Centre, CTU in + * Prague) + * */ @XmlRootElement(name = "metadataentry") -public class MetadataEntry { +public class MetadataEntry +{ String key; + String value; - public MetadataEntry() {} + String language; - public MetadataEntry(String key, String value) { - this.key = key; - this.value = value; + public MetadataEntry() + { } - 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; } - public void setValue(String value) { + public void setValue(String value) + { this.value = value; } - public String getKey() { + public String getKey() + { return key; } - public void setKey(String key) { + public void setKey(String key) + { this.key = key; } + + public String getLanguage() + { + return language; + } + + public void setLanguage(String language) + { + this.language = language; + } + } diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/ResourcePolicy.java b/dspace-rest/src/main/java/org/dspace/rest/common/ResourcePolicy.java new file mode 100644 index 0000000000..b260901f23 --- /dev/null +++ b/dspace-rest/src/main/java/org/dspace/rest/common/ResourcePolicy.java @@ -0,0 +1,181 @@ +/** + * 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.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; + } + +} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/Status.java b/dspace-rest/src/main/java/org/dspace/rest/common/Status.java new file mode 100644 index 0000000000..e9bd4f1fb5 --- /dev/null +++ b/dspace-rest/src/main/java/org/dspace/rest/common/Status.java @@ -0,0 +1,105 @@ +/** + * 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.common; + +import org.codehaus.jackson.annotate.JsonProperty; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +import javax.xml.bind.annotation.XmlRootElement; + +/** + * Used to handle/determine status of REST API. + * Mainly to know your authentication status + * + */ +@XmlRootElement(name = "status") +public class Status +{ + private boolean okay; + private boolean authenticated; + private String email; + private String fullname; + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + private String token; + + public Status() { + setOkay(true); + setAuthenticated(false); + } + + public Status(String email, String fullname, String token) { + setOkay(true); + setAuthenticated(true); + setEmail(email); + setFullname(fullname); + setToken(token); + } + + public Status(EPerson eperson, String token) { + setOkay(true); + if(eperson != null) { + setAuthenticated(true); + setEmail(eperson.getEmail()); + setFullname(eperson.getFullName()); + setToken(token); + } else { + setAuthenticated(false); + } + } + + @JsonProperty("okay") + public boolean isOkay() + { + return this.okay; + } + + @JsonProperty("okay") + public void setOkay(boolean okay) + { + this.okay = okay; + } + + @JsonProperty("authenticated") + public boolean isAuthenticated() { + return authenticated; + } + + @JsonProperty("authenticated") + public void setAuthenticated(boolean authenticated) { + this.authenticated = authenticated; + } + + @JsonProperty("email") + public String getEmail() { + return email; + } + + @JsonProperty("email") + public void setEmail(String email) { + this.email = email; + } + + @JsonProperty("fullname") + public String getFullname() { + return fullname; + } + + @JsonProperty("fullname") + public void setFullname(String fullname) { + this.fullname = fullname; + } +} diff --git a/dspace-rest/src/main/java/org/dspace/rest/common/User.java b/dspace-rest/src/main/java/org/dspace/rest/common/User.java new file mode 100644 index 0000000000..dd9477e041 --- /dev/null +++ b/dspace-rest/src/main/java/org/dspace/rest/common/User.java @@ -0,0 +1,56 @@ +/** + * 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.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; + } + +} diff --git a/dspace-rest/src/main/java/org/dspace/rest/exceptions/ContextException.java b/dspace-rest/src/main/java/org/dspace/rest/exceptions/ContextException.java new file mode 100644 index 0000000000..7dec5caeb2 --- /dev/null +++ b/dspace-rest/src/main/java/org/dspace/rest/exceptions/ContextException.java @@ -0,0 +1,35 @@ +/** + * 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.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; + } + +} diff --git a/dspace-rest/src/main/webapp/WEB-INF/web.xml b/dspace-rest/src/main/webapp/WEB-INF/web.xml index 2167ea94ca..48dec6c01c 100644 --- a/dspace-rest/src/main/webapp/WEB-INF/web.xml +++ b/dspace-rest/src/main/webapp/WEB-INF/web.xml @@ -36,23 +36,23 @@ --> 1 + DSpace REST API - /* - + + + + + DSpace REST API + /* + + + CONFIDENTIAL + + + dspace-config @@ -84,20 +84,18 @@ org.dspace.servicemanager.servlet.DSpaceKernelServletContextListener - - org.springframework.web.context.ContextLoaderListener - - + + org.springframework.web.context.ContextLoaderListener + + - - - + \ No newline at end of file