From e39c2a858281ed9530c9dc44c79fcfb2e1504aa6 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 10 Jun 2020 17:00:43 +0200 Subject: [PATCH 01/59] added ITs for Controlled Vocabularies --- .../app/rest/AuthorityRestRepositoryIT.java | 295 ++++++++++++++---- .../app/rest/matcher/VocabularyMatcher.java | 44 +++ 2 files changed, 271 insertions(+), 68 deletions(-) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyMatcher.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java index 089f781902..7054f98a01 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java @@ -16,18 +16,17 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.Date; import java.util.UUID; -import org.apache.solr.client.solrj.SolrQuery; -import org.apache.solr.client.solrj.response.QueryResponse; -import org.dspace.app.rest.matcher.AuthorityEntryMatcher; +import org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.matcher.VocabularyMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authority.PersonAuthorityValue; import org.dspace.authority.factory.AuthorityServiceFactory; +import org.dspace.content.Collection; import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.core.service.PluginService; import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -93,24 +92,137 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest AuthorityServiceFactory.getInstance().getAuthorityIndexingService().commit(); } + @Test + public void findAllTest() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.vocabularies", Matchers.containsInAnyOrder( + VocabularyMatcher.matchProperties("srsc", "srsc", false, true), + VocabularyMatcher.matchProperties("common_types", "common_types", true, false), + VocabularyMatcher.matchProperties("common_iso_languages", "common_iso_languages", true , false) + ))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("api/integration/vocabularies"))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + } + + @Test + public void findOneSRSC_Test() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies/srsc")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.contains( + VocabularyMatcher.matchProperties("srsc", "srsc", false, true) + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + } + + @Test + public void findOneCommonTypesTest() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies/common_types")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.contains( + VocabularyMatcher.matchProperties("common_types", "common_types", true, false) + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + } + @Test public void correctSrscQueryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( - get("/api/integration/authorities/srsc/entries") + get("/api/integration/vocabularies/srsc/entries") .param("metadata", "dc.subject") + .param("collection", collection.getID().toString()) .param("query", "Research") - .param("size", "1000")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(26))); + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( + VocabularyMatcher.matchVocabularyEntry("Family research", + "Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work::Family research", + "vocabularyEntry"), + VocabularyMatcher.matchVocabularyEntry("Youth research", + "Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work::Youth research", + "vocabularyEntry") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(26))) + .andExpect(jsonPath("$.page.totalPages", Matchers.is(13))) + .andExpect(jsonPath("$.page.size", Matchers.is(2))); + } + + @Test + public void controlledVocabularyEntriesWrongCollectionUUIDTest() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies/srsc/entries") + .param("metadata", "dc.subject") + .param("collection", UUID.randomUUID().toString()) + .param("query", "Research")) + .andExpect(status().isBadRequest()); + } + + @Test + public void controlledVocabularyEntriesMissingCollectionUUID_Test() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies/srsc/entries") + .param("metadata", "dc.subject") + .param("query", "Research")) + .andExpect(status().isBadRequest()); + } + + @Test + public void controlledVocabularyEntriesWrongMetadataTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies/srsc/entries") + .param("metadata", "dc.subject") + .param("collection", collection.getID().toString()) + .param("query", "Research")) + .andExpect(status().isBadRequest()); + } + + @Test + public void notScrollableVocabularyRequiredQueryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies/srsc/entries") + .param("metadata", "dc.not.existing") + .param("collection", collection.getID().toString())) + .andExpect(status().isUnprocessableEntity()); } @Test public void noResultsSrscQueryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( - get("/api/integration/authorities/srsc/entries") + get("/api/integration/vocabularies/srsc/entries") .param("metadata", "dc.subject") + .param("collection", collection.getID().toString()) .param("query", "Research2") .param("size", "1000")) .andExpect(status().isOk()) @@ -118,27 +230,67 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest } @Test - @Ignore - /** - * This functionality is currently broken, it returns all 22 values - */ - public void correctCommonTypesTest() throws Exception { + public void vocabularyEntriesCommon_typesTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform( - get("/api/integration/authorities/common_types/entries") + getClient(token).perform(get("/api/integration/vocabularies/common_types/entries") .param("metadata", "dc.type") + .param("collection", collection.getID().toString()) + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( + VocabularyMatcher.matchVocabularyEntry("Animation", "Animation", "vocabularyEntry"), + VocabularyMatcher.matchVocabularyEntry("Article", "Article", "vocabularyEntry") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(22))) + .andExpect(jsonPath("$.page.totalPages", Matchers.is(11))) + .andExpect(jsonPath("$.page.size", Matchers.is(2))); + } + + @Test + public void vocabularyEntriesCommon_typesWithQueryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies/common_types/entries") + .param("metadata", "dc.type") + .param("collection", collection.getID().toString()) .param("query", "Book") - .param("size", "1000")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(2))); + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( + VocabularyMatcher.matchVocabularyEntry("Book", "Book", "vocabularyEntry"), + VocabularyMatcher.matchVocabularyEntry("Book chapter", "Book chapter", "vocabularySuggestion") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(2))) + .andExpect(jsonPath("$.page.totalPages", Matchers.is(1))) + .andExpect(jsonPath("$.page.size", Matchers.is(2))); } @Test public void correctSolrQueryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( - get("/api/integration/authorities/SolrAuthorAuthority/entries") + get("/api/integration/vocabularies/SolrAuthorAuthority/entries") .param("metadata", "dc.contributor.author") + .param("collection", collection.getID().toString()) .param("query", "Shirasaka") .param("size", "1000")) .andExpect(status().isOk()) @@ -147,10 +299,17 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest @Test public void noResultsSolrQueryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( - get("/api/integration/authorities/SolrAuthorAuthority/entries") + get("/api/integration/vocabularies/SolrAuthorAuthority/entries") .param("metadata", "dc.contributor.author") + .param("collection", collection.getID().toString()) .param("query", "Smith") .param("size", "1000")) .andExpect(status().isOk()) @@ -158,43 +317,64 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest } @Test - public void retrieveSrscValueTest() throws Exception { - String token = getAuthToken(admin.getEmail(), password); + public void findByMetadataAndCollectionTest() throws Exception { + context.turnOffAuthorisationSystem(); - // When full projection is requested, response should include expected properties, links, and embeds. - getClient(token).perform( - get("/api/integration/authorities/srsc/entryValues/SCB1922").param("projection", "full")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", AuthorityEntryMatcher.matchFullEmbeds())) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + .param("metadata", "dc.type") + .param("collection", collection.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.contains( + VocabularyMatcher.matchProperties("common_types", "common_types", true, false) + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); } @Test - public void noResultsSrscValueTest() throws Exception { + public void findByMetadataAndCollectionUnprocessableEntityTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform( - get("/api/integration/authorities/srsc/entryValues/DOESNTEXIST")) - .andExpect(status().isNotFound()); + getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + .param("metadata", "dc.not.exist") + .param("collection", collection.getID().toString())) + .andExpect(status().isUnprocessableEntity()); + + getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + .param("metadata", "dc.type") + .param("collection", UUID.randomUUID().toString())) + .andExpect(status().isUnprocessableEntity()); } @Test - public void retrieveCommonTypesValueTest() throws Exception { - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform( - get("/api/integration/authorities/common_types/entryValues/Book").param("projection", "full")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))) - ; + public void findByMetadataAndCollectionBadRequestTest() throws Exception { + context.turnOffAuthorisationSystem(); - } + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); - @Test - public void retrieveCommonTypesWithSpaceValueTest() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform( - get("/api/integration/authorities/common_types/entryValues/Learning+Object")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + //missing metadata + getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + .param("collection", collection.getID().toString())) + .andExpect(status().isBadRequest()); + + //missing collection + getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + .param("metadata", "dc.type")) + .andExpect(status().isUnprocessableEntity()); } @Test @@ -209,25 +389,4 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest is("http://localhost/api/authz/authorization/search")) ))); } - - @Test - public void retrieveSolrValueTest() throws Exception { - String token = getAuthToken(admin.getEmail(), password); - - SolrQuery query = new SolrQuery(); - query.setQuery("*:*"); - QueryResponse queryResponse = AuthorityServiceFactory.getInstance().getAuthoritySearchService().search(query); - String id = String.valueOf(queryResponse.getResults().get(0).getFieldValue("id")); - - getClient(token).perform( - get("/api/integration/authorities/SolrAuthorAuthority/entryValues/" + id)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); - } - - @Override - public void destroy() throws Exception { - AuthorityServiceFactory.getInstance().getAuthorityIndexingService().cleanIndex(); - super.destroy(); - } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyMatcher.java new file mode 100644 index 0000000000..6e23560911 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyMatcher.java @@ -0,0 +1,44 @@ +/** + * 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.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; + +import org.hamcrest.Matcher; + +/** + * + * + * @author mykhaylo + * + */ +public class VocabularyMatcher { + + private VocabularyMatcher() {} + + public static Matcher matchProperties(String id, String name, + boolean scrollable, boolean hierarchical) { + return allOf( + hasJsonPath("$.id", is(id)), + hasJsonPath("$.name", is(name)), + hasJsonPath("$.scrollable", is(scrollable)), + hasJsonPath("$.hierarchical", is(hierarchical)), + hasJsonPath("$.type", is("vocabulary")) + ); + } + + public static Matcher matchVocabularyEntry(String display, String value, String type) { + return allOf( + hasJsonPath("$.display", is(display)), + hasJsonPath("$.value", is(value)), + hasJsonPath("$.type", is(type)) + ); + } +} From 5f6775a1223cf82cb7f34b5ab2e0883b9429e286 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 10 Jun 2020 21:30:10 +0200 Subject: [PATCH 02/59] added ITs for Vocabulary Entries --- .../app/rest/AuthorityVocabularyEntryIT.java | 158 ++++++++++++++++++ .../rest/matcher/AuthorityEntryMatcher.java | 8 + 2 files changed, 166 insertions(+) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java new file mode 100644 index 0000000000..b234ff3c90 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java @@ -0,0 +1,158 @@ +/** + * 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.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.UUID; + +import org.dspace.app.rest.matcher.AuthorityEntryMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.hamcrest.Matchers; +import org.junit.Test; + +public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTest { + + @Test + public void findOneTest() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "SCB110")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is("srsc:SCB110"))) + .andExpect(jsonPath("$.value", is("Religion/Theology"))) + .andExpect(jsonPath("$.selectable", is(true))) + .andExpect(jsonPath("$.otherInformation.id", is("SCB110"))) + .andExpect(jsonPath("$.otherInformation.note", is("Religionsvetenskap/Teologi"))) + .andExpect(jsonPath("$.otherInformation.parent", is("HUMANITIES and RELIGION"))); + } + + @Test + public void findOneBadRequestTest() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + UUID.randomUUID().toString())) + .andExpect(status().isBadRequest()); + } + + public void findOneUnauthorizedTest() throws Exception { + getClient().perform(get("/api/integration/vocabularyEntryDetails/" + "SCB110")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void srscSearchTopTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.containsInAnyOrder( + AuthorityEntryMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB20", "PHARMACY"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB21", "VETERINARY MEDICINE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB22", "INTERDISCIPLINARY RESEARCH AREAS") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); + } + + @Test + public void srscSearchFirstLevel_MATHEMATICS_Test() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + .param("vocabulary", "SCB14")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.containsInAnyOrder( + AuthorityEntryMatcher.matchAuthority("srsc:SCB1401", "Algebra, geometry and mathematical analysis"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB1402", "Applied mathematics"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB1409", "Other mathematics") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(3))); + } + + @Test + public void srscSearchTopPaginationTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc") + .param("page", "0") + .param("size", "5")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.containsInAnyOrder( + AuthorityEntryMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES") + ))) + .andExpect(jsonPath("$.page.totalElements", is(12))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.number", is(0))); + + //second page + getClient(tokenAdmin).perform(get("/api/integration/authorities/srsc/entries/search/top") + .param("page", "1") + .param("size", "5")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.containsInAnyOrder( + AuthorityEntryMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB20", "PHARMACY") + ))) + .andExpect(jsonPath("$.page.totalElements", is(12))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.number", is(1))); + + // third page + getClient(tokenAdmin).perform(get("/api/integration/authorities/srsc/entries/search/top") + .param("page", "2") + .param("size", "5")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.containsInAnyOrder( + AuthorityEntryMatcher.matchAuthority("srsc:SCB21", "VETERINARY MEDICINE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB22", "INTERDISCIPLINARY RESEARCH AREAS") + ))) + .andExpect(jsonPath("$.page.totalElements", is(12))) + .andExpect(jsonPath("$.page.totalPages", is(3))) + .andExpect(jsonPath("$.page.number", is(2))); + } + + @Test + public void searchTopBadRequestTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + .param("vocabulary", UUID.randomUUID().toString())) + .andExpect(status().isBadRequest()); + } + + @Test + public void searchTopUnauthorizedTest() throws Exception { + getClient().perform(get("/api/integration/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void retrieveSrscValueTest() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "SCB1922") + .param("projection", "full")) + .andExpect(status().isOk()); + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AuthorityEntryMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AuthorityEntryMatcher.java index 5758d3ee65..519e77d05e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AuthorityEntryMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AuthorityEntryMatcher.java @@ -51,4 +51,12 @@ public class AuthorityEntryMatcher { "authorityEntries" ); } + + public static Matcher matchAuthority(String id, String value) { + return allOf( + hasJsonPath("$.id", is(id)), + hasJsonPath("$.value", is(value)), + hasJsonPath("$.type", is("vocabularyEntryDetail")) + ); + } } From 1765d2a161f6440d54f3f2ed808ec274e9d8c84c Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 15 Jun 2020 11:53:50 +0200 Subject: [PATCH 03/59] added tests --- .../app/rest/AuthorityVocabularyEntryIT.java | 108 +++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java index b234ff3c90..c6e6e66b6a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java @@ -49,6 +49,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchTopTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc")) .andExpect(status().isOk()) @@ -67,13 +68,31 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes AuthorityEntryMatcher.matchAuthority("srsc:SCB22", "INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); + + getClient(tokenEPerson).perform(get("/api/integration/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.containsInAnyOrder( + AuthorityEntryMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB20", "PHARMACY"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB21", "VETERINARY MEDICINE"), + AuthorityEntryMatcher.matchAuthority("srsc:SCB22", "INTERDISCIPLINARY RESEARCH AREAS") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); } @Test public void srscSearchFirstLevel_MATHEMATICS_Test() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") - .param("vocabulary", "SCB14")) + getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB14" + "/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.containsInAnyOrder( AuthorityEntryMatcher.matchAuthority("srsc:SCB1401", "Algebra, geometry and mathematical analysis"), @@ -155,4 +174,89 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()); } + @Test + public void srscSearchByParentFirstLevelPaginationTest() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + // first page + getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB14" + "/children") + .param("page", "0") + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.containsInAnyOrder( + AuthorityEntryMatcher.matchAuthority("SCB1401", "Algebra, geometry and mathematical analysis"), + AuthorityEntryMatcher.matchAuthority("SCB1402", "Applied mathematics") + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))) + .andExpect(jsonPath("$.page.totalPages", is(2))) + .andExpect(jsonPath("$.page.number", is(0))); + + // second page + getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB14" + "/children") + .param("page", "1") + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.contains( + AuthorityEntryMatcher.matchAuthority("SCB1409", "Other mathematics") + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))) + .andExpect(jsonPath("$.page.totalPages", is(2))) + .andExpect(jsonPath("$.page.number", is(1))); + } + + @Test + public void srscSearchByParentSecondLevel_Applied_mathematics_Test() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB1402" + "/children")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.containsInAnyOrder( + AuthorityEntryMatcher.matchAuthority("VR140202", "Numerical analysis"), + AuthorityEntryMatcher.matchAuthority("VR140203", "Mathematical statistics"), + AuthorityEntryMatcher.matchAuthority("VR140204", "Optimization, systems theory"), + AuthorityEntryMatcher.matchAuthority("VR140205", "Theoretical computer science") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); + } + + @Test + public void srscSearchByParentEmptyTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:VR140202" + "/children")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); + } + + @Test + public void srscSearchByParentWrongIdTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/" + + UUID.randomUUID() + "/children")) + .andExpect(status().isBadRequest()); + } + + @Test + public void srscSearchTopUnauthorizedTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void srscSearchParentByChildrenTest() throws Exception { + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:VR140202" + "/children")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.contains( + AuthorityEntryMatcher.matchAuthority("SCB1402", "Applied mathematics") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + } + + @Test + public void srscSearchParentByChildrenRootTest() throws Exception { + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB11" + "/children")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); + } } From d794c1cdf0512ac45f1e9c5a5618646fee6d8c80 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 16 Jun 2020 18:22:56 +0200 Subject: [PATCH 04/59] update tests of Vocabulary --- .../app/rest/AuthorityRestRepositoryIT.java | 38 +++++++-------- .../app/rest/AuthorityVocabularyEntryIT.java | 48 ++++++++++--------- 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java index 7054f98a01..ec5566ae9f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java @@ -95,7 +95,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest @Test public void findAllTest() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies")) + getClient(token).perform(get("/api/submission/vocabularies")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularies", Matchers.containsInAnyOrder( VocabularyMatcher.matchProperties("srsc", "srsc", false, true), @@ -103,14 +103,14 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest VocabularyMatcher.matchProperties("common_iso_languages", "common_iso_languages", true , false) ))) .andExpect(jsonPath("$._links.self.href", - Matchers.containsString("api/integration/vocabularies"))) + Matchers.containsString("api/submission/vocabularies"))) .andExpect(jsonPath("$.page.totalElements", is(3))); } @Test public void findOneSRSC_Test() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/srsc")) + getClient(token).perform(get("/api/submission/vocabularies/srsc")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.contains( VocabularyMatcher.matchProperties("srsc", "srsc", false, true) @@ -121,7 +121,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest @Test public void findOneCommonTypesTest() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/common_types")) + getClient(token).perform(get("/api/submission/vocabularies/common_types")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.contains( VocabularyMatcher.matchProperties("common_types", "common_types", true, false) @@ -140,7 +140,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( - get("/api/integration/vocabularies/srsc/entries") + get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") .param("collection", collection.getID().toString()) .param("query", "Research") @@ -162,7 +162,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest @Test public void controlledVocabularyEntriesWrongCollectionUUIDTest() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/srsc/entries") + getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") .param("collection", UUID.randomUUID().toString()) .param("query", "Research")) @@ -172,7 +172,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest @Test public void controlledVocabularyEntriesMissingCollectionUUID_Test() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/srsc/entries") + getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") .param("query", "Research")) .andExpect(status().isBadRequest()); @@ -187,7 +187,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest .build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/srsc/entries") + getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") .param("collection", collection.getID().toString()) .param("query", "Research")) @@ -203,7 +203,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest .build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/srsc/entries") + getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.not.existing") .param("collection", collection.getID().toString())) .andExpect(status().isUnprocessableEntity()); @@ -220,7 +220,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( - get("/api/integration/vocabularies/srsc/entries") + get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") .param("collection", collection.getID().toString()) .param("query", "Research2") @@ -239,7 +239,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/common_types/entries") + getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") .param("metadata", "dc.type") .param("collection", collection.getID().toString()) .param("size", "2")) @@ -263,7 +263,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/common_types/entries") + getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") .param("metadata", "dc.type") .param("collection", collection.getID().toString()) .param("query", "Book") @@ -288,7 +288,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( - get("/api/integration/vocabularies/SolrAuthorAuthority/entries") + get("/api/submission/vocabularies/SolrAuthorAuthority/entries") .param("metadata", "dc.contributor.author") .param("collection", collection.getID().toString()) .param("query", "Shirasaka") @@ -307,7 +307,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( - get("/api/integration/vocabularies/SolrAuthorAuthority/entries") + get("/api/submission/vocabularies/SolrAuthorAuthority/entries") .param("metadata", "dc.contributor.author") .param("collection", collection.getID().toString()) .param("query", "Smith") @@ -325,7 +325,7 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest .build(); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection") .param("metadata", "dc.type") .param("collection", collection.getID().toString())) .andExpect(status().isOk()) @@ -345,12 +345,12 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection") .param("metadata", "dc.not.exist") .param("collection", collection.getID().toString())) .andExpect(status().isUnprocessableEntity()); - getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection") .param("metadata", "dc.type") .param("collection", UUID.randomUUID().toString())) .andExpect(status().isUnprocessableEntity()); @@ -367,12 +367,12 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest String token = getAuthToken(admin.getEmail(), password); //missing metadata - getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection") .param("collection", collection.getID().toString())) .andExpect(status().isBadRequest()); //missing collection - getClient(token).perform(get("/api/integration/vocabularies/search/byMetadataAndCollection") + getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection") .param("metadata", "dc.type")) .andExpect(status().isUnprocessableEntity()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java index c6e6e66b6a..f78add992b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java @@ -23,11 +23,14 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void findOneTest() throws Exception { + String idAuthority = "srsc:SCB110"; String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "SCB110")) + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + idAuthority)) .andExpect(status().isOk()) .andExpect(jsonPath("$.id", is("srsc:SCB110"))) - .andExpect(jsonPath("$.value", is("Religion/Theology"))) + .andExpect(jsonPath("$.value", + is("Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology"))) + .andExpect(jsonPath("$.display", is("Religion/Theology"))) .andExpect(jsonPath("$.selectable", is(true))) .andExpect(jsonPath("$.otherInformation.id", is("SCB110"))) .andExpect(jsonPath("$.otherInformation.note", is("Religionsvetenskap/Teologi"))) @@ -37,12 +40,13 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void findOneBadRequestTest() throws Exception { String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + UUID.randomUUID().toString())) + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + UUID.randomUUID().toString())) .andExpect(status().isBadRequest()); } public void findOneUnauthorizedTest() throws Exception { - getClient().perform(get("/api/integration/vocabularyEntryDetails/" + "SCB110")) + String idAuthority = "srsc:SCB110"; + getClient().perform(get("/api/submission/vocabularyEntryDetails/" + idAuthority)) .andExpect(status().isUnauthorized()); } @@ -50,7 +54,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes public void srscSearchTopTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); String tokenEPerson = getAuthToken(eperson.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.containsInAnyOrder( @@ -69,7 +73,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); - getClient(tokenEPerson).perform(get("/api/integration/vocabularyEntryDetails/search/top") + getClient(tokenEPerson).perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.containsInAnyOrder( @@ -92,7 +96,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchFirstLevel_MATHEMATICS_Test() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB14" + "/children")) + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB14" + "/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.containsInAnyOrder( AuthorityEntryMatcher.matchAuthority("srsc:SCB1401", "Algebra, geometry and mathematical analysis"), @@ -105,7 +109,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchTopPaginationTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc") .param("page", "0") .param("size", "5")) @@ -122,7 +126,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes .andExpect(jsonPath("$.page.number", is(0))); //second page - getClient(tokenAdmin).perform(get("/api/integration/authorities/srsc/entries/search/top") + getClient(tokenAdmin).perform(get("/api/submission/authorities/srsc/entries/search/top") .param("page", "1") .param("size", "5")) .andExpect(status().isOk()) @@ -138,7 +142,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes .andExpect(jsonPath("$.page.number", is(1))); // third page - getClient(tokenAdmin).perform(get("/api/integration/authorities/srsc/entries/search/top") + getClient(tokenAdmin).perform(get("/api/submission/authorities/srsc/entries/search/top") .param("page", "2") .param("size", "5")) .andExpect(status().isOk()) @@ -154,22 +158,22 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void searchTopBadRequestTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", UUID.randomUUID().toString())) .andExpect(status().isBadRequest()); } @Test public void searchTopUnauthorizedTest() throws Exception { - getClient().perform(get("/api/integration/vocabularyEntryDetails/search/top") - .param("vocabulary", "srsc")) + getClient().perform(get("/api/submission/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc:SCB16")) .andExpect(status().isUnauthorized()); } @Test public void retrieveSrscValueTest() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "SCB1922") + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + "SCB1922") .param("projection", "full")) .andExpect(status().isOk()); } @@ -178,7 +182,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes public void srscSearchByParentFirstLevelPaginationTest() throws Exception { String token = getAuthToken(eperson.getEmail(), password); // first page - getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB14" + "/children") + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB14" + "/children") .param("page", "0") .param("size", "2")) .andExpect(status().isOk()) @@ -191,7 +195,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes .andExpect(jsonPath("$.page.number", is(0))); // second page - getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB14" + "/children") + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB14" + "/children") .param("page", "1") .param("size", "2")) .andExpect(status().isOk()) @@ -206,7 +210,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchByParentSecondLevel_Applied_mathematics_Test() throws Exception { String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB1402" + "/children")) + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB1402" + "/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.containsInAnyOrder( AuthorityEntryMatcher.matchAuthority("VR140202", "Numerical analysis"), @@ -220,7 +224,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchByParentEmptyTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:VR140202" + "/children")) + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:VR140202" + "/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); } @@ -228,7 +232,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchByParentWrongIdTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/" + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/" + UUID.randomUUID() + "/children")) .andExpect(status().isBadRequest()); } @@ -236,7 +240,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchTopUnauthorizedTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/integration/vocabularyEntryDetails/search/top") + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc")) .andExpect(status().isUnauthorized()); } @@ -244,7 +248,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchParentByChildrenTest() throws Exception { String tokenEperson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEperson).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:VR140202" + "/children")) + getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:VR140202" + "/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.contains( AuthorityEntryMatcher.matchAuthority("SCB1402", "Applied mathematics") @@ -255,7 +259,7 @@ public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTes @Test public void srscSearchParentByChildrenRootTest() throws Exception { String tokenEperson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEperson).perform(get("/api/integration/vocabularyEntryDetails/" + "srsc:SCB11" + "/children")) + getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB11" + "/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); } From 8afa698e041b67134c9d92dba46bdf05b8aea80a Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Tue, 16 Jun 2020 18:35:04 +0200 Subject: [PATCH 05/59] Initial refactoring for vocabularies (Draft) --- .../org/dspace/content/authority/Choice.java | 2 + .../content/authority/ChoiceAuthority.java | 13 +- .../authority/ChoiceAuthorityServiceImpl.java | 32 +--- .../authority/DSpaceControlledVocabulary.java | 33 ---- ...InputFormSelfRegisterWrapperAuthority.java | 166 ------------------ .../service/ChoiceAuthorityService.java | 20 +-- ...lrServiceMetadataBrowseIndexingPlugin.java | 7 +- .../indexobject/ItemIndexFactoryImpl.java | 6 +- .../AuthorityEntryRestConverter.java | 8 +- .../converter/AuthorityRestConverter.java | 14 +- .../link/AuthorityEntryHalLinkFactory.java | 66 ------- .../model/SubmissionFormInputTypeRest.java | 6 +- ...t.java => VocabularyEntryDetailsRest.java} | 20 +-- .../app/rest/model/VocabularyEntryRest.java | 72 ++++++++ ...AuthorityRest.java => VocabularyRest.java} | 35 ++-- .../model/hateoas/AuthorityEntryResource.java | 8 +- .../rest/model/hateoas/AuthorityResource.java | 13 +- .../AuthorityEntryValueLinkRepository.java | 60 ------- ...ava => VocabularyEntryLinkRepository.java} | 42 +++-- ...ory.java => VocabularyRestRepository.java} | 39 ++-- .../dspace/app/rest/utils/AuthorityUtils.java | 31 +++- .../java/org/dspace/app/rest/utils/Utils.java | 4 +- 22 files changed, 208 insertions(+), 489 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/content/authority/InputFormSelfRegisterWrapperAuthority.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthorityEntryHalLinkFactory.java rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/{AuthorityEntryRest.java => VocabularyEntryDetailsRest.java} (76%) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/{AuthorityRest.java => VocabularyRest.java} (68%) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryValueLinkRepository.java rename dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/{AuthorityEntryLinkRepository.java => VocabularyEntryLinkRepository.java} (58%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/{AuthorityRestRepository.java => VocabularyRestRepository.java} (54%) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java index 9b68c75d28..9c092c7e8b 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java @@ -18,6 +18,8 @@ import java.util.Map; * @see Choices */ public class Choice { + public boolean storeAuthority = true; + /** * Authority key for this value */ diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java index d2d06fe983..c77799f83f 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java @@ -82,16 +82,7 @@ public interface ChoiceAuthority { return false; } - default boolean hasIdentifier() { - return true; + default Integer getPreloadLevel() { + return isHierarchical() ? 0 : null; } - - default public Choice getChoice(String fieldKey, String authKey, String locale) { - Choice result = new Choice(); - result.authority = authKey; - result.label = getLabel(fieldKey, authKey, locale); - result.value = getLabel(fieldKey, authKey, locale); - return result; - } - } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index 4cc3f9d6db..0e6836f47b 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -149,12 +149,12 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } @Override - public String getLabel(MetadataValue metadataValue, String locale) { - return getLabel(metadataValue.getMetadataField().toString(), metadataValue.getAuthority(), locale); + public String getLabel(MetadataValue metadataValue, Collection collection, String locale) { + return getLabel(metadataValue.getMetadataField().toString(), collection, metadataValue.getAuthority(), locale); } @Override - public String getLabel(String fieldKey, String authKey, String locale) { + public String getLabel(String fieldKey, Collection collection, String authKey, String locale) { ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey); if (ma == null) { throw new IllegalArgumentException("No choices plugin was configured for field \"" + fieldKey + "\"."); @@ -163,7 +163,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } @Override - public boolean isChoicesConfigured(String fieldKey) { + public boolean isChoicesConfigured(String fieldKey, Collection collection) { return getChoiceAuthorityMap().containsKey(fieldKey); } @@ -178,7 +178,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } @Override - public List getVariants(MetadataValue metadataValue) { + public List getVariants(MetadataValue metadataValue, Collection collection) { ChoiceAuthority ma = getChoiceAuthorityMap().get(metadataValue.getMetadataField().toString()); if (ma instanceof AuthorityVariantsSupport) { AuthorityVariantsSupport avs = (AuthorityVariantsSupport) ma; @@ -189,7 +189,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override - public String getChoiceAuthorityName(String schema, String element, String qualifier) { + public String getChoiceAuthorityName(String schema, String element, String qualifier, Collection collection) { String makeFieldKey = makeFieldKey(schema, element, qualifier); if (getChoiceAuthorityMap().containsKey(makeFieldKey)) { for (String key : this.authorities.keySet()) { @@ -370,26 +370,6 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService return closed; } - @Override - public String getChoiceMetadatabyAuthorityName(String name) { - if (authorities.isEmpty()) { - loadChoiceAuthorityConfigurations(); - } - if (authorities.containsKey(name)) { - return authorities.get(name); - } - return null; - } - - @Override - public Choice getChoice(String fieldKey, String authKey, String locale) { - ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey); - if (ma == null) { - throw new IllegalArgumentException("No choices plugin was configured for field \"" + fieldKey + "\"."); - } - return ma.getChoice(fieldKey, authKey, locale); - } - @Override public ChoiceAuthority getChoiceAuthorityByAuthorityName(String authorityName) { ChoiceAuthority ma = (ChoiceAuthority) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index 097a19eb13..40b1e6b73d 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -211,39 +211,6 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic return true; } - @Override - public Choice getChoice(String fieldKey, String authKey, String locale) { - init(); - log.debug("Getting matches for '" + authKey + "'"); - String xpathExpression = String.format(idTemplate, authKey); - XPath xpath = XPathFactory.newInstance().newXPath(); - try { - Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); - if (node != null) { - String[] authorities = new String[1]; - String[] values = new String[1]; - String[] labels = new String[1]; - String[] parent = new String[1]; - String[] note = new String[1]; - readNode(authorities, values, labels, parent, note, 0, node); - - if (values.length > 0) { - Choice choice = new Choice(authorities[0], values[0], labels[0]); - if (StringUtils.isNotBlank(parent[0])) { - choice.extras.put("parent", parent[0]); - } - if (StringUtils.isNotBlank(note[0])) { - choice.extras.put("note", note[0]); - } - return choice; - } - } - } catch (XPathExpressionException e) { - log.warn(e.getMessage(), e); - } - return null; - } - private void readNode(String[] authorities, String[] values, String[] labels, String[] parent, String[] notes, int i, Node node) { String hierarchy = this.buildString(node); diff --git a/dspace-api/src/main/java/org/dspace/content/authority/InputFormSelfRegisterWrapperAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/InputFormSelfRegisterWrapperAuthority.java deleted file mode 100644 index 8716ef38b9..0000000000 --- a/dspace-api/src/main/java/org/dspace/content/authority/InputFormSelfRegisterWrapperAuthority.java +++ /dev/null @@ -1,166 +0,0 @@ -/** - * 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.authority; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; -import org.dspace.app.util.DCInputsReader; -import org.dspace.app.util.DCInputsReaderException; -import org.dspace.content.Collection; - -/** - * This authority is registered automatically by the ChoiceAuthorityService for - * all the metadata that use a value-pair or a vocabulary in the submission-form.xml - * - * It keeps a map of form-name vs ChoiceAuthority to delegate the execution of - * the method to the specific ChoiceAuthority configured for the collection when - * the same metadata have different vocabulary or value-pair on a collection - * basis - * - * @author Andrea Bollini (andrea.bollini at 4science.it) - */ -public class InputFormSelfRegisterWrapperAuthority implements ChoiceAuthority { - - private static Logger log = - org.apache.logging.log4j.LogManager.getLogger(InputFormSelfRegisterWrapperAuthority.class); - - private Map delegates = new HashMap(); - - private static DCInputsReader dci = null; - - private void init() { - try { - if (dci == null) { - dci = new DCInputsReader(); - } - } catch (DCInputsReaderException e) { - log.error("Failed reading DCInputs initialization: ", e); - } - } - - @Override - public Choices getMatches(String field, String query, Collection collection, int start, int limit, String locale) { - String formName; - try { - init(); - if (collection == null) { - Set choices = new HashSet(); - //workaround search in all authority configured - for (ChoiceAuthority ca : delegates.values()) { - Choices tmp = ca.getMatches(field, query, null, start, limit, locale); - if (tmp.total > 0) { - Set mySet = new HashSet(Arrays.asList(tmp.values)); - choices.addAll(mySet); - } - } - if (!choices.isEmpty()) { - Choice[] results = new Choice[choices.size()]; - choices.toArray(results); - return new Choices(results, 0, choices.size(), Choices.CF_AMBIGUOUS, false); - } - } else { - formName = dci.getInputFormNameByCollectionAndField(collection, field); - return delegates.get(formName).getMatches(field, query, collection, start, limit, locale); - } - } catch (DCInputsReaderException e) { - log.error(e.getMessage(), e); - } - return new Choices(Choices.CF_NOTFOUND); - } - - @Override - public Choices getBestMatch(String field, String text, Collection collection, String locale) { - String formName; - try { - init(); - if (collection == null) { - Set choices = new HashSet(); - //workaround search in all authority configured - for (ChoiceAuthority ca : delegates.values()) { - Choices tmp = ca.getBestMatch(field, text, null, locale); - if (tmp.total > 0) { - Set mySet = new HashSet(Arrays.asList(tmp.values)); - choices.addAll(mySet); - } - } - if (!choices.isEmpty()) { - Choice[] results = new Choice[choices.size() - 1]; - choices.toArray(results); - return new Choices(results, 0, choices.size(), Choices.CF_UNCERTAIN, false); - } - } else { - formName = dci.getInputFormNameByCollectionAndField(collection, field); - return delegates.get(formName).getBestMatch(field, text, collection, locale); - } - } catch (DCInputsReaderException e) { - log.error(e.getMessage(), e); - } - return new Choices(Choices.CF_NOTFOUND); - } - - @Override - public String getLabel(String field, String key, String locale) { - // TODO we need to manage REALLY the authority - // WRONG BEHAVIOUR: now in each delegates can exists the same key with - // different value - for (ChoiceAuthority delegate : delegates.values()) { - String label = delegate.getLabel(field, key, locale); - if (StringUtils.isNotBlank(label)) { - return label; - } - } - return "UNKNOWN KEY " + key; - } - - @Override - public boolean isHierarchical() { - // TODO we need to manage REALLY the authority - // WRONG BEHAVIOUR: now in each delegates can exists the same key with - // different value - for (ChoiceAuthority delegate : delegates.values()) { - return delegate.isHierarchical(); - } - return false; - } - - @Override - public boolean isScrollable() { - // TODO we need to manage REALLY the authority - // WRONG BEHAVIOUR: now in each delegates can exists the same key with - // different value - for (ChoiceAuthority delegate : delegates.values()) { - return delegate.isScrollable(); - } - return false; - } - - @Override - public boolean hasIdentifier() { - // TODO we need to manage REALLY the authority - // WRONG BEHAVIOUR: now in each delegates can exists the same key with - // different value - for (ChoiceAuthority delegate : delegates.values()) { - return delegate.hasIdentifier(); - } - return false; - } - - public Map getDelegates() { - return delegates; - } - - public void setDelegates(Map delegates) { - this.delegates = delegates; - } -} diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java index 83db9a734e..3d0bdd7316 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java @@ -12,7 +12,6 @@ import java.util.Set; import org.dspace.content.Collection; import org.dspace.content.MetadataValue; -import org.dspace.content.authority.Choice; import org.dspace.content.authority.ChoiceAuthority; import org.dspace.content.authority.Choices; @@ -48,10 +47,10 @@ public interface ChoiceAuthorityService { * @param element element of metadata field * @param qualifier qualifier of metadata field * @return the name of the choice authority associated with the specified - * metadata. Throw IllegalArgumentException if the supplied metadat + * metadata. Throw IllegalArgumentException if the supplied metadata * is not associated with an authority choice */ - public String getChoiceAuthorityName(String schema, String element, String qualifier); + public String getChoiceAuthorityName(String schema, String element, String qualifier, Collection collection); /** * Wrapper that calls getMatches method of the plugin corresponding to @@ -112,30 +111,33 @@ public interface ChoiceAuthorityService { * the metadata field defined by schema,element,qualifier. * * @param metadataValue metadata value + * @param collection Collection owner of Item * @param locale explicit localization key if available * @return label */ - public String getLabel(MetadataValue metadataValue, String locale); + public String getLabel(MetadataValue metadataValue, Collection collection, String locale); /** * Wrapper that calls getLabel method of the plugin corresponding to * the metadata field defined by single field key. * * @param fieldKey single string identifying metadata field + * @param collection Collection owner of Item * @param locale explicit localization key if available * @param authKey authority key * @return label */ - public String getLabel(String fieldKey, String authKey, String locale); + public String getLabel(String fieldKey, Collection collection, String authKey, String locale); /** * Predicate, is there a Choices configuration of any kind for the * given metadata field? * * @param fieldKey single string identifying metadata field + * @param collection Collection owner of Item * @return true if choices are configured for this field. */ - public boolean isChoicesConfigured(String fieldKey); + public boolean isChoicesConfigured(String fieldKey, Collection collection); /** * Get the presentation keyword (should be "lookup", "select" or "suggest", but this @@ -160,11 +162,7 @@ public interface ChoiceAuthorityService { * @param metadataValue metadata value * @return List of variants */ - public List getVariants(MetadataValue metadataValue); - - public String getChoiceMetadatabyAuthorityName(String name); - - public Choice getChoice(String fieldKey, String authKey, String locale); + public List getVariants(MetadataValue metadataValue, Collection collection); public ChoiceAuthority getChoiceAuthorityByAuthorityName(String authorityName); diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java index 187c6b0600..2b2be66384 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java @@ -17,6 +17,7 @@ import org.apache.logging.log4j.Logger; import org.apache.solr.common.SolrInputDocument; import org.dspace.browse.BrowseException; import org.dspace.browse.BrowseIndex; +import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.authority.service.ChoiceAuthorityService; @@ -63,7 +64,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex return; } Item item = ((IndexableItem) indexableObject).getIndexedObject(); - + Collection collection = item.getOwningCollection(); // Get the currently configured browse indexes BrowseIndex[] bis; try { @@ -175,7 +176,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex true); if (!ignorePrefered) { preferedLabel = choiceAuthorityService - .getLabel(values.get(x), values.get(x).getLanguage()); + .getLabel(values.get(x), collection, values.get(x).getLanguage()); } List variants = null; @@ -195,7 +196,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex if (!ignoreVariants) { variants = choiceAuthorityService .getVariants( - values.get(x)); + values.get(x), collection); } if (StringUtils diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java index 7f98131566..2a1008aaf9 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java @@ -173,6 +173,8 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl discoveryConfigurations) throws SQLException, IOException { + // use the item service to retrieve the owning collection also for inprogress submission + Collection collection = (Collection) itemService.getParentObject(context, item); //Keep a list of our sort values which we added, sort values can only be added once List sortFieldsAdded = new ArrayList<>(); Map> searchFilters = null; @@ -359,7 +361,7 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl { +public class AuthorityEntryRestConverter implements DSpaceConverter { @Override - public AuthorityEntryRest convert(Choice choice, Projection projection) { - AuthorityEntryRest entry = new AuthorityEntryRest(); + public VocabularyEntryDetailsRest convert(Choice choice, Projection projection) { + VocabularyEntryDetailsRest entry = new VocabularyEntryDetailsRest(); entry.setProjection(projection); entry.setValue(choice.value); entry.setDisplay(choice.label); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java index 7e78ef7f14..f481568746 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.converter; -import org.dspace.app.rest.model.AuthorityRest; +import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.AuthorityUtils; import org.dspace.content.authority.ChoiceAuthority; @@ -23,15 +23,15 @@ import org.springframework.stereotype.Component; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @Component -public class AuthorityRestConverter implements DSpaceConverter { +public class AuthorityRestConverter implements DSpaceConverter { @Override - public AuthorityRest convert(ChoiceAuthority step, Projection projection) { - AuthorityRest authorityRest = new AuthorityRest(); + public VocabularyRest convert(ChoiceAuthority authority, Projection projection) { + VocabularyRest authorityRest = new VocabularyRest(); authorityRest.setProjection(projection); - authorityRest.setHierarchical(step.isHierarchical()); - authorityRest.setScrollable(step.isScrollable()); - authorityRest.setIdentifier(step.hasIdentifier()); + authorityRest.setHierarchical(authority.isHierarchical()); + authorityRest.setScrollable(authority.isScrollable()); + authorityRest.setPreloadLevel(authority.getPreloadLevel()); return authorityRest; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthorityEntryHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthorityEntryHalLinkFactory.java deleted file mode 100644 index e24d70a526..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthorityEntryHalLinkFactory.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * 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.link; - -import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; - -import java.util.LinkedList; - -import org.atteo.evo.inflector.English; -import org.dspace.app.rest.RestResourceController; -import org.dspace.app.rest.model.AuthorityEntryRest; -import org.dspace.app.rest.model.AuthorityRest; -import org.dspace.app.rest.model.hateoas.AuthorityEntryResource; -import org.dspace.app.rest.utils.AuthorityUtils; -import org.springframework.data.domain.Pageable; -import org.springframework.hateoas.IanaLinkRelations; -import org.springframework.hateoas.Link; -import org.springframework.stereotype.Component; -import org.springframework.web.util.UriComponentsBuilder; - -/** - * This class' purpose is to provide a factory to add links to the AuthorityEntryResource. The addLinks factory will - * be called - * from the HalLinkService class addLinks method. - */ -@Component -public class AuthorityEntryHalLinkFactory extends HalLinkFactory { - - protected void addLinks(final AuthorityEntryResource halResource, final Pageable pageable, - final LinkedList list) throws Exception { - AuthorityEntryRest entry = halResource.getContent(); - - if (entry.getOtherInformation() != null) { - if (entry.getOtherInformation().containsKey(AuthorityUtils.RESERVED_KEYMAP_PARENT)) { - UriComponentsBuilder uriComponentsBuilder = linkTo( - getMethodOn(AuthorityRest.CATEGORY, AuthorityRest.NAME) - .findRel(null, null, AuthorityRest.CATEGORY, - English.plural(AuthorityRest.NAME), - entry.getAuthorityName() + "/" + AuthorityRest.ENTRY, - entry.getOtherInformation().get(AuthorityUtils.RESERVED_KEYMAP_PARENT), null, null)) - .toUriComponentsBuilder(); - - list.add(buildLink(AuthorityUtils.RESERVED_KEYMAP_PARENT, uriComponentsBuilder.build().toString())); - } - } - String selfLinkString = linkTo( - getMethodOn().findOne(entry.getCategory(), English.plural(entry.getType()), entry.getAuthorityName())) - .toUriComponentsBuilder().build().toString() + "/entryValues/" + entry.getId(); - list.add(buildLink(IanaLinkRelations.SELF.value(), selfLinkString)); - } - - protected Class getControllerClass() { - return RestResourceController.class; - } - - protected Class getResourceClass() { - return AuthorityEntryResource.class; - } - -} - diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormInputTypeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormInputTypeRest.java index 594d715b22..cda0f6fbaa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormInputTypeRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormInputTypeRest.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; public class SubmissionFormInputTypeRest { private String type; private String regex; - private AuthorityRest authority; + private VocabularyRest authority; public String getType() { return type; @@ -39,11 +39,11 @@ public class SubmissionFormInputTypeRest { this.regex = regex; } - public AuthorityRest getAuthority() { + public VocabularyRest getAuthority() { return authority; } - public void setAuthority(AuthorityRest authority) { + public void setAuthority(VocabularyRest authority) { this.authority = authority; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java similarity index 76% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityEntryRest.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java index 9fcc01d972..0098ea6aa4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java @@ -13,19 +13,19 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import org.dspace.app.rest.RestResourceController; /** - * The Authority Entry REST Resource + * The Vocabulary Entry Details REST Resource * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -public class AuthorityEntryRest extends RestAddressableModel { - public static final String NAME = "authorityEntry"; +public class VocabularyEntryDetailsRest extends RestAddressableModel { + public static final String NAME = "vocabularyDetailEntry"; private String id; private String display; private String value; private Map otherInformation; @JsonIgnore - private String authorityName; + private String vocabularyName; public String getId() { return id; @@ -63,22 +63,22 @@ public class AuthorityEntryRest extends RestAddressableModel { return NAME; } - public String getAuthorityName() { - return authorityName; + public String getVocabularyName() { + return vocabularyName; } - public void setAuthorityName(String authorityName) { - this.authorityName = authorityName; + public void setVocabularyName(String vocabularyName) { + this.vocabularyName = vocabularyName; } @Override public String getCategory() { - return AuthorityRest.CATEGORY; + return VocabularyRest.CATEGORY; } @Override public String getType() { - return AuthorityRest.NAME; + return VocabularyRest.NAME; } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java new file mode 100644 index 0000000000..27d5a4c75e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java @@ -0,0 +1,72 @@ +/** + * 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.model; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dspace.app.rest.RestResourceController; + +/** + * An entry in a Vocabulary + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +public class VocabularyEntryRest { + public static final String NAME = "vocabularyEntry"; + private String authority; + private String display; + private String value; + private Map otherInformation; + + /** + * The Vocabulary Entry Details resource if available related to this entry + */ + @JsonIgnore + private VocabularyEntryDetailsRest vocabularyEntryDetailsRest; + + public String getDisplay() { + return display; + } + + public void setDisplay(String value) { + this.display = value; + } + + public Map getOtherInformation() { + return otherInformation; + } + + public void setOtherInformation(Map otherInformation) { + this.otherInformation = otherInformation; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public void setAuthority(String authority) { + this.authority = authority; + } + + public String getAuthority() { + return authority; + } + + public void setVocabularyEntryDetailsRest(VocabularyEntryDetailsRest vocabularyEntryDetailsRest) { + this.vocabularyEntryDetailsRest = vocabularyEntryDetailsRest; + } + + public VocabularyEntryDetailsRest getVocabularyEntryDetailsRest() { + return vocabularyEntryDetailsRest; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java similarity index 68% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityRest.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java index 3245e6f877..cc848b945b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyRest.java @@ -10,25 +10,20 @@ package org.dspace.app.rest.model; import org.dspace.app.rest.RestResourceController; /** - * The authority REST resource + * The vocabulary REST resource * * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest(name = AuthorityRest.ENTRIES, - method = "query" + @LinkRest(name = VocabularyRest.ENTRIES, + method = "filter" ), - @LinkRest( - name = AuthorityRest.ENTRY, - method = "getResource" - ) }) -public class AuthorityRest extends BaseObjectRest { +public class VocabularyRest extends BaseObjectRest { - public static final String NAME = "authority"; - public static final String CATEGORY = RestAddressableModel.INTEGRATION; + public static final String NAME = "vocabulary"; + public static final String CATEGORY = RestAddressableModel.SUBMISSION; public static final String ENTRIES = "entries"; - public static final String ENTRY = "entryValues"; private String name; @@ -36,7 +31,7 @@ public class AuthorityRest extends BaseObjectRest { private boolean hierarchical; - private boolean identifier; + private Integer preloadLevel; @Override public String getId() { @@ -67,6 +62,14 @@ public class AuthorityRest extends BaseObjectRest { this.hierarchical = hierarchical; } + public Integer getPreloadLevel() { + return preloadLevel; + } + + public void setPreloadLevel(Integer preloadLevel) { + this.preloadLevel = preloadLevel; + } + @Override public String getType() { return NAME; @@ -81,12 +84,4 @@ public class AuthorityRest extends BaseObjectRest { public String getCategory() { return CATEGORY; } - - public boolean hasIdentifier() { - return identifier; - } - - public void setIdentifier(boolean identifier) { - this.identifier = identifier; - } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityEntryResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityEntryResource.java index c99ebd6f2e..e7b0e3e78c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityEntryResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityEntryResource.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.model.hateoas; -import org.dspace.app.rest.model.AuthorityEntryRest; +import org.dspace.app.rest.model.VocabularyEntryDetailsRest; import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; /** @@ -16,11 +16,11 @@ import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -@RelNameDSpaceResource(AuthorityEntryRest.NAME) -public class AuthorityEntryResource extends HALResource { +@RelNameDSpaceResource(VocabularyEntryDetailsRest.NAME) +public class AuthorityEntryResource extends HALResource { - public AuthorityEntryResource(AuthorityEntryRest entry) { + public AuthorityEntryResource(VocabularyEntryDetailsRest entry) { super(entry); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java index 0e153097b4..6152f498fd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.model.hateoas; -import org.dspace.app.rest.model.AuthorityRest; +import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; import org.dspace.app.rest.utils.Utils; @@ -17,13 +17,10 @@ import org.dspace.app.rest.utils.Utils; * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -@RelNameDSpaceResource(AuthorityRest.NAME) -public class AuthorityResource extends DSpaceResource { - public AuthorityResource(AuthorityRest sd, Utils utils) { +@RelNameDSpaceResource(VocabularyRest.NAME) +public class AuthorityResource extends DSpaceResource { + public AuthorityResource(VocabularyRest sd, Utils utils) { super(sd, utils); - if (sd.hasIdentifier()) { - add(utils.linkToSubResource(sd, AuthorityRest.ENTRY)); - } - add(utils.linkToSubResource(sd, AuthorityRest.ENTRIES)); + add(utils.linkToSubResource(sd, VocabularyRest.ENTRIES)); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryValueLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryValueLinkRepository.java deleted file mode 100644 index c2e3c557d4..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryValueLinkRepository.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * 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 javax.servlet.http.HttpServletRequest; - -import org.dspace.app.rest.model.AuthorityEntryRest; -import org.dspace.app.rest.model.AuthorityRest; -import org.dspace.app.rest.projection.Projection; -import org.dspace.app.rest.utils.AuthorityUtils; -import org.dspace.content.authority.Choice; -import org.dspace.content.authority.ChoiceAuthority; -import org.dspace.content.authority.service.ChoiceAuthorityService; -import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Pageable; -import org.springframework.data.rest.webmvc.ResourceNotFoundException; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.stereotype.Component; - -/** - * Controller for exposition of authority services - * - * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) - */ -@Component(AuthorityRest.CATEGORY + "." + AuthorityRest.NAME + "." + AuthorityRest.ENTRY) -public class AuthorityEntryValueLinkRepository extends AbstractDSpaceRestRepository - implements LinkRestRepository { - - @Autowired - private ChoiceAuthorityService cas; - - @Autowired - private AuthorityUtils authorityUtils; - - @PreAuthorize("hasAuthority('AUTHENTICATED')") - public AuthorityEntryRest getResource(HttpServletRequest request, String name, String relId, - Pageable pageable, Projection projection) { - Context context = obtainContext(); - ChoiceAuthority choiceAuthority = cas.getChoiceAuthorityByAuthorityName(name); - Choice choice = choiceAuthority.getChoice(null, relId, context.getCurrentLocale().toString()); - if (choice == null) { - throw new ResourceNotFoundException("The authority was not found"); - } - return authorityUtils.convertEntry(choice, name, projection); - } - - /** - * Not embeddable because this is not currently a pageable subresource. - */ - @Override - public boolean isEmbeddableRelation(Object data, String name) { - return false; - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java similarity index 58% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryLinkRepository.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index 0c3ec16299..a262cad683 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -15,8 +15,10 @@ import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.rest.model.AuthorityEntryRest; -import org.dspace.app.rest.model.AuthorityRest; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.VocabularyEntryDetailsRest; +import org.dspace.app.rest.model.VocabularyEntryRest; +import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.AuthorityUtils; import org.dspace.content.Collection; @@ -37,8 +39,8 @@ import org.springframework.stereotype.Component; * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ -@Component(AuthorityRest.CATEGORY + "." + AuthorityRest.NAME + "." + AuthorityRest.ENTRIES) -public class AuthorityEntryLinkRepository extends AbstractDSpaceRestRepository +@Component(VocabularyRest.CATEGORY + "." + VocabularyRest.NAME + "." + VocabularyRest.ENTRIES) +public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired @@ -51,31 +53,37 @@ public class AuthorityEntryLinkRepository extends AbstractDSpaceRestRepository private AuthorityUtils authorityUtils; @PreAuthorize("hasAuthority('AUTHENTICATED')") - public Page query(@Nullable HttpServletRequest request, String name, + public Page filter(@Nullable HttpServletRequest request, String name, @Nullable Pageable optionalPageable, Projection projection) { Context context = obtainContext(); - String query = request == null ? null : request.getParameter("query"); + String filter = request == null ? null : request.getParameter("filter"); String metadata = request == null ? null : request.getParameter("metadata"); String uuidCollectìon = request == null ? null : request.getParameter("uuid"); + Collection collection = null; if (StringUtils.isNotBlank(uuidCollectìon)) { try { collection = cs.find(context, UUID.fromString(uuidCollectìon)); } catch (SQLException e) { - throw new RuntimeException(e); + throw new UnprocessableEntityException(uuidCollectìon + " is not a valid collection"); } } - List results = new ArrayList<>(); + + // validate the parameters + String[] tokens = org.dspace.core.Utils.tokenize(metadata); + String vocName = cas.getChoiceAuthorityName(tokens[0], tokens[1], tokens[2], collection); + if (!StringUtils.equals(name, vocName)) { + throw new UnprocessableEntityException("The vocabulary " + name + " is not allowed for the metadata " + + metadata + " and collection " + uuidCollectìon); + } + Pageable pageable = utils.getPageable(optionalPageable); - if (StringUtils.isNotBlank(metadata)) { - String[] tokens = org.dspace.core.Utils.tokenize(metadata); - String fieldKey = org.dspace.core.Utils.standardize(tokens[0], tokens[1], tokens[2], "_"); - Choices choices = cas.getMatches(fieldKey, query, collection, Math.toIntExact(pageable.getOffset()), - pageable.getPageSize(), - context.getCurrentLocale().toString()); - for (Choice value : choices.values) { - results.add(authorityUtils.convertEntry(value, name, projection)); - } + List results = new ArrayList<>(); + String fieldKey = org.dspace.core.Utils.standardize(tokens[0], tokens[1], tokens[2], "_"); + Choices choices = cas.getMatches(fieldKey, filter, collection, Math.toIntExact(pageable.getOffset()), + pageable.getPageSize(), context.getCurrentLocale().toString()); + for (Choice value : choices.values) { + results.add(authorityUtils.convertEntry(value, name, projection)); } return new PageImpl<>(results, pageable, results.size()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java similarity index 54% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityRestRepository.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java index d5dda5a0bc..179898e2d1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java @@ -8,35 +8,29 @@ package org.dspace.app.rest.repository; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Set; -import org.dspace.app.rest.DiscoverableEndpointsService; -import org.dspace.app.rest.model.AuthorityRest; -import org.dspace.app.rest.model.AuthorizationRest; +import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.AuthorityUtils; import org.dspace.content.authority.ChoiceAuthority; import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.core.Context; -import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; -import org.springframework.hateoas.Link; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; /** - * Controller for exposition of authority services + * Controller for exposition of vocabularies for the submission * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) + * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@Component(AuthorityRest.CATEGORY + "." + AuthorityRest.NAME) -public class AuthorityRestRepository extends DSpaceRestRepository - implements InitializingBean { +@Component(VocabularyRest.CATEGORY + "." + VocabularyRest.NAME) +public class VocabularyRestRepository extends DSpaceRestRepository { @Autowired private ChoiceAuthorityService cas; @@ -44,39 +38,30 @@ public class AuthorityRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { + public Page findAll(Context context, Pageable pageable) { Set authoritiesName = cas.getChoiceAuthoritiesNames(); - List results = new ArrayList<>(); + List results = new ArrayList<>(); Projection projection = utils.obtainProjection(); for (String authorityName : authoritiesName) { ChoiceAuthority source = cas.getChoiceAuthorityByAuthorityName(authorityName); - AuthorityRest result = authorityUtils.convertAuthority(source, authorityName, projection); + VocabularyRest result = authorityUtils.convertAuthority(source, authorityName, projection); results.add(result); } - return new PageImpl<>(results, pageable, results.size()); + return utils.getPage(results, pageable); } @Override - public Class getDomainClass() { - return AuthorityRest.class; + public Class getDomainClass() { + return VocabularyRest.class; } - @Override - public void afterPropertiesSet() throws Exception { - discoverableEndpointsService.register(this, Arrays.asList( - new Link("/api/" + AuthorizationRest.CATEGORY + "/" + AuthorizationRest.NAME + "/search", - AuthorizationRest.NAME + "-search"))); - } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java index 97be32ecf9..df0308fd41 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java @@ -8,8 +8,9 @@ package org.dspace.app.rest.utils; import org.dspace.app.rest.converter.ConverterService; -import org.dspace.app.rest.model.AuthorityEntryRest; -import org.dspace.app.rest.model.AuthorityRest; +import org.dspace.app.rest.model.VocabularyEntryDetailsRest; +import org.dspace.app.rest.model.VocabularyEntryRest; +import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.projection.Projection; import org.dspace.content.authority.Choice; import org.dspace.content.authority.ChoiceAuthority; @@ -39,11 +40,11 @@ public class AuthorityUtils { public boolean isChoice(String schema, String element, String qualifier) { - return cas.isChoicesConfigured(org.dspace.core.Utils.standardize(schema, element, qualifier, "_")); + return cas.isChoicesConfigured(org.dspace.core.Utils.standardize(schema, element, qualifier, "_"), null); } public String getAuthorityName(String schema, String element, String qualifier) { - return cas.getChoiceAuthorityName(schema, element, qualifier); + return cas.getChoiceAuthorityName(schema, element, qualifier, null); } public boolean isClosed(String schema, String element, String qualifier) { @@ -62,9 +63,21 @@ public class AuthorityUtils { * @param projection the name of the projection to use, or {@code null}. * @return */ - public AuthorityEntryRest convertEntry(Choice choice, String authorityName, Projection projection) { - AuthorityEntryRest entry = converter.toRest(choice, projection); - entry.setAuthorityName(authorityName); + public VocabularyEntryDetailsRest convertEntryDetails(Choice choice, String authorityName, Projection projection) { + VocabularyEntryDetailsRest entry = converter.toRest(choice, projection); + entry.setVocabularyName(authorityName); + return entry; + } + + public VocabularyEntryRest convertEntry(Choice choice, String authorityName, Projection projection) { + VocabularyEntryRest entry = new VocabularyEntryRest(); + entry.setDisplay(choice.label); + entry.setValue(choice.value); + entry.setOtherInformation(choice.extras); + entry.setAuthority(choice.authority); + if (choice.storeAuthority) { + entry.setVocabularyEntryDetailsRest(converter.toRest(choice, projection)); + } return entry; } @@ -76,8 +89,8 @@ public class AuthorityUtils { * @param projection the projecton to use. * @return */ - public AuthorityRest convertAuthority(ChoiceAuthority source, String authorityName, Projection projection) { - AuthorityRest result = converter.toRest(source, projection); + public VocabularyRest convertAuthority(ChoiceAuthority source, String authorityName, Projection projection) { + VocabularyRest result = converter.toRest(source, projection); result.setName(authorityName); return result; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java index fc3b5fb711..7f8cfef8de 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java @@ -47,7 +47,7 @@ import org.apache.log4j.Logger; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.PaginationException; import org.dspace.app.rest.exception.RepositoryNotFoundException; -import org.dspace.app.rest.model.AuthorityRest; +import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.model.LinkRest; @@ -254,7 +254,7 @@ public class Utils { return CommunityRest.NAME; } if (modelPlural.equals("authorities")) { - return AuthorityRest.NAME; + return VocabularyRest.NAME; } if (modelPlural.equals("resourcepolicies")) { return ResourcePolicyRest.NAME; From 17c383364542672efaaa1d631bd13f29ef7dc5bd Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Wed, 17 Jun 2020 10:45:34 +0200 Subject: [PATCH 06/59] Refactoring for vocabularies, second part (Draft) --- .../authority/ChoiceAuthorityServiceImpl.java | 214 +++++++++++------- 1 file changed, 133 insertions(+), 81 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index 0e6836f47b..52ce3f7d25 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -7,7 +7,9 @@ */ package org.dspace.content.authority; +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -19,6 +21,9 @@ import org.dspace.app.util.DCInput; import org.dspace.app.util.DCInputSet; import org.dspace.app.util.DCInputsReader; import org.dspace.app.util.DCInputsReaderException; +import org.dspace.app.util.SubmissionConfig; +import org.dspace.app.util.SubmissionConfigReader; +import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.content.Collection; import org.dspace.content.MetadataValue; import org.dspace.content.authority.service.ChoiceAuthorityService; @@ -54,14 +59,25 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService // map of field key to authority plugin protected Map controller = new HashMap(); + // map of field key, form definition to authority plugin + protected Map> controllerFormDefinitions = + new HashMap>(); + // map of field key to presentation type protected Map presentation = new HashMap(); // map of field key to closed value protected Map closed = new HashMap(); - // map of authority name to field key - protected Map authorities = new HashMap(); + // flag to track the initialization status of the service + private boolean initialized = false; + + // map of authority name to field keys (the same authority can be configured over multiple metadata) + protected Map> authorities = new HashMap>(); + + // map of authority name to form definition and field keys + protected Map>> authoritiesFormDefinitions = + new HashMap>>(); @Autowired(required = true) protected ConfigurationService configurationService; @@ -96,10 +112,18 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public Set getChoiceAuthoritiesNames() { - if (authorities.keySet().isEmpty()) { + init(); + Set authoritiesNames = new HashSet(); + authoritiesNames.addAll(authorities.keySet()); + authoritiesNames.addAll(authoritiesFormDefinitions.keySet()); + return authoritiesNames; + } + + private synchronized void init() { + if (!initialized) { loadChoiceAuthorityConfigurations(); + initialized = true; } - return authorities.keySet(); } @Override @@ -112,23 +136,24 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public Choices getMatches(String fieldKey, String query, Collection collection, int start, int limit, String locale) { - ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey); + ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection); if (ma == null) { throw new IllegalArgumentException( "No choices plugin was configured for field \"" + fieldKey - + "\"."); + + "\", collection=" + collection.getID().toString() + "."); } return ma.getMatches(fieldKey, query, collection, start, limit, locale); } + @Override public Choices getMatches(String fieldKey, String query, Collection collection, int start, int limit, String locale, boolean externalInput) { - ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey); + ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection); if (ma == null) { throw new IllegalArgumentException( "No choices plugin was configured for field \"" + fieldKey - + "\"."); + + "\", collection=" + collection.getID().toString() + "."); } if (externalInput && ma instanceof SolrAuthority) { ((SolrAuthority) ma).addExternalResultsInNextMatches(); @@ -139,11 +164,11 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public Choices getBestMatch(String fieldKey, String query, Collection collection, String locale) { - ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey); + ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection); if (ma == null) { throw new IllegalArgumentException( "No choices plugin was configured for field \"" + fieldKey - + "\"."); + + "\", collection=" + collection.getID().toString() + "."); } return ma.getBestMatch(fieldKey, query, collection, locale); } @@ -155,16 +180,18 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public String getLabel(String fieldKey, Collection collection, String authKey, String locale) { - ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey); + ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection); if (ma == null) { - throw new IllegalArgumentException("No choices plugin was configured for field \"" + fieldKey + "\"."); + throw new IllegalArgumentException( + "No choices plugin was configured for field \"" + fieldKey + + "\", collection=" + collection.getID().toString() + "."); } return ma.getLabel(fieldKey, authKey, locale); } @Override public boolean isChoicesConfigured(String fieldKey, Collection collection) { - return getChoiceAuthorityMap().containsKey(fieldKey); + return getAuthorityByFieldKeyCollection(fieldKey, collection) != null; } @Override @@ -179,7 +206,13 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public List getVariants(MetadataValue metadataValue, Collection collection) { - ChoiceAuthority ma = getChoiceAuthorityMap().get(metadataValue.getMetadataField().toString()); + String fieldKey = metadataValue.getMetadataField().toString(); + ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection); + if (ma == null) { + throw new IllegalArgumentException( + "No choices plugin was configured for field \"" + fieldKey + + "\", collection=" + collection.getID().toString() + "."); + } if (ma instanceof AuthorityVariantsSupport) { AuthorityVariantsSupport avs = (AuthorityVariantsSupport) ma; return avs.getVariants(metadataValue.getAuthority(), metadataValue.getLanguage()); @@ -190,41 +223,23 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public String getChoiceAuthorityName(String schema, String element, String qualifier, Collection collection) { - String makeFieldKey = makeFieldKey(schema, element, qualifier); - if (getChoiceAuthorityMap().containsKey(makeFieldKey)) { - for (String key : this.authorities.keySet()) { - if (this.authorities.get(key).equals(makeFieldKey)) { - return key; - } - } - } - return configurationService.getProperty( - CHOICES_PLUGIN_PREFIX + schema + "." + element + (qualifier != null ? "." + qualifier : "")); + //FIXME AB iterate over authorities and authoritiesFormDefinitions to identify the match if any in the map values + return null; } protected String makeFieldKey(String schema, String element, String qualifier) { return Utils.standardize(schema, element, qualifier, "_"); } - /** - * Return map of key to ChoiceAuthority plugin - * - * @return - */ - private Map getChoiceAuthorityMap() { - // If empty, load from configuration - if (controller.isEmpty()) { - loadChoiceAuthorityConfigurations(); - } - - return controller; - } - @Override public void clearCache() { controller.clear(); authorities.clear(); + controllerFormDefinitions.clear(); + authoritiesFormDefinitions.clear(); + initialized = false; } + private void loadChoiceAuthorityConfigurations() { // Get all configuration keys starting with a given prefix List propKeys = configurationService.getPropertyKeys(CHOICES_PLUGIN_PREFIX); @@ -249,17 +264,16 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService "Skipping invalid configuration for " + key + " because named plugin not found: " + authorityName); continue; } - if (!authorities.containsKey(authorityName)) { - controller.put(fkey, ma); - authorities.put(authorityName, fkey); - } else { - log.warn( - "Skipping invalid configuration for " + key + " because plugin is alredy in use: " + - authorityName + " used by " + authorities - .get(authorityName)); - continue; - } + controller.put(fkey, ma); + List fkeys; + if (!authorities.containsKey(authorityName)) { + fkeys = authorities.get(authorityName); + } else { + fkeys = new ArrayList(); + } + fkeys.add(fkey); + authorities.put(authorityName, fkeys); log.debug("Choice Control: For field=" + fkey + ", Plugin=" + ma); } autoRegisterChoiceAuthorityFromInputReader(); @@ -267,50 +281,72 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService private void autoRegisterChoiceAuthorityFromInputReader() { try { + SubmissionConfigReader itemSubmissionConfigReader = new SubmissionConfigReader(); + List submissionConfigs = itemSubmissionConfigReader + .getAllSubmissionConfigs(Integer.MAX_VALUE, 0); DCInputsReader dcInputsReader = new DCInputsReader(); - for (DCInputSet dcinputSet : dcInputsReader.getAllInputs(Integer.MAX_VALUE, 0)) { - DCInput[][] dcinputs = dcinputSet.getFields(); - for (DCInput[] dcrows : dcinputs) { - for (DCInput dcinput : dcrows) { - if (StringUtils.isNotBlank(dcinput.getPairsType()) - || StringUtils.isNotBlank(dcinput.getVocabulary())) { - String authorityName = dcinput.getPairsType(); - if (StringUtils.isBlank(authorityName)) { - authorityName = dcinput.getVocabulary(); - } - if (!StringUtils.equals(dcinput.getInputType(), "qualdrop_value")) { - String fieldKey = makeFieldKey(dcinput.getSchema(), dcinput.getElement(), - dcinput.getQualifier()); - ChoiceAuthority ca = controller.get(authorityName); - if (ca == null) { - InputFormSelfRegisterWrapperAuthority ifa = new - InputFormSelfRegisterWrapperAuthority(); - if (controller.containsKey(fieldKey)) { - ifa = (InputFormSelfRegisterWrapperAuthority) controller.get(fieldKey); + + for (SubmissionConfig subCfg : submissionConfigs) { + String submissionName = subCfg.getSubmissionName(); + List inputsBySubmissionName = dcInputsReader.getInputsBySubmissionName(submissionName); + for (DCInputSet dcinputSet : inputsBySubmissionName) { + DCInput[][] dcinputs = dcinputSet.getFields(); + for (DCInput[] dcrows : dcinputs) { + for (DCInput dcinput : dcrows) { + if (StringUtils.isNotBlank(dcinput.getPairsType()) + || StringUtils.isNotBlank(dcinput.getVocabulary())) { + String authorityName = dcinput.getPairsType(); + if (StringUtils.isBlank(authorityName)) { + authorityName = dcinput.getVocabulary(); + } + if (!StringUtils.equals(dcinput.getInputType(), "qualdrop_value")) { + String fieldKey = makeFieldKey(dcinput.getSchema(), dcinput.getElement(), + dcinput.getQualifier()); + ChoiceAuthority ca = controller.get(authorityName); + if (ca == null) { + ca = (ChoiceAuthority) pluginService + .getNamedPlugin(ChoiceAuthority.class, authorityName); + if (ca == null) { + throw new IllegalStateException("Invalid configuration for " + fieldKey + + " in submission definition " + submissionName + + ", form definition " + dcinputSet.getFormName() + + " no named plugin found: " + authorityName); + } } - ChoiceAuthority ma = (ChoiceAuthority) pluginService - .getNamedPlugin(ChoiceAuthority.class, authorityName); - if (ma == null) { - log.warn("Skipping invalid configuration for " + fieldKey - + " because named plugin not found: " + authorityName); - continue; + Map definition2authority; + if (controllerFormDefinitions.containsKey(fieldKey)) { + definition2authority = controllerFormDefinitions.get(fieldKey); } - ifa.getDelegates().put(dcinputSet.getFormName(), ma); - controller.put(fieldKey, ifa); - } + else { + definition2authority = new HashMap(); + } + definition2authority.put(submissionName, ca); + controllerFormDefinitions.put(fieldKey, definition2authority); - if (!authorities.containsKey(authorityName)) { - authorities.put(authorityName, fieldKey); - } + Map> authorityName2definitions; + if (authoritiesFormDefinitions.containsKey(authorityName)) { + authorityName2definitions = authoritiesFormDefinitions.get(authorityName); + } else { + authorityName2definitions = new HashMap>(); + } + List fields; + if (authorityName2definitions.containsKey(submissionName)) { + fields = authorityName2definitions.get(submissionName); + } else { + fields = new ArrayList(); + } + authorityName2definitions.put(submissionName, fields); + authoritiesFormDefinitions.put(fieldKey, authorityName2definitions); + } } } } } } - } catch (DCInputsReaderException e) { - throw new IllegalStateException(e.getMessage(), e); + } catch (SubmissionConfigReaderException | DCInputsReaderException e) { + throw new IllegalStateException(e); } } @@ -381,4 +417,20 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } return ma; } + + private ChoiceAuthority getAuthorityByFieldKeyCollection(String fieldKey, Collection collection) { + init(); + ChoiceAuthority ma = controller.get(fieldKey); + if (ma == null) { + SubmissionConfigReader configReader; + try { + configReader = new SubmissionConfigReader(); + SubmissionConfig submissionName = configReader.getSubmissionConfigByCollection(collection.getHandle()); + ma = controllerFormDefinitions.get(submissionName.getSubmissionName()).get(fieldKey); + } catch (SubmissionConfigReaderException e) { + throw new IllegalStateException(e); + } + } + return ma; + } } From 9849d6c41e00257e3721043661934f020ec5fac9 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 17 Jun 2020 14:19:54 +0200 Subject: [PATCH 07/59] fix checkstyle --- .../content/authority/ChoiceAuthorityServiceImpl.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index 52ce3f7d25..b06f6f3347 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -223,7 +223,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public String getChoiceAuthorityName(String schema, String element, String qualifier, Collection collection) { - //FIXME AB iterate over authorities and authoritiesFormDefinitions to identify the match if any in the map values + //FIXME AB iterate over authorities and authoritiesFormDefinitions to identify the match if any in the map values return null; } @@ -267,7 +267,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService controller.put(fkey, ma); List fkeys; - if (!authorities.containsKey(authorityName)) { + if (authorities.containsKey(authorityName)) { fkeys = authorities.get(authorityName); } else { fkeys = new ArrayList(); @@ -317,8 +317,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService Map definition2authority; if (controllerFormDefinitions.containsKey(fieldKey)) { definition2authority = controllerFormDefinitions.get(fieldKey); - } - else { + } else { definition2authority = new HashMap(); } definition2authority.put(submissionName, ca); From 4784d34066d32c1f75ce1afebfc36d6681118e16 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 17 Jun 2020 18:22:13 +0200 Subject: [PATCH 08/59] renamed classes --- ...nverter.java => VocabularyEntryDetailsRestConverter.java} | 2 +- ...horityRestConverter.java => VocabularyRestConverter.java} | 2 +- ...ntryResource.java => VocabularyEntryDetailsResource.java} | 4 ++-- .../{AuthorityResource.java => VocabularyResource.java} | 4 ++-- .../app/rest/repository/VocabularyEntryLinkRepository.java | 1 - .../src/main/java/org/dspace/app/rest/utils/Utils.java | 5 ++++- ...yVocabularyEntryIT.java => VocabularyEntryDetailsIT.java} | 2 +- ...RestRepositoryIT.java => VocabularyRestRepositoryIT.java} | 0 8 files changed, 11 insertions(+), 9 deletions(-) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/{AuthorityEntryRestConverter.java => VocabularyEntryDetailsRestConverter.java} (92%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/{AuthorityRestConverter.java => VocabularyRestConverter.java} (93%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/{AuthorityEntryResource.java => VocabularyEntryDetailsResource.java} (80%) rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/{AuthorityResource.java => VocabularyResource.java} (85%) rename dspace-server-webapp/src/test/java/org/dspace/app/rest/{AuthorityVocabularyEntryIT.java => VocabularyEntryDetailsIT.java} (99%) rename dspace-server-webapp/src/test/java/org/dspace/app/rest/{AuthorityRestRepositoryIT.java => VocabularyRestRepositoryIT.java} (100%) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityEntryRestConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyEntryDetailsRestConverter.java similarity index 92% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityEntryRestConverter.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyEntryDetailsRestConverter.java index d50ed6fc9f..63154ccf22 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityEntryRestConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyEntryDetailsRestConverter.java @@ -22,7 +22,7 @@ import org.springframework.stereotype.Component; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @Component -public class AuthorityEntryRestConverter implements DSpaceConverter { +public class VocabularyEntryDetailsRestConverter implements DSpaceConverter { @Override public VocabularyEntryDetailsRest convert(Choice choice, Projection projection) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyRestConverter.java similarity index 93% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyRestConverter.java index f481568746..5dcb05a23e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyRestConverter.java @@ -23,7 +23,7 @@ import org.springframework.stereotype.Component; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @Component -public class AuthorityRestConverter implements DSpaceConverter { +public class VocabularyRestConverter implements DSpaceConverter { @Override public VocabularyRest convert(ChoiceAuthority authority, Projection projection) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityEntryResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryDetailsResource.java similarity index 80% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityEntryResource.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryDetailsResource.java index e7b0e3e78c..f307992ff8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityEntryResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryDetailsResource.java @@ -17,10 +17,10 @@ import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @RelNameDSpaceResource(VocabularyEntryDetailsRest.NAME) -public class AuthorityEntryResource extends HALResource { +public class VocabularyEntryDetailsResource extends HALResource { - public AuthorityEntryResource(VocabularyEntryDetailsRest entry) { + public VocabularyEntryDetailsResource(VocabularyEntryDetailsRest entry) { super(entry); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyResource.java similarity index 85% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyResource.java index 6152f498fd..4a2ec01d33 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyResource.java @@ -18,8 +18,8 @@ import org.dspace.app.rest.utils.Utils; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @RelNameDSpaceResource(VocabularyRest.NAME) -public class AuthorityResource extends DSpaceResource { - public AuthorityResource(VocabularyRest sd, Utils utils) { +public class VocabularyResource extends DSpaceResource { + public VocabularyResource(VocabularyRest sd, Utils utils) { super(sd, utils); add(utils.linkToSubResource(sd, VocabularyRest.ENTRIES)); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index a262cad683..feb593ae5b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -16,7 +16,6 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.exception.UnprocessableEntityException; -import org.dspace.app.rest.model.VocabularyEntryDetailsRest; import org.dspace.app.rest.model.VocabularyEntryRest; import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.projection.Projection; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java index 7f8cfef8de..b91e28519e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java @@ -47,7 +47,6 @@ import org.apache.log4j.Logger; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.PaginationException; import org.dspace.app.rest.exception.RepositoryNotFoundException; -import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.model.LinkRest; @@ -58,6 +57,7 @@ import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.model.VersionHistoryRest; +import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.model.hateoas.EmbeddedPage; import org.dspace.app.rest.model.hateoas.HALResource; import org.dspace.app.rest.projection.CompositeProjection; @@ -268,6 +268,9 @@ public class Utils { if (StringUtils.equals(modelPlural, "properties")) { return PropertyRest.NAME; } + if (StringUtils.equals(modelPlural, "vocabularies")) { + return VocabularyRest.NAME; + } return modelPlural.replaceAll("s$", ""); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java similarity index 99% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index f78add992b..72c101ab03 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityVocabularyEntryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -19,7 +19,7 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.hamcrest.Matchers; import org.junit.Test; -public class AuthorityVocabularyEntryIT extends AbstractControllerIntegrationTest { +public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest { @Test public void findOneTest() throws Exception { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java similarity index 100% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java From d7ce794eab7ee7a658e9fb99734f9fd1705283ed Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 18 Jun 2020 10:14:56 +0200 Subject: [PATCH 09/59] fix creation of the internal maps of authorities --- .../authority/ChoiceAuthorityServiceImpl.java | 2 +- .../content/authority/DCInputAuthority.java | 5 +++++ .../app/rest/VocabularyRestRepositoryIT.java | 21 +++++++++---------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index b06f6f3347..2f454ac612 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -337,7 +337,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService fields = new ArrayList(); } authorityName2definitions.put(submissionName, fields); - authoritiesFormDefinitions.put(fieldKey, authorityName2definitions); + authoritiesFormDefinitions.put(authorityName, authorityName2definitions); } } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index a64ebdd971..5bb2251eb3 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -146,4 +146,9 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority return "UNKNOWN KEY " + key; } } + + @Override + public boolean isScrollable() { + return true; + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index ec5566ae9f..375822f4a4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -34,7 +34,7 @@ import org.springframework.beans.factory.annotation.Autowired; * This class handles all Authority related IT. It alters some config to run the tests, but it gets cleared again * after every test */ -public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest { +public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired ConfigurationService configurationService; @@ -100,11 +100,12 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$._embedded.vocabularies", Matchers.containsInAnyOrder( VocabularyMatcher.matchProperties("srsc", "srsc", false, true), VocabularyMatcher.matchProperties("common_types", "common_types", true, false), - VocabularyMatcher.matchProperties("common_iso_languages", "common_iso_languages", true , false) + VocabularyMatcher.matchProperties("common_iso_languages", "common_iso_languages", true , false), + VocabularyMatcher.matchProperties("SolrAuthorAuthority", "SolrAuthorAuthority", false , false) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("api/submission/vocabularies"))) - .andExpect(jsonPath("$.page.totalElements", is(3))); + .andExpect(jsonPath("$.page.totalElements", is(4))); } @Test @@ -112,10 +113,9 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularies/srsc")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.contains( - VocabularyMatcher.matchProperties("srsc", "srsc", false, true) - ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + .andExpect(jsonPath("$", is( + VocabularyMatcher.matchProperties("srsc", "srsc", false, true) + ))); } @Test @@ -123,10 +123,9 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularies/common_types")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.contains( - VocabularyMatcher.matchProperties("common_types", "common_types", true, false) - ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + .andExpect(jsonPath("$", is( + VocabularyMatcher.matchProperties("common_types", "common_types", true, false) + ))); } @Test From 3e8b2f87b66f61b66c12ec41e11caa6a5a19ca5c Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:28:48 +0200 Subject: [PATCH 10/59] Remove invalid test, cleanup --- .../app/rest/VocabularyEntryDetailsIT.java | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index 72c101ab03..2fbd529a9d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -37,13 +37,6 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$.otherInformation.parent", is("HUMANITIES and RELIGION"))); } - @Test - public void findOneBadRequestTest() throws Exception { - String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + UUID.randomUUID().toString())) - .andExpect(status().isBadRequest()); - } - public void findOneUnauthorizedTest() throws Exception { String idAuthority = "srsc:SCB110"; getClient().perform(get("/api/submission/vocabularyEntryDetails/" + idAuthority)) @@ -96,7 +89,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest @Test public void srscSearchFirstLevel_MATHEMATICS_Test() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB14" + "/children")) + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB14/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.containsInAnyOrder( AuthorityEntryMatcher.matchAuthority("srsc:SCB1401", "Algebra, geometry and mathematical analysis"), @@ -173,7 +166,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest @Test public void retrieveSrscValueTest() throws Exception { String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + "SCB1922") + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB1922") .param("projection", "full")) .andExpect(status().isOk()); } @@ -182,7 +175,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest public void srscSearchByParentFirstLevelPaginationTest() throws Exception { String token = getAuthToken(eperson.getEmail(), password); // first page - getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB14" + "/children") + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB14/children") .param("page", "0") .param("size", "2")) .andExpect(status().isOk()) @@ -195,7 +188,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$.page.number", is(0))); // second page - getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB14" + "/children") + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB14/children") .param("page", "1") .param("size", "2")) .andExpect(status().isOk()) @@ -210,7 +203,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest @Test public void srscSearchByParentSecondLevel_Applied_mathematics_Test() throws Exception { String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB1402" + "/children")) + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB1402/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.containsInAnyOrder( AuthorityEntryMatcher.matchAuthority("VR140202", "Numerical analysis"), @@ -224,7 +217,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest @Test public void srscSearchByParentEmptyTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:VR140202" + "/children")) + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/srsc:VR140202/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); } @@ -248,7 +241,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest @Test public void srscSearchParentByChildrenTest() throws Exception { String tokenEperson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:VR140202" + "/children")) + getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:VR140202/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.contains( AuthorityEntryMatcher.matchAuthority("SCB1402", "Applied mathematics") @@ -259,7 +252,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest @Test public void srscSearchParentByChildrenRootTest() throws Exception { String tokenEperson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/" + "srsc:SCB11" + "/children")) + getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB11/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); } From be6f7f3bfd2edc3cd1513b075ab444a4fdae6f99 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:38:12 +0200 Subject: [PATCH 11/59] Update default to reflect the rest contract the visualization is now responsability of the angular UI so it is better by default present a clean label as the hierarchy is still visible in the otherInformation --- .../authority/DSpaceControlledVocabulary.java | 19 ++++++++++++++++--- dspace/config/dspace.cfg | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index 40b1e6b73d..d97e06f250 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -65,7 +65,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic protected String vocabularyName = null; protected InputSource vocabulary = null; - protected Boolean suggestHierarchy = true; + protected Boolean suggestHierarchy = false; protected Boolean storeHierarchy = true; protected String hierarchyDelimiter = "::"; @@ -194,13 +194,26 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic } @Override - public String getLabel(String field, String key, String locale) { + public String getLabel(String key, String locale) { + return getLabel(key, this.suggestHierarchy); + } + + @Override + public String getValue(String key, String locale) { + return getLabel(key, this.storeHierarchy); + } + + private String getLabel(String key, boolean useHierarchy) { init(); String xpathExpression = String.format(idTemplate, key); XPath xpath = XPathFactory.newInstance().newXPath(); try { Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); - return node.getAttributes().getNamedItem("label").getNodeValue(); + if (useHierarchy) { + return this.buildString(node); + } else { + return node.getAttributes().getNamedItem("label").getNodeValue(); + } } catch (XPathExpressionException e) { return (""); } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 4311887f96..c07cd72df1 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1473,7 +1473,7 @@ orcid.url = https://orcid.org/ ## eg: nsi, srsc. ## Each DSpaceControlledVocabulary plugin comes with three configuration options: # vocabulary.plugin._plugin_.hierarchy.store = # default: true -# vocabulary.plugin._plugin_.hierarchy.suggest = # default: true +# vocabulary.plugin._plugin_.hierarchy.suggest = # default: false # vocabulary.plugin._plugin_.delimiter = "" # default: "::" ## ## An example using "srsc" can be found later in this section From ec3df2e523bed760fab64005ed879f147170e6c7 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:42:59 +0200 Subject: [PATCH 12/59] Fix test setup, update response code according to the contract --- .../org/dspace/app/rest/VocabularyRestRepositoryIT.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index 375822f4a4..c5cca5cf46 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -17,6 +17,7 @@ import java.util.Date; import java.util.UUID; import org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.matcher.VocabularyMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authority.PersonAuthorityValue; @@ -69,6 +70,10 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes pluginService.clearNamedPluginClasses(); cas.clearCache(); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("A parent community for all our test") + .build(); + context.restoreAuthSystemState(); PersonAuthorityValue person1 = new PersonAuthorityValue(); person1.setId(String.valueOf(UUID.randomUUID())); person1.setLastName("Shirasaka"); @@ -165,7 +170,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("metadata", "dc.subject") .param("collection", UUID.randomUUID().toString()) .param("query", "Research")) - .andExpect(status().isBadRequest()); + .andExpect(status().isUnprocessableEntity()); } @Test @@ -337,7 +342,6 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void findByMetadataAndCollectionUnprocessableEntityTest() throws Exception { context.turnOffAuthorisationSystem(); - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) .withName("Test collection") .build(); From 94581761b91f703129d50803e5d0ec8c2991d031 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:55:40 +0200 Subject: [PATCH 13/59] Fix typos --- .../org/dspace/app/rest/model/VocabularyEntryDetailsRest.java | 4 ++-- .../java/org/dspace/app/rest/model/VocabularyEntryRest.java | 3 +-- .../rest/model/hateoas/VocabularyEntryDetailsResource.java | 3 +-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java index 0098ea6aa4..e41ddafe45 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java @@ -18,7 +18,7 @@ import org.dspace.app.rest.RestResourceController; * @author Andrea Bollini (andrea.bollini at 4science.it) */ public class VocabularyEntryDetailsRest extends RestAddressableModel { - public static final String NAME = "vocabularyDetailEntry"; + public static final String NAME = "vocabularyEntryDetail"; private String id; private String display; private String value; @@ -78,7 +78,7 @@ public class VocabularyEntryDetailsRest extends RestAddressableModel { @Override public String getType() { - return VocabularyRest.NAME; + return VocabularyEntryDetailsRest.NAME; } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java index 27d5a4c75e..f9855828ea 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java @@ -10,7 +10,6 @@ package org.dspace.app.rest.model; import java.util.Map; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.dspace.app.rest.RestResourceController; /** * An entry in a Vocabulary @@ -23,7 +22,7 @@ public class VocabularyEntryRest { private String display; private String value; private Map otherInformation; - + /** * The Vocabulary Entry Details resource if available related to this entry */ diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryDetailsResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryDetailsResource.java index f307992ff8..3ba40ffa63 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryDetailsResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryDetailsResource.java @@ -11,7 +11,7 @@ import org.dspace.app.rest.model.VocabularyEntryDetailsRest; import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; /** - * Authority Rest HAL Resource. The HAL Resource wraps the REST Resource adding + * Vocabulary Entry Details Rest HAL Resource. The HAL Resource wraps the REST Resource adding * support for the links and embedded resources * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) @@ -19,7 +19,6 @@ import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; @RelNameDSpaceResource(VocabularyEntryDetailsRest.NAME) public class VocabularyEntryDetailsResource extends HALResource { - public VocabularyEntryDetailsResource(VocabularyEntryDetailsRest entry) { super(entry); } From ae60714f7d4035019f4581d39baa365333586d28 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:55:58 +0200 Subject: [PATCH 14/59] Align test with the contract --- .../app/rest/VocabularyRestRepositoryIT.java | 33 ++++++------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index c5cca5cf46..0e8b127494 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -147,7 +147,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") .param("collection", collection.getID().toString()) - .param("query", "Research") + .param("filter", "Research") .param("size", "2")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( @@ -169,7 +169,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") .param("collection", UUID.randomUUID().toString()) - .param("query", "Research")) + .param("filter", "Research")) .andExpect(status().isUnprocessableEntity()); } @@ -178,7 +178,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") - .param("query", "Research")) + .param("filter", "Research")) .andExpect(status().isBadRequest()); } @@ -192,10 +192,10 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") - .param("metadata", "dc.subject") + .param("metadata", "dc.type") .param("collection", collection.getID().toString()) - .param("query", "Research")) - .andExpect(status().isBadRequest()); + .param("filter", "Research")) + .andExpect(status().isUnprocessableEntity()); } @Test @@ -227,7 +227,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") .param("collection", collection.getID().toString()) - .param("query", "Research2") + .param("filter", "Research2") .param("size", "1000")) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); @@ -270,7 +270,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") .param("metadata", "dc.type") .param("collection", collection.getID().toString()) - .param("query", "Book") + .param("filter", "Book") .param("size", "2")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( @@ -295,7 +295,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes get("/api/submission/vocabularies/SolrAuthorAuthority/entries") .param("metadata", "dc.contributor.author") .param("collection", collection.getID().toString()) - .param("query", "Shirasaka") + .param("filter", "Shirasaka") .param("size", "1000")) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); @@ -314,7 +314,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes get("/api/submission/vocabularies/SolrAuthorAuthority/entries") .param("metadata", "dc.contributor.author") .param("collection", collection.getID().toString()) - .param("query", "Smith") + .param("filter", "Smith") .param("size", "1000")) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); @@ -379,17 +379,4 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("metadata", "dc.type")) .andExpect(status().isUnprocessableEntity()); } - - @Test - public void discoverableNestedLinkTest() throws Exception { - String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._links",Matchers.allOf( - hasJsonPath("$.authorizations.href", - is("http://localhost/api/authz/authorizations")), - hasJsonPath("$.authorization-search.href", - is("http://localhost/api/authz/authorization/search")) - ))); - } } From f44b28a8f5855dcf89f0815544be03e73e228456 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:56:23 +0200 Subject: [PATCH 15/59] Implement check for mandatory parameters --- .../app/rest/repository/VocabularyEntryLinkRepository.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index feb593ae5b..967124624a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -57,7 +57,11 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository Context context = obtainContext(); String filter = request == null ? null : request.getParameter("filter"); String metadata = request == null ? null : request.getParameter("metadata"); - String uuidCollectìon = request == null ? null : request.getParameter("uuid"); + String uuidCollectìon = request == null ? null : request.getParameter("collection"); + + if (StringUtils.isEmpty(metadata) || StringUtils.isEmpty(uuidCollectìon)) { + throw new IllegalArgumentException("the metadata and collection parameters are both required"); + } Collection collection = null; if (StringUtils.isNotBlank(uuidCollectìon)) { From d38dfdb31fe92592f34f7a91c29c5a79737a784c Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:56:44 +0200 Subject: [PATCH 16/59] Add support for : in string identifier --- .../java/org/dspace/app/rest/RestResourceController.java | 8 ++++---- .../main/java/org/dspace/app/rest/utils/RegexUtils.java | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java index a1684d782e..025f7ad03b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -149,7 +149,7 @@ public class RestResourceController implements InitializingBean { * @return single DSpaceResource */ @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT) - public DSpaceResource findOne(@PathVariable String apiCategory, @PathVariable String model, + public HALResource findOne(@PathVariable String apiCategory, @PathVariable String model, @PathVariable Integer id) { return findOneInternal(apiCategory, model, id); } @@ -180,7 +180,7 @@ public class RestResourceController implements InitializingBean { * @return single DSpaceResource */ @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG) - public DSpaceResource findOne(@PathVariable String apiCategory, @PathVariable String model, + public HALResource findOne(@PathVariable String apiCategory, @PathVariable String model, @PathVariable String id) { return findOneInternal(apiCategory, model, id); } @@ -200,7 +200,7 @@ public class RestResourceController implements InitializingBean { * @return single DSpaceResource */ @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID) - public DSpaceResource findOne(@PathVariable String apiCategory, @PathVariable String model, + public HALResource findOne(@PathVariable String apiCategory, @PathVariable String model, @PathVariable UUID uuid) { return findOneInternal(apiCategory, model, uuid); } @@ -213,7 +213,7 @@ public class RestResourceController implements InitializingBean { * @param id Identifier from request * @return single DSpaceResource */ - private DSpaceResource findOneInternal(String apiCategory, + private HALResource findOneInternal(String apiCategory, String model, ID id) { DSpaceRestRepository repository = utils.getResourceRepository(apiCategory, model); Optional modelObject = Optional.empty(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java index 8e887261db..ea0b793055 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java @@ -28,7 +28,8 @@ public class RegexUtils { * identifier (digits or uuid) */ public static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG = "/{id:^(?!^\\d+$)" + - "(?!^[0-9a-fxA-FX]{8}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{12}$)[\\w+\\-\\.]+$+}"; + "(?!^[0-9a-fxA-FX]{8}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{12}$)" + + "[\\w+\\-\\.:]+$+}"; /** * Regular expression in the request mapping to accept number as identifier From 61c346089e14429b8208ddc7c9a3a38eef94c972 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:58:13 +0200 Subject: [PATCH 17/59] Cleanup authority service implementation --- .../content/authority/ChoiceAuthority.java | 23 +++++++++- .../authority/ChoiceAuthorityServiceImpl.java | 45 +++++++++++++++++-- .../content/authority/DCInputAuthority.java | 2 +- .../content/authority/SampleAuthority.java | 2 +- .../content/authority/SolrAuthority.java | 2 +- .../content/authority/TestAuthority.java | 2 +- .../dspace/app/rest/utils/AuthorityUtils.java | 3 +- 7 files changed, 68 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java index c77799f83f..0fbab49bac 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java @@ -67,12 +67,23 @@ public interface ChoiceAuthority { * This may get called many times while populating a Web page so it should * be implemented as efficiently as possible. * - * @param field being matched for * @param key authority key known to this authority. * @param locale explicit localization key if available, or null * @return descriptive label - should always return something, never null. */ - public String getLabel(String field, String key, String locale); + public String getLabel(String key, String locale); + + /** + * Get the canonical value to store for a key in the authority. Can be localized + * given the implicit or explicit locale specification. + * + * @param key authority key known to this authority. + * @param locale explicit localization key if available, or null + * @return value to store - should always return something, never null. + */ + default String getValue(String key, String locale) { + return getLabel(key, locale); + } default boolean isHierarchical() { return false; @@ -85,4 +96,12 @@ public interface ChoiceAuthority { default Integer getPreloadLevel() { return isHierarchical() ? 0 : null; } + + default public Choice getChoice(String authKey, String locale) { + Choice result = new Choice(); + result.authority = authKey; + result.label = getLabel(authKey, locale); + result.value = getValue(authKey, locale); + return result; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index 2f454ac612..a73bf525e2 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -13,6 +13,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import org.apache.commons.lang3.StringUtils; @@ -79,6 +80,9 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService protected Map>> authoritiesFormDefinitions = new HashMap>>(); + // the item submission reader + private SubmissionConfigReader itemSubmissionConfigReader; + @Autowired(required = true) protected ConfigurationService configurationService; @Autowired(required = true) @@ -121,6 +125,12 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService private synchronized void init() { if (!initialized) { + try { + itemSubmissionConfigReader = new SubmissionConfigReader(); + } catch (SubmissionConfigReaderException e) { + // the system is in an illegal state as the submission definition is not valid + throw new IllegalStateException(e); + } loadChoiceAuthorityConfigurations(); initialized = true; } @@ -186,7 +196,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService "No choices plugin was configured for field \"" + fieldKey + "\", collection=" + collection.getID().toString() + "."); } - return ma.getLabel(fieldKey, authKey, locale); + return ma.getLabel(authKey, locale); } @Override @@ -223,7 +233,32 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public String getChoiceAuthorityName(String schema, String element, String qualifier, Collection collection) { - //FIXME AB iterate over authorities and authoritiesFormDefinitions to identify the match if any in the map values + String fieldKey = makeFieldKey(schema, element, qualifier); + // check if there is an authority configured for the metadata valid for all the collections + if (controller.containsKey(fieldKey)) { + for (Entry> authority2md : authorities.entrySet()) { + if (authority2md.getValue().contains(fieldKey)) { + return authority2md.getKey(); + } + } + } else if (collection != null && controllerFormDefinitions.containsKey(fieldKey)) { + // there is an authority configured for the metadata valid for some collections, + // check if it is the requested collection + Map controllerFormDef = controllerFormDefinitions.get(fieldKey); + SubmissionConfig submissionConfig = itemSubmissionConfigReader + .getSubmissionConfigByCollection(collection.getHandle()); + String submissionName = submissionConfig.getSubmissionName(); + // check if the requested collection has a submission definition that use an authority for the metadata + if (controllerFormDef.containsKey(submissionName)) { + for (Entry>> authority2defs2md : + authoritiesFormDefinitions.entrySet()) { + List mdByDefinition = authority2defs2md.getValue().get(submissionName); + if (mdByDefinition != null && mdByDefinition.contains(fieldKey)) { + return authority2defs2md.getKey(); + } + } + } + } return null; } @@ -237,6 +272,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService authorities.clear(); controllerFormDefinitions.clear(); authoritiesFormDefinitions.clear(); + itemSubmissionConfigReader = null; initialized = false; } @@ -281,7 +317,6 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService private void autoRegisterChoiceAuthorityFromInputReader() { try { - SubmissionConfigReader itemSubmissionConfigReader = new SubmissionConfigReader(); List submissionConfigs = itemSubmissionConfigReader .getAllSubmissionConfigs(Integer.MAX_VALUE, 0); DCInputsReader dcInputsReader = new DCInputsReader(); @@ -344,7 +379,8 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } } } - } catch (SubmissionConfigReaderException | DCInputsReaderException e) { + } catch (DCInputsReaderException e) { + // the system is in an illegal state as the submission definition is not valid throw new IllegalStateException(e); } } @@ -427,6 +463,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService SubmissionConfig submissionName = configReader.getSubmissionConfigByCollection(collection.getHandle()); ma = controllerFormDefinitions.get(submissionName.getSubmissionName()).get(fieldKey); } catch (SubmissionConfigReaderException e) { + // the system is in an illegal state as the submission definition is not valid throw new IllegalStateException(e); } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index 5bb2251eb3..42e79820c7 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -131,7 +131,7 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority } @Override - public String getLabel(String field, String key, String locale) { + public String getLabel(String key, String locale) { init(); int pos = -1; for (int i = 0; i < values.length; i++) { diff --git a/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java index 8197f180af..0aaf8e9003 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java @@ -60,7 +60,7 @@ public class SampleAuthority implements ChoiceAuthority { } @Override - public String getLabel(String field, String key, String locale) { + public String getLabel(String key, String locale) { return labels[Integer.parseInt(key)]; } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java index 5e913430b7..efc910a761 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java @@ -207,7 +207,7 @@ public class SolrAuthority implements ChoiceAuthority { } @Override - public String getLabel(String field, String key, String locale) { + public String getLabel(String key, String locale) { try { if (log.isDebugEnabled()) { log.debug("requesting label for key " + key + " using locale " + locale); diff --git a/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java index a017e8fe28..1bb3fa5450 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java @@ -70,7 +70,7 @@ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport } @Override - public String getLabel(String field, String key, String locale) { + public String getLabel(String key, String locale) { if (StringUtils.isNotBlank(key)) { return key.replaceAll("authority", "label"); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java index df0308fd41..1d15693f72 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java @@ -66,9 +66,10 @@ public class AuthorityUtils { public VocabularyEntryDetailsRest convertEntryDetails(Choice choice, String authorityName, Projection projection) { VocabularyEntryDetailsRest entry = converter.toRest(choice, projection); entry.setVocabularyName(authorityName); + entry.setId(authorityName + ":" + entry.getId()); return entry; } - + public VocabularyEntryRest convertEntry(Choice choice, String authorityName, Projection projection) { VocabularyEntryRest entry = new VocabularyEntryRest(); entry.setDisplay(choice.label); From e9abbc505b635120e25d6827063fc1b938fb2243 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 18 Jun 2020 12:59:02 +0200 Subject: [PATCH 18/59] Add initial implementation for VocabularyEntryDetails repository --- .../VocabularyEntryDetailsRestRepository.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java new file mode 100644 index 0000000000..0e4c39b7ab --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java @@ -0,0 +1,64 @@ +/** + * 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 org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.model.ResourcePolicyRest; +import org.dspace.app.rest.model.VocabularyEntryDetailsRest; +import org.dspace.app.rest.model.VocabularyRest; +import org.dspace.app.rest.utils.AuthorityUtils; +import org.dspace.content.authority.Choice; +import org.dspace.content.authority.ChoiceAuthority; +import org.dspace.content.authority.service.ChoiceAuthorityService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Controller for exposition of vocabularies entry details for the submission + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +@Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.NAME) +public class VocabularyEntryDetailsRestRepository extends DSpaceRestRepository { + + @Autowired + private ChoiceAuthorityService cas; + + @Autowired + private AuthorityUtils authorityUtils; + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @Override + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException(ResourcePolicyRest.NAME, "findAll"); + } + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @Override + public VocabularyEntryDetailsRest findOne(Context context, String name) { + String[] parts = StringUtils.split(name, ":", 2); + if (parts.length != 2) { + return null; + } + String vocabularyName = parts[0]; + String vocabularyId = parts[1]; + ChoiceAuthority source = cas.getChoiceAuthorityByAuthorityName(vocabularyName); + Choice choice = source.getChoice(vocabularyId, context.getCurrentLocale().toString()); + return authorityUtils.convertEntryDetails(choice, vocabularyName, utils.obtainProjection()); + } + + @Override + public Class getDomainClass() { + return VocabularyEntryDetailsRest.class; + } +} From 53765394803768fd5639323a30401d2d502da9fb Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 18 Jun 2020 22:07:04 +0200 Subject: [PATCH 19/59] missing filling list of metadata --- .../dspace/content/authority/ChoiceAuthorityServiceImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index a73bf525e2..a36bd653c0 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -233,6 +233,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Override public String getChoiceAuthorityName(String schema, String element, String qualifier, Collection collection) { + init(); String fieldKey = makeFieldKey(schema, element, qualifier); // check if there is an authority configured for the metadata valid for all the collections if (controller.containsKey(fieldKey)) { @@ -371,6 +372,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } else { fields = new ArrayList(); } + fields.add(fieldKey); authorityName2definitions.put(submissionName, fields); authoritiesFormDefinitions.put(authorityName, authorityName2definitions); } From 1114903645deee131620eca0c95368560266dad4 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 18 Jun 2020 22:07:43 +0200 Subject: [PATCH 20/59] refactored tests --- .../dspace/app/rest/VocabularyRestRepositoryIT.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index 0e8b127494..30de227ec2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -7,7 +7,6 @@ */ package org.dspace.app.rest; -import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -333,10 +332,9 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("metadata", "dc.type") .param("collection", collection.getID().toString())) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.contains( - VocabularyMatcher.matchProperties("common_types", "common_types", true, false) - ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + .andExpect(jsonPath("$", is( + VocabularyMatcher.matchProperties("common_types", "common_types", true, false) + ))); } @Test @@ -377,6 +375,6 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes //missing collection getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection") .param("metadata", "dc.type")) - .andExpect(status().isUnprocessableEntity()); + .andExpect(status().isBadRequest()); } } From ea9d5da7644f73e66a2f1dadfb304879949c13f0 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 19 Jun 2020 09:35:24 +0200 Subject: [PATCH 21/59] implemented search method byMetadataAndCollection --- .../repository/VocabularyRestRepository.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java index 179898e2d1..6ea08cafd6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java @@ -7,15 +7,24 @@ */ package org.dspace.app.rest.repository; +import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.UUID; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.AuthorityUtils; +import org.dspace.content.Collection; +import org.dspace.content.MetadataField; import org.dspace.content.authority.ChoiceAuthority; import org.dspace.content.authority.service.ChoiceAuthorityService; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.MetadataFieldService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -38,6 +47,12 @@ public class VocabularyRestRepository extends DSpaceRestRepository getDomainClass() { return VocabularyRest.class; From fa2769036a6253fbd6802549edc0172da8d0ea78 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 19 Jun 2020 16:36:43 +0200 Subject: [PATCH 22/59] refactored search method byMetadataAndCollection --- .../app/rest/repository/VocabularyRestRepository.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java index 6ea08cafd6..dcdf71186b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java @@ -86,13 +86,10 @@ public class VocabularyRestRepository extends DSpaceRestRepository Date: Fri, 19 Jun 2020 16:40:35 +0200 Subject: [PATCH 23/59] added VocabularyEntryResource --- .../hateoas/VocabularyEntryResource.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryResource.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryResource.java new file mode 100644 index 0000000000..c29baa9fcd --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/VocabularyEntryResource.java @@ -0,0 +1,24 @@ +/** + * 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.model.hateoas; + +import org.dspace.app.rest.model.VocabularyEntryRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; + +/** + * Vocabulary Entry Rest HAL Resource. The HAL Resource wraps the REST Resource + * adding support for the links and embedded resources + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +@RelNameDSpaceResource(VocabularyEntryRest.NAME) +public class VocabularyEntryResource extends HALResource { + public VocabularyEntryResource(VocabularyEntryRest sd) { + super(sd); + } +} From cd842fd806a55a71a6966cdb9625b7aefca628dc Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 19 Jun 2020 16:42:10 +0200 Subject: [PATCH 24/59] refactored RestResourceController --- .../java/org/dspace/app/rest/RestResourceController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java index 025f7ad03b..2a9bc58d15 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -791,7 +791,7 @@ public class RestResourceController implements InitializingBean { Method linkMethod = utils.requireMethod(linkRepository.getClass(), linkRest.method()); try { if (Page.class.isAssignableFrom(linkMethod.getReturnType())) { - Page pageResult = (Page) linkMethod + Page pageResult = (Page) linkMethod .invoke(linkRepository, request, uuid, page, utils.obtainProjection()); if (pageResult == null) { @@ -815,8 +815,8 @@ public class RestResourceController implements InitializingBean { return new EntityModel(new EmbeddedPage(link.getHref(), pageResult.map(converter::toResource), null, subpath)); } else { - RestModel object = (RestModel) linkMethod.invoke(linkRepository, request, uuid, page, - utils.obtainProjection()); + RestModel object = (RestModel) linkMethod.invoke(linkRepository, request, + uuid, page, utils.obtainProjection()); if (object == null) { response.setStatus(HttpServletResponse.SC_NO_CONTENT); return null; @@ -838,7 +838,7 @@ public class RestResourceController implements InitializingBean { throw new RuntimeException(e); } } - RestAddressableModel modelObject = repository.findById(uuid).orElse(null); + RestModel modelObject = repository.findById(uuid).orElse(null); if (modelObject == null) { throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + uuid + " not found"); From 056a0fbdae16f8e385255359d828336c460ec0d6 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 19 Jun 2020 16:44:26 +0200 Subject: [PATCH 25/59] VocabularyEntryRest must implement RestModel --- .../org/dspace/app/rest/model/VocabularyEntryRest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java index f9855828ea..41598bb7be 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java @@ -16,7 +16,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -public class VocabularyEntryRest { +public class VocabularyEntryRest implements RestModel { public static final String NAME = "vocabularyEntry"; private String authority; private String display; @@ -68,4 +68,9 @@ public class VocabularyEntryRest { public VocabularyEntryDetailsRest getVocabularyEntryDetailsRest() { return vocabularyEntryDetailsRest; } + + @Override + public String getType() { + return VocabularyEntryRest.NAME; + } } From f9f00672943f611b8bf909d3a3a26c495eb7aa19 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 19 Jun 2020 18:45:37 +0200 Subject: [PATCH 26/59] fix bug --- .../content/authority/ChoiceAuthorityServiceImpl.java | 2 +- .../org/dspace/content/authority/DCInputAuthority.java | 9 +++++++-- .../rest/repository/VocabularyEntryLinkRepository.java | 4 +++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index a36bd653c0..365e59f3a9 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -463,7 +463,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService try { configReader = new SubmissionConfigReader(); SubmissionConfig submissionName = configReader.getSubmissionConfigByCollection(collection.getHandle()); - ma = controllerFormDefinitions.get(submissionName.getSubmissionName()).get(fieldKey); + ma = controllerFormDefinitions.get(fieldKey).get(submissionName.getSubmissionName()); } catch (SubmissionConfigReaderException e) { // the system is in an illegal state as the submission definition is not valid throw new IllegalStateException(e); diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index 42e79820c7..53c7cca4c3 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -13,6 +13,7 @@ import java.util.Iterator; import java.util.List; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.util.DCInputsReader; import org.dspace.app.util.DCInputsReaderException; @@ -107,14 +108,18 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority init(); int dflt = -1; + int index = 0; Choice v[] = new Choice[values.length]; for (int i = 0; i < values.length; ++i) { - v[i] = new Choice(values[i], values[i], labels[i]); + if (query == null || StringUtils.containsIgnoreCase(values[i], query)) { + v[index] = new Choice(values[i], values[i], labels[i]); + index++; + } if (values[i].equalsIgnoreCase(query)) { dflt = i; } } - return new Choices(v, 0, v.length, Choices.CF_AMBIGUOUS, false, dflt); + return new Choices(v, 0, index, Choices.CF_AMBIGUOUS, false, dflt); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index 967124624a..eae127d1f2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -86,7 +86,9 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository Choices choices = cas.getMatches(fieldKey, filter, collection, Math.toIntExact(pageable.getOffset()), pageable.getPageSize(), context.getCurrentLocale().toString()); for (Choice value : choices.values) { - results.add(authorityUtils.convertEntry(value, name, projection)); + if (value != null) { + results.add(authorityUtils.convertEntry(value, name, projection)); + } } return new PageImpl<>(results, pageable, results.size()); } From de49227b076a65720bb3b09c1e1ca3cd0f3e643d Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 19 Jun 2020 18:46:22 +0200 Subject: [PATCH 27/59] fix tests --- .../org/dspace/app/rest/VocabularyRestRepositoryIT.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index 30de227ec2..4c2ca7339e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -157,8 +157,8 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes "Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work::Youth research", "vocabularyEntry") ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(26))) - .andExpect(jsonPath("$.page.totalPages", Matchers.is(13))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(2))) + .andExpect(jsonPath("$.page.totalPages", Matchers.is(1))) .andExpect(jsonPath("$.page.size", Matchers.is(2))); } @@ -274,7 +274,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( VocabularyMatcher.matchVocabularyEntry("Book", "Book", "vocabularyEntry"), - VocabularyMatcher.matchVocabularyEntry("Book chapter", "Book chapter", "vocabularySuggestion") + VocabularyMatcher.matchVocabularyEntry("Book chapter", "Book chapter", "vocabularyEntry") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(2))) .andExpect(jsonPath("$.page.totalPages", Matchers.is(1))) From e473f1170bb2007691a4bad885fbcc4365df6181 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 19 Jun 2020 18:47:04 +0200 Subject: [PATCH 28/59] added test --- .../dspace/app/rest/VocabularyEntryDetailsIT.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index 2fbd529a9d..20c36d6d2a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -19,8 +19,21 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.hamcrest.Matchers; import org.junit.Test; +/** + * + * + * @author Mykhaylo Boychuk (4science.it) + */ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest { + @Test + public void findAllTest() throws Exception { + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(get("/api/submission/vocabularyEntryDetails")) + .andExpect(status() + .isMethodNotAllowed()); + } + @Test public void findOneTest() throws Exception { String idAuthority = "srsc:SCB110"; @@ -37,6 +50,7 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$.otherInformation.parent", is("HUMANITIES and RELIGION"))); } + @Test public void findOneUnauthorizedTest() throws Exception { String idAuthority = "srsc:SCB110"; getClient().perform(get("/api/submission/vocabularyEntryDetails/" + idAuthority)) From efb4f6a1be9fbbb566a82c336e42bdc29a9d65f6 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 22 Jun 2020 12:00:49 +0200 Subject: [PATCH 29/59] renamed embedded link in tests --- .../org/dspace/app/rest/VocabularyRestRepositoryIT.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index 4c2ca7339e..fbe4d7c6ce 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -149,7 +149,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("filter", "Research") .param("size", "2")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( + .andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder( VocabularyMatcher.matchVocabularyEntry("Family research", "Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work::Family research", "vocabularyEntry"), @@ -247,10 +247,11 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("collection", collection.getID().toString()) .param("size", "2")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( + .andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder( VocabularyMatcher.matchVocabularyEntry("Animation", "Animation", "vocabularyEntry"), VocabularyMatcher.matchVocabularyEntry("Article", "Article", "vocabularyEntry") ))) + .andExpect(jsonPath("$._embedded.entries[*].authority").doesNotExist()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(22))) .andExpect(jsonPath("$.page.totalPages", Matchers.is(11))) .andExpect(jsonPath("$.page.size", Matchers.is(2))); @@ -272,7 +273,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("filter", "Book") .param("size", "2")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.vocabularyEntries", Matchers.containsInAnyOrder( + .andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder( VocabularyMatcher.matchVocabularyEntry("Book", "Book", "vocabularyEntry"), VocabularyMatcher.matchVocabularyEntry("Book chapter", "Book chapter", "vocabularyEntry") ))) From 73e0bd8759b69aa11bf54b6a4d3efe0a4ca17149 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Mon, 22 Jun 2020 12:15:00 +0200 Subject: [PATCH 30/59] Avoid to expose the authority where not needed --- .../main/java/org/dspace/content/authority/Choice.java | 2 -- .../content/authority/ChoiceAuthorityServiceImpl.java | 6 ++++++ .../org/dspace/content/authority/DCInputAuthority.java | 9 ++++----- .../authority/service/ChoiceAuthorityService.java | 9 +++++++++ .../rest/repository/VocabularyEntryLinkRepository.java | 5 ++--- .../java/org/dspace/app/rest/utils/AuthorityUtils.java | 10 +++++++--- 6 files changed, 28 insertions(+), 13 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java index 9c092c7e8b..9b68c75d28 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java @@ -18,8 +18,6 @@ import java.util.Map; * @see Choices */ public class Choice { - public boolean storeAuthority = true; - /** * Authority key for this value */ diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index 365e59f3a9..f907317a75 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -471,4 +471,10 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } return ma; } + + @Override + public boolean storeAuthority(String fieldKey, Collection collection) { + // currently only named authority can eventually provide real authority + return controller.containsKey(fieldKey); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index 53c7cca4c3..8be9a8286b 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -108,18 +108,17 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority init(); int dflt = -1; - int index = 0; - Choice v[] = new Choice[values.length]; + List v = new ArrayList(); for (int i = 0; i < values.length; ++i) { if (query == null || StringUtils.containsIgnoreCase(values[i], query)) { - v[index] = new Choice(values[i], values[i], labels[i]); - index++; + v.add(new Choice(null, values[i], labels[i])); } if (values[i].equalsIgnoreCase(query)) { dflt = i; } } - return new Choices(v, 0, index, Choices.CF_AMBIGUOUS, false, dflt); + Choice[] vArray = new Choice[v.size()]; + return new Choices(v.toArray(vArray), 0, v.size(), Choices.CF_AMBIGUOUS, false, dflt); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java index 3d0bdd7316..b74bd71c98 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java @@ -171,4 +171,13 @@ public interface ChoiceAuthorityService { */ public void clearCache(); + /** + * Should we store the authority key (if any) for such field key and collection? + * + * @param fieldKey single string identifying metadata field + * @param collection Collection owner of Item or where the item is submitted to + * @return true if the configuration allows to store the authority value + */ + public boolean storeAuthority(String fieldKey, Collection collection); + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index eae127d1f2..3f3407563f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -85,10 +85,9 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository String fieldKey = org.dspace.core.Utils.standardize(tokens[0], tokens[1], tokens[2], "_"); Choices choices = cas.getMatches(fieldKey, filter, collection, Math.toIntExact(pageable.getOffset()), pageable.getPageSize(), context.getCurrentLocale().toString()); + boolean storeAuthority = cas.storeAuthority(fieldKey, collection); for (Choice value : choices.values) { - if (value != null) { - results.add(authorityUtils.convertEntry(value, name, projection)); - } + results.add(authorityUtils.convertEntry(value, name, storeAuthority, projection)); } return new PageImpl<>(results, pageable, results.size()); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java index 1d15693f72..2f58c19918 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest.utils; +import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.model.VocabularyEntryDetailsRest; import org.dspace.app.rest.model.VocabularyEntryRest; @@ -70,13 +71,16 @@ public class AuthorityUtils { return entry; } - public VocabularyEntryRest convertEntry(Choice choice, String authorityName, Projection projection) { + public VocabularyEntryRest convertEntry(Choice choice, String authorityName, boolean storeAuthority, + Projection projection) { VocabularyEntryRest entry = new VocabularyEntryRest(); entry.setDisplay(choice.label); entry.setValue(choice.value); entry.setOtherInformation(choice.extras); - entry.setAuthority(choice.authority); - if (choice.storeAuthority) { + if (storeAuthority) { + entry.setAuthority(choice.authority); + } + if (StringUtils.isNotBlank(choice.authority)) { entry.setVocabularyEntryDetailsRest(converter.toRest(choice, projection)); } return entry; From de8def4418357b82b0425cdaf142bed48425f107 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 22 Jun 2020 14:44:41 +0200 Subject: [PATCH 31/59] refactoring --- .../dspace/content/authority/DCInputAuthority.java | 14 +++++++++----- .../authority/DSpaceControlledVocabulary.java | 6 ++++-- .../dspace/app/rest/model/VocabularyEntryRest.java | 4 ++++ .../repository/VocabularyEntryLinkRepository.java | 2 +- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index 8be9a8286b..5b3df48002 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -108,17 +108,21 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority init(); int dflt = -1; + int found = 0; List v = new ArrayList(); for (int i = 0; i < values.length; ++i) { if (query == null || StringUtils.containsIgnoreCase(values[i], query)) { - v.add(new Choice(null, values[i], labels[i])); - } - if (values[i].equalsIgnoreCase(query)) { - dflt = i; + if (found >= start && v.size() < limit) { + v.add(new Choice(null, values[i], labels[i])); + if (values[i].equalsIgnoreCase(query)) { + dflt = i; + } + } + found++; } } Choice[] vArray = new Choice[v.size()]; - return new Choices(v.toArray(vArray), 0, v.size(), Choices.CF_AMBIGUOUS, false, dflt); + return new Choices(v.toArray(vArray), start, found, Choices.CF_AMBIGUOUS, false, dflt); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index d97e06f250..5f3837d9de 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -152,14 +152,16 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic } XPath xpath = XPathFactory.newInstance().newXPath(); Choice[] choices; + int total = 0; try { NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET); + total = results.getLength(); String[] authorities = new String[results.getLength()]; String[] values = new String[results.getLength()]; String[] labels = new String[results.getLength()]; String[] parent = new String[results.getLength()]; String[] notes = new String[results.getLength()]; - for (int i = 0; i < results.getLength(); i++) { + for (int i = 0; i < total; i++) { Node node = results.item(i); readNode(authorities, values, labels, parent, notes, i, node); } @@ -183,7 +185,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic } catch (XPathExpressionException e) { choices = new Choice[0]; } - return new Choices(choices, 0, choices.length, Choices.CF_AMBIGUOUS, false); + return new Choices(choices, start, total, Choices.CF_AMBIGUOUS, false); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java index 41598bb7be..713d4c5209 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryRest.java @@ -10,6 +10,8 @@ package org.dspace.app.rest.model; import java.util.Map; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; /** * An entry in a Vocabulary @@ -18,6 +20,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; */ public class VocabularyEntryRest implements RestModel { public static final String NAME = "vocabularyEntry"; + + @JsonInclude(Include.NON_NULL) private String authority; private String display; private String value; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index 3f3407563f..814f9e4672 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -89,6 +89,6 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository for (Choice value : choices.values) { results.add(authorityUtils.convertEntry(value, name, storeAuthority, projection)); } - return new PageImpl<>(results, pageable, results.size()); + return new PageImpl<>(results, pageable, choices.total); } } From 64a29b1b6050c94d50fc7676085ae82033ba437b Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 22 Jun 2020 14:45:44 +0200 Subject: [PATCH 32/59] update tests --- .../app/rest/VocabularyRestRepositoryIT.java | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index fbe4d7c6ce..a1520a6e5e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -150,15 +150,13 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("size", "2")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder( - VocabularyMatcher.matchVocabularyEntry("Family research", - "Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work::Family research", - "vocabularyEntry"), - VocabularyMatcher.matchVocabularyEntry("Youth research", - "Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work::Youth research", - "vocabularyEntry") - ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(2))) - .andExpect(jsonPath("$.page.totalPages", Matchers.is(1))) + VocabularyMatcher.matchVocabularyEntry("Research Subject Categories", + "Research Subject Categories", "vocabularyEntry"), + VocabularyMatcher.matchVocabularyEntry("Family research", + "Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work::Family research", + "vocabularyEntry")))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(26))) + .andExpect(jsonPath("$.page.totalPages", Matchers.is(13))) .andExpect(jsonPath("$.page.size", Matchers.is(2))); } @@ -233,7 +231,7 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes } @Test - public void vocabularyEntriesCommon_typesTest() throws Exception { + public void vocabularyEntriesCommonTypesWithPaginationTest() throws Exception { context.turnOffAuthorisationSystem(); Collection collection = CollectionBuilder.createCollection(context, parentCommunity) @@ -242,19 +240,34 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") - .param("metadata", "dc.type") - .param("collection", collection.getID().toString()) - .param("size", "2")) + getClient(token) + .perform(get("/api/submission/vocabularies/common_types/entries").param("metadata", "dc.type") + .param("collection", collection.getID().toString()).param("size", "2")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder( - VocabularyMatcher.matchVocabularyEntry("Animation", "Animation", "vocabularyEntry"), - VocabularyMatcher.matchVocabularyEntry("Article", "Article", "vocabularyEntry") - ))) + VocabularyMatcher.matchVocabularyEntry("Animation", "Animation", "vocabularyEntry"), + VocabularyMatcher.matchVocabularyEntry("Article", "Article", "vocabularyEntry") + ))) .andExpect(jsonPath("$._embedded.entries[*].authority").doesNotExist()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(22))) .andExpect(jsonPath("$.page.totalPages", Matchers.is(11))) .andExpect(jsonPath("$.page.size", Matchers.is(2))); + + //second page + getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") + .param("metadata", "dc.type") + .param("collection", collection.getID().toString()) + .param("size", "2") + .param("page", "1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder( + VocabularyMatcher.matchVocabularyEntry("Book", "Book", "vocabularyEntry"), + VocabularyMatcher.matchVocabularyEntry("Book chapter", "Book chapter", "vocabularyEntry") + ))) + .andExpect(jsonPath("$._embedded.entries[*].authority").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(22))) + .andExpect(jsonPath("$.page.totalPages", Matchers.is(11))) + .andExpect(jsonPath("$.page.size", Matchers.is(2))); } @Test @@ -298,6 +311,10 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("filter", "Shirasaka") .param("size", "1000")) .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entries", Matchers.contains( + VocabularyMatcher.matchVocabularyEntry("Shirasaka, Seiko", "Shirasaka, Seiko", "vocabularyEntry") + ))) + .andExpect(jsonPath("$._embedded.entries[0].authority").isNotEmpty()) .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); } From 0c4985e1cf2634f775bd4cbf8551c46b439703b6 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 23 Jun 2020 14:59:15 +0200 Subject: [PATCH 33/59] added implementations for getChoicesByParent and getTopChoices --- .../org/dspace/content/authority/Choice.java | 12 ++ .../authority/ChoiceAuthorityServiceImpl.java | 34 +++++ .../authority/DSpaceControlledVocabulary.java | 138 +++++++++++++++--- .../authority/HierarchicalAuthority.java | 70 +++++++++ .../service/ChoiceAuthorityService.java | 26 ++++ .../VocabularyEntryDetailsRestConverter.java | 1 + .../VocabularyEntryDetailsResource.java | 11 +- .../dspace/app/rest/utils/AuthorityUtils.java | 4 +- .../controlledvocabulary.xsd | 1 + 9 files changed, 276 insertions(+), 21 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java diff --git a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java index 9b68c75d28..ff09e7553a 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java @@ -33,6 +33,11 @@ public class Choice { */ public String value = null; + /** + * A boolean representing if choice entry value can selected + */ + public boolean selectable = true; + public Map extras = new HashMap(); public Choice() { @@ -50,4 +55,11 @@ public class Choice { this.value = value; this.extras = extras; } + + public Choice(String authority, String label, String value, boolean selectable) { + this.authority = authority; + this.label = label; + this.value = value; + this.selectable = selectable; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index f907317a75..df5bd9335f 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -477,4 +477,38 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService // currently only named authority can eventually provide real authority return controller.containsKey(fieldKey); } + + /** + * Wrapper that calls getChoicesByParent method of the plugin. + * + * @param authorityName authority name + * @param parentId parent Id + * @param start choice at which to start, 0 is first. + * @param limit maximum number of choices to return, 0 for no limit. + * @param locale explicit localization key if available, or null + * @return a Choices object (never null). + * @see org.dspace.content.authority.ChoiceAuthority#getChoicesByParent(java.lang.String, java.lang.String, + * int, int, java.lang.String) + */ + @Override + public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale) { + HierarchicalAuthority ma = (HierarchicalAuthority) getChoiceAuthorityByAuthorityName(authorityName); + return ma.getChoicesByParent(authorityName, parentId, start, limit, locale); + } + + /** + * Wrapper that calls getTopChoices method of the plugin. + * + * @param authorityName authority name + * @param start choice at which to start, 0 is first. + * @param limit maximum number of choices to return, 0 for no limit. + * @param locale explicit localization key if available, or null + * @return a Choices object (never null). + * @see org.dspace.content.authority.ChoiceAuthority#getTopChoices(java.lang.String, int, int, java.lang.String) + */ + @Override + public Choices getTopChoices(String authorityName, int start, int limit, String locale) { + HierarchicalAuthority ma = (HierarchicalAuthority) getChoiceAuthorityByAuthorityName(authorityName); + return ma.getTopChoices(authorityName, start, limit, locale); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index 5f3837d9de..cd91636f18 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -54,13 +54,14 @@ import org.xml.sax.InputSource; * @author Michael B. Klein */ -public class DSpaceControlledVocabulary extends SelfNamedPlugin implements ChoiceAuthority { +public class DSpaceControlledVocabulary extends SelfNamedPlugin implements HierarchicalAuthority { private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DSpaceControlledVocabulary.class); protected static String xpathTemplate = "//node[contains(translate(@label,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'," + "'abcdefghijklmnopqrstuvwxyz'),'%s')]"; protected static String idTemplate = "//node[@id = '%s']"; protected static String idParentTemplate = "//node[@id = '%s']/parent::isComposedBy"; + protected static String rootTemplate = "/node"; protected static String pluginNames[] = null; protected String vocabularyName = null; @@ -68,6 +69,8 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic protected Boolean suggestHierarchy = false; protected Boolean storeHierarchy = true; protected String hierarchyDelimiter = "::"; + protected Integer preloadLevel = 1; + protected String rootNodeId = ""; public DSpaceControlledVocabulary() { super(); @@ -112,6 +115,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic String configurationPrefix = "vocabulary.plugin." + vocabularyName; storeHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.store", storeHierarchy); suggestHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.suggest", suggestHierarchy); + preloadLevel = config.getIntProperty(configurationPrefix + ".hierarchy.preloadLevel", preloadLevel); String configuredDelimiter = config.getProperty(configurationPrefix + ".delimiter"); if (configuredDelimiter != null) { hierarchyDelimiter = configuredDelimiter.replaceAll("(^\"|\"$)", ""); @@ -119,6 +123,17 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic String filename = vocabulariesPath + vocabularyName + ".xml"; log.info("Loading " + filename); vocabulary = new InputSource(filename); + + XPath xpath = XPathFactory.newInstance().newXPath(); + try { + Node rootNode = (Node) xpath.evaluate(rootTemplate, vocabulary, XPathConstants.NODE); + Node idAttr = rootNode.getAttributes().getNamedItem("id"); + if (null != idAttr) { // 'id' is optional + rootNodeId = idAttr.getNodeValue(); + } + } catch (XPathExpressionException e) { + log.warn(e.getMessage(), e); + } } } @@ -151,7 +166,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic xpathExpression += String.format(xpathTemplate, textHierarchy[i].replaceAll("'", "'").toLowerCase()); } XPath xpath = XPathFactory.newInstance().newXPath(); - Choice[] choices; + List choices = new ArrayList(); int total = 0; try { NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET); @@ -160,32 +175,41 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic String[] values = new String[results.getLength()]; String[] labels = new String[results.getLength()]; String[] parent = new String[results.getLength()]; + Boolean[] selectable = new Boolean[results.getLength()]; + ArrayList[] children = new ArrayList[results.getLength()]; String[] notes = new String[results.getLength()]; for (int i = 0; i < total; i++) { Node node = results.item(i); - readNode(authorities, values, labels, parent, notes, i, node); + children[i] = new ArrayList(); + readNode(authorities, values, labels, parent,children[i], notes, selectable, i, node); } int resultCount = labels.length - start; // limit = 0 means no limit if ((limit > 0) && (resultCount > limit)) { resultCount = limit; } - choices = new Choice[resultCount]; if (resultCount > 0) { for (int i = 0; i < resultCount; i++) { - choices[i] = new Choice(authorities[start + i], values[start + i], labels[start + i]); + Choice choice = new Choice(authorities[start + i], values[start + i], labels[start + i], + selectable[start + i]); if (StringUtils.isNotBlank(parent[i])) { - choices[i].extras.put("parent", parent[i]); + choice.extras.put("parent", parent[i]); } if (StringUtils.isNotBlank(notes[i])) { - choices[i].extras.put("note", notes[i]); + choice.extras.put("note", notes[i]); } + if (children[i].size() > 0) { + choice.extras.put("hasChildren", "true"); + } else { + choice.extras.put("hasChildren", "false"); + } + choices.add(choice); } } } catch (XPathExpressionException e) { - choices = new Choice[0]; + log.warn(e.getMessage(), e); } - return new Choices(choices, start, total, Choices.CF_AMBIGUOUS, false); + return new Choices(choices.toArray(new Choice[choices.size()]), start, total, Choices.CF_AMBIGUOUS, false); } @Override @@ -226,8 +250,8 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic return true; } - private void readNode(String[] authorities, String[] values, String[] labels, String[] parent, String[] notes, - int i, Node node) { + private void readNode(String[] authorities, String[] values, String[] labels, String[] parent, + List children, String[] notes, Boolean[] selectable, int i, Node node) { String hierarchy = this.buildString(node); if (this.suggestHierarchy) { labels[i] = hierarchy; @@ -243,13 +267,25 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic NodeList childNodes = node.getChildNodes(); for (int ci = 0; ci < childNodes.getLength(); ci++) { Node firstChild = childNodes.item(ci); - if (firstChild != null && "hasNote".equals(firstChild.getNodeName())) { - String nodeValue = firstChild.getTextContent(); - if (StringUtils.isNotBlank(nodeValue)) { - notes[i] = nodeValue; + if (firstChild != null && "isComposedBy".equals(firstChild.getNodeName())) { + for (int cii = 0; cii < firstChild.getChildNodes().getLength(); cii++) { + Node childN = firstChild.getChildNodes().item(cii); + if (childN != null && "node".equals(childN.getNodeName())) { + Node childIdAttr = childN.getAttributes().getNamedItem("id"); + if (null != childIdAttr) { + children.add(childIdAttr.getNodeValue()); + } + } } + break; } } + Node selectableAttr = node.getAttributes().getNamedItem("selectable"); + if (null != selectableAttr) { + selectable[i] = Boolean.valueOf(selectableAttr.getNodeValue()); + } else { // Default is true + selectable[i] = true; + } Node idAttr = node.getAttributes().getNamedItem("id"); if (null != idAttr) { // 'id' is optional authorities[i] = idAttr.getNodeValue(); @@ -259,8 +295,8 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic parentN = parentN.getParentNode(); if (parentN != null) { Node parentIdAttr = parentN.getAttributes().getNamedItem("id"); - if (null != parentIdAttr) { - parent[i] = parentIdAttr.getNodeValue(); + if (null != parentIdAttr && !parentIdAttr.getNodeValue().equals(rootNodeId)) { + parent[i] = hierarchy + hierarchyDelimiter + parentIdAttr.getNodeValue(); } } } @@ -271,4 +307,72 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic } } + @Override + public Choices getTopChoices(String authorityName, int start, int limit, String locale) { + init(); + String xpathExpression = rootTemplate; + List choices = getChoicesByXpath(xpathExpression); + return new Choices(choices.toArray(new Choice[choices.size()]), 0, choices.size(), Choices.CF_AMBIGUOUS, false); + } + + private List getChoicesByXpath(String xpathExpression) { + List choices = new ArrayList(); + XPath xpath = XPathFactory.newInstance().newXPath(); + try { + Node parentNode = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); + if (parentNode != null) { + NodeList childNodes = (NodeList) xpath.evaluate(".//isComposedBy", parentNode, XPathConstants.NODE); + if (null != childNodes) { + for (int i = 0; i < childNodes.getLength(); i++) { + Node childNode = childNodes.item(i); + if (childNode != null && "node".equals(childNode.getNodeName())) { + choices.add(createChoiceFromNode(childNode)); + } + } + } + } + } catch (XPathExpressionException e) { + log.warn(e.getMessage(), e); + } + return choices; + } + + private Choice createChoiceFromNode(Node node) { + if (node != null) { + String[] authorities = new String[1]; + String[] values = new String[1]; + String[] labels = new String[1]; + String[] parent = new String[1]; + String[] note = new String[1]; + Boolean[] selectable = new Boolean[1]; + List children = new ArrayList(); + readNode(authorities, values, labels, parent, children, note, selectable, 0, node); + + if (values.length > 0) { + Choice choice = new Choice(authorities[0], values[0], labels[0], selectable[0]); + if (StringUtils.isNotBlank(parent[0])) { + choice.extras.put("parent", parent[0]); + } + if (StringUtils.isNotBlank(note[0])) { + choice.extras.put("note", note[0]); + } + return choice; + } + } + return new Choice("", "", ""); + } + + @Override + public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale) { + init(); + String xpathExpression = String.format(idTemplate, parentId); + List choices = getChoicesByXpath(xpathExpression); + return new Choices(choices.toArray(new Choice[choices.size()]), 0, choices.size(), Choices.CF_AMBIGUOUS, false); + } + + @Override + public Integer getPreloadLevel() { + return preloadLevel; + } + } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java new file mode 100644 index 0000000000..200a8b809f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java @@ -0,0 +1,70 @@ +/** + * 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.authority; + +/** + * Plugin interface that supplies an authority control mechanism for + * one metadata field. + * + * @author Larry Stone + * @see ChoiceAuthority + */ +public interface HierarchicalAuthority extends ChoiceAuthority { + + /** + * Get all values from the authority that match the preferred value. + * Note that the offering was entered by the user and may contain + * mixed/incorrect case, whitespace, etc so the plugin should be careful + * to clean up user data before making comparisons. + * + * Value of a "Name" field will be in canonical DSpace person name format, + * which is "Lastname, Firstname(s)", e.g. "Smith, John Q.". + * + * Some authorities with a small set of values may simply return the whole + * set for any sample value, although it's a good idea to set the + * defaultSelected index in the Choices instance to the choice, if any, + * that matches the value. + * + * @param authorityName authority name + * @param start choice at which to start, 0 is first. + * @param limit maximum number of choices to return, 0 for no limit. + * @param locale explicit localization key if available, or null + * @return a Choices object (never null). + */ + public Choices getTopChoices(String authorityName, int start, int limit, String locale); + + /** + * Get all values from the authority that match the preferred value. + * Note that the offering was entered by the user and may contain + * mixed/incorrect case, whitespace, etc so the plugin should be careful + * to clean up user data before making comparisons. + * + * Value of a "Name" field will be in canonical DSpace person name format, + * which is "Lastname, Firstname(s)", e.g. "Smith, John Q.". + * + * Some authorities with a small set of values may simply return the whole + * set for any sample value, although it's a good idea to set the + * defaultSelected index in the Choices instance to the choice, if any, + * that matches the value. + * + * @param authorityName authority name + * @param parentId user's value to match + * @param start choice at which to start, 0 is first. + * @param limit maximum number of choices to return, 0 for no limit. + * @param locale explicit localization key if available, or null + * @return a Choices object (never null). + */ + public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale); + + public Integer getPreloadLevel(); + + default boolean isHierarchical() { + return true; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java index b74bd71c98..fbd474a6f1 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java @@ -180,4 +180,30 @@ public interface ChoiceAuthorityService { */ public boolean storeAuthority(String fieldKey, Collection collection); + /** + * Wrapper that calls getChoicesByParent method of the plugin. + * + * @param authorityName authority name + * @param parentId parent Id + * @param start choice at which to start, 0 is first. + * @param limit maximum number of choices to return, 0 for no limit. + * @param locale explicit localization key if available, or null + * @return a Choices object (never null). + * @see org.dspace.content.authority.ChoiceAuthority#getChoicesByParent(java.lang.String, java.lang.String, + * int, int, java.lang.String) + */ + public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale); + + /** + * Wrapper that calls getTopChoices method of the plugin. + * + * @param authorityName authority name + * @param start choice at which to start, 0 is first. + * @param limit maximum number of choices to return, 0 for no limit. + * @param locale explicit localization key if available, or null + * @return a Choices object (never null). + * @see org.dspace.content.authority.ChoiceAuthority#getTopChoices(java.lang.String, int, int, java.lang.String) + */ + public Choices getTopChoices(String authorityName, int start, int limit, String locale); + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyEntryDetailsRestConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyEntryDetailsRestConverter.java index 63154ccf22..358a71455d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyEntryDetailsRestConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/VocabularyEntryDetailsRestConverter.java @@ -32,6 +32,7 @@ public class VocabularyEntryDetailsRestConverter implements DSpaceConverter { +public class VocabularyEntryDetailsResource extends DSpaceResource { - public VocabularyEntryDetailsResource(VocabularyEntryDetailsRest entry) { - super(entry); + public VocabularyEntryDetailsResource(VocabularyEntryDetailsRest entry, Utils utils) { + super(entry, utils); + if (entry.isInHierarchicalVocabulary()) { + add(utils.linkToSubResource(entry, VocabularyEntryDetailsRest.PARENT)); + add(utils.linkToSubResource(entry, VocabularyEntryDetailsRest.CHILDREN)); + } } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java index 2f58c19918..70a586007c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java @@ -64,10 +64,12 @@ public class AuthorityUtils { * @param projection the name of the projection to use, or {@code null}. * @return */ - public VocabularyEntryDetailsRest convertEntryDetails(Choice choice, String authorityName, Projection projection) { + public VocabularyEntryDetailsRest convertEntryDetails(Choice choice, String authorityName, + boolean isHierarchical, Projection projection) { VocabularyEntryDetailsRest entry = converter.toRest(choice, projection); entry.setVocabularyName(authorityName); entry.setId(authorityName + ":" + entry.getId()); + entry.setInHierarchicalVocabulary(isHierarchical); return entry; } diff --git a/dspace/config/controlled-vocabularies/controlledvocabulary.xsd b/dspace/config/controlled-vocabularies/controlledvocabulary.xsd index 30fbb7f8ad..7a5defefbd 100644 --- a/dspace/config/controlled-vocabularies/controlledvocabulary.xsd +++ b/dspace/config/controlled-vocabularies/controlledvocabulary.xsd @@ -58,6 +58,7 @@ or refer to the Web site http://dspace-dev.dsi.uminho.pt. + From ed5ea81241386b5abf10356857960486d7c5e24f Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 23 Jun 2020 22:45:54 +0200 Subject: [PATCH 34/59] added implementations for link child and parent repositories --- .../authority/ChoiceAuthorityServiceImpl.java | 6 ++ .../authority/DSpaceControlledVocabulary.java | 61 +++++++++++--- .../authority/HierarchicalAuthority.java | 11 +++ .../service/ChoiceAuthorityService.java | 11 +++ .../model/VocabularyEntryDetailsRest.java | 24 ++++++ ...aryEntryDetailsChildrenLinkRepository.java | 83 +++++++++++++++++++ ...ularyEntryDetailsParentLinkRepository.java | 65 +++++++++++++++ 7 files changed, 248 insertions(+), 13 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index df5bd9335f..09724bb179 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -511,4 +511,10 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService HierarchicalAuthority ma = (HierarchicalAuthority) getChoiceAuthorityByAuthorityName(authorityName); return ma.getTopChoices(authorityName, start, limit, locale); } + + @Override + public Choice getParentChoice(String authorityName, String vocabularyId, int start, int limit, String locale) { + HierarchicalAuthority ma = (HierarchicalAuthority) getChoiceAuthorityByAuthorityName(authorityName); + return ma.getParentChoice(authorityName, vocabularyId, start, limit, locale); + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index cd91636f18..7a8cddfd82 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -10,7 +10,9 @@ package org.dspace.content.authority; import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; @@ -190,19 +192,9 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera } if (resultCount > 0) { for (int i = 0; i < resultCount; i++) { - Choice choice = new Choice(authorities[start + i], values[start + i], labels[start + i], + Choice choice = new Choice(authorities[start + i], labels[start + i], values[start + i], selectable[start + i]); - if (StringUtils.isNotBlank(parent[i])) { - choice.extras.put("parent", parent[i]); - } - if (StringUtils.isNotBlank(notes[i])) { - choice.extras.put("note", notes[i]); - } - if (children[i].size() > 0) { - choice.extras.put("hasChildren", "true"); - } else { - choice.extras.put("hasChildren", "false"); - } + choice.extras = addOtherInformation(parent[i], notes[i], children[i], authorities[i]); choices.add(choice); } } @@ -212,6 +204,24 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera return new Choices(choices.toArray(new Choice[choices.size()]), start, total, Choices.CF_AMBIGUOUS, false); } + private Map addOtherInformation(String parentCurr, String noteCurr, + ArrayList childrenCurr, String authorityCurr) { + Map extras = new HashMap(); + if (StringUtils.isNotBlank(parentCurr)) { + extras.put("parent", parentCurr); + } + if (StringUtils.isNotBlank(noteCurr)) { + extras.put("note", noteCurr); + } + if (childrenCurr.size() > 0) { + extras.put("hasChildren", "true"); + } else { + extras.put("hasChildren", "false"); + } + extras.put("id", authorityCurr); + return extras; + } + @Override public Choices getBestMatch(String field, String text, Collection collection, String locale) { init(); @@ -296,7 +306,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera if (parentN != null) { Node parentIdAttr = parentN.getAttributes().getNamedItem("id"); if (null != parentIdAttr && !parentIdAttr.getNodeValue().equals(rootNodeId)) { - parent[i] = hierarchy + hierarchyDelimiter + parentIdAttr.getNodeValue(); + parent[i] = buildString(parentN); } } } @@ -370,6 +380,31 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera return new Choices(choices.toArray(new Choice[choices.size()]), 0, choices.size(), Choices.CF_AMBIGUOUS, false); } + @Override + public Choice getParentChoice(String authorityName, String parentId, int start, int limit, String locale) { + init(); + String xpathExpression = String.format(idTemplate, parentId); + Choice choice = getParent(xpathExpression); + return choice; + } + + private Choice getParent(String xpathExpression) { + Choice choice = null; + XPath xpath = XPathFactory.newInstance().newXPath(); + try { + Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); + if (node != null) { + Node parent = node.getParentNode().getParentNode(); + if (null != parent) { + choice = createChoiceFromNode(parent); + } + } + } catch (XPathExpressionException e) { + log.warn(e.getMessage(), e); + } + return choice; + } + @Override public Integer getPreloadLevel() { return preloadLevel; diff --git a/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java index 200a8b809f..18e088eb54 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java @@ -61,6 +61,17 @@ public interface HierarchicalAuthority extends ChoiceAuthority { */ public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale); + /** + * + * @param authorityName authority name + * @param vocabularyId user's value to match + * @param start choice at which to start, 0 is first. + * @param limit maximum number of choices to return, 0 for no limit. + * @param locale explicit localization key if available, or null + * @return a Choice object (never null). + */ + public Choice getParentChoice(String authorityName, String vocabularyId, int start, int limit, String locale); + public Integer getPreloadLevel(); default boolean isHierarchical() { diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java index fbd474a6f1..7afad12539 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java @@ -12,6 +12,7 @@ import java.util.Set; import org.dspace.content.Collection; import org.dspace.content.MetadataValue; +import org.dspace.content.authority.Choice; import org.dspace.content.authority.ChoiceAuthority; import org.dspace.content.authority.Choices; @@ -206,4 +207,14 @@ public interface ChoiceAuthorityService { */ public Choices getTopChoices(String authorityName, int start, int limit, String locale); + /** + * + * @param authorityName authority name + * @param vocabularyId child id + * @param start choice at which to start, 0 is first. + * @param limit maximum number of choices to return, 0 for no limit. + * @param locale explicit localization key if available, or null + * @return a Choice object (never null). + */ + public Choice getParentChoice(String authorityName, String vocabularyId, int start, int limit, String locale); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java index e41ddafe45..42ce1f47a5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java @@ -17,12 +17,21 @@ import org.dspace.app.rest.RestResourceController; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ +@LinksRest(links = { + @LinkRest(name = VocabularyEntryDetailsRest.PARENT, method = "getParent"), + @LinkRest(name = VocabularyEntryDetailsRest.CHILDREN, method = "getChildren") + }) public class VocabularyEntryDetailsRest extends RestAddressableModel { public static final String NAME = "vocabularyEntryDetail"; + public static final String PARENT = "parent"; + public static final String CHILDREN = "children"; private String id; private String display; private String value; private Map otherInformation; + private boolean selectable; + @JsonIgnore + private boolean isInHierarchicalVocabulary = false; @JsonIgnore private String vocabularyName; @@ -86,4 +95,19 @@ public class VocabularyEntryDetailsRest extends RestAddressableModel { return RestResourceController.class; } + public Boolean isSelectable() { + return selectable; + } + + public void setSelectable(Boolean selectable) { + this.selectable = selectable; + } + + public void setInHierarchicalVocabulary(boolean isInHierarchicalVocabulary) { + this.isInHierarchicalVocabulary = isInHierarchicalVocabulary; + } + + public boolean isInHierarchicalVocabulary() { + return isInHierarchicalVocabulary; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java new file mode 100644 index 0000000000..3c002d8b25 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java @@ -0,0 +1,83 @@ +/** + * 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 java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.NotFoundException; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.exception.PaginationException; +import org.dspace.app.rest.model.VocabularyEntryDetailsRest; +import org.dspace.app.rest.model.VocabularyRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.utils.AuthorityUtils; +import org.dspace.content.authority.Choice; +import org.dspace.content.authority.ChoiceAuthority; +import org.dspace.content.authority.Choices; +import org.dspace.content.authority.service.ChoiceAuthorityService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository to expose the parent of a vocabulary entry details in an hierarchical vocabulary + * + * @author Mykhaylo Boychuk (4Science.it) + */ +@Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.NAME + "." + VocabularyEntryDetailsRest.CHILDREN) +public class VocabularyEntryDetailsChildrenLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + private ChoiceAuthorityService choiceAuthorityService; + + @Autowired + private AuthorityUtils authorityUtils; + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page getChildren(@Nullable HttpServletRequest request, String name, + @Nullable Pageable pageable, Projection projection) { + + Context context = obtainContext(); + String[] parts = StringUtils.split(name, ":", 2); + if (parts.length != 2) { + return null; + } + String vocabularyName = parts[0]; + String id = parts[1]; + + List results = new ArrayList(); + ChoiceAuthority authority = choiceAuthorityService.getChoiceAuthorityByAuthorityName(vocabularyName); + if (StringUtils.isNotBlank(id) && authority.isHierarchical()) { + Choices choices = choiceAuthorityService.getChoicesByParent(vocabularyName, id, (int) pageable.getOffset(), + pageable.getPageSize(), context.getCurrentLocale().toString()); + for (Choice value : choices.values) { + results.add(authorityUtils.convertEntryDetails(value, vocabularyName, authority.isHierarchical(), + utils.obtainProjection())); + } + } else { + throw new NotFoundException(); + } + Page resources; + try { + resources = utils.getPage(results, pageable); + } catch (PaginationException pe) { + resources = new PageImpl(new ArrayList(), pageable, + pe.getTotal()); + } + return resources; + } +} + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java new file mode 100644 index 0000000000..c105c69747 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java @@ -0,0 +1,65 @@ +/** + * 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 javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.NotFoundException; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.VocabularyEntryDetailsRest; +import org.dspace.app.rest.model.VocabularyRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.utils.AuthorityUtils; +import org.dspace.content.authority.Choice; +import org.dspace.content.authority.ChoiceAuthority; +import org.dspace.content.authority.service.ChoiceAuthorityService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository to expose the parent of a vocabulary entry details in an hierarchical vocabulary + * + * @author Mykhaylo Boychuk ($science.it) + */ +@Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.NAME + "." + VocabularyEntryDetailsRest.PARENT) +public class VocabularyEntryDetailsParentLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + private ChoiceAuthorityService choiceAuthorityService; + + @Autowired + private AuthorityUtils authorityUtils; + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public VocabularyEntryDetailsRest getParent(@Nullable HttpServletRequest request, String name, + @Nullable Pageable pageable, Projection projection) { + Context context = obtainContext(); + String[] parts = StringUtils.split(name, ":", 2); + if (parts.length != 2) { + return null; + } + String vocabularyName = parts[0]; + String id = parts[1]; + + ChoiceAuthority authority = choiceAuthorityService.getChoiceAuthorityByAuthorityName(vocabularyName); + Choice choice = null; + if (StringUtils.isNotBlank(id) && authority.isHierarchical()) { + choice = choiceAuthorityService.getParentChoice(vocabularyName, id, (int) pageable.getOffset(), + pageable.getPageSize(), context.getCurrentLocale().toString()); + } else { + throw new NotFoundException(); + } + return authorityUtils.convertEntryDetails(choice, vocabularyName, authority.isHierarchical(), + utils.obtainProjection()); + } +} From b940e3d41a59bbf181b227942425d68367bc4ff2 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 23 Jun 2020 22:48:54 +0200 Subject: [PATCH 35/59] implemented search method top --- .../VocabularyEntryDetailsRestRepository.java | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java index 0e4c39b7ab..7a417319cc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java @@ -7,7 +7,13 @@ */ package org.dspace.app.rest.repository; +import java.util.ArrayList; +import java.util.List; + import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.exception.PaginationException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.VocabularyEntryDetailsRest; @@ -15,10 +21,12 @@ import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.utils.AuthorityUtils; import org.dspace.content.authority.Choice; import org.dspace.content.authority.ChoiceAuthority; +import org.dspace.content.authority.Choices; import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -54,7 +62,33 @@ public class VocabularyEntryDetailsRestRepository extends DSpaceRestRepository findAllTop(@Parameter(value = "vocabulary", required = true) + String vocabularyId, Pageable pageable) { + Context context = obtainContext(); + List results = new ArrayList(); + ChoiceAuthority source = cas.getChoiceAuthorityByAuthorityName(vocabularyId); + if (source.isHierarchical()) { + Choices choices = cas.getTopChoices(vocabularyId, (int)pageable.getOffset(), pageable.getPageSize(), + context.getCurrentLocale().toString()); + for (Choice value : choices.values) { + results.add(authorityUtils.convertEntryDetails(value, vocabularyId, source.isHierarchical(), + utils.obtainProjection())); + } + } + Page resources; + try { + resources = utils.getPage(results, pageable); + } catch (PaginationException pe) { + resources = new PageImpl(new ArrayList(), pageable, + pe.getTotal()); + } + return resources; } @Override From 8c5dc56193910ed3cfdaae85cf4391046128f515 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 23 Jun 2020 22:50:55 +0200 Subject: [PATCH 36/59] updated tests of VocabularyEntryDetails --- .../content/authority/ChoiceAuthority.java | 8 + .../app/rest/VocabularyEntryDetailsIT.java | 187 ++++++++++-------- ...ava => VocabularyEntryDedailsMatcher.java} | 4 +- 3 files changed, 114 insertions(+), 85 deletions(-) rename dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/{AuthorityEntryMatcher.java => VocabularyEntryDedailsMatcher.java} (95%) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java index 0fbab49bac..a3bfeb9e83 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java @@ -7,6 +7,9 @@ */ package org.dspace.content.authority; +import java.util.HashMap; +import java.util.Map; + import org.dspace.content.Collection; /** @@ -85,6 +88,10 @@ public interface ChoiceAuthority { return getLabel(key, locale); } + default Map getExtra(String key, String locale) { + return new HashMap(); + } + default boolean isHierarchical() { return false; } @@ -102,6 +109,7 @@ public interface ChoiceAuthority { result.authority = authKey; result.label = getLabel(authKey, locale); result.value = getValue(authKey, locale); + result.extras.putAll(getExtra(authKey, locale)); return result; } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index 20c36d6d2a..b6cf3bab6c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -14,7 +14,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.UUID; -import org.dspace.app.rest.matcher.AuthorityEntryMatcher; +import org.dspace.app.rest.matcher.VocabularyEntryDedailsMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.hamcrest.Matchers; import org.junit.Test; @@ -64,38 +64,40 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.containsInAnyOrder( - AuthorityEntryMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB20", "PHARMACY"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB21", "VETERINARY MEDICINE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB22", "INTERDISCIPLINARY RESEARCH AREAS") + .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB17", + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB21", "PHARMACY"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB22", "VETERINARY MEDICINE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); getClient(tokenEPerson).perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.containsInAnyOrder( - AuthorityEntryMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB20", "PHARMACY"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB21", "VETERINARY MEDICINE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB22", "INTERDISCIPLINARY RESEARCH AREAS") + .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB17", + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB21", "PHARMACY"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB22", "VETERINARY MEDICINE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); } @@ -105,11 +107,14 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB14/children")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.containsInAnyOrder( - AuthorityEntryMatcher.matchAuthority("srsc:SCB1401", "Algebra, geometry and mathematical analysis"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB1402", "Applied mathematics"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB1409", "Other mathematics") - ))) + .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1401", + "Algebra, geometry and mathematical analysis"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1402", "Applied mathematics"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1409", "Other mathematics") + ))) + .andExpect(jsonPath("$._embedded.children[0].otherInformation.parent", + is("Research Subject Categories::MATHEMATICS"))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(3))); } @@ -121,41 +126,44 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("page", "0") .param("size", "5")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.containsInAnyOrder( - AuthorityEntryMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES") + .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) .andExpect(jsonPath("$.page.number", is(0))); //second page - getClient(tokenAdmin).perform(get("/api/submission/authorities/srsc/entries/search/top") + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc") .param("page", "1") .param("size", "5")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.containsInAnyOrder( - AuthorityEntryMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB20", "PHARMACY") + .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB17", + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB21", "PHARMACY") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) .andExpect(jsonPath("$.page.number", is(1))); // third page - getClient(tokenAdmin).perform(get("/api/submission/authorities/srsc/entries/search/top") + getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc") .param("page", "2") .param("size", "5")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.containsInAnyOrder( - AuthorityEntryMatcher.matchAuthority("srsc:SCB21", "VETERINARY MEDICINE"), - AuthorityEntryMatcher.matchAuthority("srsc:SCB22", "INTERDISCIPLINARY RESEARCH AREAS") + .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB22", "VETERINARY MEDICINE"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) @@ -177,14 +185,6 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(status().isUnauthorized()); } - @Test - public void retrieveSrscValueTest() throws Exception { - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB1922") - .param("projection", "full")) - .andExpect(status().isOk()); - } - @Test public void srscSearchByParentFirstLevelPaginationTest() throws Exception { String token = getAuthToken(eperson.getEmail(), password); @@ -193,9 +193,10 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("page", "0") .param("size", "2")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.containsInAnyOrder( - AuthorityEntryMatcher.matchAuthority("SCB1401", "Algebra, geometry and mathematical analysis"), - AuthorityEntryMatcher.matchAuthority("SCB1402", "Applied mathematics") + .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1401", + "Algebra, geometry and mathematical analysis"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1402", "Applied mathematics") ))) .andExpect(jsonPath("$.page.totalElements", is(3))) .andExpect(jsonPath("$.page.totalPages", is(2))) @@ -206,8 +207,8 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("page", "1") .param("size", "2")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.contains( - AuthorityEntryMatcher.matchAuthority("SCB1409", "Other mathematics") + .andExpect(jsonPath("$._embedded.children", Matchers.contains( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1409", "Other mathematics") ))) .andExpect(jsonPath("$.page.totalElements", is(3))) .andExpect(jsonPath("$.page.totalPages", is(2))) @@ -218,14 +219,14 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest public void srscSearchByParentSecondLevel_Applied_mathematics_Test() throws Exception { String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB1402/children")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.containsInAnyOrder( - AuthorityEntryMatcher.matchAuthority("VR140202", "Numerical analysis"), - AuthorityEntryMatcher.matchAuthority("VR140203", "Mathematical statistics"), - AuthorityEntryMatcher.matchAuthority("VR140204", "Optimization, systems theory"), - AuthorityEntryMatcher.matchAuthority("VR140205", "Theoretical computer science") - ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:VR140202", "Numerical analysis"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:VR140203", "Mathematical statistics"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:VR140204", "Optimization, systems theory"), + VocabularyEntryDedailsMatcher.matchAuthority("srsc:VR140205", "Theoretical computer science") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); } @Test @@ -246,28 +247,48 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest @Test public void srscSearchTopUnauthorizedTest() throws Exception { - String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top") - .param("vocabulary", "srsc")) - .andExpect(status().isUnauthorized()); + getClient().perform(get("/api/submission/vocabularyEntryDetails/search/top") + .param("vocabulary", "srsc")) + .andExpect(status().isUnauthorized()); } @Test - public void srscSearchParentByChildrenTest() throws Exception { + public void findParentByChildTest() throws Exception { String tokenEperson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:VR140202/children")) + getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB11/parent")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.authorityEntries", Matchers.contains( - AuthorityEntryMatcher.matchAuthority("SCB1402", "Applied mathematics") - ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + .andExpect(jsonPath("$", is(VocabularyEntryDedailsMatcher.matchAuthority( + "srsc:ResearchSubjectCategories", "Research Subject Categories") + ))); } @Test - public void srscSearchParentByChildrenRootTest() throws Exception { + public void findParentByChildSecondLevelTest() throws Exception { String tokenEperson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB11/children")) + getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB180/parent")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(0))); + .andExpect(jsonPath("$", is( + VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB18", "MEDICINE") + ))); + } + + @Test + public void findParentByChildBadRequestTest() throws Exception { + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/" + UUID.randomUUID() + "/parent")) + .andExpect(status().isBadRequest()); + } + + @Test + public void findParentByChildUnauthorizedTest() throws Exception { + getClient().perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB180/parent")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findParentRootChildTest() throws Exception { + String tokenEperson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc/parent")) + .andExpect(status().isNoContent()); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AuthorityEntryMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDedailsMatcher.java similarity index 95% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AuthorityEntryMatcher.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDedailsMatcher.java index 519e77d05e..323d5ab14a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/AuthorityEntryMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDedailsMatcher.java @@ -18,9 +18,9 @@ import org.hamcrest.Matcher; /** * This matcher has been created so that we can use a predefined Matcher class to verify Authority Entries */ -public class AuthorityEntryMatcher { +public class VocabularyEntryDedailsMatcher { - private AuthorityEntryMatcher() { + private VocabularyEntryDedailsMatcher() { } public static Matcher matchAuthorityEntry(String id, String display, String value) { From 13727fb49aee07e838a1b0951be38b598689af89 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 24 Jun 2020 12:36:34 +0200 Subject: [PATCH 37/59] Added management of the parametr exact --- .../VocabularyEntryLinkRepository.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index 814f9e4672..4507026cf3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -14,6 +14,7 @@ import java.util.UUID; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.VocabularyEntryRest; @@ -55,7 +56,9 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository public Page filter(@Nullable HttpServletRequest request, String name, @Nullable Pageable optionalPageable, Projection projection) { Context context = obtainContext(); + String exact = request == null ? null : request.getParameter("exact"); String filter = request == null ? null : request.getParameter("filter"); + String entryID = request == null ? null : request.getParameter("entryID"); String metadata = request == null ? null : request.getParameter("metadata"); String uuidCollectìon = request == null ? null : request.getParameter("collection"); @@ -63,6 +66,10 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository throw new IllegalArgumentException("the metadata and collection parameters are both required"); } + if (StringUtils.isNotBlank(filter) && StringUtils.isNotBlank(entryID)) { + throw new IllegalArgumentException("required only one of the parameters: filter or entryID"); + } + Collection collection = null; if (StringUtils.isNotBlank(uuidCollectìon)) { try { @@ -79,12 +86,17 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository throw new UnprocessableEntityException("The vocabulary " + name + " is not allowed for the metadata " + metadata + " and collection " + uuidCollectìon); } - Pageable pageable = utils.getPageable(optionalPageable); List results = new ArrayList<>(); String fieldKey = org.dspace.core.Utils.standardize(tokens[0], tokens[1], tokens[2], "_"); - Choices choices = cas.getMatches(fieldKey, filter, collection, Math.toIntExact(pageable.getOffset()), - pageable.getPageSize(), context.getCurrentLocale().toString()); + + Choices choices = null; + if (BooleanUtils.toBoolean(exact)) { + choices = cas.getBestMatch(fieldKey, filter, collection, context.getCurrentLocale().toString()); + } else { + choices = cas.getMatches(fieldKey, filter, collection, Math.toIntExact(pageable.getOffset()), + pageable.getPageSize(), context.getCurrentLocale().toString()); + } boolean storeAuthority = cas.storeAuthority(fieldKey, collection); for (Choice value : choices.values) { results.add(authorityUtils.convertEntry(value, name, storeAuthority, projection)); From f39ce1cf54944ccbe7eae5aca4661f70669e7657 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 24 Jun 2020 12:37:09 +0200 Subject: [PATCH 38/59] Added ITs --- .../app/rest/VocabularyRestRepositoryIT.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index a1520a6e5e..bddfb7f5ba 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -395,4 +395,61 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .param("metadata", "dc.type")) .andExpect(status().isBadRequest()); } + + @Test + public void linkedEntitiesWithExactParamTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") + .param("metadata", "dc.type") + .param("collection", collection.getID().toString()) + .param("filter", "Animation") + .param("exact", "true")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entries", Matchers.contains( + VocabularyMatcher.matchVocabularyEntry("Animation", "Animation", "vocabularyEntry") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); + } + + @Test + public void linkedEntitiesWrongMetataForAuthorityTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") + .param("metadata", "dc.type") + .param("collection", collection.getID().toString()) + .param("filter", "Animation")) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void linkedEntitiesWithFilterAndEntryIdTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") + .param("metadata", "dc.subject") + .param("collection", collection.getID().toString()) + .param("filter", "Research") + .param("entryID", "VR131402")) + .andExpect(status().isBadRequest()); + } } From c69168b27def235ed3451f34423d187f98e37361 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 25 Jun 2020 00:06:00 +0200 Subject: [PATCH 39/59] Fix test and final cleanup --- .../authority/ChoiceAuthorityServiceImpl.java | 6 +- .../authority/DSpaceControlledVocabulary.java | 311 ++++++++++-------- .../authority/HierarchicalAuthority.java | 6 +- .../service/ChoiceAuthorityService.java | 6 +- .../converter/SubmissionFormConverter.java | 15 +- .../rest/exception/LinkNotFoundException.java | 30 ++ .../model/VocabularyEntryDetailsRest.java | 6 +- ...aryEntryDetailsChildrenLinkRepository.java | 16 +- ...ularyEntryDetailsParentLinkRepository.java | 5 +- .../VocabularyEntryDetailsRestRepository.java | 14 +- .../VocabularyEntryLinkRepository.java | 8 + .../dspace/app/rest/utils/AuthorityUtils.java | 6 + .../app/rest/VocabularyEntryDetailsIT.java | 177 ++++++---- ...ava => VocabularyEntryDetailsMatcher.java} | 26 +- 14 files changed, 374 insertions(+), 258 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/LinkNotFoundException.java rename dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/{VocabularyEntryDedailsMatcher.java => VocabularyEntryDetailsMatcher.java} (65%) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index 09724bb179..5e15d4d74a 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -458,7 +458,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService private ChoiceAuthority getAuthorityByFieldKeyCollection(String fieldKey, Collection collection) { init(); ChoiceAuthority ma = controller.get(fieldKey); - if (ma == null) { + if (ma == null && collection != null) { SubmissionConfigReader configReader; try { configReader = new SubmissionConfigReader(); @@ -513,8 +513,8 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } @Override - public Choice getParentChoice(String authorityName, String vocabularyId, int start, int limit, String locale) { + public Choice getParentChoice(String authorityName, String vocabularyId, String locale) { HierarchicalAuthority ma = (HierarchicalAuthority) getChoiceAuthorityByAuthorityName(authorityName); - return ma.getParentChoice(authorityName, vocabularyId, start, limit, locale); + return ma.getParentChoice(authorityName, vocabularyId, locale); } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index 7a8cddfd82..c729c547c7 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -62,6 +62,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera protected static String xpathTemplate = "//node[contains(translate(@label,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'," + "'abcdefghijklmnopqrstuvwxyz'),'%s')]"; protected static String idTemplate = "//node[@id = '%s']"; + protected static String labelTemplate = "//node[@label = '%s']"; protected static String idParentTemplate = "//node[@id = '%s']/parent::isComposedBy"; protected static String rootTemplate = "/node"; protected static String pluginNames[] = null; @@ -168,44 +169,122 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera xpathExpression += String.format(xpathTemplate, textHierarchy[i].replaceAll("'", "'").toLowerCase()); } XPath xpath = XPathFactory.newInstance().newXPath(); - List choices = new ArrayList(); int total = 0; + List choices = new ArrayList(); try { NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET); total = results.getLength(); - String[] authorities = new String[results.getLength()]; - String[] values = new String[results.getLength()]; - String[] labels = new String[results.getLength()]; - String[] parent = new String[results.getLength()]; - Boolean[] selectable = new Boolean[results.getLength()]; - ArrayList[] children = new ArrayList[results.getLength()]; - String[] notes = new String[results.getLength()]; - for (int i = 0; i < total; i++) { - Node node = results.item(i); - children[i] = new ArrayList(); - readNode(authorities, values, labels, parent,children[i], notes, selectable, i, node); - } - int resultCount = labels.length - start; - // limit = 0 means no limit - if ((limit > 0) && (resultCount > limit)) { - resultCount = limit; - } - if (resultCount > 0) { - for (int i = 0; i < resultCount; i++) { - Choice choice = new Choice(authorities[start + i], labels[start + i], values[start + i], - selectable[start + i]); - choice.extras = addOtherInformation(parent[i], notes[i], children[i], authorities[i]); - choices.add(choice); - } - } + choices = getChoicesFromNodeList(results, start, limit); } catch (XPathExpressionException e) { log.warn(e.getMessage(), e); + return new Choices(true); } - return new Choices(choices.toArray(new Choice[choices.size()]), start, total, Choices.CF_AMBIGUOUS, false); + return new Choices(choices.toArray(new Choice[choices.size()]), start, total, Choices.CF_AMBIGUOUS, + total > start + limit); + } + + @Override + public Choices getBestMatch(String field, String text, Collection collection, String locale) { + init(); + log.debug("Getting best matches for '" + text + "'"); + String xpathExpression = ""; + String[] textHierarchy = text.split(hierarchyDelimiter, -1); + for (int i = 0; i < textHierarchy.length; i++) { + xpathExpression += String.format(labelTemplate, textHierarchy[i].replaceAll("'", "'").toLowerCase()); + } + XPath xpath = XPathFactory.newInstance().newXPath(); + List choices = new ArrayList(); + try { + NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET); + choices = getChoicesFromNodeList(results, 0, 1); + } catch (XPathExpressionException e) { + log.warn(e.getMessage(), e); + return new Choices(true); + } + return new Choices(choices.toArray(new Choice[choices.size()]), 0, choices.size(), Choices.CF_AMBIGUOUS, false); + } + + @Override + public String getLabel(String key, String locale) { + return getNodeLabel(key, this.suggestHierarchy); + } + + @Override + public String getValue(String key, String locale) { + return getNodeLabel(key, this.storeHierarchy); + } + + @Override + public Choice getChoice(String authKey, String locale) { + Node node; + try { + node = getNode(authKey); + } catch (XPathExpressionException e) { + return null; + } + return createChoiceFromNode(node); + } + + @Override + public boolean isHierarchical() { + return true; + } + + @Override + public Choices getTopChoices(String authorityName, int start, int limit, String locale) { + init(); + String xpathExpression = rootTemplate; + return getChoicesByXpath(xpathExpression, start, limit); + } + + @Override + public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale) { + init(); + String xpathExpression = String.format(idTemplate, parentId); + return getChoicesByXpath(xpathExpression, start, limit); + } + + @Override + public Choice getParentChoice(String authorityName, String parentId, String locale) { + init(); + String xpathExpression = String.format(idTemplate, parentId); + Choice choice = getParent(xpathExpression); + return choice; + } + + @Override + public Integer getPreloadLevel() { + return preloadLevel; + } + + private Node getNode(String key) throws XPathExpressionException { + init(); + String xpathExpression = String.format(idTemplate, key); + XPath xpath = XPathFactory.newInstance().newXPath(); + Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); + return node; + } + + private List getChoicesFromNodeList(NodeList results, int start, int limit) { + List choices = new ArrayList(); + for (int i = 0; i < results.getLength(); i++) { + if (i < start) { + continue; + } + if (choices.size() == limit) { + break; + } + Node node = results.item(i); + Choice choice = new Choice(getAuthority(node), getLabel(node), getValue(node), + isSelectable(node)); + choice.extras = addOtherInformation(getParent(node), getNote(node), getChildren(node), getAuthority(node)); + choices.add(choice); + } + return choices; } private Map addOtherInformation(String parentCurr, String noteCurr, - ArrayList childrenCurr, String authorityCurr) { + List childrenCurr, String authorityCurr) { Map extras = new HashMap(); if (StringUtils.isNotBlank(parentCurr)) { extras.put("parent", parentCurr); @@ -222,29 +301,9 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera return extras; } - @Override - public Choices getBestMatch(String field, String text, Collection collection, String locale) { - init(); - log.debug("Getting best match for '" + text + "'"); - return getMatches(field, text, collection, 0, 2, locale); - } - - @Override - public String getLabel(String key, String locale) { - return getLabel(key, this.suggestHierarchy); - } - - @Override - public String getValue(String key, String locale) { - return getLabel(key, this.storeHierarchy); - } - - private String getLabel(String key, boolean useHierarchy) { - init(); - String xpathExpression = String.format(idTemplate, key); - XPath xpath = XPathFactory.newInstance().newXPath(); + private String getNodeLabel(String key, boolean useHierarchy) { try { - Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); + Node node = getNode(key); if (useHierarchy) { return this.buildString(node); } else { @@ -255,25 +314,40 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera } } - @Override - public boolean isHierarchical() { - return true; - } - - private void readNode(String[] authorities, String[] values, String[] labels, String[] parent, - List children, String[] notes, Boolean[] selectable, int i, Node node) { + private String getLabel(Node node) { String hierarchy = this.buildString(node); if (this.suggestHierarchy) { - labels[i] = hierarchy; + return hierarchy; } else { - labels[i] = node.getAttributes().getNamedItem("label").getNodeValue(); - } - if (this.storeHierarchy) { - values[i] = hierarchy; - } else { - values[i] = node.getAttributes().getNamedItem("label").getNodeValue(); + return node.getAttributes().getNamedItem("label").getNodeValue(); } + } + private String getValue(Node node) { + String hierarchy = this.buildString(node); + if (this.storeHierarchy) { + return hierarchy; + } else { + return node.getAttributes().getNamedItem("label").getNodeValue(); + } + } + + private String getNote(Node node) { + NodeList childNodes = node.getChildNodes(); + for (int ci = 0; ci < childNodes.getLength(); ci++) { + Node firstChild = childNodes.item(ci); + if (firstChild != null && "hasNote".equals(firstChild.getNodeName())) { + String nodeValue = firstChild.getTextContent(); + if (StringUtils.isNotBlank(nodeValue)) { + return nodeValue; + } + } + } + return null; + } + + private List getChildren(Node node) { + List children = new ArrayList(); NodeList childNodes = node.getChildNodes(); for (int ci = 0; ci < childNodes.getLength(); ci++) { Node firstChild = childNodes.item(ci); @@ -290,102 +364,80 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera break; } } + return children; + } + + private boolean isSelectable(Node node) { Node selectableAttr = node.getAttributes().getNamedItem("selectable"); if (null != selectableAttr) { - selectable[i] = Boolean.valueOf(selectableAttr.getNodeValue()); + return Boolean.valueOf(selectableAttr.getNodeValue()); } else { // Default is true - selectable[i] = true; + return true; } - Node idAttr = node.getAttributes().getNamedItem("id"); - if (null != idAttr) { // 'id' is optional - authorities[i] = idAttr.getNodeValue(); - if (isHierarchical()) { - Node parentN = node.getParentNode(); - if (parentN != null) { - parentN = parentN.getParentNode(); - if (parentN != null) { - Node parentIdAttr = parentN.getAttributes().getNamedItem("id"); - if (null != parentIdAttr && !parentIdAttr.getNodeValue().equals(rootNodeId)) { - parent[i] = buildString(parentN); - } - } + } + + private String getParent(Node node) { + Node parentN = node.getParentNode(); + if (parentN != null) { + parentN = parentN.getParentNode(); + if (parentN != null) { + Node parentIdAttr = parentN.getAttributes().getNamedItem("id"); + if (null != parentIdAttr && !parentIdAttr.getNodeValue().equals(rootNodeId)) { + return buildString(parentN); } } + } + return null; + } + + private String getAuthority(Node node) { + Node idAttr = node.getAttributes().getNamedItem("id"); + if (null != idAttr) { // 'id' is optional + return idAttr.getNodeValue(); } else { - authorities[i] = null; - parent[i] = null; + return null; } } - @Override - public Choices getTopChoices(String authorityName, int start, int limit, String locale) { - init(); - String xpathExpression = rootTemplate; - List choices = getChoicesByXpath(xpathExpression); - return new Choices(choices.toArray(new Choice[choices.size()]), 0, choices.size(), Choices.CF_AMBIGUOUS, false); - } - - private List getChoicesByXpath(String xpathExpression) { + private Choices getChoicesByXpath(String xpathExpression, int start, int limit) { List choices = new ArrayList(); XPath xpath = XPathFactory.newInstance().newXPath(); try { Node parentNode = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); + int count = 0; if (parentNode != null) { NodeList childNodes = (NodeList) xpath.evaluate(".//isComposedBy", parentNode, XPathConstants.NODE); if (null != childNodes) { for (int i = 0; i < childNodes.getLength(); i++) { Node childNode = childNodes.item(i); if (childNode != null && "node".equals(childNode.getNodeName())) { + if (count < start || choices.size() >= limit) { + count++; + continue; + } + count++; choices.add(createChoiceFromNode(childNode)); } } } + return new Choices(choices.toArray(new Choice[choices.size()]), start, count, + Choices.CF_AMBIGUOUS, false); } } catch (XPathExpressionException e) { log.warn(e.getMessage(), e); + return new Choices(true); } - return choices; + return new Choices(false); } private Choice createChoiceFromNode(Node node) { if (node != null) { - String[] authorities = new String[1]; - String[] values = new String[1]; - String[] labels = new String[1]; - String[] parent = new String[1]; - String[] note = new String[1]; - Boolean[] selectable = new Boolean[1]; - List children = new ArrayList(); - readNode(authorities, values, labels, parent, children, note, selectable, 0, node); - - if (values.length > 0) { - Choice choice = new Choice(authorities[0], values[0], labels[0], selectable[0]); - if (StringUtils.isNotBlank(parent[0])) { - choice.extras.put("parent", parent[0]); - } - if (StringUtils.isNotBlank(note[0])) { - choice.extras.put("note", note[0]); - } - return choice; - } + Choice choice = new Choice(getAuthority(node), getLabel(node), getValue(node), + isSelectable(node)); + choice.extras = addOtherInformation(getParent(node), getNote(node),getChildren(node), getAuthority(node)); + return choice; } - return new Choice("", "", ""); - } - - @Override - public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale) { - init(); - String xpathExpression = String.format(idTemplate, parentId); - List choices = getChoicesByXpath(xpathExpression); - return new Choices(choices.toArray(new Choice[choices.size()]), 0, choices.size(), Choices.CF_AMBIGUOUS, false); - } - - @Override - public Choice getParentChoice(String authorityName, String parentId, int start, int limit, String locale) { - init(); - String xpathExpression = String.format(idTemplate, parentId); - Choice choice = getParent(xpathExpression); - return choice; + return null; } private Choice getParent(String xpathExpression) { @@ -405,9 +457,4 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera return choice; } - @Override - public Integer getPreloadLevel() { - return preloadLevel; - } - } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java index 18e088eb54..279f62d22f 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java @@ -65,12 +65,10 @@ public interface HierarchicalAuthority extends ChoiceAuthority { * * @param authorityName authority name * @param vocabularyId user's value to match - * @param start choice at which to start, 0 is first. - * @param limit maximum number of choices to return, 0 for no limit. * @param locale explicit localization key if available, or null - * @return a Choice object (never null). + * @return a Choice object */ - public Choice getParentChoice(String authorityName, String vocabularyId, int start, int limit, String locale); + public Choice getParentChoice(String authorityName, String vocabularyId, String locale); public Integer getPreloadLevel(); diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java index 7afad12539..bfcee338b9 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java @@ -211,10 +211,8 @@ public interface ChoiceAuthorityService { * * @param authorityName authority name * @param vocabularyId child id - * @param start choice at which to start, 0 is first. - * @param limit maximum number of choices to return, 0 for no limit. * @param locale explicit localization key if available, or null - * @return a Choice object (never null). + * @return the parent Choice object if any */ - public Choice getParentChoice(String authorityName, String vocabularyId, int start, int limit, String locale); + public Choice getParentChoice(String authorityName, String vocabularyId, String locale); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java index f8f34612eb..bb817bffea 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java @@ -122,7 +122,8 @@ public class SubmissionFormConverter implements DSpaceConverter getModelClass() { return DCInputSet.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/LinkNotFoundException.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/LinkNotFoundException.java new file mode 100644 index 0000000000..5710b7a176 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/LinkNotFoundException.java @@ -0,0 +1,30 @@ +/** + * 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.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * This is the exception to capture details about a not existing linked resource + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "This link is not found in the system") +public class LinkNotFoundException extends RuntimeException { + String apiCategory; + String model; + String id; + + public LinkNotFoundException(String apiCategory, String model, String id) { + this.apiCategory = apiCategory; + this.model = model; + this.id = id; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java index 42ce1f47a5..1418bd8216 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java @@ -31,7 +31,7 @@ public class VocabularyEntryDetailsRest extends RestAddressableModel { private Map otherInformation; private boolean selectable; @JsonIgnore - private boolean isInHierarchicalVocabulary = false; + private boolean inHierarchicalVocabulary = false; @JsonIgnore private String vocabularyName; @@ -104,10 +104,10 @@ public class VocabularyEntryDetailsRest extends RestAddressableModel { } public void setInHierarchicalVocabulary(boolean isInHierarchicalVocabulary) { - this.isInHierarchicalVocabulary = isInHierarchicalVocabulary; + this.inHierarchicalVocabulary = isInHierarchicalVocabulary; } public boolean isInHierarchicalVocabulary() { - return isInHierarchicalVocabulary; + return inHierarchicalVocabulary; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java index 3c002d8b25..f9e8e5a7ad 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java @@ -11,10 +11,9 @@ import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.NotFoundException; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.rest.exception.PaginationException; +import org.dspace.app.rest.exception.LinkNotFoundException; import org.dspace.app.rest.model.VocabularyEntryDetailsRest; import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.projection.Projection; @@ -67,17 +66,12 @@ public class VocabularyEntryDetailsChildrenLinkRepository extends AbstractDSpace results.add(authorityUtils.convertEntryDetails(value, vocabularyName, authority.isHierarchical(), utils.obtainProjection())); } + Page resources = new PageImpl(results, pageable, + choices.total); + return resources; } else { - throw new NotFoundException(); + throw new LinkNotFoundException(VocabularyRest.CATEGORY, VocabularyEntryDetailsRest.NAME, name); } - Page resources; - try { - resources = utils.getPage(results, pageable); - } catch (PaginationException pe) { - resources = new PageImpl(new ArrayList(), pageable, - pe.getTotal()); - } - return resources; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java index c105c69747..bebb0fdf85 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java @@ -42,7 +42,7 @@ public class VocabularyEntryDetailsParentLinkRepository extends AbstractDSpaceRe @PreAuthorize("hasAuthority('AUTHENTICATED')") public VocabularyEntryDetailsRest getParent(@Nullable HttpServletRequest request, String name, - @Nullable Pageable pageable, Projection projection) { + @Nullable Pageable pageable, Projection projection) { Context context = obtainContext(); String[] parts = StringUtils.split(name, ":", 2); if (parts.length != 2) { @@ -54,8 +54,7 @@ public class VocabularyEntryDetailsParentLinkRepository extends AbstractDSpaceRe ChoiceAuthority authority = choiceAuthorityService.getChoiceAuthorityByAuthorityName(vocabularyName); Choice choice = null; if (StringUtils.isNotBlank(id) && authority.isHierarchical()) { - choice = choiceAuthorityService.getParentChoice(vocabularyName, id, (int) pageable.getOffset(), - pageable.getPageSize(), context.getCurrentLocale().toString()); + choice = choiceAuthorityService.getParentChoice(vocabularyName, id, context.getCurrentLocale().toString()); } else { throw new NotFoundException(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java index 7a417319cc..620bb527b1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java @@ -13,7 +13,7 @@ import java.util.List; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; -import org.dspace.app.rest.exception.PaginationException; +import org.dspace.app.rest.exception.LinkNotFoundException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.VocabularyEntryDetailsRest; @@ -80,15 +80,11 @@ public class VocabularyEntryDetailsRestRepository extends DSpaceRestRepository resources = new PageImpl(results, pageable, + choices.total); + return resources; } - Page resources; - try { - resources = utils.getPage(results, pageable); - } catch (PaginationException pe) { - resources = new PageImpl(new ArrayList(), pageable, - pe.getTotal()); - } - return resources; + throw new LinkNotFoundException(VocabularyRest.CATEGORY, VocabularyEntryDetailsRest.NAME, vocabularyId); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index 4507026cf3..e18fc35d84 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -93,6 +93,14 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository Choices choices = null; if (BooleanUtils.toBoolean(exact)) { choices = cas.getBestMatch(fieldKey, filter, collection, context.getCurrentLocale().toString()); + } else if (StringUtils.isNotBlank(entryID)) { + Choice choice = cas.getChoiceAuthorityByAuthorityName(vocName).getChoice(entryID, + context.getCurrentLocale().toString()); + if (choice != null) { + choices = new Choices(new Choice[] {choice}, 0, 1, Choices.CF_ACCEPTED, false); + } else { + choices = new Choices(false); + } } else { choices = cas.getMatches(fieldKey, filter, collection, Math.toIntExact(pageable.getOffset()), pageable.getPageSize(), context.getCurrentLocale().toString()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java index 70a586007c..22e1ff6101 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java @@ -66,6 +66,9 @@ public class AuthorityUtils { */ public VocabularyEntryDetailsRest convertEntryDetails(Choice choice, String authorityName, boolean isHierarchical, Projection projection) { + if (choice == null) { + return null; + } VocabularyEntryDetailsRest entry = converter.toRest(choice, projection); entry.setVocabularyName(authorityName); entry.setId(authorityName + ":" + entry.getId()); @@ -75,6 +78,9 @@ public class AuthorityUtils { public VocabularyEntryRest convertEntry(Choice choice, String authorityName, boolean storeAuthority, Projection projection) { + if (choice == null) { + return null; + } VocabularyEntryRest entry = new VocabularyEntryRest(); entry.setDisplay(choice.label); entry.setValue(choice.value); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index b6cf3bab6c..7af95646e8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -14,7 +14,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.UUID; -import org.dspace.app.rest.matcher.VocabularyEntryDedailsMatcher; +import org.dspace.app.rest.matcher.VocabularyEntryDetailsMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.hamcrest.Matchers; import org.junit.Test; @@ -47,7 +47,8 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$.selectable", is(true))) .andExpect(jsonPath("$.otherInformation.id", is("SCB110"))) .andExpect(jsonPath("$.otherInformation.note", is("Religionsvetenskap/Teologi"))) - .andExpect(jsonPath("$.otherInformation.parent", is("HUMANITIES and RELIGION"))); + .andExpect(jsonPath("$.otherInformation.parent", + is("Research Subject Categories::HUMANITIES and RELIGION"))); } @Test @@ -65,19 +66,31 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("vocabulary", "srsc")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB17", - "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB21", "PHARMACY"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB22", "VETERINARY MEDICINE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS") + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB11", "HUMANITIES and RELIGION", + "Research Subject Categories::HUMANITIES and RELIGION"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB12", "LAW/JURISPRUDENCE", + "Research Subject Categories::LAW/JURISPRUDENCE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB13", "SOCIAL SCIENCES", + "Research Subject Categories::SOCIAL SCIENCES"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB14", "MATHEMATICS", + "Research Subject Categories::MATHEMATICS"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB15", "NATURAL SCIENCES", + "Research Subject Categories::NATURAL SCIENCES"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB16", "TECHNOLOGY", + "Research Subject Categories::TECHNOLOGY"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB17", + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING", + "Research Subject Categories::FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB18", "MEDICINE", + "Research Subject Categories::MEDICINE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB19", "ODONTOLOGY", + "Research Subject Categories::ODONTOLOGY"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB21", "PHARMACY", + "Research Subject Categories::PHARMACY"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB22", "VETERINARY MEDICINE", + "Research Subject Categories::VETERINARY MEDICINE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS", + "Research Subject Categories::INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); @@ -85,19 +98,31 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("vocabulary", "srsc")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB17", - "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB21", "PHARMACY"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB22", "VETERINARY MEDICINE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS") + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB11", "HUMANITIES and RELIGION", + "Research Subject Categories::HUMANITIES and RELIGION"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB12", "LAW/JURISPRUDENCE", + "Research Subject Categories::LAW/JURISPRUDENCE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB13", "SOCIAL SCIENCES", + "Research Subject Categories::SOCIAL SCIENCES"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB14", "MATHEMATICS", + "Research Subject Categories::MATHEMATICS"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB15", "NATURAL SCIENCES", + "Research Subject Categories::NATURAL SCIENCES"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB16", "TECHNOLOGY", + "Research Subject Categories::TECHNOLOGY"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB17", + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING", + "Research Subject Categories::FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB18", "MEDICINE", + "Research Subject Categories::MEDICINE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB19", "ODONTOLOGY", + "Research Subject Categories::ODONTOLOGY"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB21", "PHARMACY", + "Research Subject Categories::PHARMACY"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB22", "VETERINARY MEDICINE", + "Research Subject Categories::VETERINARY MEDICINE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS", + "Research Subject Categories::INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); } @@ -108,13 +133,16 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB14/children")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1401", - "Algebra, geometry and mathematical analysis"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1402", "Applied mathematics"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1409", "Other mathematics") + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1401", + "Algebra, geometry and mathematical analysis", + "Research Subject Categories::MATHEMATICS::Algebra, geometry and mathematical analysis"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1402", "Applied mathematics", + "Research Subject Categories::MATHEMATICS::Applied mathematics"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1409", "Other mathematics", + "Research Subject Categories::MATHEMATICS::Other mathematics") ))) - .andExpect(jsonPath("$._embedded.children[0].otherInformation.parent", - is("Research Subject Categories::MATHEMATICS"))) + .andExpect(jsonPath("$._embedded.children[*].otherInformation.parent", + Matchers.everyItem(is("Research Subject Categories::MATHEMATICS")))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(3))); } @@ -127,11 +155,16 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("size", "5")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB11", "HUMANITIES and RELIGION"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB12", "LAW/JURISPRUDENCE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB13", "SOCIAL SCIENCES"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB14", "MATHEMATICS"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB15", "NATURAL SCIENCES") + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB11", "HUMANITIES and RELIGION", + "Research Subject Categories::HUMANITIES and RELIGION"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB12", "LAW/JURISPRUDENCE", + "Research Subject Categories::LAW/JURISPRUDENCE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB13", "SOCIAL SCIENCES", + "Research Subject Categories::SOCIAL SCIENCES"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB14", "MATHEMATICS", + "Research Subject Categories::MATHEMATICS"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB15", "NATURAL SCIENCES", + "Research Subject Categories::NATURAL SCIENCES") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) @@ -144,12 +177,17 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("size", "5")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB16", "TECHNOLOGY"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB17", - "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB18", "MEDICINE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB19", "ODONTOLOGY"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB21", "PHARMACY") + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB16", "TECHNOLOGY", + "Research Subject Categories::TECHNOLOGY"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB17", + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING", + "Research Subject Categories::FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB18", "MEDICINE", + "Research Subject Categories::MEDICINE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB19", "ODONTOLOGY", + "Research Subject Categories::ODONTOLOGY"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB21", "PHARMACY", + "Research Subject Categories::PHARMACY") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) @@ -162,8 +200,10 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("size", "5")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB22", "VETERINARY MEDICINE"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS") + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB22", "VETERINARY MEDICINE", + "Research Subject Categories::VETERINARY MEDICINE"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS", + "Research Subject Categories::INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) @@ -194,9 +234,11 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("size", "2")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1401", - "Algebra, geometry and mathematical analysis"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1402", "Applied mathematics") + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1401", + "Algebra, geometry and mathematical analysis", + "Research Subject Categories::MATHEMATICS::Algebra, geometry and mathematical analysis"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1402", "Applied mathematics", + "Research Subject Categories::MATHEMATICS::Applied mathematics") ))) .andExpect(jsonPath("$.page.totalElements", is(3))) .andExpect(jsonPath("$.page.totalPages", is(2))) @@ -208,7 +250,8 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .param("size", "2")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.children", Matchers.contains( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB1409", "Other mathematics") + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1409", "Other mathematics", + "Research Subject Categories::MATHEMATICS::Other mathematics") ))) .andExpect(jsonPath("$.page.totalElements", is(3))) .andExpect(jsonPath("$.page.totalPages", is(2))) @@ -219,14 +262,18 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest public void srscSearchByParentSecondLevel_Applied_mathematics_Test() throws Exception { String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB1402/children")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:VR140202", "Numerical analysis"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:VR140203", "Mathematical statistics"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:VR140204", "Optimization, systems theory"), - VocabularyEntryDedailsMatcher.matchAuthority("srsc:VR140205", "Theoretical computer science") - ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140202", "Numerical analysis", + "Research Subject Categories::MATHEMATICS::Applied mathematics::Numerical analysis"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140203", "Mathematical statistics", + "Research Subject Categories::MATHEMATICS::Applied mathematics::Mathematical statistics"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140204", "Optimization, systems theory", + "Research Subject Categories::MATHEMATICS::Applied mathematics::Optimization, systems theory"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140205", "Theoretical computer science", + "Research Subject Categories::MATHEMATICS::Applied mathematics::Theoretical computer science") + ))) + .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); } @Test @@ -257,8 +304,8 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest String tokenEperson = getAuthToken(eperson.getEmail(), password); getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB11/parent")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", is(VocabularyEntryDedailsMatcher.matchAuthority( - "srsc:ResearchSubjectCategories", "Research Subject Categories") + .andExpect(jsonPath("$", is(VocabularyEntryDetailsMatcher.matchAuthorityEntry( + "srsc:ResearchSubjectCategories", "Research Subject Categories", "Research Subject Categories") ))); } @@ -268,7 +315,8 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB180/parent")) .andExpect(status().isOk()) .andExpect(jsonPath("$", is( - VocabularyEntryDedailsMatcher.matchAuthority("srsc:SCB18", "MEDICINE") + VocabularyEntryDetailsMatcher.matchAuthorityEntry( + "srsc:SCB18", "MEDICINE","Research Subject Categories::MEDICINE") ))); } @@ -288,7 +336,8 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest @Test public void findParentRootChildTest() throws Exception { String tokenEperson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc/parent")) - .andExpect(status().isNoContent()); + getClient(tokenEperson) + .perform(get("/api/submission/vocabularyEntryDetails/srsc:ResearchSubjectCategory/parent")) + .andExpect(status().isNoContent()); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDedailsMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDetailsMatcher.java similarity index 65% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDedailsMatcher.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDetailsMatcher.java index 323d5ab14a..67747cff66 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDedailsMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDetailsMatcher.java @@ -8,7 +8,6 @@ package org.dspace.app.rest.matcher; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; -import static org.dspace.app.rest.matcher.HalMatcher.matchEmbeds; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -18,9 +17,9 @@ import org.hamcrest.Matcher; /** * This matcher has been created so that we can use a predefined Matcher class to verify Authority Entries */ -public class VocabularyEntryDedailsMatcher { +public class VocabularyEntryDetailsMatcher { - private VocabularyEntryDedailsMatcher() { + private VocabularyEntryDetailsMatcher() { } public static Matcher matchAuthorityEntry(String id, String display, String value) { @@ -31,7 +30,7 @@ public class VocabularyEntryDedailsMatcher { public static Matcher matchLinks() { return allOf( - hasJsonPath("$._links.self.href", containsString("api/integration/authority/"))); + hasJsonPath("$._links.self.href", containsString("api/submission/vocabularyEntryDetails/"))); } private static Matcher matchProperties(String id, String display, String value) { @@ -39,24 +38,7 @@ public class VocabularyEntryDedailsMatcher { hasJsonPath("$.id", is(id)), hasJsonPath("$.display", is(display)), hasJsonPath("$.value", is(value)), - hasJsonPath("$.type", is("authority")) - ); - } - - /** - * Gets a matcher for all expected embeds when the full projection is requested. - */ - public static Matcher matchFullEmbeds() { - return matchEmbeds( - "authorityEntries" - ); - } - - public static Matcher matchAuthority(String id, String value) { - return allOf( - hasJsonPath("$.id", is(id)), - hasJsonPath("$.value", is(value)), hasJsonPath("$.type", is("vocabularyEntryDetail")) - ); + ); } } From 6b923e0cb53cb344b8e8e3e7738c0b458c2b4e2b Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 25 Jun 2020 09:23:34 +0200 Subject: [PATCH 40/59] Fix test - DSpaceControlledVocabulary now honor the pagination --- .../content/authority/DSpaceControlledVocabularyTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java b/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java index 0d431a5a5b..84306ac034 100644 --- a/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java +++ b/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java @@ -78,7 +78,7 @@ public class DSpaceControlledVocabularyTest extends AbstractDSpaceTest { String text = "north 40"; Collection collection = null; int start = 0; - int limit = 0; + int limit = 10; String locale = null; // This "farm" Controlled Vocab is included in TestEnvironment data // (under /src/test/data/dspaceFolder/) and it should be auto-loaded From 82aae2395f22ca3d7631664ff4d14e56b77f5d8c Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 25 Jun 2020 13:33:06 +0200 Subject: [PATCH 41/59] Remove unnecessary change in the MetadataAuthorityService --- .../MetadataAuthorityServiceImpl.java | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/MetadataAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/MetadataAuthorityServiceImpl.java index 6a5b17a029..c542c6a89e 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/MetadataAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/MetadataAuthorityServiceImpl.java @@ -14,12 +14,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; -import org.dspace.app.util.DCInput; -import org.dspace.app.util.DCInputSet; -import org.dspace.app.util.DCInputsReader; -import org.dspace.app.util.DCInputsReaderException; import org.dspace.content.MetadataField; import org.dspace.content.authority.service.MetadataAuthorityService; import org.dspace.content.service.MetadataFieldService; @@ -144,8 +139,6 @@ public class MetadataAuthorityServiceImpl implements MetadataAuthorityService { if (dmc >= Choices.CF_UNSET) { defaultMinConfidence = dmc; } - - autoRegisterAuthorityFromInputReader(); } } @@ -205,7 +198,6 @@ public class MetadataAuthorityServiceImpl implements MetadataAuthorityService { } } - /** * Give the minimal level of confidence required to consider valid an authority value * for the given metadata. @@ -229,35 +221,4 @@ public class MetadataAuthorityServiceImpl implements MetadataAuthorityService { } return copy; } - - - private void autoRegisterAuthorityFromInputReader() { - try { - DCInputsReader dcInputsReader = new DCInputsReader(); - for (DCInputSet dcinputSet : dcInputsReader.getAllInputs(Integer.MAX_VALUE, 0)) { - DCInput[][] dcinputs = dcinputSet.getFields(); - for (DCInput[] dcrows : dcinputs) { - for (DCInput dcinput : dcrows) { - if (StringUtils.isNotBlank(dcinput.getPairsType()) - || StringUtils.isNotBlank(dcinput.getVocabulary())) { - String authorityName = dcinput.getPairsType(); - if (StringUtils.isBlank(authorityName)) { - authorityName = dcinput.getVocabulary(); - } - if (!StringUtils.equals(dcinput.getInputType(), "qualdrop_value")) { - String fieldKey = makeFieldKey(dcinput.getSchema(), dcinput.getElement(), - dcinput.getQualifier()); - boolean req = ConfigurationManager - .getBooleanProperty("authority.required." + fieldKey, false); - controlled.put(fieldKey, true); - isAuthorityRequired.put(fieldKey, req); - } - } - } - } - } - } catch (DCInputsReaderException e) { - throw new IllegalStateException(e.getMessage(), e); - } - } } From 76181a682919be046a4540a1c9ffe18d1a53dcbc Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 25 Jun 2020 17:37:24 +0200 Subject: [PATCH 42/59] Restore test for authority as was before - no authorities for value pairs --- dspace-api/src/test/data/dspaceFolder/config/local.cfg | 5 +++++ dspace-api/src/test/java/org/dspace/content/ItemTest.java | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 51ce1a0165..029c4be664 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -109,6 +109,11 @@ plugin.sequence.java.util.Collection = \ java.util.Stack, \ java.util.TreeSet +# Enable a test authority control on dc.language.iso field +choices.plugin.dc.language.iso = common_iso_languages +choices.presentation.dc.language.iso = select +authority.controlled.dc.language.iso = true + ########################################### # PROPERTIES USED TO TEST CONFIGURATION # # PROPERTY EXPOSURE VIA REST # diff --git a/dspace-api/src/test/java/org/dspace/content/ItemTest.java b/dspace-api/src/test/java/org/dspace/content/ItemTest.java index 8c3cfa5a04..494368230b 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemTest.java @@ -490,8 +490,8 @@ public class ItemTest extends AbstractDSpaceObjectTest { // Set the item to have two pieces of metadata for dc.type and dc2.type String dcType = "DC-TYPE"; String testType = "TEST-TYPE"; - itemService.addMetadata(context, it, "dc", "type", null, null, dcType, "accepted", 0); - itemService.addMetadata(context, it, "test", "type", null, null, testType, "accepted", 0); + itemService.addMetadata(context, it, "dc", "type", null, null, dcType); + itemService.addMetadata(context, it, "test", "type", null, null, testType); // Check that only one is returned when we ask for all dc.type values List values = itemService.getMetadata(it, "dc", "type", null, null); From 42dd930060cfaad1dc742c8b9821fa4453e523da Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 25 Jun 2020 17:47:17 +0200 Subject: [PATCH 43/59] Rename authority in controlledVocabulary, add related IT --- .../converter/SubmissionFormConverter.java | 4 +- .../rest/model/submit/SelectableMetadata.java | 15 ++++--- .../app/rest/SubmissionFormsControllerIT.java | 26 +++++++++++ .../matcher/SubmissionFormFieldMatcher.java | 44 ++++++++++++++++--- 4 files changed, 77 insertions(+), 12 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java index bb817bffea..ce54169cf0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java @@ -118,7 +118,7 @@ public class SubmissionFormConverter implements DSpaceConverter matchFormFieldDefinition(String type, String label, String mandatoryMessage, - boolean repeatable, - String hints, String style, String metadata) { + boolean repeatable, + String hints, String style, String metadata) { + return matchFormFieldDefinition(type, label, mandatoryMessage, repeatable, hints, style, metadata, null); + } + + /** + * Check the json representation of a submission form + * + * @param type + * the expected input type + * @param label + * the expected label + * @param mandatoryMessage + * the expected mandatoryMessage, can be null. If not empty the field is expected to be flagged as + * mandatory + * @param repeatable + * the expected repeatable flag + * @param hints + * the expected hints message + * @param style + * the expected style for the field, can be null. If null the corresponding json path is expected to be + * missing + * @param metadata + * the expected metadata + * @param controlled vocabulary + * the expected controlled vocabulary, can be null. If null the corresponding json path is expected to be + * missing + * @return a Matcher for all the condition above + */ + public static Matcher matchFormFieldDefinition(String type, String label, String mandatoryMessage, + boolean repeatable, String hints, String style, + String metadata, String controlledVocabulary) { return allOf( // check each field definition hasJsonPath("$.input.type", is(type)), hasJsonPath("$.label", containsString(label)), hasJsonPath("$.selectableMetadata[0].metadata", is(metadata)), + controlledVocabulary != null ? hasJsonPath("$.selectableMetadata[0].controlledVocabulary", + is(controlledVocabulary)) : hasNoJsonPath("$.selectableMetadata[0].controlledVocabulary"), mandatoryMessage != null ? hasJsonPath("$.mandatoryMessage", containsString(mandatoryMessage)) : hasNoJsonPath("$.mandatoryMessage"), hasJsonPath("$.mandatory", is(mandatoryMessage != null)), From a8234f2004cfdd0fce7c496b8718379bb67f71ab Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 25 Jun 2020 19:25:27 +0200 Subject: [PATCH 44/59] Fix vocabulary related links test in the root endpoint --- .../java/org/dspace/app/rest/RootRestResourceControllerIT.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RootRestResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RootRestResourceControllerIT.java index 6c1c4a9427..b4cee1672e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RootRestResourceControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RootRestResourceControllerIT.java @@ -49,7 +49,8 @@ public class RootRestResourceControllerIT extends AbstractControllerIntegrationT //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) //Check that all required root links are present and that they are absolute - .andExpect(jsonPath("$._links.authorities.href", startsWith(BASE_REST_SERVER_URL))) + .andExpect(jsonPath("$._links.vocabularies.href", startsWith(BASE_REST_SERVER_URL))) + .andExpect(jsonPath("$._links.vocabularyEntryDetails.href", startsWith(BASE_REST_SERVER_URL))) .andExpect(jsonPath("$._links.bitstreamformats.href", startsWith(BASE_REST_SERVER_URL))) .andExpect(jsonPath("$._links.bitstreams.href", startsWith(BASE_REST_SERVER_URL))) .andExpect(jsonPath("$._links.browses.href", startsWith(BASE_REST_SERVER_URL))) From ba150b1ec7e33e9a2c1cf38b94927f016ff03862 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 25 Jun 2020 19:26:52 +0200 Subject: [PATCH 45/59] Fix cleanup testenv --- .../dspace/app/rest/VocabularyRestRepositoryIT.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index bddfb7f5ba..898e63cf78 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -26,6 +26,7 @@ import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.core.service.PluginService; import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -96,6 +97,15 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes AuthorityServiceFactory.getInstance().getAuthorityIndexingService().commit(); } + @Override + @After + // We need to cleanup the authorities cache once than the configuration has been restored + public void destroy() throws Exception { + super.destroy(); + pluginService.clearNamedPluginClasses(); + cas.clearCache(); + } + @Test public void findAllTest() throws Exception { String token = getAuthToken(admin.getEmail(), password); From 322b56b42275c0fdc2cdd7809ee653acab4392d7 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 25 Jun 2020 20:14:01 +0200 Subject: [PATCH 46/59] Expose the vocabularyName to the submission form definition --- .../app/rest/converter/SubmissionFormConverter.java | 11 ++++++++++- .../dspace/app/rest/SubmissionFormsControllerIT.java | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java index ce54169cf0..3f233a34c7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java @@ -115,7 +115,8 @@ public class SubmissionFormConverter implements DSpaceConverter getModelClass() { return DCInputSet.class; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java index 73b6224d9b..dee7d4c611 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java @@ -160,7 +160,7 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe null, true, "Select the type(s) of content of the item. To select more than one value in the " + "list, you may have to hold down the \"CTRL\" or \"Shift\" key.", - "dc.type") + null, "dc.type", "common_types") ))) ; } From ff1b40d85998ebe6e2ec671dddcff0c20d228c7b Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Mon, 29 Jun 2020 16:18:05 +0200 Subject: [PATCH 47/59] remove unused attributes --- .../app/rest/model/SubmissionFormInputTypeRest.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormInputTypeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormInputTypeRest.java index cda0f6fbaa..ff5481443b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormInputTypeRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormInputTypeRest.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; public class SubmissionFormInputTypeRest { private String type; private String regex; - private VocabularyRest authority; public String getType() { return type; @@ -39,11 +38,4 @@ public class SubmissionFormInputTypeRest { this.regex = regex; } - public VocabularyRest getAuthority() { - return authority; - } - - public void setAuthority(VocabularyRest authority) { - this.authority = authority; - } } From 6333fdc499e73c23da778a51edac27d00be4ccda Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Tue, 30 Jun 2020 21:43:57 +0200 Subject: [PATCH 48/59] Add test to check links in the vocabulary entry details and relative fix --- .../app/rest/model/VocabularyEntryDetailsRest.java | 11 +---------- .../dspace/app/rest/VocabularyEntryDetailsIT.java | 14 +++++++++----- .../matcher/VocabularyEntryDetailsMatcher.java | 6 +++--- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java index 1418bd8216..42644c8c85 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/VocabularyEntryDetailsRest.java @@ -21,11 +21,10 @@ import org.dspace.app.rest.RestResourceController; @LinkRest(name = VocabularyEntryDetailsRest.PARENT, method = "getParent"), @LinkRest(name = VocabularyEntryDetailsRest.CHILDREN, method = "getChildren") }) -public class VocabularyEntryDetailsRest extends RestAddressableModel { +public class VocabularyEntryDetailsRest extends BaseObjectRest { public static final String NAME = "vocabularyEntryDetail"; public static final String PARENT = "parent"; public static final String CHILDREN = "children"; - private String id; private String display; private String value; private Map otherInformation; @@ -36,14 +35,6 @@ public class VocabularyEntryDetailsRest extends RestAddressableModel { @JsonIgnore private String vocabularyName; - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - public String getDisplay() { return display; } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index 7af95646e8..a50ccea072 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest; +import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -40,15 +41,18 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + idAuthority)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.id", is("srsc:SCB110"))) - .andExpect(jsonPath("$.value", - is("Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology"))) - .andExpect(jsonPath("$.display", is("Religion/Theology"))) + .andExpect(jsonPath("$", + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB110", "Religion/Theology", + "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology"))) .andExpect(jsonPath("$.selectable", is(true))) .andExpect(jsonPath("$.otherInformation.id", is("SCB110"))) .andExpect(jsonPath("$.otherInformation.note", is("Religionsvetenskap/Teologi"))) .andExpect(jsonPath("$.otherInformation.parent", - is("Research Subject Categories::HUMANITIES and RELIGION"))); + is("Research Subject Categories::HUMANITIES and RELIGION"))) + .andExpect(jsonPath("$._links.parent.href", + endsWith("api/submission/vocabularyEntryDetails/srsc:SCB110/parent"))) + .andExpect(jsonPath("$._links.children.href", + endsWith("api/submission/vocabularyEntryDetails/srsc:SCB110/children"))); } @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDetailsMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDetailsMatcher.java index 67747cff66..8eb2cba3c4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDetailsMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/VocabularyEntryDetailsMatcher.java @@ -25,12 +25,12 @@ public class VocabularyEntryDetailsMatcher { public static Matcher matchAuthorityEntry(String id, String display, String value) { return allOf( matchProperties(id, display, value), - matchLinks()); + matchLinks(id)); } - public static Matcher matchLinks() { + public static Matcher matchLinks(String id) { return allOf( - hasJsonPath("$._links.self.href", containsString("api/submission/vocabularyEntryDetails/"))); + hasJsonPath("$._links.self.href", containsString("api/submission/vocabularyEntryDetails/" + id))); } private static Matcher matchProperties(String id, String display, String value) { From e4c98b4742309511eb96b2ed844a928508ba7dc0 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 1 Jul 2020 21:34:09 +0200 Subject: [PATCH 49/59] Fixed issue with DSpaceControlledVocabulary getBestMatch --- .../dspace/content/authority/DSpaceControlledVocabulary.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index c729c547c7..ce2237dd93 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -190,7 +190,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera String xpathExpression = ""; String[] textHierarchy = text.split(hierarchyDelimiter, -1); for (int i = 0; i < textHierarchy.length; i++) { - xpathExpression += String.format(labelTemplate, textHierarchy[i].replaceAll("'", "'").toLowerCase()); + xpathExpression += String.format(labelTemplate, textHierarchy[i].replaceAll("'", "'")); } XPath xpath = XPathFactory.newInstance().newXPath(); List choices = new ArrayList(); From 5a0844429a8704826e4bd7d2c2454120b5765d9a Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Wed, 1 Jul 2020 23:12:34 +0200 Subject: [PATCH 50/59] Add missing javadocs --- .../org/dspace/content/authority/Choice.java | 5 +++- .../content/authority/ChoiceAuthority.java | 27 +++++++++++++++++++ .../authority/HierarchicalAuthority.java | 5 ++++ .../service/ChoiceAuthorityService.java | 4 ++- .../dspace/app/rest/utils/AuthorityUtils.java | 12 +++++++++ 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java index ff09e7553a..697642798b 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java @@ -34,7 +34,10 @@ public class Choice { public String value = null; /** - * A boolean representing if choice entry value can selected + * A boolean representing if choice entry value can selected (usually true). + * Hierarchical authority can flag some choice as not selectable to force the + * use to choice a more detailed terms in the tree, such a leaf or a deeper + * branch */ public boolean selectable = true; diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java index a3bfeb9e83..ff196e2b79 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java @@ -88,18 +88,45 @@ public interface ChoiceAuthority { return getLabel(key, locale); } + /** + * Get a map of additional information related to the specified key in the + * authority. + * + * @param key the key of the entry + * @param locale explicit localization key if available, or null + * @return a map of additional information related to the key + */ default Map getExtra(String key, String locale) { return new HashMap(); } + /** + * Return true for hierarchical authorities + * + * @return true if hierarchical, default false + */ default boolean isHierarchical() { return false; } + /** + * Scrollable authorities allows the scroll of the entries without applying + * filter/query to the + * {@link #getMatches(String, String, Collection, int, int, String)} + * + * @return true if scrollable, default false + */ default boolean isScrollable() { return false; } + /** + * Hierarchical authority can provide an hint for the UI about how many levels + * preload to improve the UX. It provides a valid default for hierarchical + * authorities + * + * @return 0 if hierarchical, null otherwise + */ default Integer getPreloadLevel() { return isHierarchical() ? 0 : null; } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java index 279f62d22f..ac6c59e9d6 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java @@ -70,8 +70,13 @@ public interface HierarchicalAuthority extends ChoiceAuthority { */ public Choice getParentChoice(String authorityName, String vocabularyId, String locale); + /** + * Provides an hint for the UI to preload some levels to improve the UX. It + * usually mean that these preloaded level will be shown expanded by default + */ public Integer getPreloadLevel(); + @Override default boolean isHierarchical() { return true; } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java index bfcee338b9..335a837ee9 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java @@ -208,7 +208,9 @@ public interface ChoiceAuthorityService { public Choices getTopChoices(String authorityName, int start, int limit, String locale); /** - * + * Return the direct parent of an entry identified by its id in an hierarchical + * authority. + * * @param authorityName authority name * @param vocabularyId child id * @param locale explicit localization key if available, or null diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java index 22e1ff6101..fc54fe194e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java @@ -76,6 +76,18 @@ public class AuthorityUtils { return entry; } + /** + * This utility method is currently a workaround to enrich the REST object with + * information from the parent vocabulary that is not referenced by the Choice + * model + * + * @param choice the dspace-api choice to expose as vocabulary entry + * @param authorityName the name of the vocabulary + * @param storeAuthority true if the entry id should be exposed as + * an authority for storing it in the metadatavalue + * @param projection the rest projection to apply + * @return the vocabulary entry rest reppresentation of the provided choice + */ public VocabularyEntryRest convertEntry(Choice choice, String authorityName, boolean storeAuthority, Projection projection) { if (choice == null) { From 18bc3e1fcba23d00c3d113e7eb085d12f59d40ad Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 2 Jul 2020 19:54:37 +0200 Subject: [PATCH 51/59] Refactor the ChoiceAuthority to remove the unnecessary dependency from the field and collection --- .../content/authority/ChoiceAuthority.java | 36 +++-- .../authority/ChoiceAuthorityServiceImpl.java | 12 +- .../content/authority/DCInputAuthority.java | 11 +- .../authority/DSpaceControlledVocabulary.java | 12 +- .../content/authority/SampleAuthority.java | 18 ++- .../content/authority/SolrAuthority.java | 43 +++++- .../content/authority/TestAuthority.java | 18 ++- .../dspace/core/LegacyPluginServiceImpl.java | 4 +- .../java/org/dspace/core/NameAwarePlugin.java | 42 ++++++ .../java/org/dspace/core/SelfNamedPlugin.java | 25 +--- .../DSpaceControlledVocabularyTest.java | 3 +- .../VocabularyEntryLinkRepository.java | 45 ++---- .../app/rest/VocabularyRestRepositoryIT.java | 135 +----------------- 13 files changed, 179 insertions(+), 225 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/core/NameAwarePlugin.java diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java index ff196e2b79..1ea5f18ac2 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java @@ -10,7 +10,7 @@ package org.dspace.content.authority; import java.util.HashMap; import java.util.Map; -import org.dspace.content.Collection; +import org.dspace.core.NameAwarePlugin; /** * Plugin interface that supplies an authority control mechanism for @@ -20,7 +20,7 @@ import org.dspace.content.Collection; * @see ChoiceAuthorityServiceImpl * @see MetadataAuthorityServiceImpl */ -public interface ChoiceAuthority { +public interface ChoiceAuthority extends NameAwarePlugin { /** * Get all values from the authority that match the preferred value. * Note that the offering was entered by the user and may contain @@ -35,15 +35,13 @@ public interface ChoiceAuthority { * defaultSelected index in the Choices instance to the choice, if any, * that matches the value. * - * @param field being matched for * @param text user's value to match - * @param collection database ID of Collection for context (owner of Item) * @param start choice at which to start, 0 is first. * @param limit maximum number of choices to return, 0 for no limit. * @param locale explicit localization key if available, or null * @return a Choices object (never null). */ - public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale); + public Choices getMatches(String text, int start, int limit, String locale); /** * Get the single "best" match (if any) of a value in the authority @@ -54,13 +52,11 @@ public interface ChoiceAuthority { * This call is typically used in non-interactive metadata ingest * where there is no interactive agent to choose from among options. * - * @param field being matched for * @param text user's value to match - * @param collection database ID of Collection for context (owner of Item) * @param locale explicit localization key if available, or null * @return a Choices object (never null) with 1 or 0 values. */ - public Choices getBestMatch(String field, String text, Collection collection, String locale); + public Choices getBestMatch(String field, String locale); /** * Get the canonical user-visible "label" (i.e. short descriptive text) @@ -131,6 +127,17 @@ public interface ChoiceAuthority { return isHierarchical() ? 0 : null; } + /** + * Build the preferred choice associated with the authKey. The default + * implementation delegate the creato to the {@link #getLabel(String, String)} + * {@link #getValue(String, String)} and {@link #getExtra(String, String)} + * methods but can be directly overriden for better efficiency or special + * scenario + * + * @param authKey authority key known to this authority. + * @param locale explicit localization key if available, or null + * @return the preferred choice for this authKey and locale + */ default public Choice getChoice(String authKey, String locale) { Choice result = new Choice(); result.authority = authKey; @@ -139,4 +146,17 @@ public interface ChoiceAuthority { result.extras.putAll(getExtra(authKey, locale)); return result; } + + /** + * Provide a recommendation to store the authority in the metadata value if + * available in the in the provided choice(s). Usually ChoiceAuthority should + * recommend that so the default is true and it only need to be implemented in + * the unusual scenario + * + * @return true if the authority provided in any choice of this + * authority should be stored in the metadata value + */ + default public boolean storeAuthority() { + return true; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index 5e15d4d74a..3b1a6a7654 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -88,9 +88,9 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService @Autowired(required = true) protected PluginService pluginService; - private final String CHOICES_PLUGIN_PREFIX = "choices.plugin."; - private final String CHOICES_PRESENTATION_PREFIX = "choices.presentation."; - private final String CHOICES_CLOSED_PREFIX = "choices.closed."; + final static String CHOICES_PLUGIN_PREFIX = "choices.plugin."; + final static String CHOICES_PRESENTATION_PREFIX = "choices.presentation."; + final static String CHOICES_CLOSED_PREFIX = "choices.closed."; protected ChoiceAuthorityServiceImpl() { } @@ -152,7 +152,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService "No choices plugin was configured for field \"" + fieldKey + "\", collection=" + collection.getID().toString() + "."); } - return ma.getMatches(fieldKey, query, collection, start, limit, locale); + return ma.getMatches(query, start, limit, locale); } @@ -168,7 +168,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService if (externalInput && ma instanceof SolrAuthority) { ((SolrAuthority) ma).addExternalResultsInNextMatches(); } - return ma.getMatches(fieldKey, query, collection, start, limit, locale); + return ma.getMatches(query, start, limit, locale); } @Override @@ -180,7 +180,7 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService "No choices plugin was configured for field \"" + fieldKey + "\", collection=" + collection.getID().toString() + "."); } - return ma.getBestMatch(fieldKey, query, collection, locale); + return ma.getBestMatch(query, locale); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index 5b3df48002..d3c51f9dc7 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -17,7 +17,6 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.util.DCInputsReader; import org.dspace.app.util.DCInputsReaderException; -import org.dspace.content.Collection; import org.dspace.core.SelfNamedPlugin; /** @@ -55,6 +54,12 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority super(); } + @Override + public boolean storeAuthority() { + // For backward compatibility value pairs don't store authority in + // the metadatavalue + return false; + } public static String[] getPluginNames() { if (pluginNames == null) { initPluginNames(); @@ -104,7 +109,7 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority @Override - public Choices getMatches(String field, String query, Collection collection, int start, int limit, String locale) { + public Choices getMatches(String query, int start, int limit, String locale) { init(); int dflt = -1; @@ -126,7 +131,7 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority } @Override - public Choices getBestMatch(String field, String text, Collection collection, String locale) { + public Choices getBestMatch(String text, String locale) { init(); for (int i = 0; i < values.length; ++i) { if (text.equalsIgnoreCase(values[i])) { diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index ce2237dd93..0b3c82e218 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -21,7 +21,6 @@ import javax.xml.xpath.XPathFactory; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; -import org.dspace.content.Collection; import org.dspace.core.SelfNamedPlugin; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; @@ -79,6 +78,13 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera super(); } + @Override + public boolean storeAuthority() { + // For backward compatibility controlled vocabularies don't store the node id in + // the metadatavalue + return false; + } + public static String[] getPluginNames() { if (pluginNames == null) { initPluginNames(); @@ -160,7 +166,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera } @Override - public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale) { + public Choices getMatches(String text, int start, int limit, String locale) { init(); log.debug("Getting matches for '" + text + "'"); String xpathExpression = ""; @@ -184,7 +190,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera } @Override - public Choices getBestMatch(String field, String text, Collection collection, String locale) { + public Choices getBestMatch(String text, String locale) { init(); log.debug("Getting best matches for '" + text + "'"); String xpathExpression = ""; diff --git a/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java index 0aaf8e9003..e6cc9b9d44 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/SampleAuthority.java @@ -7,13 +7,13 @@ */ package org.dspace.content.authority; -import org.dspace.content.Collection; - /** * This is a *very* stupid test fixture for authority control, and also * serves as a trivial example of an authority plugin implementation. */ public class SampleAuthority implements ChoiceAuthority { + private String pluginInstanceName; + protected static String values[] = { "sun", "mon", @@ -35,7 +35,7 @@ public class SampleAuthority implements ChoiceAuthority { }; @Override - public Choices getMatches(String field, String query, Collection collection, int start, int limit, String locale) { + public Choices getMatches(String query, int start, int limit, String locale) { int dflt = -1; Choice v[] = new Choice[values.length]; for (int i = 0; i < values.length; ++i) { @@ -48,7 +48,7 @@ public class SampleAuthority implements ChoiceAuthority { } @Override - public Choices getBestMatch(String field, String text, Collection collection, String locale) { + public Choices getBestMatch(String text, String locale) { for (int i = 0; i < values.length; ++i) { if (text.equalsIgnoreCase(values[i])) { Choice v[] = new Choice[1]; @@ -63,4 +63,14 @@ public class SampleAuthority implements ChoiceAuthority { public String getLabel(String key, String locale) { return labels[Integer.parseInt(key)]; } + + @Override + public String getPluginInstanceName() { + return pluginInstanceName; + } + + @Override + public void setPluginInstanceName(String name) { + this.pluginInstanceName = name; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java index efc910a761..c93e6db786 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/SolrAuthority.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; @@ -24,8 +25,9 @@ import org.dspace.authority.AuthorityValue; import org.dspace.authority.SolrAuthorityInterface; import org.dspace.authority.factory.AuthorityServiceFactory; import org.dspace.authority.service.AuthorityValueService; -import org.dspace.content.Collection; import org.dspace.core.ConfigurationManager; +import org.dspace.core.NameAwarePlugin; +import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; /** @@ -35,7 +37,14 @@ import org.dspace.services.factory.DSpaceServicesFactory; * @author Mark Diggory (markd at atmire dot com) */ public class SolrAuthority implements ChoiceAuthority { + /** the name assigned to the specific instance by the PluginService, @see {@link NameAwarePlugin} **/ + private String authorityName; + /** + * the metadata managed by the plugin instance, derived from its authority name + * in the form schema_element_qualifier + */ + private String field; protected SolrAuthorityInterface source = DSpaceServicesFactory.getInstance().getServiceManager() .getServiceByName("AuthoritySource", SolrAuthorityInterface.class); @@ -45,8 +54,9 @@ public class SolrAuthority implements ChoiceAuthority { protected boolean externalResults = false; protected final AuthorityValueService authorityValueService = AuthorityServiceFactory.getInstance() .getAuthorityValueService(); - - public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale, + protected final ConfigurationService configurationService = DSpaceServicesFactory.getInstance() + .getConfigurationService(); + public Choices getMatches(String text, int start, int limit, String locale, boolean bestMatch) { if (limit == 0) { limit = 10; @@ -193,13 +203,13 @@ public class SolrAuthority implements ChoiceAuthority { } @Override - public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale) { - return getMatches(field, text, collection, start, limit, locale, true); + public Choices getMatches(String text, int start, int limit, String locale) { + return getMatches(text, start, limit, locale, true); } @Override - public Choices getBestMatch(String field, String text, Collection collection, String locale) { - Choices matches = getMatches(field, text, collection, 0, 1, locale, false); + public Choices getBestMatch(String text, String locale) { + Choices matches = getMatches(text, 0, 1, locale, false); if (matches.values.length != 0 && !matches.values[0].value.equalsIgnoreCase(text)) { matches = new Choices(false); } @@ -276,4 +286,23 @@ public class SolrAuthority implements ChoiceAuthority { public void addExternalResultsInNextMatches() { this.externalResults = true; } + + @Override + public void setPluginInstanceName(String name) { + authorityName = name; + for (Entry conf : configurationService.getProperties().entrySet()) { + if (StringUtils.startsWith((String) conf.getKey(), ChoiceAuthorityServiceImpl.CHOICES_PLUGIN_PREFIX) + && StringUtils.equals((String) conf.getValue(), authorityName)) { + field = ((String) conf.getKey()).substring(ChoiceAuthorityServiceImpl.CHOICES_PLUGIN_PREFIX.length()) + .replace(".", "_"); + // exit the look immediately as we have found it + break; + } + } + } + + @Override + public String getPluginInstanceName() { + return authorityName; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java index 1bb3fa5450..15c000e978 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/TestAuthority.java @@ -11,7 +11,6 @@ import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.StringUtils; -import org.dspace.content.Collection; /** * This is a *very* stupid test fixture for authority control with AuthorityVariantsSupport. @@ -19,6 +18,7 @@ import org.dspace.content.Collection; * @author Andrea Bollini (CILEA) */ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport { + private String pluginInstanceName; @Override public List getVariants(String key, String locale) { @@ -33,8 +33,7 @@ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport } @Override - public Choices getMatches(String field, String text, Collection collection, - int start, int limit, String locale) { + public Choices getMatches(String text, int start, int limit, String locale) { Choices choices = new Choices(false); if (StringUtils.isNotBlank(text)) { @@ -52,8 +51,7 @@ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport } @Override - public Choices getBestMatch(String field, String text, Collection collection, - String locale) { + public Choices getBestMatch(String text, String locale) { Choices choices = new Choices(false); if (StringUtils.isNotBlank(text)) { @@ -76,4 +74,14 @@ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport } return "Unknown"; } + + @Override + public String getPluginInstanceName() { + return pluginInstanceName; + } + + @Override + public void setPluginInstanceName(String name) { + this.pluginInstanceName = name; + } } diff --git a/dspace-api/src/main/java/org/dspace/core/LegacyPluginServiceImpl.java b/dspace-api/src/main/java/org/dspace/core/LegacyPluginServiceImpl.java index f8291dc977..ea8cdc1403 100644 --- a/dspace-api/src/main/java/org/dspace/core/LegacyPluginServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/core/LegacyPluginServiceImpl.java @@ -345,8 +345,8 @@ public class LegacyPluginServiceImpl implements PluginService { " for interface=" + iname + " pluginName=" + name); Object result = pluginClass.newInstance(); - if (result instanceof SelfNamedPlugin) { - ((SelfNamedPlugin) result).setPluginInstanceName(name); + if (result instanceof NameAwarePlugin) { + ((NameAwarePlugin) result).setPluginInstanceName(name); } return result; } diff --git a/dspace-api/src/main/java/org/dspace/core/NameAwarePlugin.java b/dspace-api/src/main/java/org/dspace/core/NameAwarePlugin.java new file mode 100644 index 0000000000..6c562ea04c --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/core/NameAwarePlugin.java @@ -0,0 +1,42 @@ +/** + * 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.core; + +/** + * This is the interface that should be implemented by all the named plugin that + * like to be aware of their name + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + * @version $Revision$ + * @see org.dspace.core.service.PluginService + */ +public interface NameAwarePlugin { + + /** + * Get the instance's particular name. + * Returns the name by which the class was chosen when + * this instance was created. Only works for instances created + * by PluginService, or if someone remembers to call setPluginName. + *

+ * Useful when the implementation class wants to be configured differently + * when it is invoked under different names. + * + * @return name or null if not available. + */ + public String getPluginInstanceName(); + + /** + * Set the name under which this plugin was instantiated. + * Not to be invoked by application code, it is + * called automatically by PluginService.getNamedPlugin() + * when the plugin is instantiated. + * + * @param name -- name used to select this class. + */ + public void setPluginInstanceName(String name); +} diff --git a/dspace-api/src/main/java/org/dspace/core/SelfNamedPlugin.java b/dspace-api/src/main/java/org/dspace/core/SelfNamedPlugin.java index 2bdcf830e7..680fa15c80 100644 --- a/dspace-api/src/main/java/org/dspace/core/SelfNamedPlugin.java +++ b/dspace-api/src/main/java/org/dspace/core/SelfNamedPlugin.java @@ -28,7 +28,7 @@ package org.dspace.core; * @version $Revision$ * @see org.dspace.core.service.PluginService */ -public abstract class SelfNamedPlugin { +public abstract class SelfNamedPlugin implements NameAwarePlugin { // the specific alias used to find the class that created this instance. private String myName = null; @@ -52,30 +52,13 @@ public abstract class SelfNamedPlugin { return null; } - /** - * Get an instance's particular name. - * Returns the name by which the class was chosen when - * this instance was created. Only works for instances created - * by PluginService, or if someone remembers to call setPluginName. - *

- * Useful when the implementation class wants to be configured differently - * when it is invoked under different names. - * - * @return name or null if not available. - */ + @Override public String getPluginInstanceName() { return myName; } - /** - * Set the name under which this plugin was instantiated. - * Not to be invoked by application code, it is - * called automatically by PluginService.getNamedPlugin() - * when the plugin is instantiated. - * - * @param name -- name used to select this class. - */ - protected void setPluginInstanceName(String name) { + @Override + public void setPluginInstanceName(String name) { myName = name; } } diff --git a/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java b/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java index 84306ac034..77cf105dd4 100644 --- a/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java +++ b/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java @@ -86,8 +86,7 @@ public class DSpaceControlledVocabularyTest extends AbstractDSpaceTest { DSpaceControlledVocabulary instance = (DSpaceControlledVocabulary) CoreServiceFactory.getInstance().getPluginService().getNamedPlugin(Class.forName(PLUGIN_INTERFACE), "farm"); assertNotNull(instance); - Choices result = instance.getMatches(field, text, collection, start, - limit, locale); + Choices result = instance.getMatches(text, start, limit, locale); assertEquals("the farm::north 40", result.values[0].value); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index e18fc35d84..1f24592022 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -7,10 +7,8 @@ */ package org.dspace.app.rest.repository; -import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import java.util.UUID; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; @@ -21,8 +19,8 @@ import org.dspace.app.rest.model.VocabularyEntryRest; import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.AuthorityUtils; -import org.dspace.content.Collection; import org.dspace.content.authority.Choice; +import org.dspace.content.authority.ChoiceAuthority; import org.dspace.content.authority.Choices; import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.content.service.CollectionService; @@ -31,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -59,42 +58,26 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository String exact = request == null ? null : request.getParameter("exact"); String filter = request == null ? null : request.getParameter("filter"); String entryID = request == null ? null : request.getParameter("entryID"); - String metadata = request == null ? null : request.getParameter("metadata"); - String uuidCollectìon = request == null ? null : request.getParameter("collection"); - - if (StringUtils.isEmpty(metadata) || StringUtils.isEmpty(uuidCollectìon)) { - throw new IllegalArgumentException("the metadata and collection parameters are both required"); - } if (StringUtils.isNotBlank(filter) && StringUtils.isNotBlank(entryID)) { throw new IllegalArgumentException("required only one of the parameters: filter or entryID"); } - Collection collection = null; - if (StringUtils.isNotBlank(uuidCollectìon)) { - try { - collection = cs.find(context, UUID.fromString(uuidCollectìon)); - } catch (SQLException e) { - throw new UnprocessableEntityException(uuidCollectìon + " is not a valid collection"); - } - } - - // validate the parameters - String[] tokens = org.dspace.core.Utils.tokenize(metadata); - String vocName = cas.getChoiceAuthorityName(tokens[0], tokens[1], tokens[2], collection); - if (!StringUtils.equals(name, vocName)) { - throw new UnprocessableEntityException("The vocabulary " + name + " is not allowed for the metadata " - + metadata + " and collection " + uuidCollectìon); - } Pageable pageable = utils.getPageable(optionalPageable); List results = new ArrayList<>(); - String fieldKey = org.dspace.core.Utils.standardize(tokens[0], tokens[1], tokens[2], "_"); - + ChoiceAuthority ca = cas.getChoiceAuthorityByAuthorityName(name); + if (ca == null) { + throw new ResourceNotFoundException("the vocabulary named " + name + "doesn't exist"); + } + if (!ca.isScrollable() && StringUtils.isBlank(filter) && StringUtils.isBlank(entryID)) { + throw new UnprocessableEntityException( + "one of filter or entryID parameter is required for not scrollable vocabularies"); + } Choices choices = null; if (BooleanUtils.toBoolean(exact)) { - choices = cas.getBestMatch(fieldKey, filter, collection, context.getCurrentLocale().toString()); + choices = ca.getBestMatch(filter, context.getCurrentLocale().toString()); } else if (StringUtils.isNotBlank(entryID)) { - Choice choice = cas.getChoiceAuthorityByAuthorityName(vocName).getChoice(entryID, + Choice choice = ca.getChoice(entryID, context.getCurrentLocale().toString()); if (choice != null) { choices = new Choices(new Choice[] {choice}, 0, 1, Choices.CF_ACCEPTED, false); @@ -102,10 +85,10 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository choices = new Choices(false); } } else { - choices = cas.getMatches(fieldKey, filter, collection, Math.toIntExact(pageable.getOffset()), + choices = ca.getMatches(filter, Math.toIntExact(pageable.getOffset()), pageable.getPageSize(), context.getCurrentLocale().toString()); } - boolean storeAuthority = cas.storeAuthority(fieldKey, collection); + boolean storeAuthority = ca.storeAuthority(); for (Choice value : choices.values) { results.add(authorityUtils.convertEntry(value, name, storeAuthority, projection)); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index 898e63cf78..2b1de5ae21 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -144,18 +144,9 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void correctSrscQueryTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); - String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( get("/api/submission/vocabularies/srsc/entries") - .param("metadata", "dc.subject") - .param("collection", collection.getID().toString()) .param("filter", "Research") .param("size", "2")) .andExpect(status().isOk()) @@ -170,70 +161,19 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(jsonPath("$.page.size", Matchers.is(2))); } - @Test - public void controlledVocabularyEntriesWrongCollectionUUIDTest() throws Exception { - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") - .param("metadata", "dc.subject") - .param("collection", UUID.randomUUID().toString()) - .param("filter", "Research")) - .andExpect(status().isUnprocessableEntity()); - } - - @Test - public void controlledVocabularyEntriesMissingCollectionUUID_Test() throws Exception { - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") - .param("metadata", "dc.subject") - .param("filter", "Research")) - .andExpect(status().isBadRequest()); - } - - @Test - public void controlledVocabularyEntriesWrongMetadataTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") - .param("metadata", "dc.type") - .param("collection", collection.getID().toString()) - .param("filter", "Research")) - .andExpect(status().isUnprocessableEntity()); - } - @Test public void notScrollableVocabularyRequiredQueryTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") - .param("metadata", "dc.not.existing") - .param("collection", collection.getID().toString())) + getClient(token).perform(get("/api/submission/vocabularies/srsc/entries")) .andExpect(status().isUnprocessableEntity()); } @Test public void noResultsSrscQueryTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); - String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( get("/api/submission/vocabularies/srsc/entries") .param("metadata", "dc.subject") - .param("collection", collection.getID().toString()) .param("filter", "Research2") .param("size", "1000")) .andExpect(status().isOk()) @@ -242,17 +182,9 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void vocabularyEntriesCommonTypesWithPaginationTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); - String token = getAuthToken(admin.getEmail(), password); getClient(token) - .perform(get("/api/submission/vocabularies/common_types/entries").param("metadata", "dc.type") - .param("collection", collection.getID().toString()).param("size", "2")) + .perform(get("/api/submission/vocabularies/common_types/entries").param("size", "2")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder( VocabularyMatcher.matchVocabularyEntry("Animation", "Animation", "vocabularyEntry"), @@ -265,8 +197,6 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes //second page getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") - .param("metadata", "dc.type") - .param("collection", collection.getID().toString()) .param("size", "2") .param("page", "1")) .andExpect(status().isOk()) @@ -282,17 +212,8 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void vocabularyEntriesCommon_typesWithQueryTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); - String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") - .param("metadata", "dc.type") - .param("collection", collection.getID().toString()) .param("filter", "Book") .param("size", "2")) .andExpect(status().isOk()) @@ -307,17 +228,9 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void correctSolrQueryTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( get("/api/submission/vocabularies/SolrAuthorAuthority/entries") - .param("metadata", "dc.contributor.author") - .param("collection", collection.getID().toString()) .param("filter", "Shirasaka") .param("size", "1000")) .andExpect(status().isOk()) @@ -330,17 +243,9 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void noResultsSolrQueryTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform( get("/api/submission/vocabularies/SolrAuthorAuthority/entries") - .param("metadata", "dc.contributor.author") - .param("collection", collection.getID().toString()) .param("filter", "Smith") .param("size", "1000")) .andExpect(status().isOk()) @@ -350,7 +255,6 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void findByMetadataAndCollectionTest() throws Exception { context.turnOffAuthorisationSystem(); - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) .withName("Test collection") .build(); @@ -408,17 +312,8 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes @Test public void linkedEntitiesWithExactParamTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); - String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularies/common_types/entries") - .param("metadata", "dc.type") - .param("collection", collection.getID().toString()) .param("filter", "Animation") .param("exact", "true")) .andExpect(status().isOk()) @@ -428,36 +323,10 @@ public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); } - @Test - public void linkedEntitiesWrongMetataForAuthorityTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); - - String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") - .param("metadata", "dc.type") - .param("collection", collection.getID().toString()) - .param("filter", "Animation")) - .andExpect(status().isUnprocessableEntity()); - } - @Test public void linkedEntitiesWithFilterAndEntryIdTest() throws Exception { - context.turnOffAuthorisationSystem(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Test collection") - .build(); - context.restoreAuthSystemState(); - String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(get("/api/submission/vocabularies/srsc/entries") - .param("metadata", "dc.subject") - .param("collection", collection.getID().toString()) .param("filter", "Research") .param("entryID", "VR131402")) .andExpect(status().isBadRequest()); From 675c975d2940fe48c15c71044b108668c5a86ea1 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 2 Jul 2020 21:11:56 +0200 Subject: [PATCH 52/59] Use a more explicit name for the storeAuthorityInMetadata method --- .../main/java/org/dspace/content/authority/ChoiceAuthority.java | 2 +- .../java/org/dspace/content/authority/DCInputAuthority.java | 2 +- .../dspace/content/authority/DSpaceControlledVocabulary.java | 2 +- .../app/rest/repository/VocabularyEntryLinkRepository.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java index 1ea5f18ac2..8977970e1f 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java @@ -156,7 +156,7 @@ public interface ChoiceAuthority extends NameAwarePlugin { * @return true if the authority provided in any choice of this * authority should be stored in the metadata value */ - default public boolean storeAuthority() { + default public boolean storeAuthorityInMetadata() { return true; } } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index d3c51f9dc7..f46494706b 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -55,7 +55,7 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority } @Override - public boolean storeAuthority() { + public boolean storeAuthorityInMetadata() { // For backward compatibility value pairs don't store authority in // the metadatavalue return false; diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index 0b3c82e218..e933eee9bb 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -79,7 +79,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera } @Override - public boolean storeAuthority() { + public boolean storeAuthorityInMetadata() { // For backward compatibility controlled vocabularies don't store the node id in // the metadatavalue return false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index 1f24592022..7e4f989b0a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -88,7 +88,7 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository choices = ca.getMatches(filter, Math.toIntExact(pageable.getOffset()), pageable.getPageSize(), context.getCurrentLocale().toString()); } - boolean storeAuthority = ca.storeAuthority(); + boolean storeAuthority = ca.storeAuthorityInMetadata(); for (Choice value : choices.values) { results.add(authorityUtils.convertEntry(value, name, storeAuthority, projection)); } From 8fd328855a69c711ea48cecaff7c911788dc15ff Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Fri, 17 Jul 2020 17:07:20 +0200 Subject: [PATCH 53/59] code cleanup and fix for project in vocabulary entry details --- .../org/dspace/content/authority/Choice.java | 26 ++++ .../content/authority/ChoiceAuthority.java | 2 +- .../authority/ChoiceAuthorityServiceImpl.java | 123 +++++++++++------- .../authority/DSpaceControlledVocabulary.java | 65 ++++----- .../authority/HierarchicalAuthority.java | 3 +- .../service/ChoiceAuthorityService.java | 6 + ...aryEntryDetailsChildrenLinkRepository.java | 4 +- ...ularyEntryDetailsParentLinkRepository.java | 4 +- .../VocabularyEntryLinkRepository.java | 2 +- .../app/rest/VocabularyEntryDetailsIT.java | 106 +++++++++++++-- 10 files changed, 238 insertions(+), 103 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java index 697642798b..6d73bdb5ea 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/Choice.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/Choice.java @@ -46,12 +46,28 @@ public class Choice { public Choice() { } + /** + * Minimal constructor for this data object. It assumes an empty map of extras + * information and a selected choice + * + * @param authority the authority key + * @param value the text value to store in the metadata + * @param label the value to display to the user + */ public Choice(String authority, String value, String label) { this.authority = authority; this.value = value; this.label = label; } + /** + * Constructor to quickly setup the data object for basic authorities. The choice is assumed to be selectable. + * + * @param authority the authority key + * @param value the text value to store in the metadata + * @param label the value to display to the user + * @param extras a key value map of extra information related to this choice + */ public Choice(String authority, String label, String value, Map extras) { this.authority = authority; this.label = label; @@ -59,6 +75,16 @@ public class Choice { this.extras = extras; } + /** + * Constructor for common need of Hierarchical authorities that want to + * explicitely set the selectable flag + * + * @param authority the authority key + * @param value the text value to store in the metadata + * @param label the value to display to the user + * @param selectable true if the choice can be selected, false if the a more + * accurate choice should be preferred + */ public Choice(String authority, String label, String value, boolean selectable) { this.authority = authority; this.label = label; diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java index 8977970e1f..750e761f3d 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthority.java @@ -56,7 +56,7 @@ public interface ChoiceAuthority extends NameAwarePlugin { * @param locale explicit localization key if available, or null * @return a Choices object (never null) with 1 or 0 values. */ - public Choices getBestMatch(String field, String locale); + public Choices getBestMatch(String text, String locale); /** * Get the canonical user-visible "label" (i.e. short descriptive text) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index 3b1a6a7654..7d4b2e0da6 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -129,7 +129,8 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService itemSubmissionConfigReader = new SubmissionConfigReader(); } catch (SubmissionConfigReaderException e) { // the system is in an illegal state as the submission definition is not valid - throw new IllegalStateException(e); + throw new IllegalStateException("Error reading the item submission configuration: " + e.getMessage(), + e); } loadChoiceAuthorityConfigurations(); initialized = true; @@ -316,66 +317,54 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService autoRegisterChoiceAuthorityFromInputReader(); } + /** + * This method will register all the authorities that are required due to the + * submission forms configuration. This includes authorities for value pairs and + * xml vocabularies + */ private void autoRegisterChoiceAuthorityFromInputReader() { try { List submissionConfigs = itemSubmissionConfigReader .getAllSubmissionConfigs(Integer.MAX_VALUE, 0); DCInputsReader dcInputsReader = new DCInputsReader(); + // loop over all the defined item submission configuration for (SubmissionConfig subCfg : submissionConfigs) { String submissionName = subCfg.getSubmissionName(); List inputsBySubmissionName = dcInputsReader.getInputsBySubmissionName(submissionName); + // loop over the submission forms configuration eventually associated with the submission panel for (DCInputSet dcinputSet : inputsBySubmissionName) { DCInput[][] dcinputs = dcinputSet.getFields(); for (DCInput[] dcrows : dcinputs) { for (DCInput dcinput : dcrows) { + // for each input in the form check if it is associated with a real value pairs + // or an xml vocabulary + String authorityName = null; if (StringUtils.isNotBlank(dcinput.getPairsType()) - || StringUtils.isNotBlank(dcinput.getVocabulary())) { - String authorityName = dcinput.getPairsType(); - if (StringUtils.isBlank(authorityName)) { - authorityName = dcinput.getVocabulary(); - } - if (!StringUtils.equals(dcinput.getInputType(), "qualdrop_value")) { - String fieldKey = makeFieldKey(dcinput.getSchema(), dcinput.getElement(), - dcinput.getQualifier()); - ChoiceAuthority ca = controller.get(authorityName); + && !StringUtils.equals(dcinput.getInputType(), "qualdrop_value")) { + authorityName = dcinput.getPairsType(); + } else if (StringUtils.isNotBlank(dcinput.getVocabulary())) { + authorityName = dcinput.getVocabulary(); + } + + // do we have an authority? + if (StringUtils.isNotBlank(authorityName)) { + String fieldKey = makeFieldKey(dcinput.getSchema(), dcinput.getElement(), + dcinput.getQualifier()); + ChoiceAuthority ca = controller.get(authorityName); + if (ca == null) { + ca = (ChoiceAuthority) pluginService + .getNamedPlugin(ChoiceAuthority.class, authorityName); if (ca == null) { - ca = (ChoiceAuthority) pluginService - .getNamedPlugin(ChoiceAuthority.class, authorityName); - if (ca == null) { - throw new IllegalStateException("Invalid configuration for " + fieldKey - + " in submission definition " + submissionName - + ", form definition " + dcinputSet.getFormName() - + " no named plugin found: " + authorityName); - } + throw new IllegalStateException("Invalid configuration for " + fieldKey + + " in submission definition " + submissionName + + ", form definition " + dcinputSet.getFormName() + + " no named plugin found: " + authorityName); } - - Map definition2authority; - if (controllerFormDefinitions.containsKey(fieldKey)) { - definition2authority = controllerFormDefinitions.get(fieldKey); - } else { - definition2authority = new HashMap(); - } - definition2authority.put(submissionName, ca); - controllerFormDefinitions.put(fieldKey, definition2authority); - - Map> authorityName2definitions; - if (authoritiesFormDefinitions.containsKey(authorityName)) { - authorityName2definitions = authoritiesFormDefinitions.get(authorityName); - } else { - authorityName2definitions = new HashMap>(); - } - - List fields; - if (authorityName2definitions.containsKey(submissionName)) { - fields = authorityName2definitions.get(submissionName); - } else { - fields = new ArrayList(); - } - fields.add(fieldKey); - authorityName2definitions.put(submissionName, fields); - authoritiesFormDefinitions.put(authorityName, authorityName2definitions); } + + addAuthorityToFormCacheMap(submissionName, fieldKey, ca); + addFormDetailsToAuthorityCacheMap(submissionName, authorityName, fieldKey); } } } @@ -387,6 +376,52 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } } + /** + * Add the form/field to the cache map keeping track of which form/field are + * associated with the specific authority name + * + * @param submissionName the form definition name + * @param authorityName the name of the authority plugin + * @param fieldKey the field key that use the authority + */ + private void addFormDetailsToAuthorityCacheMap(String submissionName, String authorityName, String fieldKey) { + Map> authorityName2definitions; + if (authoritiesFormDefinitions.containsKey(authorityName)) { + authorityName2definitions = authoritiesFormDefinitions.get(authorityName); + } else { + authorityName2definitions = new HashMap>(); + } + + List fields; + if (authorityName2definitions.containsKey(submissionName)) { + fields = authorityName2definitions.get(submissionName); + } else { + fields = new ArrayList(); + } + fields.add(fieldKey); + authorityName2definitions.put(submissionName, fields); + authoritiesFormDefinitions.put(authorityName, authorityName2definitions); + } + + /** + * Add the authority plugin to the cache map keeping track of which authority is + * used by a specific form/field + * + * @param submissionName the submission definition name + * @param fieldKey the field key that require the authority + * @param ca the authority plugin + */ + private void addAuthorityToFormCacheMap(String submissionName, String fieldKey, ChoiceAuthority ca) { + Map definition2authority; + if (controllerFormDefinitions.containsKey(fieldKey)) { + definition2authority = controllerFormDefinitions.get(fieldKey); + } else { + definition2authority = new HashMap(); + } + definition2authority.put(submissionName, ca); + controllerFormDefinitions.put(fieldKey, definition2authority); + } + /** * Return map of key to presentation * diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index e933eee9bb..00c74bea9d 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -62,7 +62,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera "'abcdefghijklmnopqrstuvwxyz'),'%s')]"; protected static String idTemplate = "//node[@id = '%s']"; protected static String labelTemplate = "//node[@label = '%s']"; - protected static String idParentTemplate = "//node[@id = '%s']/parent::isComposedBy"; + protected static String idParentTemplate = "//node[@id = '%s']/parent::isComposedBy/parent::node"; protected static String rootTemplate = "/node"; protected static String pluginNames[] = null; @@ -72,7 +72,6 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera protected Boolean storeHierarchy = true; protected String hierarchyDelimiter = "::"; protected Integer preloadLevel = 1; - protected String rootNodeId = ""; public DSpaceControlledVocabulary() { super(); @@ -132,17 +131,6 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera String filename = vocabulariesPath + vocabularyName + ".xml"; log.info("Loading " + filename); vocabulary = new InputSource(filename); - - XPath xpath = XPathFactory.newInstance().newXPath(); - try { - Node rootNode = (Node) xpath.evaluate(rootTemplate, vocabulary, XPathConstants.NODE); - Node idAttr = rootNode.getAttributes().getNamedItem("id"); - if (null != idAttr) { // 'id' is optional - rootNodeId = idAttr.getNodeValue(); - } - } catch (XPathExpressionException e) { - log.warn(e.getMessage(), e); - } } } @@ -251,11 +239,16 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera } @Override - public Choice getParentChoice(String authorityName, String parentId, String locale) { + public Choice getParentChoice(String authorityName, String childId, String locale) { init(); - String xpathExpression = String.format(idTemplate, parentId); - Choice choice = getParent(xpathExpression); - return choice; + try { + String xpathExpression = String.format(idParentTemplate, childId); + Choice choice = createChoiceFromNode(getNodeFromXPath(xpathExpression)); + return choice; + } catch (XPathExpressionException e) { + log.error(e.getMessage(), e); + return null; + } } @Override @@ -263,9 +256,21 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera return preloadLevel; } + private boolean isRootElement(Node node) { + if (node != null && node.getOwnerDocument().getDocumentElement().equals(node)) { + return true; + } + return false; + } + private Node getNode(String key) throws XPathExpressionException { init(); String xpathExpression = String.format(idTemplate, key); + Node node = getNodeFromXPath(xpathExpression); + return node; + } + + private Node getNodeFromXPath(String xpathExpression) throws XPathExpressionException { XPath xpath = XPathFactory.newInstance().newXPath(); Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); return node; @@ -386,11 +391,8 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera Node parentN = node.getParentNode(); if (parentN != null) { parentN = parentN.getParentNode(); - if (parentN != null) { - Node parentIdAttr = parentN.getAttributes().getNamedItem("id"); - if (null != parentIdAttr && !parentIdAttr.getNodeValue().equals(rootNodeId)) { - return buildString(parentN); - } + if (parentN != null && !isRootElement(parentN)) { + return buildString(parentN); } } return null; @@ -437,7 +439,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera } private Choice createChoiceFromNode(Node node) { - if (node != null) { + if (node != null && !isRootElement(node)) { Choice choice = new Choice(getAuthority(node), getLabel(node), getValue(node), isSelectable(node)); choice.extras = addOtherInformation(getParent(node), getNote(node),getChildren(node), getAuthority(node)); @@ -446,21 +448,4 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Hiera return null; } - private Choice getParent(String xpathExpression) { - Choice choice = null; - XPath xpath = XPathFactory.newInstance().newXPath(); - try { - Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); - if (node != null) { - Node parent = node.getParentNode().getParentNode(); - if (null != parent) { - choice = createChoiceFromNode(parent); - } - } - } catch (XPathExpressionException e) { - log.warn(e.getMessage(), e); - } - return choice; - } - } diff --git a/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java index ac6c59e9d6..c25b74d354 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/HierarchicalAuthority.java @@ -62,7 +62,8 @@ public interface HierarchicalAuthority extends ChoiceAuthority { public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale); /** - * + * It returns the parent choice in the hierarchy if any + * * @param authorityName authority name * @param vocabularyId user's value to match * @param locale explicit localization key if available, or null diff --git a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java index 335a837ee9..1cc5075d02 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/service/ChoiceAuthorityService.java @@ -165,6 +165,12 @@ public interface ChoiceAuthorityService { */ public List getVariants(MetadataValue metadataValue, Collection collection); + /** + * Return the ChoiceAuthority instance identified by the specified name + * + * @param authorityName the ChoiceAuthority instance name + * @return the ChoiceAuthority identified by the specified name + */ public ChoiceAuthority getChoiceAuthorityByAuthorityName(String authorityName); /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java index f9e8e5a7ad..044710d25b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java @@ -47,7 +47,7 @@ public class VocabularyEntryDetailsChildrenLinkRepository extends AbstractDSpace @PreAuthorize("hasAuthority('AUTHENTICATED')") public Page getChildren(@Nullable HttpServletRequest request, String name, - @Nullable Pageable pageable, Projection projection) { + @Nullable Pageable optionalPageable, Projection projection) { Context context = obtainContext(); String[] parts = StringUtils.split(name, ":", 2); @@ -56,7 +56,7 @@ public class VocabularyEntryDetailsChildrenLinkRepository extends AbstractDSpace } String vocabularyName = parts[0]; String id = parts[1]; - + Pageable pageable = utils.getPageable(optionalPageable); List results = new ArrayList(); ChoiceAuthority authority = choiceAuthorityService.getChoiceAuthorityByAuthorityName(vocabularyName); if (StringUtils.isNotBlank(id) && authority.isHierarchical()) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java index bebb0fdf85..379928d9cc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java @@ -42,7 +42,7 @@ public class VocabularyEntryDetailsParentLinkRepository extends AbstractDSpaceRe @PreAuthorize("hasAuthority('AUTHENTICATED')") public VocabularyEntryDetailsRest getParent(@Nullable HttpServletRequest request, String name, - @Nullable Pageable pageable, Projection projection) { + @Nullable Pageable optionalPageable, Projection projection) { Context context = obtainContext(); String[] parts = StringUtils.split(name, ":", 2); if (parts.length != 2) { @@ -53,7 +53,7 @@ public class VocabularyEntryDetailsParentLinkRepository extends AbstractDSpaceRe ChoiceAuthority authority = choiceAuthorityService.getChoiceAuthorityByAuthorityName(vocabularyName); Choice choice = null; - if (StringUtils.isNotBlank(id) && authority.isHierarchical()) { + if (StringUtils.isNotBlank(id) && authority != null && authority.isHierarchical()) { choice = choiceAuthorityService.getParentChoice(vocabularyName, id, context.getCurrentLocale().toString()); } else { throw new NotFoundException(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index 7e4f989b0a..9d75ef87c3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -60,7 +60,7 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository String entryID = request == null ? null : request.getParameter("entryID"); if (StringUtils.isNotBlank(filter) && StringUtils.isNotBlank(entryID)) { - throw new IllegalArgumentException("required only one of the parameters: filter or entryID"); + throw new IllegalArgumentException("the filter and entryID parameters are mutually exclusive"); } Pageable pageable = utils.getPageable(optionalPageable); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index a50ccea072..c867f8b614 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -17,6 +17,7 @@ import java.util.UUID; import org.dspace.app.rest.matcher.VocabularyEntryDetailsMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.Test; @@ -62,6 +63,23 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest .andExpect(status().isUnauthorized()); } + @Test + public void findOneNotFoundTest() throws Exception { + String idAuthority = "srsc:not-existing"; + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + idAuthority)) + .andExpect(status().isNotFound()); + + // try with a special id missing only the entry-id part + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:")) + .andExpect(status().isNotFound()); + + // try to retrieve the xml root that is not a entry itself + getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:ResearchSubjectCategories")) + .andExpect(status().isNotFound()); + + } + @Test public void srscSearchTopTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); @@ -305,16 +323,6 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest @Test public void findParentByChildTest() throws Exception { - String tokenEperson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB11/parent")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", is(VocabularyEntryDetailsMatcher.matchAuthorityEntry( - "srsc:ResearchSubjectCategories", "Research Subject Categories", "Research Subject Categories") - ))); - } - - @Test - public void findParentByChildSecondLevelTest() throws Exception { String tokenEperson = getAuthToken(eperson.getEmail(), password); getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB180/parent")) .andExpect(status().isOk()) @@ -338,10 +346,84 @@ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest } @Test - public void findParentRootChildTest() throws Exception { + public void findParentTopTest() throws Exception { String tokenEperson = getAuthToken(eperson.getEmail(), password); getClient(tokenEperson) - .perform(get("/api/submission/vocabularyEntryDetails/srsc:ResearchSubjectCategory/parent")) + .perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB11/parent")) .andExpect(status().isNoContent()); } + + @Test + public void srscProjectionTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform( + get("/api/submission/vocabularyEntryDetails/srsc:SCB110").param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.parent", + VocabularyEntryDetailsMatcher.matchAuthorityEntry( + "srsc:SCB11", "HUMANITIES and RELIGION", + "Research Subject Categories::HUMANITIES and RELIGION"))) + .andExpect(jsonPath("$._embedded.children._embedded.children", matchAllSrscSC110Children())) + .andExpect(jsonPath("$._embedded.children._embedded.children[*].otherInformation.parent", + Matchers.everyItem( + is("Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology")))); + + getClient(tokenAdmin).perform( + get("/api/submission/vocabularyEntryDetails/srsc:SCB110").param("embed", "children")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.children._embedded.children", matchAllSrscSC110Children())) + .andExpect(jsonPath("$._embedded.children._embedded.children[*].otherInformation.parent", + Matchers.everyItem( + is("Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology")))); + + getClient(tokenAdmin).perform( + get("/api/submission/vocabularyEntryDetails/srsc:SCB110").param("embed", "parent")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.parent", + VocabularyEntryDetailsMatcher.matchAuthorityEntry( + "srsc:SCB11", "HUMANITIES and RELIGION", + "Research Subject Categories::HUMANITIES and RELIGION"))); + } + + private Matcher> matchAllSrscSC110Children() { + return Matchers.containsInAnyOrder( + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110102", + "History of religion", + "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::History of religion"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110103", + "Church studies", + "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Church studies"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110104", + "Missionary studies", + "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Missionary studies"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110105", + "Systematic theology", + "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Systematic theology"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110106", + "Islamology", + "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Islamology"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110107", + "Faith and reason", + "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Faith and reason"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110108", + "Sociology of religion", + "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Sociology of religion"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110109", + "Psychology of religion", + "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Psychology of religion"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110110", + "Philosophy of religion", + "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Philosophy of religion"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110111", + "New Testament exegesis", + "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::New Testament exegesis"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110112", + "Old Testament exegesis", + "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Old Testament exegesis"), + VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110113", + "Dogmatics with symbolics", + "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Dogmatics with symbolics") + ); + } + } From 9863b1c6d53bc3002a3d246bcf40339853a2af4b Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Sat, 18 Jul 2020 16:07:04 +0200 Subject: [PATCH 54/59] Add link to the vocabularyEntryDetails search methods from the root --- .../VocabularyEntryDetailsRestRepository.java | 17 ++++++++++++++++- .../app/rest/VocabularyEntryDetailsIT.java | 13 +++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java index 620bb527b1..5ac17b3851 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java @@ -8,9 +8,11 @@ package org.dspace.app.rest.repository; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.DiscoverableEndpointsService; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.exception.LinkNotFoundException; @@ -24,10 +26,12 @@ import org.dspace.content.authority.ChoiceAuthority; import org.dspace.content.authority.Choices; import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.core.Context; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; +import org.springframework.hateoas.Link; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -37,7 +41,8 @@ import org.springframework.stereotype.Component; * @author Andrea Bollini (andrea.bollini at 4science.it) */ @Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.NAME) -public class VocabularyEntryDetailsRestRepository extends DSpaceRestRepository { +public class VocabularyEntryDetailsRestRepository extends DSpaceRestRepository + implements InitializingBean { @Autowired private ChoiceAuthorityService cas; @@ -45,6 +50,16 @@ public class VocabularyEntryDetailsRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index c867f8b614..e5eab1aa98 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest; +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -27,6 +28,18 @@ import org.junit.Test; * @author Mykhaylo Boychuk (4science.it) */ public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest { + @Test + public void discoverableNestedLinkTest() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links",Matchers.allOf( + hasJsonPath("$.vocabularyEntryDetails.href", + is("http://localhost/api/submission/vocabularyEntryDetails")), + hasJsonPath("$.vocabularyEntryDetails-search.href", + is("http://localhost/api/submission/vocabularyEntryDetails/search")) + ))); + } @Test public void findAllTest() throws Exception { From 769dd064d09fb90fe491dab8a397a040bc6ada4f Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Sat, 18 Jul 2020 16:08:45 +0200 Subject: [PATCH 55/59] Add support and test for locales on vocabularies --- .../authority/ChoiceAuthorityServiceImpl.java | 2 + .../content/authority/DCInputAuthority.java | 102 +++++++---- .../main/java/org/dspace/core/I18nUtil.java | 17 ++ .../dspaceFolder/config/submission-forms.xml | 64 +++++++ .../converter/SubmissionFormConverter.java | 3 +- .../VocabularyEntryDetailsRestRepository.java | 6 +- .../dspace/app/rest/utils/AuthorityUtils.java | 2 + .../app/rest/SubmissionFormsControllerIT.java | 160 ++++++++++++++++-- .../app/rest/VocabularyRestRepositoryIT.java | 62 ++++++- 9 files changed, 367 insertions(+), 51 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index 7d4b2e0da6..ad7de882c6 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -272,6 +272,8 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService public void clearCache() { controller.clear(); authorities.clear(); + presentation.clear(); + closed.clear(); controllerFormDefinitions.clear(); authoritiesFormDefinitions.clear(); itemSubmissionConfigReader = null; diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index f46494706b..d95528524d 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -9,14 +9,20 @@ package org.dspace.content.authority; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.util.DCInputsReader; import org.dspace.app.util.DCInputsReaderException; +import org.dspace.core.I18nUtil; import org.dspace.core.SelfNamedPlugin; /** @@ -44,10 +50,10 @@ import org.dspace.core.SelfNamedPlugin; public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority { private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DCInputAuthority.class); - private String values[] = null; - private String labels[] = null; + private Map values = null; + private Map labels = null; - private static DCInputsReader dci = null; + private static Map dcis = null; private static String pluginNames[] = null; public DCInputAuthority() { @@ -60,6 +66,10 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority // the metadatavalue return false; } + public static void reset() { + pluginNames = null; + } + public static String[] getPluginNames() { if (pluginNames == null) { initPluginNames(); @@ -69,20 +79,28 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority } private static synchronized void initPluginNames() { + Locale[] locales = I18nUtil.getSupportedLocales(); + Set names = new HashSet(); if (pluginNames == null) { try { - if (dci == null) { - dci = new DCInputsReader(); + dcis = new HashMap(); + for (Locale locale : locales) { + dcis.put(locale, new DCInputsReader(I18nUtil.getInputFormsFileName(locale))); + } + for (Locale l : locales) { + Iterator pi = dcis.get(l).getPairsNameIterator(); + while (pi.hasNext()) { + names.add((String) pi.next()); + } + } + DCInputsReader dcirDefault = new DCInputsReader(); + Iterator pi = dcirDefault.getPairsNameIterator(); + while (pi.hasNext()) { + names.add((String) pi.next()); } } catch (DCInputsReaderException e) { log.error("Failed reading DCInputs initialization: ", e); } - List names = new ArrayList(); - Iterator pi = dci.getPairsNameIterator(); - while (pi.hasNext()) { - names.add((String) pi.next()); - } - pluginNames = names.toArray(new String[names.size()]); log.debug("Got plugin names = " + Arrays.deepToString(pluginNames)); } @@ -91,19 +109,27 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority // once-only load of values and labels private void init() { if (values == null) { + values = new HashMap(); + labels = new HashMap(); String pname = this.getPluginInstanceName(); - List pairs = dci.getPairs(pname); - if (pairs != null) { - values = new String[pairs.size() / 2]; - labels = new String[pairs.size() / 2]; - for (int i = 0; i < pairs.size(); i += 2) { - labels[i / 2] = pairs.get(i); - values[i / 2] = pairs.get(i + 1); + for (Locale l : dcis.keySet()) { + DCInputsReader dci = dcis.get(l); + List pairs = dci.getPairs(pname); + if (pairs != null) { + String[] valuesLocale = new String[pairs.size() / 2]; + String[]labelsLocale = new String[pairs.size() / 2]; + for (int i = 0; i < pairs.size(); i += 2) { + labelsLocale[i / 2] = pairs.get(i); + valuesLocale[i / 2] = pairs.get(i + 1); + } + values.put(l.getLanguage(), valuesLocale); + labels.put(l.getLanguage(), labelsLocale); + log.debug("Found pairs for name=" + pname + ",locale=" + l); + } else { + log.error("Failed to find any pairs for name=" + pname, new IllegalStateException()); } - log.debug("Found pairs for name=" + pname); - } else { - log.error("Failed to find any pairs for name=" + pname, new IllegalStateException()); } + } } @@ -111,15 +137,17 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority @Override public Choices getMatches(String query, int start, int limit, String locale) { init(); - + Locale currentLocale = I18nUtil.getSupportedLocale(locale); + String[] valuesLocale = values.get(currentLocale.getLanguage()); + String[] labelsLocale = labels.get(currentLocale.getLanguage()); int dflt = -1; int found = 0; List v = new ArrayList(); - for (int i = 0; i < values.length; ++i) { - if (query == null || StringUtils.containsIgnoreCase(values[i], query)) { + for (int i = 0; i < valuesLocale.length; ++i) { + if (query == null || StringUtils.containsIgnoreCase(valuesLocale[i], query)) { if (found >= start && v.size() < limit) { - v.add(new Choice(null, values[i], labels[i])); - if (values[i].equalsIgnoreCase(query)) { + v.add(new Choice(null, valuesLocale[i], labelsLocale[i])); + if (valuesLocale[i].equalsIgnoreCase(query)) { dflt = i; } } @@ -133,10 +161,13 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority @Override public Choices getBestMatch(String text, String locale) { init(); - for (int i = 0; i < values.length; ++i) { - if (text.equalsIgnoreCase(values[i])) { + Locale currentLocale = I18nUtil.getSupportedLocale(locale); + String[] valuesLocale = values.get(currentLocale.getLanguage()); + String[] labelsLocale = labels.get(currentLocale.getLanguage()); + for (int i = 0; i < valuesLocale.length; ++i) { + if (text.equalsIgnoreCase(valuesLocale[i])) { Choice v[] = new Choice[1]; - v[0] = new Choice(String.valueOf(i), values[i], labels[i]); + v[0] = new Choice(String.valueOf(i), valuesLocale[i], labelsLocale[i]); return new Choices(v, 0, v.length, Choices.CF_UNCERTAIN, false, 0); } } @@ -146,15 +177,22 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority @Override public String getLabel(String key, String locale) { init(); + + // Get default if locale is empty + if (StringUtils.isBlank(locale)) { + locale = I18nUtil.getDefaultLocale().getLanguage(); + } + + String[] labelsLocale = labels.get(locale); int pos = -1; - for (int i = 0; i < values.length; i++) { - if (values[i].equals(key)) { + for (int i = 0; i < labelsLocale.length; i++) { + if (labelsLocale[i].equals(key)) { pos = i; break; } } if (pos != -1) { - return labels[pos]; + return labelsLocale[pos]; } else { return "UNKNOWN KEY " + key; } diff --git a/dspace-api/src/main/java/org/dspace/core/I18nUtil.java b/dspace-api/src/main/java/org/dspace/core/I18nUtil.java index d06b1e9bc8..cd0609e29f 100644 --- a/dspace-api/src/main/java/org/dspace/core/I18nUtil.java +++ b/dspace-api/src/main/java/org/dspace/core/I18nUtil.java @@ -191,6 +191,23 @@ public class I18nUtil { return supportedLocale; } + /** + * Gets the appropriate supported Locale according for a given Locale If + * no appropriate supported locale is found, the DEFAULTLOCALE is used + * + * @param locale String to find the corresponding Locale + * @return supportedLocale + * Locale for session according to locales supported by this DSpace instance as set in dspace.cfg + */ + public static Locale getSupportedLocale(String locale) { + Locale currentLocale = null; + if (locale != null) { + currentLocale = I18nUtil.getSupportedLocale(new Locale(locale)); + } else { + currentLocale = I18nUtil.getDefaultLocale(); + } + return currentLocale; + } /** * Get the appropriate localized version of submission-forms.xml according to language settings diff --git a/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml b/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml index 6ddfef9b83..ecf2483bab 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml @@ -282,6 +282,70 @@ it, please enter the types and the actual numbers or codes. +

+ + + dc + contributor + author + + name + false + You must enter at least the author. + Enter the names of the authors of this item in the form Lastname, Firstname [i.e. Smith, Josh or Smith, J]. + + + + + person + affiliation + name + + onebox + false + + Enter the affiliation of the author as stated on the publication. + + +
+ +
+ + + dc + contributor + author + true + + onebox + Author field that can be associated with an authority providing suggestion + + + + + + dc + contributor + editor + false + + name + Editor field that can be associated with an authority providing the special name lookup + + + + + + dc + subject + true + + onebox + Subject field that can be associated with an authority providing lookup + + + +
diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java index 3f233a34c7..339f601dc4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java @@ -187,7 +187,8 @@ public class SubmissionFormConverter implements DSpaceConverter Date: Sat, 18 Jul 2020 18:26:43 +0200 Subject: [PATCH 56/59] Keep the message rethrowing the exception --- .../content/authority/ChoiceAuthorityServiceImpl.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index ad7de882c6..ddce3331e2 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -374,7 +374,8 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService } } catch (DCInputsReaderException e) { // the system is in an illegal state as the submission definition is not valid - throw new IllegalStateException(e); + throw new IllegalStateException("Error reading the item submission configuration: " + e.getMessage(), + e); } } @@ -503,7 +504,8 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService ma = controllerFormDefinitions.get(fieldKey).get(submissionName.getSubmissionName()); } catch (SubmissionConfigReaderException e) { // the system is in an illegal state as the submission definition is not valid - throw new IllegalStateException(e); + throw new IllegalStateException("Error reading the item submission configuration: " + e.getMessage(), + e); } } return ma; From bd6982bb150ac87455b7ae4cb48cc61f36a3ef57 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 30 Jul 2020 17:53:39 +0200 Subject: [PATCH 57/59] Describe the structure of the map fields --- .../dspace/content/authority/DCInputAuthority.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java index d95528524d..b1d8cf36a5 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java @@ -50,9 +50,21 @@ import org.dspace.core.SelfNamedPlugin; public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority { private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DCInputAuthority.class); + /** + * The map of the values available for a specific language. Examples of keys are + * "en", "it", "uk" + */ private Map values = null; + + /** + * The map of the labels available for a specific language. Examples of keys are + * "en", "it", "uk" + */ private Map labels = null; + /** + * The map of the input form reader associated to use for a specific java locale + */ private static Map dcis = null; private static String pluginNames[] = null; From ed58d44329c67b1810851d9795364c7acbc00d0b Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Thu, 30 Jul 2020 18:02:53 +0200 Subject: [PATCH 58/59] Use a more meaningful variable name --- .../authority/ChoiceAuthorityServiceImpl.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java index ddce3331e2..0e05852af0 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/ChoiceAuthorityServiceImpl.java @@ -388,22 +388,22 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService * @param fieldKey the field key that use the authority */ private void addFormDetailsToAuthorityCacheMap(String submissionName, String authorityName, String fieldKey) { - Map> authorityName2definitions; + Map> submissionDefinitionNames2fieldKeys; if (authoritiesFormDefinitions.containsKey(authorityName)) { - authorityName2definitions = authoritiesFormDefinitions.get(authorityName); + submissionDefinitionNames2fieldKeys = authoritiesFormDefinitions.get(authorityName); } else { - authorityName2definitions = new HashMap>(); + submissionDefinitionNames2fieldKeys = new HashMap>(); } List fields; - if (authorityName2definitions.containsKey(submissionName)) { - fields = authorityName2definitions.get(submissionName); + if (submissionDefinitionNames2fieldKeys.containsKey(submissionName)) { + fields = submissionDefinitionNames2fieldKeys.get(submissionName); } else { fields = new ArrayList(); } fields.add(fieldKey); - authorityName2definitions.put(submissionName, fields); - authoritiesFormDefinitions.put(authorityName, authorityName2definitions); + submissionDefinitionNames2fieldKeys.put(submissionName, fields); + authoritiesFormDefinitions.put(authorityName, submissionDefinitionNames2fieldKeys); } /** From 70c861a9e9e88b6a852f04d1ed85022c6bb25bf7 Mon Sep 17 00:00:00 2001 From: Danilo Di Nuzzo Date: Thu, 6 Aug 2020 10:33:43 +0200 Subject: [PATCH 59/59] fix builder import --- .../java/org/dspace/app/rest/SubmissionFormsControllerIT.java | 2 +- .../java/org/dspace/app/rest/VocabularyRestRepositoryIT.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java index 903a68748b..66938e5991 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java @@ -22,10 +22,10 @@ import org.dspace.app.rest.matcher.SubmissionFormFieldMatcher; import org.dspace.app.rest.repository.SubmissionFormRestRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.util.DCInputsReaderException; +import org.dspace.builder.EPersonBuilder; import org.dspace.content.authority.DCInputAuthority; import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.core.service.PluginService; -import org.dspace.builder.EPersonBuilder; import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index aace474d4b..738a334b82 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -16,13 +16,13 @@ import java.util.Date; import java.util.Locale; import java.util.UUID; -import org.dspace.app.rest.builder.CollectionBuilder; -import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.matcher.VocabularyMatcher; import org.dspace.app.rest.repository.SubmissionFormRestRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authority.PersonAuthorityValue; import org.dspace.authority.factory.AuthorityServiceFactory; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; import org.dspace.content.Collection; import org.dspace.content.authority.DCInputAuthority; import org.dspace.content.authority.service.ChoiceAuthorityService;