Skip to content

Commit

Permalink
Merge branch 'master' into MODBULKOPS-406
Browse files Browse the repository at this point in the history
  • Loading branch information
obozhko-folio authored Dec 18, 2024
2 parents c06a8d7 + 9b45246 commit dd33f7a
Show file tree
Hide file tree
Showing 28 changed files with 733 additions and 126 deletions.
8 changes: 4 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
<version>3.3.6</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>

Expand Down Expand Up @@ -32,15 +32,15 @@
<argLine />
<bulk-operations.yaml.file>${project.basedir}/src/main/resources/swagger.api/bulk-operations.yaml</bulk-operations.yaml.file>

<folio-spring-base.version>8.2.1</folio-spring-base.version>
<folio-spring-cql.version>8.2.1</folio-spring-cql.version>
<folio-spring-base.version>8.2.2</folio-spring-base.version>
<folio-spring-cql.version>8.2.2</folio-spring-cql.version>
<folio-service-tools.version>4.1.0</folio-service-tools.version>
<mapstruct.version>1.5.3.Final</mapstruct.version>
<apache-commons-io.version>2.17.0</apache-commons-io.version>
<apache-commons-collections.version>4.4</apache-commons-collections.version>
<lombok.mapstruct-binding.version>0.2.0</lombok.mapstruct-binding.version>
<hibernate-types-52.version>2.10.3</hibernate-types-52.version>
<folio-s3-client.version>2.2.0</folio-s3-client.version>
<folio-s3-client.version>2.2.1</folio-s3-client.version>
<folio-module-descriptor-validator.version>1.0.0</folio-module-descriptor-validator.version>
<opencsv.version>5.7.1</opencsv.version>
<feign-jackson.version>12.1</feign-jackson.version>
Expand Down
41 changes: 22 additions & 19 deletions src/main/java/org/folio/bulkops/domain/bean/Instance.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,18 @@
import lombok.With;
import org.folio.bulkops.domain.converter.BooleanConverter;
import org.folio.bulkops.domain.converter.ContributorListConverter;
import org.folio.bulkops.domain.converter.DateWithoutTimeConverter;
import org.folio.bulkops.domain.converter.InstanceFormatListConverter;
import org.folio.bulkops.domain.converter.InstanceNoteListConverter;
import org.folio.bulkops.domain.converter.InstanceStatusConverter;
import org.folio.bulkops.domain.converter.InstanceTypeConverter;
import org.folio.bulkops.domain.converter.ModeOfIssuanceConverter;
import org.folio.bulkops.domain.converter.NatureOfContentTermListConverter;
import org.folio.bulkops.domain.converter.SeriesListConverter;
import org.folio.bulkops.domain.converter.InstanceStatisticalCodeListConverter;
import org.folio.bulkops.domain.converter.StringConverter;
import org.folio.bulkops.domain.converter.StringListPipedConverter;
import org.folio.bulkops.domain.dto.DataType;
import org.folio.bulkops.domain.dto.IdentifierType;

import java.util.Date;
import java.util.List;

@Data
Expand All @@ -41,6 +39,7 @@ public class Instance implements BulkOperationsEntity {
public static final String INSTANCE_HRID = "Instance HRID";
public static final String INSTANCE_SOURCE = "Source";
public static final String INSTANCE_MODE_OF_ISSUANCE = "Mode of issuance";
public static final String INSTANCE_STATISTICAL_CODES = "Statistical code";
public static final String INSTANCE_RESOURCE_TITLE = "Resource title";
public static final String INSTANCE_INDEX_TITLE = "Index title";
public static final String INSTANCE_SERIES_STATEMENTS = "Series statements";
Expand Down Expand Up @@ -117,88 +116,94 @@ public class Instance implements BulkOperationsEntity {
@UnifiedTableCell(visible = false)
private String modeOfIssuanceId;

@JsonProperty("statisticalCodeIds")
@CsvCustomBindByName(column = INSTANCE_STATISTICAL_CODES, converter = InstanceStatisticalCodeListConverter.class)
@CsvCustomBindByPosition(position = 9, converter = InstanceStatisticalCodeListConverter.class)
@UnifiedTableCell(visible = false)
private List<String> statisticalCodeIds;

@JsonProperty("administrativeNotes")
@CsvCustomBindByName(column = INSTANCE_ADMINISTRATIVE_NOTE, converter = StringListPipedConverter.class)
@CsvCustomBindByPosition(position = 9, converter = StringListPipedConverter.class)
@CsvCustomBindByPosition(position = 10, converter = StringListPipedConverter.class)
@UnifiedTableCell(visible = false)
private List<String> administrativeNotes;

@JsonProperty("title")
@CsvCustomBindByName(column = INSTANCE_RESOURCE_TITLE, converter = StringConverter.class)
@CsvCustomBindByPosition(position = 10, converter = StringConverter.class)
@CsvCustomBindByPosition(position = 11, converter = StringConverter.class)
@UnifiedTableCell
private String title;

@JsonProperty("indexTitle")
@CsvCustomBindByName(column = INSTANCE_INDEX_TITLE, converter = StringConverter.class)
@CsvCustomBindByPosition(position = 11, converter = StringConverter.class)
@CsvCustomBindByPosition(position = 12, converter = StringConverter.class)
@UnifiedTableCell(visible = false)
private String indexTitle;

@JsonProperty("series")
@CsvCustomBindByName(column = INSTANCE_SERIES_STATEMENTS, converter = SeriesListConverter.class)
@CsvCustomBindByPosition(position = 12, converter = SeriesListConverter.class)
@CsvCustomBindByPosition(position = 13, converter = SeriesListConverter.class)
@UnifiedTableCell(visible = false)
private List<Series> series;

@JsonProperty("contributors")
@CsvCustomBindByName(column = INSTANCE_CONTRIBUTORS, converter = ContributorListConverter.class)
@CsvCustomBindByPosition(position = 13, converter = ContributorListConverter.class)
@CsvCustomBindByPosition(position = 14, converter = ContributorListConverter.class)
@UnifiedTableCell
private List<ContributorName> contributors;

@JsonProperty("editions")
@CsvCustomBindByName(column = INSTANCE_EDITION, converter = StringListPipedConverter.class)
@CsvCustomBindByPosition(position = 14, converter = StringListPipedConverter.class)
@CsvCustomBindByPosition(position = 15, converter = StringListPipedConverter.class)
@UnifiedTableCell(visible = false)
private List<String> editions;

@JsonProperty("physicalDescriptions")
@CsvCustomBindByName(column = INSTANCE_PHYSICAL_DESCRIPTION, converter = StringListPipedConverter.class)
@CsvCustomBindByPosition(position = 15, converter = StringListPipedConverter.class)
@CsvCustomBindByPosition(position = 16, converter = StringListPipedConverter.class)
@UnifiedTableCell(visible = false)
private List<String> physicalDescriptions;

@JsonProperty("instanceTypeId")
@CsvCustomBindByName(column = INSTANCE_RESOURCE_TYPE, converter = InstanceTypeConverter.class)
@CsvCustomBindByPosition(position = 16, converter = InstanceTypeConverter.class)
@CsvCustomBindByPosition(position = 17, converter = InstanceTypeConverter.class)
@UnifiedTableCell
private String instanceTypeId;

@JsonProperty("natureOfContentTermIds")
@CsvCustomBindByName(column = INSTANCE_NATURE_OF_CONTENT, converter = NatureOfContentTermListConverter.class)
@CsvCustomBindByPosition(position = 17, converter = NatureOfContentTermListConverter.class)
@CsvCustomBindByPosition(position = 18, converter = NatureOfContentTermListConverter.class)
@UnifiedTableCell(visible = false)
private List<String> natureOfContentTermIds;

@JsonProperty("instanceFormatIds")
@CsvCustomBindByName(column = INSTANCE_FORMATS, converter = InstanceFormatListConverter.class)
@CsvCustomBindByPosition(position = 18, converter = InstanceFormatListConverter.class)
@CsvCustomBindByPosition(position = 19, converter = InstanceFormatListConverter.class)
@UnifiedTableCell(visible = false)
private List<String> instanceFormatIds;

@JsonProperty("languages")
@CsvCustomBindByName(column = INSTANCE_LANGUAGES, converter = StringListPipedConverter.class)
@CsvCustomBindByPosition(position = 19, converter = StringListPipedConverter.class)
@CsvCustomBindByPosition(position = 20, converter = StringListPipedConverter.class)
@UnifiedTableCell(visible = false)
private List<String> languages;

@JsonProperty("publicationFrequency")
@Valid
@CsvCustomBindByName(column = INSTANCE_PUBLICATION_FREQUENCY, converter = StringListPipedConverter.class)
@CsvCustomBindByPosition(position = 20, converter = StringListPipedConverter.class)
@CsvCustomBindByPosition(position = 21, converter = StringListPipedConverter.class)
@UnifiedTableCell(visible = false)
private List<String> publicationFrequency;

@JsonProperty("publicationRange")
@CsvCustomBindByName(column = INSTANCE_PUBLICATION_RANGE, converter = StringListPipedConverter.class)
@CsvCustomBindByPosition(position = 21, converter = StringListPipedConverter.class)
@CsvCustomBindByPosition(position = 22, converter = StringListPipedConverter.class)
@UnifiedTableCell(visible = false)
private List<String> publicationRange;

@JsonProperty("notes")
@CsvCustomBindByName(column = "Notes", converter = InstanceNoteListConverter.class)
@CsvCustomBindByPosition(position = 22, converter = InstanceNoteListConverter.class)
@CsvCustomBindByPosition(position = 23, converter = InstanceNoteListConverter.class)
@UnifiedTableCell(visible = false)
private List<InstanceNote> instanceNotes;

Expand All @@ -216,8 +221,6 @@ public class Instance implements BulkOperationsEntity {
private List<Publication> publications;
@JsonProperty("electronicAccess")
private List<ElectronicAccess> electronicAccesses;
@JsonProperty("statisticalCodeIds")
private List<String> statisticalCodeIds;
@JsonProperty("sourceRecordFormat")
private String sourceRecordFormat;
@JsonProperty("statusUpdatedDate")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.folio.bulkops.domain.converter;

import org.folio.bulkops.domain.format.SpecialCharacterEscaper;
import org.folio.bulkops.service.InstanceReferenceHelper;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static org.folio.bulkops.util.Constants.ARRAY_DELIMITER;

public class InstanceStatisticalCodeListConverter extends BaseConverter<List<String>> {
@Override
public List<String> convertToObject(String value) {
return Arrays.stream(value.split(ARRAY_DELIMITER))
.map(SpecialCharacterEscaper::restore)
.map(name -> InstanceReferenceHelper.service().getStatisticalCodeByName(name).getId())
.filter(Objects::nonNull)
.toList();
}

@Override
public String convertToString(List<String> object) {
return object.stream()
.filter(Objects::nonNull)
.map(id -> InstanceReferenceHelper.service().getStatisticalCodeById(id).getName())
.map(SpecialCharacterEscaper::escape)
.collect(Collectors.joining(ARRAY_DELIMITER));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

import lombok.extern.log4j.Log4j2;

import static org.folio.bulkops.domain.dto.UpdateActionType.REMOVE_ALL;
import static org.folio.bulkops.domain.dto.UpdateOptionType.STATISTICAL_CODE;
import static org.folio.bulkops.util.FolioExecutionContextUtil.prepareContextForTenant;

@Log4j2
Expand Down Expand Up @@ -54,6 +56,14 @@ public UpdatedEntityHolder process(String identifier, T entity, BulkOperationRul
var holder = UpdatedEntityHolder.builder().build();
var updated = clone(entity);
var preview = clone(entity);
try {
validator().validate(rules);
} catch (RuleValidationException e) {
log.warn(String.format("Rule validation exception: %s", e.getMessage()));
errorService.saveError(rules.getBulkOperationRules().get(0).getBulkOperationId(), identifier, e.getMessage());
} catch (Exception e) {
log.error(e.getMessage());
}
for (BulkOperationRule rule : rules.getBulkOperationRules()) {
var details = rule.getRuleDetails();
var option = details.getOption();
Expand Down Expand Up @@ -93,6 +103,14 @@ public UpdatedEntityHolder process(String identifier, T entity, BulkOperationRul
*/
public abstract Validator<UpdateOptionType, Action, BulkOperationRule> validator(T entity);

public StatisticalCodeValidator<BulkOperationRuleCollection> validator() {
return rules -> {
if (getNumberOfRulesWithStatisticalCode(rules) > 1 && existsRuleWithStatisticalCodeAndRemoveAll(rules)) {
throw new RuleValidationException("Combination REMOVE_ALL with other actions is not supported for Statistical code");
}
};
}

/**
* Returns {@link Consumer<T>} for applying changes for entity of type {@link T}
*
Expand Down Expand Up @@ -136,4 +154,13 @@ public String getRecordPropertyName(UpdateOptionType optionType) {
private boolean isTenantApplicableForProcessingAsMember(T entity) {
return entity.getRecordBulkOperationEntity().getClass() != User.class && consortiaService.isTenantMember(entity.getTenant());
}

private long getNumberOfRulesWithStatisticalCode(BulkOperationRuleCollection rules) {
return rules.getBulkOperationRules().stream().filter(rule -> rule.getRuleDetails().getOption() == STATISTICAL_CODE).count();
}

private boolean existsRuleWithStatisticalCodeAndRemoveAll(BulkOperationRuleCollection rules) {
return rules.getBulkOperationRules().stream().anyMatch(rule -> rule.getRuleDetails().getOption() == STATISTICAL_CODE &&
rule.getRuleDetails().getActions().stream().anyMatch(act -> act.getType() == REMOVE_ALL));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.folio.bulkops.processor;

import org.folio.bulkops.exception.RuleValidationException;

@FunctionalInterface
public interface StatisticalCodeValidator<T> {
void validate(T t) throws RuleValidationException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public Updater<ExtendedInstance> updater(UpdateOptionType option, Action action,
return extendedInstance -> extendedInstance.getEntity().setDiscoverySuppress(false);
}
}
return instanceNotesUpdaterFactory.getUpdater(option, action).orElseGet(() -> instance -> {
return instanceNotesUpdaterFactory.getUpdater(option, action, forPreview).orElseGet(() -> instance -> {
throw new BulkOperationException(format("Combination %s and %s isn't supported yet", option, action.getType()));
});
}
Expand All @@ -81,6 +81,9 @@ public ExtendedInstance clone(ExtendedInstance extendedInstance) {
.map(instanceNote -> instanceNote.toBuilder().build())
.toList()));
}
if (instance.getStatisticalCodeIds() != null) {
clone.setStatisticalCodeIds(new ArrayList<>(clone.getStatisticalCodeIds()));
}
return ExtendedInstance.builder().tenantId(extendedInstance.getTenantId()).entity(clone).build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import static org.folio.bulkops.domain.dto.UpdateActionType.MARK_AS_STAFF_ONLY;
import static org.folio.bulkops.domain.dto.UpdateOptionType.ADMINISTRATIVE_NOTE;
import static org.folio.bulkops.domain.dto.UpdateOptionType.INSTANCE_NOTE;
import static org.folio.bulkops.domain.dto.UpdateOptionType.STATISTICAL_CODE;
import static org.folio.bulkops.util.Constants.STAFF_ONLY_NOTE_PARAMETER_KEY;

import lombok.AllArgsConstructor;
Expand All @@ -30,15 +31,17 @@ public class InstanceNotesUpdaterFactory {

public static final String INSTANCE_NOTE_TYPE_ID_KEY = "INSTANCE_NOTE_TYPE_ID_KEY";
private final AdministrativeNotesUpdater administrativeNotesUpdater;
private final StatisticalCodesUpdater statisticalCodesUpdater;

public Optional<Updater<ExtendedInstance>> getUpdater(UpdateOptionType option, Action action) {
public Optional<Updater<ExtendedInstance>> getUpdater(UpdateOptionType option, Action action, boolean forPreview) {
return switch (action.getType()) {
case MARK_AS_STAFF_ONLY, REMOVE_MARK_AS_STAFF_ONLY -> Optional.of(extendedInstance -> setStaffOnly(extendedInstance.getEntity(), action, option));
case REMOVE_ALL -> Optional.of(extendedInstance -> removeAll(extendedInstance.getEntity(), action, option));
case ADD_TO_EXISTING -> Optional.of(extendedInstance -> addToExisting(extendedInstance.getEntity(), action, option));
case ADD_TO_EXISTING -> Optional.of(extendedInstance -> addToExisting(extendedInstance.getEntity(), action, option, forPreview));
case FIND_AND_REMOVE_THESE -> Optional.of(extendedInstance -> findAndRemove(extendedInstance.getEntity(), action, option));
case FIND_AND_REPLACE -> Optional.of(extendedInstance -> findAndReplace(extendedInstance.getEntity(), action, option));
case CHANGE_TYPE -> Optional.of(extendedInstance -> changeNoteType(extendedInstance.getEntity(), action, option));
case REMOVE_SOME -> Optional.of(extendedInstance -> removeSome(extendedInstance.getEntity(), action, option));
default -> Optional.empty();
};
}
Expand All @@ -57,14 +60,24 @@ private void removeAll(Instance instance, Action action, UpdateOptionType option
instance.setAdministrativeNotes(Collections.emptyList());
} else if (INSTANCE_NOTE.equals(option)) {
instance.setInstanceNotes(removeNotesByTypeId(instance.getInstanceNotes(), action.getParameters()));
} else if (STATISTICAL_CODE.equals(option)) {
instance.setStatisticalCodeIds(Collections.emptyList());
}
}

private void addToExisting(Instance instance, Action action, UpdateOptionType option) {
private void removeSome(Instance instance, Action action, UpdateOptionType option) {
if (STATISTICAL_CODE.equals(option)) {
instance.setStatisticalCodeIds(statisticalCodesUpdater.removeSomeStatisticalCodeIds(action.getUpdated(), instance.getStatisticalCodeIds()));
}
}

private void addToExisting(Instance instance, Action action, UpdateOptionType option, boolean forPreview) {
if (ADMINISTRATIVE_NOTE.equals(option)) {
instance.setAdministrativeNotes(administrativeNotesUpdater.addToAdministrativeNotes(action.getUpdated(), instance.getAdministrativeNotes()));
} else if (INSTANCE_NOTE.equals(option)) {
instance.setInstanceNotes(addToNotesByTypeId(instance.getInstanceNotes(), action.getParameters(), action.getUpdated()));
} else if (STATISTICAL_CODE.equals(option)) {
instance.setStatisticalCodeIds(statisticalCodesUpdater.addToStatisticalCodeIds(action.getUpdated(), instance.getStatisticalCodeIds(), forPreview));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.folio.bulkops.processor.folio;

import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static java.util.Objects.isNull;

@Component
public class StatisticalCodesUpdater {

public List<String> addToStatisticalCodeIds(String statisticalCodeIdsFromAction, List<String> existingStatisticalCodeIds, boolean forPreview) {
var newStatisticalCodeIds = new ArrayList<>(Arrays.asList(statisticalCodeIdsFromAction.split(",")));
if (isNull(existingStatisticalCodeIds)) {
return forPreview ? newStatisticalCodeIds : newStatisticalCodeIds.stream().distinct().toList();
}
if (forPreview) {
existingStatisticalCodeIds.addAll(newStatisticalCodeIds);
} else {
newStatisticalCodeIds.stream().distinct().filter(newCode -> !existingStatisticalCodeIds.contains(newCode))
.forEach(existingStatisticalCodeIds::add);
}
return existingStatisticalCodeIds;
}

public List<String> removeSomeStatisticalCodeIds(String statisticalCodeIdsFromAction, List<String> existingStatisticalCodeIds) {
var statisticalCodeIdsToRemove = new ArrayList<>(Arrays.asList(statisticalCodeIdsFromAction.split(",")));
if (isNull(existingStatisticalCodeIds)) {
return Collections.emptyList();
}
existingStatisticalCodeIds.removeAll(statisticalCodeIdsToRemove);
return existingStatisticalCodeIds;
}
}
Loading

0 comments on commit dd33f7a

Please sign in to comment.