From 63c9de308dc61eed11b1b34d5d81ef8181edd665 Mon Sep 17 00:00:00 2001 From: Paurikova2 <107862249+Paurikova2@users.noreply.github.com> Date: Wed, 3 May 2023 15:25:39 +0200 Subject: [PATCH] internal/migrate-clarin-dspace5-to-clarin-dspace7-continue (#220) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * export table data as json * controller for group import * upgrade values type in database * alter table * controller for group2group import * controller for group2eperson * Removed varchar limitations (#218) Co-authored-by: MilanMajchrák * create new cntroller for item and service for workspaceitem * changed Controller from ItemRest to WorkspaceRest * added test * fix error in controller * create workflowitem from workspaceitem * problem with adding submitter and creating workflowitem * import workflowitem and item * fix incorrect class in log' * 'reate bitstream without file, error with protected consturctor * test for create bundle for existing file * missing committed xml file * fix errors for bitstream validation * correction of the test errors * added primary bitstream to bitstream * test for import items * fix item tests - inArchive * fix import item tests * correction of found errors * controller for import eperson and user_registration * import logo of collection and community * modified bitstream import and added most_recent_checksum import * fix specifically date format * added test for import logo * new builder for bitstream without item and bundle, fix test errors * checkstyle and comments * fix tests * checkstyle and fix test * fix failed test * Fixed review issues. * 74-BE license definition missing attribute values * Fixed review requests. * Removed unnecessary files --------- Co-authored-by: milanmajchrak <90026355+milanmajchrak@users.noreply.github.com> Co-authored-by: MilanMajchrák --- .../java/org/dspace/content/Bitstream.java | 3 + .../content/ClarinBitstreamServiceImpl.java | 128 +++++++ .../dspace/content/CollectionServiceImpl.java | 28 +- .../dspace/content/CommunityServiceImpl.java | 31 +- .../ClarinWorkspaceItemServiceImpl.java | 74 ++++ .../content/service/CollectionService.java | 11 + .../content/service/CommunityService.java | 11 + .../clarin/ClarinBitstreamService.java | 58 +++ .../clarin/ClarinWorkspaceItemService.java | 57 +++ .../handle/HandleClarinServiceImpl.java | 4 +- ...07.28__Upgrade_to_Lindat_Clarin_schema.sql | 3 +- .../org/dspace/builder/BitstreamBuilder.java | 24 ++ .../builder/ClarinBitstreamBuilder.java | 77 ++++ .../rest/ClarinBitstreamImportController.java | 223 +++++++++++ .../ClarinLicenseImportRestController.java | 23 +- .../app/rest/ClarinLogoImportController.java | 170 +++++++++ .../ClarinEPersonImportController.java | 153 ++++++++ .../repository/ClarinGroupRestController.java | 189 ++++++++++ .../ClarinItemImportController.java | 350 ++++++++++++++++++ .../ClarinUserMetadataRestController.java | 1 - .../ClarinBitstreamImportControllerIT.java | 285 ++++++++++++++ .../rest/ClarinEPersonImportControllerIT.java | 216 +++++++++++ .../app/rest/ClarinGroupRestControllerIT.java | 111 ++++++ .../rest/ClarinItemImportControllerIT.java | 260 +++++++++++++ .../rest/ClarinLicenseImportControllerIT.java | 33 +- .../rest/ClarinLogoImportControllerIT.java | 85 +++++ .../app/rest/HandleRestRepositoryIT.java | 2 +- dspace/config/spring/api/core-services.xml | 2 + 28 files changed, 2576 insertions(+), 36 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/content/ClarinBitstreamServiceImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/content/clarin/ClarinWorkspaceItemServiceImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinBitstreamService.java create mode 100644 dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinWorkspaceItemService.java create mode 100644 dspace-api/src/test/java/org/dspace/builder/ClarinBitstreamBuilder.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinBitstreamImportController.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinLogoImportController.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinEPersonImportController.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinGroupRestController.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinItemImportController.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinBitstreamImportControllerIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinEPersonImportControllerIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinGroupRestControllerIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinItemImportControllerIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinLogoImportControllerIT.java diff --git a/dspace-api/src/main/java/org/dspace/content/Bitstream.java b/dspace-api/src/main/java/org/dspace/content/Bitstream.java index 0042bfb74ed..f7aaf224dcd 100644 --- a/dspace-api/src/main/java/org/dspace/content/Bitstream.java +++ b/dspace-api/src/main/java/org/dspace/content/Bitstream.java @@ -23,6 +23,7 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.clarin.ClarinBitstreamService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.hibernate.proxy.HibernateProxyHelper; @@ -83,6 +84,8 @@ public class Bitstream extends DSpaceObject implements DSpaceObjectLegacySupport * {@link org.dspace.content.service.BitstreamService#create(Context, Bundle, InputStream)} * or * {@link org.dspace.content.service.BitstreamService#create(Context, InputStream)} + * or + * {@link ClarinBitstreamService#create(Context, Bundle)} */ protected Bitstream() { } diff --git a/dspace-api/src/main/java/org/dspace/content/ClarinBitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ClarinBitstreamServiceImpl.java new file mode 100644 index 00000000000..d46df6b5d96 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/ClarinBitstreamServiceImpl.java @@ -0,0 +1,128 @@ +/** + * 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.content; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.sql.SQLException; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.dao.BitstreamDAO; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.BundleService; +import org.dspace.content.service.clarin.ClarinBitstreamService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.event.Event; +import org.dspace.storage.bitstore.DSBitStoreService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Service interface class for the Bitstream object created for Clarin-Dspace import. + * Contains methods needed to import bitstream when dspace5 migrating to dspace7. + * The implementation of this class is autowired by spring. + * This class should never be accessed directly. + * + * @author Michaela Paurikova (michaela.paurikova at dataquest.sk) + */ +//If this class wants to catch the Bitstream protected constructor, it must be in this package! +public class ClarinBitstreamServiceImpl implements ClarinBitstreamService { + /** + * log4j logger + */ + private static Logger log = org.apache.logging.log4j.LogManager.getLogger(ClarinBitstreamServiceImpl.class); + + // Checksum algorithm + private static final String CSA = "MD5"; + + @Autowired + private DSBitStoreService storeService; + @Autowired + protected BitstreamDAO bitstreamDAO; + @Autowired + protected AuthorizeService authorizeService; + @Autowired + protected BundleService bundleService; + @Autowired + protected BitstreamService bitstreamService; + + protected ClarinBitstreamServiceImpl() { + } + + @Override + public Bitstream create(Context context, Bundle bundle) throws SQLException, AuthorizeException { + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException( + "You must be an admin to create an empty bitstream"); + } + //create empty bitstream + Bitstream bitstream = bitstreamDAO.create(context, new Bitstream()); + + // Set the format to "unknown" + bitstreamService.setFormat(context, bitstream, null); + context.addEvent( + new Event(Event.CREATE, Constants.BITSTREAM, bitstream.getID(), + null, bitstreamService.getIdentifiers(context, bitstream))); + + //add bitstream to bundle if the bundle is entered + if (Objects.nonNull(bundle)) { + bundleService.addBitstream(context, bundle, bitstream); + } + log.debug("Created new empty Bitstream with id: " + bitstream.getID()); + return bitstream; + } + + @Override + public boolean addExistingFile(Context context, Bitstream bitstream, Long expectedSizeBytes, + String expectedCheckSum, String expectedChecksumAlgorithm) + throws IOException, SQLException, AuthorizeException { + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException( + "You must be an admin to add existing file to bitstream"); + } + if (Objects.isNull(bitstream) || StringUtils.isBlank(bitstream.getInternalId())) { + throw new IllegalStateException( + "Cannot add file to bitstream because it is entered incorrectly."); + } + //get file from assetstore based on internal_id + //recalculate check fields + storeService.put(bitstream, new ByteArrayInputStream(storeService.get(bitstream).readAllBytes())); + //check that new calculated values match the expected values + if (!valid(bitstream, expectedSizeBytes, expectedCheckSum, expectedChecksumAlgorithm)) { + //an error occurred - expected and calculated values do not match + //delete all created data + bitstreamService.delete(context, bitstream); + bitstreamService.expunge(context, bitstream); + log.debug("Cannot add file with internal id: " + + bitstream.getInternalId() + " to bitstream with id: " + bitstream.getID() + + " because the validation is incorrectly."); + return false; + } + bitstreamService.update(context, bitstream); + return true; + } + + /** + * Validation control. + * Control that expected values (method attributes) match with bitstream calculated values. + * @param bitstream bitstream + * @param expectedSizeBytes expected size bytes + * @param expectedCheckSum expected checksum + * @param expectedChecksumAlgorithm expected checksum algorithm + * @return bitstream values match with expected values + */ + private boolean valid(Bitstream bitstream, long expectedSizeBytes, + String expectedCheckSum, String expectedChecksumAlgorithm) { + return bitstream.getSizeBytes() == expectedSizeBytes && bitstream.getChecksum().equals(expectedCheckSum) && + bitstream.getChecksumAlgorithm().equals(expectedChecksumAlgorithm); + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index 84ca1692ccf..d36ddffddc9 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -365,23 +365,31 @@ public Bitstream setLogo(Context context, Collection collection, InputStream is) "collection_id=" + collection.getID())); } else { Bitstream newLogo = bitstreamService.create(context, is); - collection.setLogo(newLogo); - // now create policy for logo bitstream - // to match our READ policy - List policies = authorizeService - .getPoliciesActionFilter(context, collection, Constants.READ); - authorizeService.addPolicies(context, policies, newLogo); - - log.info(LogHelper.getHeader(context, "set_logo", - "collection_id=" + collection.getID() + "logo_bitstream_id=" - + newLogo.getID())); + //added for data migration by Upgrade Dspace-Clarin + addLogo(context, collection, newLogo); } collection.setModified(); return collection.getLogo(); } + @Override + public void addLogo(Context context, Collection collection, Bitstream newLogo) + throws SQLException, AuthorizeException { + collection.setLogo(newLogo); + + // now create policy for logo bitstream + // to match our READ policy + List policies = authorizeService + .getPoliciesActionFilter(context, collection, Constants.READ); + authorizeService.addPolicies(context, policies, newLogo); + + log.info(LogHelper.getHeader(context, "set_logo", + "collection_id=" + collection.getID() + "logo_bitstream_id=" + + newLogo.getID())); + } + @Override public Group createWorkflowGroup(Context context, Collection collection, int step) throws SQLException, AuthorizeException { diff --git a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java index 15ac1c58a69..d7e459fdf0a 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -119,7 +119,8 @@ public Community create(Community parent, Context context, String handle, // of 'anonymous' READ Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS); - authorizeService.createResourcePolicy(context, newCommunity, anonymousGroup, null, Constants.READ, null); + authorizeService.createResourcePolicy(context, newCommunity, anonymousGroup,null, + Constants.READ, null); communityDAO.save(context, newCommunity); @@ -241,22 +242,30 @@ public Bitstream setLogo(Context context, Community community, InputStream is) if (is != null) { Bitstream newLogo = bitstreamService.create(context, is); - community.setLogo(newLogo); - // now create policy for logo bitstream - // to match our READ policy - List policies = authorizeService - .getPoliciesActionFilter(context, community, Constants.READ); - authorizeService.addPolicies(context, policies, newLogo); - - log.info(LogHelper.getHeader(context, "set_logo", - "community_id=" + community.getID() + "logo_bitstream_id=" - + newLogo.getID())); + //added for data migration by Upgrade Dspace-Clarin + addLogo(context, community, newLogo); } return community.getLogo(); } + @Override + public void addLogo(Context context, Community community, Bitstream newLogo) + throws SQLException, AuthorizeException { + community.setLogo(newLogo); + + // now create policy for logo bitstream + // to match our READ policy + List policies = authorizeService + .getPoliciesActionFilter(context, community, Constants.READ); + authorizeService.addPolicies(context, policies, newLogo); + + log.info(LogHelper.getHeader(context, "set_logo", + "community_id=" + community.getID() + "logo_bitstream_id=" + + newLogo.getID())); + } + @Override public void update(Context context, Community community) throws SQLException, AuthorizeException { // Check authorisation diff --git a/dspace-api/src/main/java/org/dspace/content/clarin/ClarinWorkspaceItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/clarin/ClarinWorkspaceItemServiceImpl.java new file mode 100644 index 00000000000..542d984d5e4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/clarin/ClarinWorkspaceItemServiceImpl.java @@ -0,0 +1,74 @@ +/** + * 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.content.clarin; + +import java.sql.SQLException; +import java.util.Objects; +import java.util.UUID; + +import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Collection; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.dao.WorkspaceItemDAO; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.content.service.clarin.ClarinWorkspaceItemService; +import org.dspace.core.Context; +import org.dspace.core.LogHelper; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Service interface class for the WorkspaceItem object created for Clarin-Dspace import. + * Contains methods needed to import bitstream when dspace5 migrating to dspace7. + * The implementation of this class is autowired by spring. + * This class should never be accessed directly. + * + * @author Michaela Paurikova(michaela.paurikova at dataquest.sk) + */ +public class ClarinWorkspaceItemServiceImpl implements ClarinWorkspaceItemService { + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger( + ClarinWorkspaceItemServiceImpl.class); + @Autowired + private WorkspaceItemService workspaceItemService; + @Autowired + private WorkspaceItemDAO workspaceItemDAO; + + @Override + public WorkspaceItem create(Context context, Collection collection, boolean multipleTitles, boolean publishedBefore, + boolean multipleFiles, Integer stageReached, Integer pageReached, + boolean template) throws AuthorizeException, SQLException { + + //create empty workspace item with item + WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, false); + //set workspace item values based on input values + workspaceItem.setPublishedBefore(publishedBefore); + workspaceItem.setMultipleFiles(multipleFiles); + workspaceItem.setMultipleTitles(multipleTitles); + workspaceItem.setPageReached(pageReached); + workspaceItem.setStageReached(stageReached); + return workspaceItem; + } + + @Override + public WorkspaceItem find(Context context, UUID uuid) throws SQLException { + //find workspace item by its UUID + WorkspaceItem workspaceItem = workspaceItemDAO.findByID(context, WorkspaceItem.class, uuid); + + //create log if the workspace item is not found + if (log.isDebugEnabled()) { + if (Objects.nonNull(workspaceItem)) { + log.debug(LogHelper.getHeader(context, "find_workspace_item", + "not_found,workspace_item_uuid=" + uuid)); + } else { + log.debug(LogHelper.getHeader(context, "find_workspace_item", + "workspace_item_uuid=" + uuid)); + } + } + return workspaceItem; + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java index 90db5c73140..828789702a6 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java @@ -132,6 +132,17 @@ public Collection create(Context context, Community community, String handle, UU public Bitstream setLogo(Context context, Collection collection, InputStream is) throws AuthorizeException, IOException, SQLException; + /** + * Add the created logo bitstream to collection and create policy to logo bitstream. + * This method is added for data migration by Upgrade Clarin, where bitstream already exists. + * @param context context + * @param collection collection + * @param newLogo bitstream of new logo + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + public void addLogo(Context context, Collection collection, Bitstream newLogo) + throws SQLException, AuthorizeException; /** * Create a workflow group for the given step if one does not already exist. * Returns either the newly created group or the previously existing one. diff --git a/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java b/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java index c089bcec8df..c47d638b406 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CommunityService.java @@ -122,6 +122,17 @@ public Community create(Community parent, Context context, public Bitstream setLogo(Context context, Community community, InputStream is) throws AuthorizeException, IOException, SQLException; + /** + * Add the created logo bitstream to community and create policy to logo bitstream. + * This method is added for data migration by Upgrade Clarin, where bitstream already exists. + * @param context context + * @param community community + * @param newLogo bitstream of new logo + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + public void addLogo(Context context, Community community, Bitstream newLogo) + throws SQLException, AuthorizeException; /** * Create a default administrators group if one does not already exist. * Returns either the newly created group or the previously existing one. diff --git a/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinBitstreamService.java b/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinBitstreamService.java new file mode 100644 index 00000000000..b9a69706804 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinBitstreamService.java @@ -0,0 +1,58 @@ +/** + * 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.content.service.clarin; + +import java.io.IOException; +import java.sql.SQLException; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.core.Context; + +/** + * Service interface class for the Bitstream object created for Clarin-Dspace import. + * Contains methods needed to import bitstream when dspace5 migrating to dspace7. + * The implementation of this class is autowired by spring. + * + * @author Michaela Paurikova (michaela.paurikova at dataquest.sk) + */ +public interface ClarinBitstreamService { + + /** + * Create new empty bitstream without file and with bitstream format "unknown". + * Add bitstream to bundle if the bundle is entered. + * @param context context + * @param bundle The bundle in which our bitstream should be added. + * @return the newly created bitstream + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + public Bitstream create(Context context, Bundle bundle) throws SQLException, AuthorizeException; + + /** + * Add an existing file to bitstream. + * The file must be stored in assetstore under internal_id. Internal_id must be specified in input bitstream. + * Method transforms data from assetstore back into a stream of bits and fills the bitstream + * check fields (checksum, sizeBytes, checksum algorithm). + * The bitstream is stored into database only if the error was not occur: + * calculated and expected check fields values match. + * @param context context + * @param bitstream bitstream with entered internal_id + * @param expectedSizeBytes expected size + * @param expectedCheckSum expected checksum + * @param expectedChecksumAlgorithm expected checksum algorithm + * @return existing file was successfully added to the bitstream + * @throws IOException If a problem occurs while storing the bits + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + public boolean addExistingFile(Context context, Bitstream bitstream, Long expectedSizeBytes, + String expectedCheckSum, String expectedChecksumAlgorithm) + throws IOException, SQLException, AuthorizeException ; +} diff --git a/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinWorkspaceItemService.java b/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinWorkspaceItemService.java new file mode 100644 index 00000000000..2794db22640 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinWorkspaceItemService.java @@ -0,0 +1,57 @@ +/** + * 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.content.service.clarin; + +import java.sql.SQLException; +import java.util.UUID; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Collection; +import org.dspace.content.WorkspaceItem; +import org.dspace.core.Context; + +/** + * Service interface class for the WorkspaceItem object created for Clarin-Dspace import. + * Contains methods needed to import bitstream when dspace5 migrating to dspace7. + * The implementation of this class is autowired by spring. + * + * @author Michaela Paurikova(michaela.paurikova at dataquest.sk) + */ +public interface ClarinWorkspaceItemService { + + /** + * Create a new empty workspace item. + * Set workspace item attributes by its input values. + * @param context context + * @param collection Collection being submitted to + * @param multipleTitles contains multiple titles + * @param publishedBefore published before + * @param multipleFiles contains multiple files + * @param stageReached stage reached + * @param pageReached page reached + * @param template if true, the workspace item starts as a copy + * of the collection's template item + * @return created workspace item + * @throws AuthorizeException if authorization error + * @throws SQLException if database error + */ + public WorkspaceItem create(Context context, Collection collection, + boolean multipleTitles, boolean publishedBefore, + boolean multipleFiles, Integer stageReached, + Integer pageReached, boolean template) + throws AuthorizeException, SQLException; + + /*** + * Find workspace item by its UUID. + * @param context context + * @param uuid workspace item UUID + * @return found workspace item + * @throws SQLException if database error + */ + public WorkspaceItem find(Context context, UUID uuid) throws SQLException; +} diff --git a/dspace-api/src/main/java/org/dspace/handle/HandleClarinServiceImpl.java b/dspace-api/src/main/java/org/dspace/handle/HandleClarinServiceImpl.java index e9e3a81c52b..7ff8d14390f 100644 --- a/dspace-api/src/main/java/org/dspace/handle/HandleClarinServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/handle/HandleClarinServiceImpl.java @@ -26,7 +26,6 @@ import org.dspace.content.Community; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; -import org.dspace.content.MetadataFieldServiceImpl; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.content.service.ItemService; @@ -53,13 +52,14 @@ public class HandleClarinServiceImpl implements HandleClarinService { /** * log4j logger */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(MetadataFieldServiceImpl.class); + private static Logger log = org.apache.logging.log4j.LogManager.getLogger(HandleClarinServiceImpl.class); @Autowired(required = true) protected HandleDAO handleDAO; @Autowired(required = true) protected HandleClarinDAO handleClarinDAO; + protected SiteService siteService; @Autowired(required = true) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.2_2022.07.28__Upgrade_to_Lindat_Clarin_schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.2_2022.07.28__Upgrade_to_Lindat_Clarin_schema.sql index 99283bc116f..601a842418c 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.2_2022.07.28__Upgrade_to_Lindat_Clarin_schema.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.2_2022.07.28__Upgrade_to_Lindat_Clarin_schema.sql @@ -482,4 +482,5 @@ ALTER TABLE eperson ALTER COLUMN email TYPE character varying(256); ALTER TABLE metadatafieldregistry - ALTER COLUMN element TYPE character varying(128); \ No newline at end of file + +ALTER COLUMN element TYPE character varying(128); diff --git a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java index f72ce70cd4d..a5d387f70e5 100644 --- a/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/BitstreamBuilder.java @@ -11,6 +11,7 @@ import java.io.InputStream; import java.sql.SQLException; import java.util.List; +import java.util.UUID; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; @@ -272,4 +273,27 @@ protected DSpaceObjectService getService() { return bitstreamService; } + /** + * Delete the Test bitstream referred to by the given uuid. + * Implemented for Clarin Dspace. + * @param uuid UUID of Test Bitstream to delete + * @throws SQLException + * @throws IOException + */ + public static void deleteBitstream(UUID uuid) throws SQLException, IOException { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + Bitstream bitstream = bitstreamService.find(c, uuid); + if (bitstream != null) { + try { + bitstreamService.delete(c, bitstream); + bitstreamService.expunge(c, bitstream); + c.commit(); + } catch (AuthorizeException e) { + throw new RuntimeException(e); + } + } + c.complete(); + } + } } diff --git a/dspace-api/src/test/java/org/dspace/builder/ClarinBitstreamBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ClarinBitstreamBuilder.java new file mode 100644 index 00000000000..d5be559971f --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/ClarinBitstreamBuilder.java @@ -0,0 +1,77 @@ +/** + * 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.builder; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.SQLException; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bitstream; +import org.dspace.content.service.DSpaceObjectService; +import org.dspace.core.Context; + +/** + * Builder class to build bitstreams in test cases. + * This builder was created for Clarin-Dspace import. + * + * @author Michaela Paurikova(michaela.paurikova at dataquest.sk) + */ +public class ClarinBitstreamBuilder extends AbstractDSpaceObjectBuilder { + + private Bitstream bitstream; + + protected ClarinBitstreamBuilder(Context context) { + super(context); + } + + public static ClarinBitstreamBuilder createBitstream(Context context, InputStream is) + throws SQLException, IOException { + ClarinBitstreamBuilder builder = new ClarinBitstreamBuilder(context); + return builder.create(context, is); + } + + private ClarinBitstreamBuilder create(Context context, InputStream is) + throws SQLException, IOException { + this.context = context; + bitstream = bitstreamService.create(context, is); + + return this; + } + + @Override + public void cleanup() throws Exception { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + // Ensure object and any related objects are reloaded before checking to see what needs cleanup + bitstream = c.reloadEntity(bitstream); + if (bitstream != null) { + delete(c, bitstream); + c.complete(); + } + } + } + + @Override + protected DSpaceObjectService getService() { + return bitstreamService; + } + + @Override + public Bitstream build() throws SQLException, AuthorizeException { + try { + bitstreamService.update(context, bitstream); + context.dispatchEvents(); + indexingService.commit(); + } catch (Exception e) { + return null; + } + + return bitstream; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinBitstreamImportController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinBitstreamImportController.java new file mode 100644 index 00000000000..8e0fa20a528 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinBitstreamImportController.java @@ -0,0 +1,223 @@ +/** + * 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.app.rest; + +import static org.dspace.app.rest.utils.ContextUtil.obtainContext; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.converter.MetadataConverter; +import org.dspace.app.rest.model.BitstreamRest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.checker.service.MostRecentChecksumService; +import org.dspace.content.Bitstream; +import org.dspace.content.BitstreamFormat; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamFormatService; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.BundleService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.clarin.ClarinBitstreamService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * Specialized controller created for Clarin-Dspace import bitstream. + * + * @author Michaela Paurikova (michaela.paurikova at dataquest.sk) + */ +@RestController +@RequestMapping("/api/clarin/import/" + BitstreamRest.CATEGORY) +public class ClarinBitstreamImportController { + private static final Logger log = org.apache.logging.log4j.LogManager + .getLogger(ClarinBitstreamImportController.class); + @Autowired + private BundleService bundleService; + @Autowired + private ClarinBitstreamService clarinBitstreamService; + @Autowired + private BitstreamService bitstreamService; + @Autowired + private AuthorizeService authorizeService; + @Autowired + private MetadataConverter metadataConverter; + @Autowired + private ItemService itemService; + @Autowired + private BitstreamFormatService bitstreamFormatService; + @Autowired + private ConverterService converter; + @Autowired + private Utils utils; + @Autowired + private MostRecentChecksumService checksumService; + + /** + * Endpoint for import bitstream, whose file already exists in assetstore under internal_id + * from request param. + * The mapping for requested endpoint, for example + *
+     * {@code
+     * https:///api/clarin/import/core/bitstream
+     * }
+     * 
+ * @param request request + * @return created bitstream converted to rest object + */ + @PreAuthorize("hasAuthority('ADMIN')") + @RequestMapping(method = RequestMethod.POST, value = "/bitstream") + public BitstreamRest importBitstreamForExistingFile(HttpServletRequest request) { + Context context = obtainContext(request); + if (Objects.isNull(context)) { + throw new RuntimeException("Context is null!"); + } + Bundle bundle = null; + //bundle2bitstream + String bundleUUIDString = request.getParameter("bundle_id"); + if (StringUtils.isNotBlank(bundleUUIDString)) { + UUID bundleUUID = UUID.fromString(bundleUUIDString); + try { + bundle = bundleService.find(context, bundleUUID); + } catch (SQLException e) { + log.error("Something went wrong trying to find the Bundle with uuid: " + bundleUUID, e); + } + } + BitstreamRest bitstreamRest; + Bitstream bitstream; + Item item = null; + try { + //process bitstream creation + ObjectMapper mapper = new ObjectMapper(); + bitstreamRest = mapper.readValue(request.getInputStream(), BitstreamRest.class); + //create empty bitstream + bitstream = clarinBitstreamService.create(context, bundle); + //internal_id contains path to file + String internalId = request.getParameter("internal_id"); + bitstream.setInternalId(internalId); + String storeNumberString = request.getParameter("storeNumber"); + bitstream.setStoreNumber(getIntegerFromString(storeNumberString)); + String sequenceIdString = request.getParameter("sequenceId"); + Integer sequenceId = getIntegerFromString(sequenceIdString); + bitstream.setSequenceID(sequenceId); + //add bitstream format + String bitstreamFormatIdString = request.getParameter("bitstreamFormat"); + Integer bitstreamFormatId = getIntegerFromString(bitstreamFormatIdString); + BitstreamFormat bitstreamFormat = null; + if (!Objects.isNull(bitstreamFormatId)) { + bitstreamFormat = bitstreamFormatService.find(context, bitstreamFormatId); + } + bitstream.setFormat(context, bitstreamFormat); + String deletedString = request.getParameter("deleted"); + //join created bitstream with file stored in assetstore + if (!clarinBitstreamService.addExistingFile(context, bitstream, bitstreamRest.getSizeBytes(), + bitstreamRest.getCheckSum().getValue(), bitstreamRest.getCheckSum().getCheckSumAlgorithm())) { + return null; + } + + if (bitstreamRest.getMetadata().getMap().size() > 0) { + metadataConverter.setMetadata(context, bitstream, bitstreamRest.getMetadata()); + } + if (Boolean.parseBoolean(deletedString)) { + bitstreamService.delete(context, bitstream); + } else { + //set bitstream as primary bitstream for bundle + //if bitstream is not primary bitstream, bundle is null + String primaryBundleUUIDString = request.getParameter("primaryBundle_id"); + if (StringUtils.isNotBlank(primaryBundleUUIDString)) { + UUID primaryBundleUUID = UUID.fromString(primaryBundleUUIDString); + try { + Bundle primaryBundle = bundleService.find(context, primaryBundleUUID); + primaryBundle.setPrimaryBitstreamID(bitstream); + bundleService.update(context, primaryBundle); + } catch (SQLException e) { + log.error("Something went wrong trying to find the Bundle with uuid: " + + primaryBundleUUID, e); + } + } + } + bitstreamService.update(context, bitstream); + + if (bundle != null) { + List items = bundle.getItems(); + if (!items.isEmpty()) { + item = items.get(0); + } + if (item != null && !(authorizeService.authorizeActionBoolean(context, item, Constants.WRITE) + && authorizeService.authorizeActionBoolean(context, item, Constants.ADD))) { + throw new AccessDeniedException("You do not have write rights to update the Bundle's item"); + } + if (item != null) { + itemService.update(context, item); + } + bundleService.update(context, bundle); + } + bitstreamRest = converter.toRest(bitstream, utils.obtainProjection()); + context.commit(); + } catch (AuthorizeException | SQLException | IOException e) { + String message = "Something went wrong with trying to create the single bitstream for file " + + "with internal_id: " + + request.getParameter("internal_id") + + " for bundle with uuid: " + bundle.getID(); + log.error(message, e); + throw new RuntimeException("message", e); + } + return bitstreamRest; + } + + /** + * Update bitstream checksum for bitstream, whose are not yet updated. + * The mapping for requested endpoint, for example + *
+     * {@code
+     * https:///api/clarin/import/core/bitstream/checksum
+     * }
+     * 
+ * @param request request + * @throws SQLException if database error + */ + @PreAuthorize("hasAuthority('ADMIN')") + @RequestMapping(method = RequestMethod.POST, value = "/bitstream/checksum") + public void doUpdateBitstreamsChecksum(HttpServletRequest request) throws SQLException { + Context context = obtainContext(request); + if (Objects.isNull(context)) { + throw new RuntimeException("Context is null!"); + } + checksumService.updateMissingBitstreams(context); + } + + /** + * Convert String value to Integer. + * @param value input value + * @return input value converted to Integer + */ + private Integer getIntegerFromString(String value) { + Integer output = null; + if (StringUtils.isNotBlank(value)) { + output = Integer.parseInt(value); + } + return output; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinLicenseImportRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinLicenseImportRestController.java index 2dc6ae8b007..e0e92521964 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinLicenseImportRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinLicenseImportRestController.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.UUID; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.BadRequestException; @@ -27,8 +28,10 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.content.clarin.ClarinLicense; import org.dspace.content.clarin.ClarinLicenseLabel; +import org.dspace.content.clarin.ClarinUserRegistration; import org.dspace.content.service.clarin.ClarinLicenseLabelService; import org.dspace.content.service.clarin.ClarinLicenseService; +import org.dspace.content.service.clarin.ClarinUserRegistrationService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -53,13 +56,15 @@ @RequestMapping("/api/licenses/import") public class ClarinLicenseImportRestController { private static final Logger log = org.apache.logging.log4j.LogManager - .getLogger(BitstreamRestController.class); + .getLogger(ClarinLicenseImportRestController.class); private Dictionary licenseLabelsIds = new Hashtable<>(); private Dictionary> licenseToLicenseLabel = new Hashtable<>(); @Autowired private ClarinLicenseLabelService clarinLicenseLabelService; @Autowired private ClarinLicenseService clarinLicenseService; + @Autowired + private ClarinUserRegistrationService clarinUserRegistrationService; /** * This method import labels in json format into database. @@ -248,16 +253,21 @@ public ResponseEntity importLicenses(@RequestBody(required = false) List userRegistrations = clarinUserRegistrationService.findByEPersonUUID( + context, epersonId); + ClarinUserRegistration userRegistration = userRegistrations.size() > 0 ? + userRegistrations.get(0) : null; + //TODO + //String createdOnString = jsonLicense.get("created_on").asText(); if (Objects.nonNull(clarinLicenseService.findByName(context, name))) { errors.add(label_id); @@ -267,6 +277,7 @@ public ResponseEntity importLicenses(@RequestBody(required = false) List + * {@code + * https:///api/clarin/import/logo/community + * } + * + * @param request request + * @return community converted to the rest + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @RequestMapping(method = POST, path = "/community") + public CommunityRest addCommunityLogo(HttpServletRequest request) throws SQLException, AuthorizeException { + Context context = obtainContext(request); + if (Objects.isNull(context)) { + throw new RuntimeException("Context is null!"); + } + //get community and bitstream UUIDs + String communityUUIDString = request.getParameter("community_id"); + UUID communityUUID = UUID.fromString(communityUUIDString); + String bitstreamUUIDString = request.getParameter("bitstream_id"); + UUID bitstreamUUID = UUID.fromString(bitstreamUUIDString); + + //find community and bitstream + Community community = communityService.find(context, communityUUID); + Bitstream newLogo = bitstreamService.find(context, bitstreamUUID); + + //controls + if (Objects.isNull(community) || Objects.isNull(newLogo)) { + throw new UnprocessableEntityException( + "The input data are entered incorrectly!"); + } + if (!newLogo.getFormat(context).getShortDescription().equals("Unknown")) { + throw new UnprocessableEntityException( + "The bitstream format of bitstream with id: " + newLogo.getID() + " is not Unknown!"); + } + if (community.getLogo() != null) { + throw new UnprocessableEntityException( + "The community with the given uuid already has a logo: " + community.getID()); + } + + //add logo to community + communityService.addLogo(context, community, newLogo); + communityService.update(context, community); + bitstreamService.update(context, newLogo); + log.error("Logo with id: + " + newLogo.getID() + " was successfully added to community " + + "with id: " + community.getID()); + + CommunityRest communityRest = converter.toRest(community, utils.obtainProjection()); + context.commit(); + return communityRest; + } + + /** + * The endpoint for connecting collection with its logo, which is already stored in database as a bitstream. + * The mapping for requested endpoint, for example + *
+     * {@code
+     * https:///api/clarin/import/logo/collection
+     * }
+     * 
+ * @param request request + * @return collection converted to the rest + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @RequestMapping(method = POST, path = "/collection") + public CollectionRest addCollectionLogo(HttpServletRequest request) throws SQLException, AuthorizeException { + Context context = obtainContext(request); + if (Objects.isNull(context)) { + throw new RuntimeException("Context is null!"); + } + //get collection and bitstream UUIDs + String collectionUUIDString = request.getParameter("collection_id"); + UUID collectionUUID = UUID.fromString(collectionUUIDString); + String bitstreamUUIDString = request.getParameter("bitstream_id"); + UUID bitstreamUUID = UUID.fromString(bitstreamUUIDString); + + //find collection and bitstream + Collection collection = collectionService.find(context, collectionUUID); + Bitstream newLogo = bitstreamService.find(context, bitstreamUUID); + + //controls + if (Objects.isNull(collection) || Objects.isNull(newLogo)) { + throw new UnprocessableEntityException( + "The input data are entered incorrectly!"); + } + if (!newLogo.getFormat(context).getShortDescription().equals("Unknown")) { + throw new UnprocessableEntityException( + "The bitstream format of bitstream with id: " + newLogo.getID() + " is not Unknown!"); + } + if (collection.getLogo() != null) { + throw new UnprocessableEntityException( + "The collection with the given uuid already has a logo: " + collection.getID()); + } + + //add logo to collection + collectionService.addLogo(context, collection, newLogo); + collectionService.update(context, collection); + bitstreamService.update(context, newLogo); + log.error("Logo with id: + " + newLogo.getID() + " was successfully added to collection " + + "with id: " + collection.getID()); + + CollectionRest collectionRest = converter.toRest(collection, utils.obtainProjection()); + context.commit(); + return collectionRest; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinEPersonImportController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinEPersonImportController.java new file mode 100644 index 00000000000..3eaadcffc79 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinEPersonImportController.java @@ -0,0 +1,153 @@ +/** + * 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.app.rest.repository; + +import static org.dspace.app.rest.utils.ContextUtil.obtainContext; + +import java.sql.SQLException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Objects; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.model.EPersonRest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.clarin.ClarinUserRegistration; +import org.dspace.content.service.clarin.ClarinUserRegistrationService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.service.EPersonService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * Specialized controller created for Clarin-Dspace eperson import. + * + * @author Michaela Paurikova (michaela.paurikova at dataquest.sk) + */ +@RestController +@RequestMapping("/api/clarin/import/" + EPersonRest.EPERSON) +public class ClarinEPersonImportController { + @Autowired + private EPersonRestRepository ePersonRestRepository; + @Autowired + private ConverterService converter; + @Autowired + private EPersonService ePersonService; + @Autowired + private ClarinUserRegistrationService clarinUserRegistrationService; + @Autowired + private Utils utils; + + /** + * Endpoint for import eperson. Create user registration if it exists. + * The mapping for requested endpoint, for example + *
+     * {@code
+     * https:///api/clarin/import/eperson
+     * }
+     * 
+ * @param request request + * @return created eperson converted to rest + * @throws AuthorizeException if authorization error + * @throws SQLException if database error + */ + @PreAuthorize("hasAuthority('ADMIN')") + @RequestMapping(method = RequestMethod.POST) + public EPersonRest importEPerson(HttpServletRequest request) + throws AuthorizeException, SQLException { + Context context = obtainContext(request); + if (Objects.isNull(context)) { + throw new RuntimeException("Context is null!"); + } + + //get eperson attribute values + String selfRegisteredString = request.getParameter("selfRegistered"); + boolean selfRegistered = getBooleanFromString(selfRegisteredString); + String lastActiveString = request.getParameter("lastActive"); + Date lastActive; + try { + lastActive = getDateFromString(lastActiveString); + } catch (ParseException e) { + throw new RuntimeException("Cannot import eperson, because the last_active is entered incorrectly!"); + } + //salt and digest_algorithm are changing with password + EPersonRest epersonRest = ePersonRestRepository.createAndReturn(context); + EPerson eperson = ePersonService.find(context, UUID.fromString(epersonRest.getUuid())); + eperson.setSelfRegistered(selfRegistered); + eperson.setLastActive(lastActive); + ePersonService.update(context, eperson); + + String hasUserRegistrationString = request.getParameter("userRegistration"); + boolean userRegistration = getBooleanFromString(hasUserRegistrationString); + + //create user registration if exists + if (userRegistration) { + String organization = request.getParameter("organization"); + String confirmationString = request.getParameter("confirmation"); + boolean confirmation = getBooleanFromString(confirmationString); + + ClarinUserRegistration clarinUserRegistration = new ClarinUserRegistration(); + clarinUserRegistration.setOrganization(organization); + clarinUserRegistration.setConfirmation(confirmation); + clarinUserRegistration.setEmail(eperson.getEmail()); + clarinUserRegistration.setPersonID(eperson.getID()); + clarinUserRegistrationService.create(context, clarinUserRegistration); + } + epersonRest = converter.toRest(eperson, utils.obtainProjection()); + context.complete(); + + return epersonRest; + } + + /** + * Convert String value to boolean. + * @param value input value + * @return converted input value to boolean + */ + private boolean getBooleanFromString(String value) { + boolean output = false; + if (StringUtils.isNotBlank(value)) { + output = Boolean.parseBoolean(value); + } + return output; + } + + /** + * Convert String value to Date. + * Expects two possible date formats, but more can be added. + * @param value + * @return converted input value to Date + * @throws ParseException if parse error + */ + private Date getDateFromString(String value) throws ParseException { + Date output = null; + if (StringUtils.isBlank(value)) { + return null; + } + + DateFormat df; + df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + try { + output = df.parse(value); + } catch (ParseException e) { + df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + output = df.parse(value); + } + return output; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinGroupRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinGroupRestController.java new file mode 100644 index 00000000000..3369e2397c1 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinGroupRestController.java @@ -0,0 +1,189 @@ +/** + * 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.app.rest.repository; + +import static java.util.regex.Pattern.compile; +import static org.apache.http.HttpStatus.SC_NO_CONTENT; +import static org.dspace.app.rest.utils.ContextUtil.obtainContext; +import static org.dspace.app.rest.utils.RegexUtils.REGEX_UUID; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.GroupRest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.app.util.AuthorizeUtil; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.EPersonService; +import org.dspace.eperson.service.GroupService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Specialized controller created for Clarin-Dspace group import. + * + * @author Michaela Paurikova (michaela.paurikova at dataquest.sk) + */ +@RestController +@RequestMapping("/api/clarin/" + GroupRest.CATEGORY + "/" + GroupRest.GROUPS) +public class ClarinGroupRestController { + @Autowired + private GroupService groupService; + @Autowired + private EPersonService ePersonService; + @Autowired + Utils utils; + + /** + * Method to add one or more subgroups to a group. + * This method is similar with method addChildGroups in GroupRestController, + * but here we remove from incorrectly input grouplink letter \". + * The mapping for requested endpoint, for example + *
+     * {@code
+     * https:///api/clarin/eperson/groups/26453b4d-e513-44e8-8d5b-395f62972eff/subgroups
+     * }
+     * 
+ * @param uuid the uuid of the group to add the subgroups to + * @param response response + * @param request request + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @RequestMapping(method = POST, path = "/{uuid}/subgroups") + public void addChildGroups(@PathVariable UUID uuid, HttpServletResponse response, HttpServletRequest request) + throws SQLException, AuthorizeException { + + Context context = obtainContext(request); + Group parentGroup = groupService.find(context, uuid); + if (Objects.isNull(parentGroup)) { + throw new ResourceNotFoundException("parent group is not found for uuid: " + uuid); + } + + AuthorizeUtil.authorizeManageGroup(context, parentGroup); + List groupLinks = utils.getStringListFromRequest(request); + + List childGroups = new ArrayList<>(); + for (String groupLink : groupLinks) { + groupLink = groupLink.replace("\"", ""); + Optional childGroup = findGroup(context, groupLink); + if (!childGroup.isPresent() || !canAddGroup(context, parentGroup, childGroup.get())) { + throw new UnprocessableEntityException("cannot add child group: " + groupLink); + } + childGroups.add(childGroup.get()); + } + + for (Group childGroup : childGroups) { + groupService.addMember(context, parentGroup, childGroup); + } + // this is required to trigger the rebuild of the group2group cache + groupService.update(context, parentGroup); + context.complete(); + + response.setStatus(SC_NO_CONTENT); + } + + /** + * Method to add one or more members to a group. + * This method is similar with method addMembers in GroupRestController, + * but here we remove from incorrectly input grouplink letter \". + * The mapping for requested endpoint, for example + *
+     * {@code
+     * https:///api/clarin/eperson/groups/26453b4d-e513-44e8-8d5b-395f62972eff/epersons
+     * }
+     * 
+ * @param uuid the uuid of the group to add the members to + * @param response response + * @param request request + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @RequestMapping(method = POST, path = "/{uuid}/epersons") + public void addMembers(@PathVariable UUID uuid, HttpServletResponse response, HttpServletRequest request) + throws SQLException, AuthorizeException { + + Context context = obtainContext(request); + + Group parentGroup = groupService.find(context, uuid); + if (parentGroup == null) { + throw new ResourceNotFoundException("parent group is not found for uuid: " + uuid); + } + + AuthorizeUtil.authorizeManageGroup(context, parentGroup); + + List memberLinks = utils.getStringListFromRequest(request); + + List members = new ArrayList<>(); + for (String memberLink : memberLinks) { + memberLink = memberLink.replace("\"", ""); + Optional member = findEPerson(context, memberLink); + if (!member.isPresent()) { + throw new UnprocessableEntityException("cannot add child group: " + memberLink); + } + members.add(member.get()); + } + + for (EPerson member : members) { + groupService.addMember(context, parentGroup, member); + } + + context.complete(); + + response.setStatus(SC_NO_CONTENT); + } + + private Optional findGroup(Context context, String groupLink) throws SQLException { + + Group group = null; + + Pattern linkPattern = compile("^.*/(" + REGEX_UUID + ")/?$"); + Matcher matcher = linkPattern.matcher(groupLink); + if (matcher.matches()) { + group = groupService.find(context, UUID.fromString(matcher.group(1))); + } + + return Optional.ofNullable(group); + } + + private Optional findEPerson(Context context, String groupLink) throws SQLException { + + EPerson ePerson = null; + + Pattern linkPattern = compile("^.*/(" + REGEX_UUID + ")/?$"); + Matcher matcher = linkPattern.matcher(groupLink); + if (matcher.matches()) { + ePerson = ePersonService.find(context, UUID.fromString(matcher.group(1))); + } + + return Optional.ofNullable(ePerson); + } + + private boolean canAddGroup(Context context, Group parentGroup, Group childGroup) throws SQLException { + return !groupService.isParentOf(context, childGroup, parentGroup); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinItemImportController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinItemImportController.java new file mode 100644 index 00000000000..a2ce280499e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinItemImportController.java @@ -0,0 +1,350 @@ +/** + * 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.app.rest.repository; + +import static org.dspace.app.rest.utils.ContextUtil.obtainContext; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Objects; +import java.util.UUID; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.converter.MetadataConverter; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.WorkspaceItemRest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.InstallItemService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.content.service.clarin.ClarinWorkspaceItemService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.service.EPersonService; +import org.dspace.handle.service.HandleClarinService; +import org.dspace.util.UUIDUtils; +import org.dspace.workflow.WorkflowException; +import org.dspace.xmlworkflow.service.XmlWorkflowService; +import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * Specialized controller created for Clarin-Dspace import item, workspace item and workflow item. + * + * @author Michaela Paurikova (michaela.paurikova at dataquest.sk) + */ +@RestController +@RequestMapping("/api/clarin/import") +public class ClarinItemImportController { + @Autowired + private CollectionService collectionService; + @Autowired + private ClarinWorkspaceItemService clarinWorkspaceItemService; + @Autowired + private WorkspaceItemService workspaceItemService; + @Autowired + private MetadataConverter metadataConverter; + @Autowired + private ConverterService converter; + @Autowired + private ItemService itemService; + @Autowired + private Utils utils; + @Autowired + private HandleClarinService handleService; + @Autowired + XmlWorkflowService workflowService; + @Autowired(required = true) + protected AuthorizeService authorizeService; + @Autowired + private EPersonService ePersonService; + @Autowired + InstallItemService installItemService; + + /** + * Endpoint for import workspace item. + * The mapping for requested endpoint, for example + *
+     * {@code
+     * https:///api/clarin/import/workspaceitem
+     * }
+     * 
+ * @param request + * @return + * @throws AuthorizeException + * @throws SQLException + */ + @PreAuthorize("hasAuthority('ADMIN')") + @RequestMapping(method = RequestMethod.POST, value = "/workspaceitem") + public WorkspaceItemRest importWorkspaceItem(HttpServletRequest request) + throws AuthorizeException, SQLException { + Context context = obtainContext(request); + if (Objects.isNull(context)) { + throw new RuntimeException("Context is null!"); + } + + ObjectMapper mapper = new ObjectMapper(); + ItemRest itemRest = null; + try { + ServletInputStream input = request.getInputStream(); + itemRest = mapper.readValue(input, ItemRest.class); + } catch (IOException e1) { + throw new UnprocessableEntityException("Error parsing request body", e1); + } + + //get item attribute values + String owningCollectionUuidString = request.getParameter("owningCollection"); + String multipleTitlesString = request.getParameter("multipleTitles"); + String publishedBeforeString = request.getParameter("publishedBefore"); + String multipleFilesString = request.getParameter("multipleFiles"); + String stageReachedString = request.getParameter("stageReached"); + String pageReachedString = request.getParameter("pageReached"); + + UUID owningCollectionUuid = UUIDUtils.fromString(owningCollectionUuidString); + Collection collection = collectionService.find(context, owningCollectionUuid); + if (Objects.isNull(collection)) { + throw new DSpaceBadRequestException("The given owningCollection parameter is invalid: " + + owningCollectionUuid); + } + + //convert input values to correct formats + boolean multipleTitles = getBooleanFromString(multipleTitlesString); + boolean publishedBefore = getBooleanFromString(publishedBeforeString); + boolean multipleFiles = getBooleanFromString(multipleFilesString); + Integer stageReached = getIntegerFromString(stageReachedString); + Integer pageReached = getIntegerFromString(pageReachedString); + + //the submitter of created workspace item is the current user + //required submitter is different from the current user, so we need to save current user and set it for + //the time to create workspace item to required submitter + EPerson currUser = context.getCurrentUser(); + String epersonUUIDString = request.getParameter("epersonUUID"); + UUID epersonUUID = UUIDUtils.fromString(epersonUUIDString); + EPerson eperson = ePersonService.find(context, epersonUUID); + context.setCurrentUser(eperson); + //we have to turn off authorization system, because in service there are authorization controls + context.turnOffAuthorisationSystem(); + WorkspaceItem workspaceItem = clarinWorkspaceItemService.create(context, collection, multipleTitles, + publishedBefore, multipleFiles, stageReached, pageReached,false); + context.restoreAuthSystemState(); + //set current user back to saved current user + context.setCurrentUser(currUser); + + Item item = workspaceItem.getItem(); + //the method set withdraw to true and isArchived to false + if (itemRest.getWithdrawn()) { + //withdraw is working with eperson, not with the current user + context.setCurrentUser(eperson); + context.turnOffAuthorisationSystem(); + itemService.withdraw(context, item); + context.restoreAuthSystemState(); + context.setCurrentUser(currUser); + } + //set item attributes to input values + item.setArchived(itemRest.getInArchive()); + item.setDiscoverable(itemRest.getDiscoverable()); + item.setLastModified(itemRest.getLastModified()); + metadataConverter.setMetadata(context, item, itemRest.getMetadata()); + if (!Objects.isNull(itemRest.getHandle())) { + item.addHandle(handleService.findByHandle(context, itemRest.getHandle())); + } + + // save changes + workspaceItemService.update(context, workspaceItem); + itemService.update(context, item); + WorkspaceItemRest workspaceItemRest = converter.toRest(workspaceItem, utils.obtainProjection()); + context.complete(); + + return workspaceItemRest; + } + + /** + * Get item rest based on workspace item id. + * The mapping for requested endpoint, for example + *
+     * {@code
+     * https:///api/clarin/import/26453b4d-e513-44e8-8d5b-395f62972eff/item
+     * }
+     * 
+ * @param id workspace item id + * @param request request + * @return item of workspace item converted to rest + * @throws SQLException if database error + */ + @PreAuthorize("hasAuthority('ADMIN')") + @RequestMapping(method = RequestMethod.GET, path = "/{id}/item") + public ItemRest getWorkspaceitemItem(@PathVariable int id, HttpServletRequest request) throws SQLException { + Context context = obtainContext(request); + if (Objects.isNull(context)) { + throw new RuntimeException("Contex is null!"); + } + //find workspace item based on id + WorkspaceItem workspaceItem = workspaceItemService.find(context, id); + //return item of found workspace item + return converter.toRest(workspaceItem.getItem(), utils.obtainProjection()); + } + + /** + * Endpoint for import workflow item. + * The mapping for requested endpoint, for example + *
+     * {@code
+     * https:///api/clarin/import/workflowitem
+     * }
+     * 
+ * @param request request + * @return response entity + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + * @throws WorkflowException + * @throws IOException + */ + @PreAuthorize("hasAuthority('ADMIN')") + @RequestMapping(method = RequestMethod.POST, value = "/workflowitem") + public ResponseEntity importWorkflowItem(HttpServletRequest request) throws SQLException, AuthorizeException, + WorkflowException, IOException { + Context context = obtainContext(request); + if (Objects.isNull(context)) { + throw new RuntimeException("Contex is null!"); + } + + //workflow item is created from workspace item, so workspace item must be created before + //id of workspace item + String workspaceIdString = request.getParameter("id"); + WorkspaceItem wsi = workspaceItemService.find(context, Integer.parseInt(workspaceIdString)); + //create workflow item from workspace item + XmlWorkflowItem wf = workflowService.start(context, wsi); + context.commit(); + return new ResponseEntity<>("Import workflowitem was successful", HttpStatus.OK); + } + + /** + * Endpoint for import item. + * The mapping for requested endpoint, for example + *
+     * {@code
+     * https:///api/clarin/import/item
+     * }
+     * 
+ * @param request request + * @return created item converted to rest + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + */ + @PreAuthorize("hasAuthority('ADMIN')") + @RequestMapping(method = RequestMethod.POST, value = "/item") + public ItemRest importItem(HttpServletRequest request) throws SQLException, AuthorizeException { + Context context = obtainContext(request); + if (Objects.isNull(context)) { + throw new RuntimeException("Context is null!"); + } + + //each item has owning collection + String owningCollectionUuidString = request.getParameter("owningCollection"); + ObjectMapper mapper = new ObjectMapper(); + ItemRest itemRest = null; + try { + ServletInputStream input = request.getInputStream(); + itemRest = mapper.readValue(input, ItemRest.class); + } catch (IOException e1) { + throw new UnprocessableEntityException("Error parsing request body", e1); + } + + UUID owningCollectionUuid = UUIDUtils.fromString(owningCollectionUuidString); + //find owning collection of item + Collection collection = collectionService.find(context, owningCollectionUuid); + if (collection == null) { + throw new DSpaceBadRequestException("The given owningCollection parameter is invalid: " + + owningCollectionUuid); + } + + //if we want to create item, we have to firstly create workspace item + //submitter if workspace item is different from current user + EPerson currUser = context.getCurrentUser(); + String epersonUUIDString = request.getParameter("epersonUUID"); + UUID epersonUUID = UUIDUtils.fromString(epersonUUIDString); + EPerson eperson = ePersonService.find(context, epersonUUID); + context.setCurrentUser(eperson); + context.turnOffAuthorisationSystem(); + WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, false); + context.restoreAuthSystemState(); + context.setCurrentUser(currUser); + + //created item + Item item = workspaceItem.getItem(); + item.setOwningCollection(collection); + //the method set withdraw to true and isArchived to false + if (itemRest.getWithdrawn()) { + //withdraw is working with eperson, not with the current user + context.setCurrentUser(eperson); + context.turnOffAuthorisationSystem(); + itemService.withdraw(context, item); + context.restoreAuthSystemState(); + context.setCurrentUser(currUser); + } + item.setDiscoverable(itemRest.getDiscoverable()); + item.setLastModified(itemRest.getLastModified()); + metadataConverter.setMetadata(context, item, itemRest.getMetadata()); + if (!Objects.isNull(itemRest.getHandle())) { + item.addHandle(handleService.findByHandle(context, itemRest.getHandle())); + } + //remove workspaceitem and create collection2item + Item itemToReturn = installItemService.installItem(context, workspaceItem); + //set isArchived back to false + itemToReturn.setArchived(itemRest.getInArchive()); + itemService.update(context, itemToReturn); + itemRest = converter.toRest(itemToReturn, utils.obtainProjection()); + context.complete(); + return itemRest; + } + + /** + * Convert String input value to boolean. + * @param value input value + * @return converted input value to boolean + */ + private boolean getBooleanFromString(String value) { + boolean output = false; + if (StringUtils.isNotBlank(value)) { + output = Boolean.parseBoolean(value); + } + return output; + } + + /** + * Convert String input value to Integer. + * @param value input value + * @return converted input value to Integer + */ + private Integer getIntegerFromString(String value) { + Integer output = -1; + if (StringUtils.isNotBlank(value)) { + output = Integer.parseInt(value); + } + return output; + } +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinUserMetadataRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinUserMetadataRestController.java index 214edbbe798..1bac66f5e91 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinUserMetadataRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinUserMetadataRestController.java @@ -192,7 +192,6 @@ private void sendEmailWithDownloadLink(Context context, UUID bitstreamUUID, log.error("Cannot send the email because: " + e.getMessage()); throw new MessagingException(e.getMessage()); } - } private String getEmailFromUserMetadata(List clarinUserMetadataRestList) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinBitstreamImportControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinBitstreamImportControllerIT.java new file mode 100644 index 00000000000..bfd748b4a1f --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinBitstreamImportControllerIT.java @@ -0,0 +1,285 @@ +/** + * 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.app.rest; + +import static com.jayway.jsonpath.JsonPath.read; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.sql.SQLException; +import java.util.Map; +import java.util.UUID; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.dspace.app.rest.test.AbstractEntityIntegrationTest; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.BundleBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Bitstream; +import org.dspace.content.BitstreamFormat; +import org.dspace.content.Bundle; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamFormatService; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.BundleService; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +/** + * Integration test to test the /api/clarin/import/core/* endpoints + * + * @author Michaela Paurikova (michaela.paurikova at dataquest.sk) + */ +public class ClarinBitstreamImportControllerIT extends AbstractEntityIntegrationTest { + @Autowired + private AuthorizeService authorizeService; + @Autowired + private BitstreamService bitstreamService; + @Autowired + private BundleService bundleService; + private JsonNodeFactory jsonNodeFactory = new JsonNodeFactory(true); + @Autowired + private BitstreamFormatService bitstreamFormatService; + + private Bundle bundle; + private String token; + private BitstreamFormat bitstreamFormat; + private Bitstream bitstream; + private UUID uuid; + private String checkSumsAlg = "MD5"; + private long sizeBytes; + private String checkSum; + private String internalId; + private int storeNumber; + private boolean deleted; + private int sequence; + + + @Before + public void setup() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Item item = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .build(); + bundle = BundleBuilder.createBundle(context, item) + .withName("TESTINGBUNDLE") + .build(); + token = getAuthToken(admin.getEmail(), password); + bitstreamFormat = bitstreamFormatService.create(context); + String input = "Hello, World!"; + MockMultipartFile file = new MockMultipartFile("file", "hello.txt", + MediaType.TEXT_PLAIN_VALUE, + input.getBytes()); + context.restoreAuthSystemState(); + + //create bitstream and store file + MvcResult mvcResult = getClient(token) + .perform(MockMvcRequestBuilders.fileUpload("/api/core/bundles/" + + bundle.getID() + "/bitstreams") + .file(file)) + .andExpect(status().isCreated()) + .andReturn(); + ObjectMapper mapper = new ObjectMapper(); + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String bitstreamId = String.valueOf(map.get("id")); + bitstream = bitstreamService.find(context, UUID.fromString(bitstreamId)); + uuid = bitstream.getID(); + sizeBytes = bitstream.getSizeBytes(); + checkSum = bitstream.getChecksum(); + internalId = bitstream.getInternalId(); + storeNumber = bitstream.getStoreNumber(); + deleted = bitstream.isDeleted(); + sequence = bitstream.getSequenceID(); + + //delete bitstream + context.turnOffAuthorisationSystem(); + BitstreamBuilder.deleteBitstream(bitstream.getID()); + context.restoreAuthSystemState(); + bitstream = bitstreamService.find(context, UUID.fromString(bitstreamId)); + assertNull(bitstream); + } + + @Test + public void importBitstreamForExistingFileWithBundleTest() throws Exception { + //input data + ObjectNode checksumNode = jsonNodeFactory.objectNode(); + checksumNode.set("checkSumAlgorithm", jsonNodeFactory.textNode(checkSumsAlg)); + checksumNode.set("value", jsonNodeFactory.textNode(checkSum)); + ObjectNode node = jsonNodeFactory.objectNode(); + node.set("sizeBytes", jsonNodeFactory.textNode(Long.toString(sizeBytes))); + node.set("checkSum", checksumNode); + + //create new bitstream for existing file + ObjectMapper mapper = new ObjectMapper(); + uuid = UUID.fromString(read( getClient(token).perform(post("/api/clarin/import/core/bitstream") + .content(mapper.writeValueAsBytes(node)) + .contentType(contentType) + .param("internal_id", internalId) + .param("storeNumber", Integer.toString(storeNumber)) + .param("bitstreamFormat", Integer.toString(bitstreamFormat.getID())) + .param("deleted", Boolean.toString(deleted)) + .param("sequenceId", Integer.toString(sequence)) + .param("primaryBundle_id", "") + .param("bundle_id", bundle.getID().toString())) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(), + "$.id")); + + checkCreatedBitstream(uuid, internalId, storeNumber, bitstreamFormat.getID(), sequence, deleted, sizeBytes, + checkSum); + + //clean all + context.turnOffAuthorisationSystem(); + BitstreamBuilder.deleteBitstream(uuid); + context.restoreAuthSystemState(); + } + + @Test + public void importBitstreamForExistingFileWithoutBundleTest() throws Exception { + //input data + ObjectNode checksumNode = jsonNodeFactory.objectNode(); + checksumNode.set("checkSumAlgorithm", jsonNodeFactory.textNode(checkSumsAlg)); + checksumNode.set("value", jsonNodeFactory.textNode(checkSum)); + ObjectNode node = jsonNodeFactory.objectNode(); + node.set("sizeBytes", jsonNodeFactory.textNode(Long.toString(sizeBytes))); + node.set("checkSum", checksumNode); + + //create new bitstream for existing file + ObjectMapper mapper = new ObjectMapper(); + uuid = UUID.fromString(read( getClient(token).perform(post("/api/clarin/import/core/bitstream") + .content(mapper.writeValueAsBytes(node)) + .contentType(contentType) + .param("internal_id", internalId) + .param("storeNumber", Integer.toString(storeNumber)) + .param("bitstreamFormat", Integer.toString(bitstreamFormat.getID())) + .param("deleted", Boolean.toString(deleted)) + .param("sequenceId", Integer.toString(sequence)) + .param("primaryBundle_id", "") + .param("bundle_id", "")) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(), + "$.id")); + + checkCreatedBitstream(uuid, internalId, storeNumber, bitstreamFormat.getID(), sequence, deleted, sizeBytes, + checkSum); + + //clean all + context.turnOffAuthorisationSystem(); + BitstreamBuilder.deleteBitstream(uuid); + context.restoreAuthSystemState(); + } + + @Test + public void importBitstreamForExistingFileAsPrimaryBitstreamOfBundleTest() throws Exception { + //input data + ObjectNode checksumNode = jsonNodeFactory.objectNode(); + checksumNode.set("checkSumAlgorithm", jsonNodeFactory.textNode(checkSumsAlg)); + checksumNode.set("value", jsonNodeFactory.textNode(checkSum)); + ObjectNode node = jsonNodeFactory.objectNode(); + node.set("sizeBytes", jsonNodeFactory.textNode(Long.toString(sizeBytes))); + node.set("checkSum", checksumNode); + + //create new bitstream for existing file + ObjectMapper mapper = new ObjectMapper(); + uuid = UUID.fromString(read( getClient(token).perform(post("/api/clarin/import/core/bitstream") + .content(mapper.writeValueAsBytes(node)) + .contentType(contentType) + .param("internal_id", internalId) + .param("storeNumber", Integer.toString(storeNumber)) + .param("bitstreamFormat", Integer.toString(bitstreamFormat.getID())) + .param("deleted", Boolean.toString(deleted)) + .param("sequenceId", Integer.toString(sequence)) + .param("primaryBundle_id", bundle.getID().toString()) + .param("bundle_id", "")) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(), + "$.id")); + + checkCreatedBitstream(uuid, internalId, storeNumber, bitstreamFormat.getID(), sequence, deleted, sizeBytes, + checkSum); + bundle = bundleService.find(context, bundle.getID()); + assertEquals(bundle.getPrimaryBitstream().getID(), bitstream.getID()); + + //clean all + bundle.setPrimaryBitstreamID(null); + bundleService.update(context, bundle); + context.turnOffAuthorisationSystem(); + BitstreamBuilder.deleteBitstream(uuid); + context.restoreAuthSystemState(); + } + + @Test + public void importBitstreamForExistingFileValidationErrorTest() throws Exception { + assertEquals(bitstreamService.findAll(context).size(), 0); + //input data + ObjectNode checksumNode = jsonNodeFactory.objectNode(); + checksumNode.set("checkSumAlgorithm", jsonNodeFactory.textNode(checkSumsAlg)); + checksumNode.set("value", jsonNodeFactory.textNode("555")); + ObjectNode node = jsonNodeFactory.objectNode(); + node.set("sizeBytes", jsonNodeFactory.textNode(Long.toString(sizeBytes))); + node.set("checkSum", checksumNode); + + //create new bitstream for existing file + ObjectMapper mapper = new ObjectMapper(); + boolean emptyResponse = getClient(token).perform(post("/api/clarin/import/core/bitstream") + .content(mapper.writeValueAsBytes(node)) + .contentType(contentType) + .param("internal_id", internalId) + .param("storeNumber", Integer.toString(storeNumber)) + .param("bitstreamFormat", Integer.toString(bitstreamFormat.getID())) + .param("deleted", Boolean.toString(deleted)) + .param("sequenceId", Integer.toString(sequence)) + .param("primaryBundle_id", "") + .param("bundle_id", bundle.getID().toString())) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString().isEmpty(); + + //bitstream with validation error cannot be created + assertTrue(emptyResponse); + assertEquals(bitstreamService.findAll(context).size(), 0); + } + + private void checkCreatedBitstream(UUID uuid, String internalId, int storeNumber, + Integer bitstreamFormat, int sequence, boolean deleted, long sizeBytes, + String checkSum) throws SQLException { + bitstream = bitstreamService.find(context, uuid); + assertEquals(bitstream.getChecksum(), checkSum); + assertEquals(bitstream.getSizeBytes(), sizeBytes); + assertEquals(bitstream.getFormat(context).getID(), bitstreamFormat); + assertEquals(bitstream.getInternalId(), internalId); + assertEquals(bitstream.getStoreNumber(), storeNumber); + assertEquals(bitstream.getSequenceID(), sequence); + assertEquals(bitstream.isDeleted(), deleted); + assertEquals(bitstream.getChecksumAlgorithm(), "MD5"); + } +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinEPersonImportControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinEPersonImportControllerIT.java new file mode 100644 index 00000000000..aaa8183d9de --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinEPersonImportControllerIT.java @@ -0,0 +1,216 @@ +/** + * 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.app.rest; + +import static com.jayway.jsonpath.JsonPath.read; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.rest.model.EPersonRest; +import org.dspace.app.rest.model.MetadataRest; +import org.dspace.app.rest.model.MetadataValueRest; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.ClarinUserRegistrationBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.content.clarin.ClarinUserRegistration; +import org.dspace.content.service.clarin.ClarinUserRegistrationService; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.service.EPersonService; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration test to test the /api/clarin/import/eperson/* endpoints + * + * @author Michaela Paurikova (michaela.paurikova at dataquest.sk) + */ +public class ClarinEPersonImportControllerIT extends AbstractControllerIntegrationTest { + + @Autowired + private EPersonService ePersonService; + + @Autowired + private ClarinUserRegistrationService clarinUserRegistrationService; + + @Test + public void createEpersonWithUserRegistrationTest() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + EPersonRest data = new EPersonRest(); + MetadataRest metadataRest = new MetadataRest(); + data.setEmail("createtest@example.com"); + data.setCanLogIn(true); + MetadataValueRest surname = new MetadataValueRest(); + surname.setValue("Doe"); + metadataRest.put("eperson.lastname", surname); + MetadataValueRest firstname = new MetadataValueRest(); + firstname.setValue("John"); + metadataRest.put("eperson.firstname", firstname); + data.setMetadata(metadataRest); + + AtomicReference idRef = new AtomicReference(); + + String authToken = getAuthToken(admin.getEmail(), password); + + try { + getClient(authToken).perform(post("/api/clarin/import/eperson") + .content(mapper.writeValueAsBytes(data)) + .contentType(contentType) + .param("projection", "full") + .param("selfRegistered", "true") + .param("lastActive", "2018-02-10T13:21:29.733") + .param("userRegistration", "true") + .param("organization", "https://test.com") + .param("confirmation", "false")) + .andExpect(status().isOk()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); + + EPerson createdEperson = ePersonService.find(context, idRef.get()); + + assertEquals(getStringFromDate(createdEperson.getLastActive()), "2018-02-10T13:21:29.733"); + assertTrue(createdEperson.getSelfRegistered()); + assertEquals(createdEperson.getEmail(),"createtest@example.com"); + assertTrue(createdEperson.canLogIn()); + assertFalse(createdEperson.getRequireCertificate()); + assertEquals(createdEperson.getFirstName(), "John"); + assertEquals(createdEperson.getLastName(), "Doe"); + + //control the creation of the user registration + List userRegistrations = clarinUserRegistrationService.findByEPersonUUID( + context, idRef.get()); + assertEquals(userRegistrations.size(), 1); + ClarinUserRegistration userRegistration = userRegistrations.get(0); + assertEquals(userRegistration.getEmail(), "createtest@example.com"); + assertEquals(userRegistration.getOrganization(), "https://test.com"); + assertFalse(userRegistration.isConfirmation()); + //clean all + ClarinUserRegistrationBuilder.deleteClarinUserRegistration(userRegistration.getID()); + } finally { + EPersonBuilder.deleteEPerson(idRef.get()); + } + } + + @Test + public void createEpersonWithoutUserRegistrationTest() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + EPersonRest data = new EPersonRest(); + MetadataRest metadataRest = new MetadataRest(); + data.setEmail("createtest@example.com"); + data.setCanLogIn(true); + MetadataValueRest surname = new MetadataValueRest(); + surname.setValue("Doe"); + metadataRest.put("eperson.lastname", surname); + MetadataValueRest firstname = new MetadataValueRest(); + firstname.setValue("John"); + metadataRest.put("eperson.firstname", firstname); + data.setMetadata(metadataRest); + + AtomicReference idRef = new AtomicReference(); + + String authToken = getAuthToken(admin.getEmail(), password); + + try { + getClient(authToken).perform(post("/api/clarin/import/eperson") + .content(mapper.writeValueAsBytes(data)) + .contentType(contentType) + .param("projection", "full") + .param("selfRegistered", "true") + .param("lastActive", "2018-02-10T13:21:29.733") + .param("userRegistration", "false")) + .andExpect(status().isOk()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); + + EPerson createdEperson = ePersonService.find(context, idRef.get()); + assertEquals(getStringFromDate(createdEperson.getLastActive()), "2018-02-10T13:21:29.733"); + assertTrue(createdEperson.getSelfRegistered()); + + //control the creation of the user registration + List userRegistrations = clarinUserRegistrationService.findByEPersonUUID(context, + idRef.get()); + assertEquals(userRegistrations.size(), 0); + } finally { + EPersonBuilder.deleteEPerson(idRef.get()); + } + } + + @Test + public void createEpersonWithUserRegistrationDifferentLastActiveFormatTest() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + EPersonRest data = new EPersonRest(); + MetadataRest metadataRest = new MetadataRest(); + data.setEmail("createtest@example.com"); + data.setCanLogIn(true); + MetadataValueRest surname = new MetadataValueRest(); + surname.setValue("Doe"); + metadataRest.put("eperson.lastname", surname); + MetadataValueRest firstname = new MetadataValueRest(); + firstname.setValue("John"); + metadataRest.put("eperson.firstname", firstname); + data.setMetadata(metadataRest); + + AtomicReference idRef = new AtomicReference(); + + String authToken = getAuthToken(admin.getEmail(), password); + + try { + getClient(authToken).perform(post("/api/clarin/import/eperson") + .content(mapper.writeValueAsBytes(data)) + .contentType(contentType) + .param("projection", "full") + .param("selfRegistered", "true") + .param("lastActive", "2018-02-10T13:21:29.733") + .param("userRegistration", "true") + .param("organization", "https://test.com") + .param("confirmation", "false")) + .andExpect(status().isOk()) + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); + + EPerson createdEperson = ePersonService.find(context, idRef.get()); + + assertEquals(getStringFromDate(createdEperson.getLastActive()), "2018-02-10T13:21:29.733"); + assertTrue(createdEperson.getSelfRegistered()); + assertEquals(createdEperson.getEmail(),"createtest@example.com"); + assertTrue(createdEperson.canLogIn()); + assertFalse(createdEperson.getRequireCertificate()); + assertEquals(createdEperson.getFirstName(), "John"); + assertEquals(createdEperson.getLastName(), "Doe"); + + //control the creation of the user registration + List userRegistrations = clarinUserRegistrationService.findByEPersonUUID(context, + idRef.get()); + assertEquals(userRegistrations.size(), 1); + ClarinUserRegistration userRegistration = userRegistrations.get(0); + assertEquals(userRegistration.getEmail(), "createtest@example.com"); + assertEquals(userRegistration.getOrganization(), "https://test.com"); + assertFalse(userRegistration.isConfirmation()); + //clean all + ClarinUserRegistrationBuilder.deleteClarinUserRegistration(userRegistration.getID()); + } finally { + EPersonBuilder.deleteEPerson(idRef.get()); + } + } + + private String getStringFromDate(Date value) throws ParseException { + DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + return df.format(value); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinGroupRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinGroupRestControllerIT.java new file mode 100644 index 00000000000..d73c62d6c7f --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinGroupRestControllerIT.java @@ -0,0 +1,111 @@ +/** + * 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.app.rest; + +import static org.junit.Assert.assertTrue; +import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE; +import static org.springframework.http.MediaType.parseMediaType; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.content.Collection; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.GroupService; +import org.junit.Before; +import org.junit.Test; + +/** + * Integration test to test the /api/clarin/eperson/groups/* endpoints. + * + * @author Michaela Paurikova (michaela.paurikova at dataquest.sk) + */ +public class ClarinGroupRestControllerIT extends AbstractControllerIntegrationTest { + Collection collection; + + @Before + public void setup() { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("test").build(); + collection = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + context.restoreAuthSystemState(); + } + + @Test + public void addChildGroupTest() throws Exception { + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + context.turnOffAuthorisationSystem(); + EPerson member = EPersonBuilder.createEPerson(context).build(); + Group parentGroup = GroupBuilder.createGroup(context).build(); + Group parentGroupWithPreviousSubgroup = GroupBuilder.createGroup(context).build(); + Group subGroup = GroupBuilder.createGroup(context).withParent(parentGroupWithPreviousSubgroup) + .addMember(eperson).build(); + Group childGroup1 = GroupBuilder.createGroup(context).addMember(member).build(); + Group childGroup2 = GroupBuilder.createGroup(context).build(); + context.restoreAuthSystemState(); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform( + post("/api/clarin/eperson/groups/" + parentGroup.getID() + "/subgroups") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() + ) + ).andExpect(status().isNoContent()); + getClient(authToken).perform( + post("/api/clarin/eperson/groups/" + parentGroupWithPreviousSubgroup.getID() + "/subgroups") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + childGroup1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + childGroup2.getID() + ) + ).andExpect(status().isNoContent()); + + parentGroup = context.reloadEntity(parentGroup); + parentGroupWithPreviousSubgroup = context.reloadEntity(parentGroupWithPreviousSubgroup); + subGroup = context.reloadEntity(subGroup); + childGroup1 = context.reloadEntity(childGroup1); + childGroup2 = context.reloadEntity(childGroup2); + + assertTrue( + groupService.isMember(parentGroup, childGroup1) + ); + assertTrue( + groupService.isMember(parentGroup, childGroup2) + ); + // member of the added groups should be member of the group now + assertTrue( + groupService.isMember(context, member, parentGroup) + ); + + // verify that the previous subGroup is still here + assertTrue( + groupService.isMember(parentGroupWithPreviousSubgroup, childGroup1) + ); + assertTrue( + groupService.isMember(parentGroupWithPreviousSubgroup, childGroup2) + ); + assertTrue( + groupService.isMember(parentGroupWithPreviousSubgroup, subGroup) + ); + // and that both the member of the added groups than existing ones are still member + assertTrue( + groupService.isMember(context, member, parentGroupWithPreviousSubgroup) + ); + assertTrue( + groupService.isMember(context, eperson, parentGroupWithPreviousSubgroup) + ); + + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinItemImportControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinItemImportControllerIT.java new file mode 100644 index 00000000000..ea2c074b6cc --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinItemImportControllerIT.java @@ -0,0 +1,260 @@ +/** + * 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.app.rest; + +import static com.jayway.jsonpath.JsonPath.read; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.List; +import java.util.UUID; +import javax.ws.rs.core.MediaType; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.WorkflowItemBuilder; +import org.dspace.builder.WorkspaceItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; +import org.dspace.workflow.WorkflowItem; +import org.dspace.workflow.WorkflowItemService; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration test to test the /api/clarin/import/* endpoints + * + * @author Michaela Paurikova (michaela.paurikova at dataquest.sk) + */ +public class ClarinItemImportControllerIT extends AbstractControllerIntegrationTest { + private JsonNodeFactory jsonNodeFactory = new JsonNodeFactory(true); + @Autowired + private WorkspaceItemService workspaceItemService; + @Autowired + private WorkflowItemService workflowItemService; + @Autowired + private ItemService itemService; + @Autowired + private ConfigurationService configurationService; + + private Collection col; + private EPerson submitter; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + col = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1") + .withWorkflowGroup(1, admin).build(); + submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword("dspace") + .build(); + context.restoreAuthSystemState(); + } + + @Test + public void importWorkspaceItemAndItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + ObjectNode node = jsonNodeFactory.objectNode(); + node.set("discoverable", jsonNodeFactory.textNode("false")); + node.set("inArchive", jsonNodeFactory.textNode("false")); + node.set("lastModified", jsonNodeFactory.textNode(null)); + context.restoreAuthSystemState(); + + ObjectMapper mapper = new ObjectMapper(); + String token = getAuthToken(admin.getEmail(), password); + + int id = read(getClient(token).perform(post("/api/clarin/import/workspaceitem") + .content(mapper.writeValueAsBytes(node)) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON) + .param("owningCollection", col.getID().toString()) + .param("multipleTitles", "true") + .param("publishedBefore", "false") + .param("multipleFiles", "false") + .param("stageReached", "1") + .param("pageReached", "123") + .param("epersonUUID", submitter.getID().toString())) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(), + "$.id"); + + WorkspaceItem workspaceItem = workspaceItemService.find(context, id); + assertNotNull(workspaceItem); + assertTrue(workspaceItem.hasMultipleTitles()); + assertFalse(workspaceItem.isPublishedBefore()); + assertFalse(workspaceItem.hasMultipleFiles()); + assertEquals(workspaceItem.getStageReached(), 1); + assertEquals(workspaceItem.getPageReached(), 123); + assertEquals(workspaceItem.getCollection().getID(), col.getID()); + + UUID uuid = UUID.fromString(read(getClient(token).perform(get("/api/clarin/import/" + id + "/item")) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(), + "$.id")); + Item item = itemService.find(context, uuid); + assertNotNull(item); + assertEquals(item.getSubmitter().getID(), submitter.getID()); + assertFalse(item.isDiscoverable()); + assertFalse(item.isArchived()); + assertNull(item.getOwningCollection()); + + //clean all + context.turnOffAuthorisationSystem(); + WorkspaceItemBuilder.deleteWorkspaceItem(workspaceItem.getID()); + context.restoreAuthSystemState(); + } + + @Test + public void importWorkflowItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + ObjectNode node = jsonNodeFactory.objectNode(); + node.set("discoverable", jsonNodeFactory.textNode("false")); + node.set("inArchive", jsonNodeFactory.textNode("false")); + node.set("lastModified", jsonNodeFactory.textNode(null)); + context.restoreAuthSystemState(); + + ObjectMapper mapper = new ObjectMapper(); + String token = getAuthToken(admin.getEmail(), password); + + int id = read(getClient(token).perform(post("/api/clarin/import/workspaceitem") + .content(mapper.writeValueAsBytes(node)) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON) + .param("owningCollection", col.getID().toString()) + .param("multipleTitles", "true") + .param("publishedBefore", "false") + .param("multipleFiles", "false") + .param("stageReached", "1") + .param("pageReached", "123") + .param("epersonUUID", submitter.getID().toString())) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(), + "$.id"); + + getClient(token).perform(post("/api/clarin/import/workflowitem") + .contentType(MediaType.APPLICATION_JSON) + .param("id", Integer.toString(id))) + .andExpect(status().isOk()); + + List workflowItems = workflowItemService.findAll(context); + assertEquals(workflowItems.size(), 1); + WorkflowItem workflowItem = workflowItems.get(0); + assertEquals(workflowItem.getCollection().getID(), col.getID()); + assertFalse(workflowItem.isPublishedBefore()); + assertFalse(workflowItem.hasMultipleFiles()); + assertTrue(workflowItem.hasMultipleTitles()); + Item item = itemService.find(context, workflowItem.getItem().getID()); + assertNotNull(item); + assertEquals(item.getSubmitter().getID(), submitter.getID()); + assertFalse(item.isDiscoverable()); + assertFalse(item.isArchived()); + assertNull(item.getOwningCollection()); + + //clean all + context.turnOffAuthorisationSystem(); + WorkflowItemBuilder.deleteWorkflowItem(workflowItem.getID()); + context.restoreAuthSystemState(); + } + + @Test + public void importWithdrawnItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + ObjectNode node = jsonNodeFactory.objectNode(); + node.set("withdrawn", jsonNodeFactory.textNode("true")); + node.set("inArchive", jsonNodeFactory.textNode("false")); + node.set("discoverable", jsonNodeFactory.textNode("true")); + context.restoreAuthSystemState(); + + ObjectMapper mapper = new ObjectMapper(); + String token = getAuthToken(admin.getEmail(), password); + + UUID uuid = UUID.fromString(read(getClient(token).perform(post("/api/clarin/import/item") + .content(mapper.writeValueAsBytes(node)) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON) + .param("owningCollection", col.getID().toString()) + .param("epersonUUID", submitter.getID().toString())) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(), + "$.id")); + + //workspaceitem should nt exist + List workflowItems = workspaceItemService.findAll(context); + assertEquals(workflowItems.size(), 0); + //contoling of the created item + Item item = itemService.find(context, uuid); + assertTrue(item.isWithdrawn()); + assertFalse(item.isArchived()); + assertTrue(item.isDiscoverable()); + assertEquals(item.getSubmitter().getID(), submitter.getID()); + assertEquals(item.getOwningCollection().getID(), col.getID()); + + //clean all + context.turnOffAuthorisationSystem(); + ItemBuilder.deleteItem(uuid); + context.restoreAuthSystemState(); + } + + @Test + public void importArchivedItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + ObjectNode node = jsonNodeFactory.objectNode(); + node.set("withdrawn", jsonNodeFactory.textNode("false")); + node.set("inArchive", jsonNodeFactory.textNode("true")); + node.set("discoverable", jsonNodeFactory.textNode("false")); + context.restoreAuthSystemState(); + + ObjectMapper mapper = new ObjectMapper(); + String token = getAuthToken(admin.getEmail(), password); + + UUID uuid = UUID.fromString(read(getClient(token).perform(post("/api/clarin/import/item") + .content(mapper.writeValueAsBytes(node)) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON) + .param("owningCollection", col.getID().toString()) + .param("epersonUUID", submitter.getID().toString())) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(), + "$.id")); + + //workspaceitem should nt exist + List workflowItems = workspaceItemService.findAll(context); + assertEquals(workflowItems.size(), 0); + //contoling of the created item + Item item = itemService.find(context, uuid); + assertFalse(item.isWithdrawn()); + assertTrue(item.isArchived()); + assertFalse(item.isDiscoverable()); + assertEquals(item.getSubmitter().getID(), submitter.getID()); + assertEquals(item.getOwningCollection().getID(), col.getID()); + + //clean all + context.turnOffAuthorisationSystem(); + ItemBuilder.deleteItem(uuid); + context.restoreAuthSystemState(); + } +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinLicenseImportControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinLicenseImportControllerIT.java index 3cd38099197..0b9d38d3dd7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinLicenseImportControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinLicenseImportControllerIT.java @@ -29,8 +29,10 @@ import org.dspace.app.rest.converter.ClarinLicenseLabelConverter; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.ClarinUserRegistrationBuilder; import org.dspace.content.clarin.ClarinLicense; import org.dspace.content.clarin.ClarinLicenseLabel; +import org.dspace.content.clarin.ClarinUserRegistration; import org.dspace.content.service.clarin.ClarinLicenseLabelService; import org.dspace.content.service.clarin.ClarinLicenseService; import org.dspace.core.Context; @@ -81,6 +83,10 @@ public class ClarinLicenseImportControllerIT extends AbstractControllerIntegrati @Test public void importLicensesTest() throws Exception { + context.turnOffAuthorisationSystem(); + ClarinUserRegistration userRegistration = ClarinUserRegistrationBuilder + .createClarinUserRegistration(context).withEPersonID(admin.getID()).build(); + context.restoreAuthSystemState(); ObjectMapper mapper = new ObjectMapper(); JSONParser parser = new JSONParser(); BufferedReader bufferReader = new BufferedReader(new FileReader(getClass().getResource(LICENSE_LABELS) @@ -155,7 +161,7 @@ public void importLicensesTest() throws Exception { node.set("license_id", jsonNodeFactory.textNode(jsonObject.get("license_id").toString())); node.set("name", jsonNodeFactory.textNode(jsonObject.get("name").toString())); node.set("definition", jsonNodeFactory.textNode(jsonObject.get("definition").toString())); - node.set("eperson_id", jsonNodeFactory.textNode(jsonObject.get("eperson_id").toString())); + node.set("eperson_id", jsonNodeFactory.textNode(admin.getID().toString())); node.set("label_id", jsonNodeFactory.textNode(jsonObject.get("label_id").toString())); node.set("confirmation", jsonNodeFactory.textNode(jsonObject.get("confirmation").toString())); node.set("required_info", jsonNodeFactory.textNode(Objects.isNull(jsonObject.get("required_info")) ? @@ -163,13 +169,11 @@ public void importLicensesTest() throws Exception { nodes.add(node); //for test control - - license = new ClarinLicense(); license.setId(Integer.parseInt(jsonObject.get("license_id").toString())); license.setName(jsonObject.get("name").toString()); license.setDefinition(jsonObject.get("definition").toString()); - //license.setEpersonID(Integer.parseInt(jsonObject.get("eperson_id").toString())); + license.setEperson(userRegistration); Set labels = new HashSet<>(); labels.add(this.licenseLabelIDDictionary.get(Integer.parseInt(jsonObject.get("label_id").toString()))); license.setLicenseLabels(labels); @@ -201,6 +205,7 @@ public void importLicensesTest() throws Exception { Assert.assertEquals(clarinLicense.getRequiredInfo(), oldLicense.getRequiredInfo()); Assert.assertEquals(clarinLicense.getLicenseLabels().size(), extendedMappingDictionary.get( oldLicense.getID()).size()); + Assert.assertEquals(clarinLicense.getEperson().getID(), userRegistration.getID()); List clarinLicenseLabels = clarinLicense.getLicenseLabels(); for (ClarinLicenseLabel label: clarinLicenseLabels) { ClarinLicenseLabel oldLabel = licenseLabelDictionary.get(label.getLabel()); @@ -376,6 +381,10 @@ public void extendedMappingLabelDoesntExist() throws Exception { @Test public void licenseIDDoesntExist() throws Exception { + context.turnOffAuthorisationSystem(); + ClarinUserRegistration userRegistration = ClarinUserRegistrationBuilder + .createClarinUserRegistration(context).withEPersonID(admin.getID()).build(); + context.restoreAuthSystemState(); ObjectMapper mapper = new ObjectMapper(); JSONParser parser = new JSONParser(); BufferedReader bufferReader = new BufferedReader(new FileReader(getClass().getResource(LICENSES_TEST) @@ -394,6 +403,7 @@ public void licenseIDDoesntExist() throws Exception { node.set("definition", jsonNodeFactory.textNode(jsonObject.get("definition").toString())); node.set("eperson_id", jsonNodeFactory.textNode(jsonObject.get("eperson_id").toString())); node.set("label_id", jsonNodeFactory.textNode(jsonObject.get("label_id").toString())); + node.set("eperson_id", jsonNodeFactory.textNode(admin.getID().toString())); node.set("confirmation", jsonNodeFactory.textNode(jsonObject.get("confirmation").toString())); node.set("required_info", jsonNodeFactory.textNode(Objects.isNull(jsonObject.get("required_info")) ? null : jsonObject.get("required_info").toString())); @@ -410,6 +420,10 @@ public void licenseIDDoesntExist() throws Exception { @Test public void licenseIDisNull() throws Exception { + context.turnOffAuthorisationSystem(); + ClarinUserRegistration userRegistration = ClarinUserRegistrationBuilder + .createClarinUserRegistration(context).withEPersonID(admin.getID()).build(); + context.restoreAuthSystemState(); ObjectMapper mapper = new ObjectMapper(); JSONParser parser = new JSONParser(); BufferedReader bufferReader = new BufferedReader(new FileReader(getClass().getResource(LICENSES_TEST) @@ -429,6 +443,7 @@ public void licenseIDisNull() throws Exception { node.set("definition", jsonNodeFactory.textNode(jsonObject.get("definition").toString())); node.set("eperson_id", jsonNodeFactory.textNode(jsonObject.get("eperson_id").toString())); node.set("label_id", jsonNodeFactory.textNode(jsonObject.get("label_id").toString())); + node.set("eperson_id", jsonNodeFactory.textNode(admin.getID().toString())); node.set("confirmation", jsonNodeFactory.textNode(jsonObject.get("confirmation").toString())); node.set("required_info", jsonNodeFactory.textNode(Objects.isNull(jsonObject.get("required_info")) ? null : jsonObject.get("required_info").toString())); @@ -445,6 +460,10 @@ public void licenseIDisNull() throws Exception { @Test public void licenseIncorrectArgument() throws Exception { + context.turnOffAuthorisationSystem(); + ClarinUserRegistration userRegistration = ClarinUserRegistrationBuilder + .createClarinUserRegistration(context).withEPersonID(admin.getID()).build(); + context.restoreAuthSystemState(); ObjectMapper mapper = new ObjectMapper(); JSONParser parser = new JSONParser(); BufferedReader bufferReader = new BufferedReader(new FileReader(getClass().getResource(LICENSES_TEST) @@ -462,6 +481,7 @@ public void licenseIncorrectArgument() throws Exception { node.set("license_id", jsonNodeFactory.textNode(jsonObject.get("license_id").toString())); node.set("eperson_id", jsonNodeFactory.textNode(jsonObject.get("eperson_id").toString())); node.set("label_id", jsonNodeFactory.textNode(jsonObject.get("label_id").toString())); + node.set("eperson_id", jsonNodeFactory.textNode(admin.getID().toString())); node.set("confirmation", jsonNodeFactory.textNode(jsonObject.get("confirmation").toString())); node.set("required_info", jsonNodeFactory.textNode(Objects.isNull(jsonObject.get("required_info")) ? null : jsonObject.get("required_info").toString())); @@ -477,6 +497,10 @@ public void licenseIncorrectArgument() throws Exception { } public void licenseLabelDoesntExist() throws Exception { + context.turnOffAuthorisationSystem(); + ClarinUserRegistration userRegistration = ClarinUserRegistrationBuilder + .createClarinUserRegistration(context).withEPersonID(admin.getID()).build(); + context.restoreAuthSystemState(); ObjectMapper mapper = new ObjectMapper(); JSONParser parser = new JSONParser(); BufferedReader bufferReader = new BufferedReader(new FileReader(getClass().getResource(LICENSES_TEST) @@ -495,6 +519,7 @@ public void licenseLabelDoesntExist() throws Exception { node.set("license_id", jsonNodeFactory.textNode(jsonObject.get("license_id").toString())); node.set("eperson_id", jsonNodeFactory.textNode(jsonObject.get("eperson_id").toString())); node.set("label_id", jsonNodeFactory.textNode("1000")); + node.set("eperson_id", jsonNodeFactory.textNode(admin.getID().toString())); node.set("confirmation", jsonNodeFactory.textNode(jsonObject.get("confirmation").toString())); node.set("required_info", jsonNodeFactory.textNode(Objects.isNull(jsonObject.get("required_info")) ? null : jsonObject.get("required_info").toString())); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinLogoImportControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinLogoImportControllerIT.java new file mode 100644 index 00000000000..affb0d1862e --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinLogoImportControllerIT.java @@ -0,0 +1,85 @@ +/** + * 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.app.rest; + +import static org.apache.commons.codec.CharEncoding.UTF_8; +import static org.apache.commons.io.IOUtils.toInputStream; +import static org.junit.Assert.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.test.AbstractEntityIntegrationTest; +import org.dspace.builder.ClarinBitstreamBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.content.Bitstream; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.CommunityService; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration test to test the /api/clarin/import/logo/* endpoints + * + * @author Michaela Paurikova (michaela.paurikova at dataquest.sk) + */ +public class ClarinLogoImportControllerIT extends AbstractEntityIntegrationTest { + private Bitstream bitstream; + + @Autowired + private CommunityService communityService; + @Autowired + private CollectionService collectionService; + + @Before + public void setup() throws Exception { + context.turnOffAuthorisationSystem(); + bitstream = ClarinBitstreamBuilder.createBitstream(context, toInputStream("test", UTF_8)).build(); + context.restoreAuthSystemState(); + } + + @Test + public void addCommunityLogoTest() throws Exception { + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).build(); + context.restoreAuthSystemState(); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform( + post("/api/clarin/import/logo/community") + .contentType(contentType) + .param("bitstream_id", bitstream.getID().toString()) + .param("community_id", community.getID().toString())) + .andExpect(status().isOk()); + + community = communityService.find(context, community.getID()); + assertEquals(community.getLogo().getID(), bitstream.getID()); + } + + @Test + public void addCollectionLogoTest() throws Exception { + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, community).build(); + context.restoreAuthSystemState(); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform( + post("/api/clarin/import/logo/collection") + .contentType(contentType) + .param("bitstream_id", bitstream.getID().toString()) + .param("collection_id", collection.getID().toString())) + .andExpect(status().isOk()); + + collection = collectionService.find(context, collection.getID()); + assertEquals(collection.getLogo().getID(), bitstream.getID()); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/HandleRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/HandleRestRepositoryIT.java index 6c11f8227a8..234419552a7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/HandleRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/HandleRestRepositoryIT.java @@ -113,7 +113,7 @@ public void findAll() throws Exception { ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/handles"))) - .andExpect(jsonPath("$.page.size", is(100)));; + .andExpect(jsonPath("$.page.size", is(100))); this.cleanHandles(); } diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index 42804441c44..76cc48ddda6 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -39,6 +39,7 @@ + @@ -89,6 +90,7 @@ +