Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MODINVSTOR-1270] Allow Subject sources and Subject type to have consortium source #1098

Merged
merged 7 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions ramls/subject-source.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
},
"source": {
"type": "string",
"description": "label indicating where the subject source entry originates from, i.e. 'folio' or 'local'",
"description": "label indicating where the subject source entry originates from, i.e. 'folio', 'local' or 'consortium'",
"enum": [
"folio",
"local"
"local",
"consortium"
]
},
"metadata": {
Expand All @@ -29,6 +30,7 @@
}
},
"required": [
"name"
"name",
"source"
]
}
8 changes: 5 additions & 3 deletions ramls/subject-type.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
},
"source": {
"type": "string",
"description": "label indicating where the subject type entry originates from, i.e. 'folio' or 'local'",
"description": "label indicating where the subject type entry originates from, i.e. 'folio', 'local' or 'consortium'",
"enum": [
"folio",
"local"
"local",
"consortium"
]
},
"metadata": {
Expand All @@ -25,6 +26,7 @@
}
},
"required": [
"name"
"name",
"source"
]
}
8 changes: 6 additions & 2 deletions src/main/java/org/folio/rest/support/ResponseUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ public final class ResponseUtil {

public static final String SOURCE_CANNOT_BE_FOLIO =
"Illegal operation: Source field cannot be set to folio";
public static final String SOURCE_CANNOT_BE_UPDATED =
"Illegal operation: Source field cannot be updated";
public static final String SOURCE_FOLIO_CANNOT_BE_UPDATED =
"Illegal operation: Source folio cannot be updated";
public static final String SOURCE_CANNOT_BE_UPDATED_AT_NON_ECS =
"Illegal operation: Source field cannot be updated at non-consortium tenant";
public static final String SOURCE_CONSORTIUM_CANNOT_BE_APPLIED =
"Illegal operation: Source consortium cannot be applied at non-consortium tenant";

private ResponseUtil() { }

Expand Down
73 changes: 73 additions & 0 deletions src/main/java/org/folio/rest/support/SubjectUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.folio.rest.support;

import static io.vertx.core.Future.succeededFuture;
import static org.folio.rest.jaxrs.resource.SubjectSources.PostSubjectSourcesResponse.respond422WithApplicationJson;
import static org.folio.rest.support.ResponseUtil.SOURCE_CANNOT_BE_FOLIO;
import static org.folio.rest.support.ResponseUtil.SOURCE_CANNOT_BE_UPDATED_AT_NON_ECS;
import static org.folio.rest.support.ResponseUtil.SOURCE_CONSORTIUM_CANNOT_BE_APPLIED;
import static org.folio.rest.support.ResponseUtil.SOURCE_FOLIO_CANNOT_BE_UPDATED;
import static org.folio.rest.tools.utils.ValidationHelper.createValidationErrorMessage;

import io.vertx.core.Future;
import java.util.Map;
import java.util.Optional;
import javax.ws.rs.core.Response;
import org.folio.rest.jaxrs.model.Errors;
import org.folio.rest.jaxrs.model.SubjectSource;
import org.folio.services.consortium.ConsortiumService;

public final class SubjectUtil {
private static final String SOURCE = "source";

private SubjectUtil() {
}

public static Future<Optional<Errors>> validateSubjectSourceCreate(String subjectSource,
ConsortiumService consortiumService,
Map<String, String> okapiHeaders) {
if (SubjectSource.Source.FOLIO.value().equals(subjectSource)) {
return Future.succeededFuture(getValidationErrorMessage(subjectSource, SOURCE_CANNOT_BE_FOLIO));
}

if (SubjectSource.Source.CONSORTIUM.value().equals(subjectSource)) {
return consortiumService.getConsortiumData(okapiHeaders)
.map(consortiumDataOptional -> {
if (consortiumDataOptional.isEmpty()) {
return getValidationErrorMessage(subjectSource, SOURCE_CONSORTIUM_CANNOT_BE_APPLIED);
}
return Optional.empty();
});
}
return Future.succeededFuture(Optional.empty());
}

public static Future<Optional<Errors>> validateSubjectSourceUpdate(String incomingSubjectSource,
String existingSubjectSource,
ConsortiumService consortiumService,
Map<String, String> okapiHeaders) {
if (!existingSubjectSource.equals(incomingSubjectSource)) {
if (SubjectSource.Source.FOLIO.value().equals(incomingSubjectSource)) {
return Future.succeededFuture(getValidationErrorMessage(incomingSubjectSource, SOURCE_CANNOT_BE_FOLIO));
}
if (SubjectSource.Source.FOLIO.value().equals(existingSubjectSource)) {
return Future.succeededFuture(getValidationErrorMessage(incomingSubjectSource, SOURCE_FOLIO_CANNOT_BE_UPDATED));
}
return consortiumService.getConsortiumData(okapiHeaders)
.map(consortiumDataOptional -> {
if (consortiumDataOptional.isEmpty()) {
return getValidationErrorMessage(incomingSubjectSource, SOURCE_CANNOT_BE_UPDATED_AT_NON_ECS);
}
return Optional.empty();
});
}
return Future.succeededFuture(Optional.empty());
}
RomanChernetskyi marked this conversation as resolved.
Show resolved Hide resolved

public static Future<Response> sourceValidationError(Errors errors) {
return succeededFuture(respond422WithApplicationJson(errors));
}

private static Optional<Errors> getValidationErrorMessage(String subjectSource, String errorMessage) {
return Optional.of(createValidationErrorMessage(SOURCE, subjectSource, errorMessage));
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
package org.folio.services.subjectsource;

import static io.vertx.core.Future.succeededFuture;
import static org.folio.rest.jaxrs.resource.SubjectSources.DeleteSubjectSourcesBySubjectSourceIdResponse;
import static org.folio.rest.jaxrs.resource.SubjectSources.PostSubjectSourcesResponse;
import static org.folio.rest.jaxrs.resource.SubjectSources.PostSubjectSourcesResponse.respond422WithApplicationJson;
import static org.folio.rest.jaxrs.resource.SubjectSources.PutSubjectSourcesBySubjectSourceIdResponse;
import static org.folio.rest.persist.PgUtil.deleteById;
import static org.folio.rest.persist.PgUtil.get;
import static org.folio.rest.persist.PgUtil.post;
import static org.folio.rest.persist.PgUtil.put;
import static org.folio.rest.support.ResponseUtil.SOURCE_CANNOT_BE_FOLIO;
import static org.folio.rest.support.ResponseUtil.SOURCE_CANNOT_BE_UPDATED;
import static org.folio.rest.tools.utils.ValidationHelper.createValidationErrorMessage;
import static org.folio.rest.support.SubjectUtil.sourceValidationError;
import static org.folio.rest.support.SubjectUtil.validateSubjectSourceCreate;
import static org.folio.rest.support.SubjectUtil.validateSubjectSourceUpdate;

import io.vertx.core.Context;
import io.vertx.core.Future;
import java.util.Map;
import javax.ws.rs.core.Response;
import org.folio.persist.SubjectSourceRepository;
import org.folio.rest.exceptions.NotFoundException;
import org.folio.rest.jaxrs.model.SubjectSource;
import org.folio.rest.jaxrs.model.SubjectSources;
import org.folio.rest.jaxrs.resource.SubjectSources.GetSubjectSourcesBySubjectSourceIdResponse;
import org.folio.rest.jaxrs.resource.SubjectSources.GetSubjectSourcesResponse;
import org.folio.rest.persist.PgUtil;
import org.folio.services.caches.ConsortiumDataCache;
import org.folio.services.consortium.ConsortiumService;
import org.folio.services.consortium.ConsortiumServiceImpl;
import org.folio.services.domainevent.SubjectSourceDomainEventPublisher;

public class SubjectSourceService {
Expand All @@ -33,12 +35,15 @@ public class SubjectSourceService {
private final Map<String, String> okapiHeaders;
private final SubjectSourceRepository repository;
private final SubjectSourceDomainEventPublisher domainEventService;
private final ConsortiumService consortiumService;

public SubjectSourceService(Context context, Map<String, String> okapiHeaders) {
this.context = context;
this.okapiHeaders = okapiHeaders;
this.repository = new SubjectSourceRepository(context, okapiHeaders);
this.domainEventService = new SubjectSourceDomainEventPublisher(context, okapiHeaders);
this.consortiumService = new ConsortiumServiceImpl(context.owner().createHttpClient(),
context.get(ConsortiumDataCache.class.getName()));
}

public Future<Response> getByQuery(String cql, int offset, int limit) {
Expand All @@ -52,22 +57,25 @@ public Future<Response> getById(String id) {
}

public Future<Response> create(SubjectSource subjectSource) {
if (subjectSource.getSource().equals(SubjectSource.Source.FOLIO)) {
return sourceValidationError(subjectSource.getSource().value(), SOURCE_CANNOT_BE_FOLIO);
}
return post(SUBJECT_SOURCE, subjectSource, okapiHeaders, context, PostSubjectSourcesResponse.class)
.onSuccess(domainEventService.publishCreated());
return validateSubjectSourceCreate(subjectSource.getSource().value(), consortiumService, okapiHeaders)
.compose(errorsOptional -> errorsOptional.isPresent() ? sourceValidationError(errorsOptional.get()) :
createSubjectSource(subjectSource));
}

public Future<Response> update(String id, SubjectSource subjectSource) {
if (subjectSource.getId() == null) {
subjectSource.setId(id);
}

return repository.getById(id)
.compose(oldSubjectSource -> {
if (!oldSubjectSource.getSource().equals(subjectSource.getSource())) {
return sourceValidationError(subjectSource.getSource().value(), SOURCE_CANNOT_BE_UPDATED);
if (oldSubjectSource != null) {
return validateSubjectSourceUpdate(subjectSource.getSource().value(), oldSubjectSource.getSource().value(),
consortiumService, okapiHeaders)
.compose(errorsOptional -> errorsOptional.isPresent()
? sourceValidationError(errorsOptional.get()) : updateSubjectSource(id, subjectSource));
}
return put(SUBJECT_SOURCE, subjectSource, id, okapiHeaders, context,
PutSubjectSourcesBySubjectSourceIdResponse.class)
.onSuccess(domainEventService.publishUpdated(subjectSource));
return Future.failedFuture(new NotFoundException("SubjectSource was not found"));
});
}

Expand All @@ -79,10 +87,14 @@ public Future<Response> delete(String id) {
);
}

private Future<Response> sourceValidationError(String field, String message) {
return succeededFuture(
respond422WithApplicationJson(
createValidationErrorMessage("source", field,
message)));
private Future<Response> createSubjectSource(SubjectSource subjectSource) {
return post(SUBJECT_SOURCE, subjectSource, okapiHeaders, context, PostSubjectSourcesResponse.class)
.onSuccess(domainEventService.publishCreated());
}

private Future<Response> updateSubjectSource(String id, SubjectSource subjectSource) {
return put(SUBJECT_SOURCE, subjectSource, id, okapiHeaders, context,
PutSubjectSourcesBySubjectSourceIdResponse.class)
.onSuccess(domainEventService.publishUpdated(subjectSource));
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
package org.folio.services.subjecttype;

import static io.vertx.core.Future.succeededFuture;
import static org.folio.rest.jaxrs.resource.SubjectTypes.PostSubjectTypesResponse.respond422WithApplicationJson;
import static org.folio.rest.persist.PgUtil.deleteById;
import static org.folio.rest.persist.PgUtil.get;
import static org.folio.rest.persist.PgUtil.post;
import static org.folio.rest.persist.PgUtil.put;
import static org.folio.rest.support.ResponseUtil.SOURCE_CANNOT_BE_FOLIO;
import static org.folio.rest.support.ResponseUtil.SOURCE_CANNOT_BE_UPDATED;
import static org.folio.rest.tools.utils.ValidationHelper.createValidationErrorMessage;
import static org.folio.rest.support.SubjectUtil.sourceValidationError;
import static org.folio.rest.support.SubjectUtil.validateSubjectSourceCreate;
import static org.folio.rest.support.SubjectUtil.validateSubjectSourceUpdate;

import io.vertx.core.Context;
import io.vertx.core.Future;
import java.util.Map;
import javax.ws.rs.core.Response;
import org.folio.persist.SubjectTypeRepository;
import org.folio.rest.exceptions.NotFoundException;
import org.folio.rest.jaxrs.model.SubjectType;
import org.folio.rest.jaxrs.model.SubjectTypes;
import org.folio.rest.jaxrs.resource.SubjectTypes.DeleteSubjectTypesBySubjectTypeIdResponse;
Expand All @@ -23,22 +22,27 @@
import org.folio.rest.jaxrs.resource.SubjectTypes.PostSubjectTypesResponse;
import org.folio.rest.jaxrs.resource.SubjectTypes.PutSubjectTypesBySubjectTypeIdResponse;
import org.folio.rest.persist.PgUtil;
import org.folio.services.caches.ConsortiumDataCache;
import org.folio.services.consortium.ConsortiumService;
import org.folio.services.consortium.ConsortiumServiceImpl;
import org.folio.services.domainevent.SubjectTypeDomainEventPublisher;

public class SubjectTypeService {

public static final String SUBJECT_TYPE = "subject_type";

private final Context context;
private final Map<String, String> okapiHeaders;
private final SubjectTypeRepository repository;
private final SubjectTypeDomainEventPublisher domainEventService;
private final ConsortiumService consortiumService;

public SubjectTypeService(Context context, Map<String, String> okapiHeaders) {
this.context = context;
this.okapiHeaders = okapiHeaders;
this.repository = new SubjectTypeRepository(context, okapiHeaders);
this.domainEventService = new SubjectTypeDomainEventPublisher(context, okapiHeaders);
this.consortiumService = new ConsortiumServiceImpl(context.owner().createHttpClient(),
context.get(ConsortiumDataCache.class.getName()));
}

public Future<Response> getByQuery(String cql, int offset, int limit) {
Expand All @@ -52,21 +56,25 @@ public Future<Response> getById(String id) {
}

public Future<Response> create(SubjectType subjectType) {
if (subjectType.getSource().equals(SubjectType.Source.FOLIO)) {
return sourceValidationError(subjectType.getSource().value(), SOURCE_CANNOT_BE_FOLIO);
}
return post(SUBJECT_TYPE, subjectType, okapiHeaders, context, PostSubjectTypesResponse.class)
.onSuccess(domainEventService.publishCreated());
return validateSubjectSourceCreate(subjectType.getSource().value(), consortiumService, okapiHeaders)
.compose(errorsOptional -> errorsOptional.isPresent() ? sourceValidationError(errorsOptional.get()) :
createSubjectType(subjectType));
}

public Future<Response> update(String id, SubjectType subjectType) {
if (subjectType.getId() == null) {
subjectType.setId(id);
}

return repository.getById(id)
.compose(oldSubjectType -> {
if (!oldSubjectType.getSource().equals(subjectType.getSource())) {
return sourceValidationError(subjectType.getSource().value(), SOURCE_CANNOT_BE_UPDATED);
if (oldSubjectType != null) {
return validateSubjectSourceUpdate(subjectType.getSource().value(), oldSubjectType.getSource().value(),
consortiumService, okapiHeaders)
.compose(errorsOptional -> errorsOptional.isPresent()
? sourceValidationError(errorsOptional.get()) : updateSubjectType(id, subjectType));
}
return put(SUBJECT_TYPE, subjectType, id, okapiHeaders, context, PutSubjectTypesBySubjectTypeIdResponse.class)
.onSuccess(domainEventService.publishUpdated(subjectType));
return Future.failedFuture(new NotFoundException("SubjectType was not found"));
});
}

Expand All @@ -78,10 +86,13 @@ public Future<Response> delete(String id) {
);
}

private Future<Response> sourceValidationError(String field, String message) {
return succeededFuture(
respond422WithApplicationJson(
createValidationErrorMessage("source", field,
message)));
private Future<Response> createSubjectType(SubjectType subjectType) {
return post(SUBJECT_TYPE, subjectType, okapiHeaders, context, PostSubjectTypesResponse.class)
.onSuccess(domainEventService.publishCreated());
}

private Future<Response> updateSubjectType(String id, SubjectType subjectType) {
return put(SUBJECT_TYPE, subjectType, id, okapiHeaders, context, PutSubjectTypesBySubjectTypeIdResponse.class)
.onSuccess(domainEventService.publishUpdated(subjectType));
}
}
17 changes: 3 additions & 14 deletions src/test/java/org/folio/rest/api/HoldingsStorageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public void beforeEach() {

WireMock.reset();
mockUserTenantsForNonConsortiumMember();
mockUserTenantsForConsortiumMember();
mockUserTenantsForConsortiumMember(CONSORTIUM_MEMBER_TENANT);
mockUserTenantsForTenantWithoutPermissions();
}

Expand Down Expand Up @@ -3233,8 +3233,8 @@ private static void prepareThreeHoldingSource() {
.put("id", sourceId)
.put("name", "holding source name for " + sourceId));
holdingsSourceClient.create(new JsonObject()
.put("id", sourceId)
.put("name", "holding source name for " + sourceId),
.put("id", sourceId)
.put("name", "holding source name for " + sourceId),
CONSORTIUM_MEMBER_TENANT);
}
}
Expand Down Expand Up @@ -3416,17 +3416,6 @@ private List<String> getTags(JsonObject json) {
.toList();
}

private void mockUserTenantsForConsortiumMember() {
JsonObject userTenantsCollection = new JsonObject()
.put("userTenants", new JsonArray()
.add(new JsonObject()
.put("centralTenantId", "CENTRAL_TENANT_ID")
.put("consortiumId", "mobius")));
WireMock.stubFor(WireMock.get(USER_TENANTS_PATH)
.withHeader(X_OKAPI_TENANT, equalToIgnoreCase(CONSORTIUM_MEMBER_TENANT))
.willReturn(WireMock.ok().withBody(userTenantsCollection.encodePrettily())));
}

private void mockUserTenantsForTenantWithoutPermissions() {
WireMock.stubFor(WireMock.get(USER_TENANTS_PATH)
.withHeader(X_OKAPI_TENANT, equalToIgnoreCase(TENANT_WITHOUT_USER_TENANTS_PERMISSIONS))
Expand Down
4 changes: 3 additions & 1 deletion src/test/java/org/folio/rest/api/StorageTestSuite.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@
StatisticalCodeTest.class,
UpcIsmnMigrationScriptTest.class,
InstanceStorageInstancesBulkApiTest.class,
PublicationPeriodMigrationTest.class
PublicationPeriodMigrationTest.class,
SubjectSourceTest.class,
SubjectTypeTest.class

// These fail.
//ReferenceTablesTest.class,
Expand Down
Loading
Loading