Tony CHEMIT pushed to branch feature/issue-2846 at ultreiaio / ird-observe
Commits:
-
519d78f9
by Tony Chemit at 2024-09-30T15:59:51+02:00
-
d5112573
by Tony Chemit at 2024-09-30T16:00:12+02:00
-
e3a9e816
by Tony Chemit at 2024-09-30T16:00:12+02:00
-
d73e9c61
by Tony Chemit at 2024-09-30T16:00:12+02:00
-
7c55ec38
by Tony Chemit at 2024-09-30T16:00:12+02:00
-
14bedb12
by Tony Chemit at 2024-09-30T16:00:12+02:00
-
cfb14bc2
by Tony Chemit at 2024-09-30T16:00:12+02:00
-
bf6a14a2
by Tony Chemit at 2024-09-30T16:00:12+02:00
-
ac66dc3d
by Tony Chemit at 2024-09-30T16:05:43+02:00
-
73e16a66
by Tony Chemit at 2024-09-30T16:06:25+02:00
-
824a3395
by Tony Chemit at 2024-09-30T16:06:39+02:00
-
f231c455
by Tony Chemit at 2024-09-30T16:54:09+02:00
-
9dcbee37
by Tony Chemit at 2024-09-30T17:02:54+02:00
-
a04f842e
by Tony Chemit at 2024-09-30T17:02:54+02:00
24 changed files:
- client/datasource/editor/api/src/main/java/fr/ird/observe/client/datasource/editor/api/content/referential/ContentReferentialUIHandler.java
- client/datasource/editor/api/src/main/java/fr/ird/observe/client/datasource/editor/api/content/referential/ContentReferentialUIModelStates.java
- client/datasource/editor/api/src/main/java/fr/ird/observe/client/datasource/editor/api/content/referential/ReferentialSavePredicate.java
- client/datasource/editor/api/src/main/java/fr/ird/observe/client/datasource/editor/api/content/referential/actions/ReplaceReferentialUsages.java
- client/datasource/editor/api/src/main/java/fr/ird/observe/client/datasource/editor/api/content/referential/usage/UsageForDesactivateUIHandler.java
- core/api/services/pom.xml
- + core/api/services/src/main/resources/META-INF/services/Observe/ReferentialSecondLevelDeepBehaviourModel.json
- + core/api/services/src/test/java/fr/ird/observe/services/service/referential/ObserveReferentialSecondLevelDeepBehaviourModelTest.java
- core/persistence/java/src/main/java/fr/ird/observe/entities/referential/common/OceanSpi.java
- core/services/local/src/main/java/fr/ird/observe/services/local/service/referential/ReferentialServiceLocalSupport.java
- core/services/local/src/test/java/fr/ird/observe/services/local/service/referential/UnidirectionalResultIssue2208Test.java
- core/services/test/src/main/java/fr/ird/observe/services/service/referential/ReferentialServiceFixtures.java
- + model/src/main/models/Observe/dto/class/referentialSecondLevelDeepBehaviour.properties
- + toolkit/api-services/src/main/java/fr/ird/observe/services/service/referential/ExtraSaveRequest.java
- + toolkit/api-services/src/main/java/fr/ird/observe/services/service/referential/ReferentialSecondLevelDeepBehaviourModel.java
- toolkit/api-services/src/main/java/fr/ird/observe/services/service/referential/ReferentialService.java
- toolkit/persistence/src/main/java/fr/ird/observe/spi/context/ReferentialDtoEntityContext.java
- + toolkit/persistence/src/main/java/fr/ird/observe/spi/result/AddExtraSqlScripts.java
- toolkit/persistence/src/main/java/fr/ird/observe/spi/result/AddUpdateLastUpdateDateTableStep.java
- toolkit/persistence/src/main/java/fr/ird/observe/spi/result/PersistenceResultBuilder.java
- toolkit/templates/src/main/java/fr/ird/observe/toolkit/templates/GenerateServices.java
- toolkit/templates/src/main/java/fr/ird/observe/toolkit/templates/ToolkitTagValues.java
- + toolkit/templates/src/main/java/fr/ird/observe/toolkit/templates/services/GenerateReferentialSecondLevelDeepBehaviourClass.java
- + toolkit/templates/src/main/java/fr/ird/observe/toolkit/templates/services/GenerateReferentialSecondLevelDeepBehaviourFile.java
Changes:
| ... | ... | @@ -44,11 +44,11 @@ import fr.ird.observe.client.datasource.editor.api.content.referential.actions.S |
| 44 | 44 | import fr.ird.observe.client.datasource.editor.api.content.referential.actions.ShowUsagesReferential;
|
| 45 | 45 | import fr.ird.observe.client.util.UIHelper;
|
| 46 | 46 | import fr.ird.observe.datasource.security.ConcurrentModificationException;
|
| 47 | -import fr.ird.observe.dto.ToolkitIdLabel;
|
|
| 48 | 47 | import fr.ird.observe.dto.reference.ReferentialDtoReference;
|
| 49 | 48 | import fr.ird.observe.dto.referential.ReferentialDto;
|
| 50 | 49 | import fr.ird.observe.dto.referential.ReferentialDtoReferenceWithNoCodeAware;
|
| 51 | 50 | import fr.ird.observe.services.service.SaveResultDto;
|
| 51 | +import fr.ird.observe.services.service.referential.ExtraSaveRequest;
|
|
| 52 | 52 | import io.ultreia.java4all.i18n.I18n;
|
| 53 | 53 | import io.ultreia.java4all.jaxx.widgets.list.ListHeader;
|
| 54 | 54 | import org.apache.logging.log4j.LogManager;
|
| ... | ... | @@ -284,14 +284,8 @@ public class ContentReferentialUIHandler<D extends ReferentialDto, R extends Ref |
| 284 | 284 | }
|
| 285 | 285 | |
| 286 | 286 | protected SaveResultDto doSave(D bean) throws ConcurrentModificationException {
|
| 287 | - ToolkitIdLabel replaceReference = getModel().getStates().getReplaceReference();
|
|
| 288 | - if (replaceReference != null) {
|
|
| 289 | - String id = bean.getId();
|
|
| 290 | - String replaceId = replaceReference.getId();
|
|
| 291 | - log.info(String.format("Do replace reference before save (%s → %s)", id, replaceId));
|
|
| 292 | - getReferentialService().replaceReference(getModel().getSource().getScope().getMainType(), id, replaceId);
|
|
| 293 | - }
|
|
| 294 | - SaveResultDto result = getReferentialService().save(bean);
|
|
| 287 | + ExtraSaveRequest request = getModel().getStates().toExtraSaveRequest();
|
|
| 288 | + SaveResultDto result = getReferentialService().save(bean, request);
|
|
| 295 | 289 | bean.copy(getModel().getStates().getForm().getObject());
|
| 296 | 290 | return result;
|
| 297 | 291 | }
|
| ... | ... | @@ -30,6 +30,7 @@ import fr.ird.observe.client.datasource.editor.api.content.ContentUIModel; |
| 30 | 30 | import fr.ird.observe.client.datasource.editor.api.content.ContentUIModelStates;
|
| 31 | 31 | import fr.ird.observe.client.datasource.editor.api.content.actions.save.SavePredicate;
|
| 32 | 32 | import fr.ird.observe.client.util.init.DefaultUIInitializerResult;
|
| 33 | +import fr.ird.observe.dto.ToolkitIdBean;
|
|
| 33 | 34 | import fr.ird.observe.dto.ToolkitIdDtoBean;
|
| 34 | 35 | import fr.ird.observe.dto.ToolkitIdLabel;
|
| 35 | 36 | import fr.ird.observe.dto.form.Form;
|
| ... | ... | @@ -38,11 +39,20 @@ import fr.ird.observe.dto.referential.ReferentialDto; |
| 38 | 39 | import fr.ird.observe.dto.referential.ReferentialLocale;
|
| 39 | 40 | import fr.ird.observe.navigation.id.Project;
|
| 40 | 41 | import fr.ird.observe.services.ObserveServicesProvider;
|
| 42 | +import fr.ird.observe.services.service.referential.ExtraSaveRequest;
|
|
| 43 | +import org.apache.logging.log4j.LogManager;
|
|
| 44 | +import org.apache.logging.log4j.Logger;
|
|
| 41 | 45 | |
| 42 | 46 | import javax.swing.ListModel;
|
| 47 | +import java.util.Collection;
|
|
| 48 | +import java.util.LinkedHashMap;
|
|
| 43 | 49 | import java.util.LinkedList;
|
| 44 | 50 | import java.util.List;
|
| 51 | +import java.util.Map;
|
|
| 45 | 52 | import java.util.Objects;
|
| 53 | +import java.util.Optional;
|
|
| 54 | +import java.util.Set;
|
|
| 55 | +import java.util.stream.Collectors;
|
|
| 46 | 56 | |
| 47 | 57 | /**
|
| 48 | 58 | * Created on 30/10/2020.
|
| ... | ... | @@ -52,6 +62,7 @@ import java.util.Objects; |
| 52 | 62 | */
|
| 53 | 63 | public abstract class ContentReferentialUIModelStates<D extends ReferentialDto, R extends ReferentialDtoReference> extends ContentUIModelStates {
|
| 54 | 64 | public static final String PROPERTY_SELECTED_BEAN_REFERENCE = "selectedBeanReference";
|
| 65 | + private static final Logger log = LogManager.getLogger(ContentReferentialUIModelStates.class);
|
|
| 55 | 66 | /**
|
| 56 | 67 | * Reference cache.
|
| 57 | 68 | */
|
| ... | ... | @@ -67,6 +78,12 @@ public abstract class ContentReferentialUIModelStates<D extends ReferentialDto, |
| 67 | 78 | private Form<D> form;
|
| 68 | 79 | private ToolkitIdLabel replaceReference;
|
| 69 | 80 | private ContentReferentialUI<D, R, ?> ui;
|
| 81 | + /**
|
|
| 82 | + * To keep the second level usages when necessary).
|
|
| 83 | + *
|
|
| 84 | + * @since 9.4.0
|
|
| 85 | + */
|
|
| 86 | + private Map<Class<? extends ReferentialDto>, Collection<ToolkitIdLabel>> secondLevelUsages;
|
|
| 70 | 87 | |
| 71 | 88 | public ContentReferentialUIModelStates(ContentReferentialUIModel<D, R> model, D bean) {
|
| 72 | 89 | source = Objects.requireNonNull(model).getSource();
|
| ... | ... | @@ -194,7 +211,45 @@ public abstract class ContentReferentialUIModelStates<D extends ReferentialDto, |
| 194 | 211 | return mainType;
|
| 195 | 212 | }
|
| 196 | 213 | |
| 214 | + public ExtraSaveRequest toExtraSaveRequest() {
|
|
| 215 | + ToolkitIdLabel replaceReference = getReplaceReference();
|
|
| 216 | + if (replaceReference != null) {
|
|
| 217 | + String id = bean.getId();
|
|
| 218 | + String replaceId = replaceReference.getId();
|
|
| 219 | + log.info("Do replace reference before save ({} → {})", id, replaceId);
|
|
| 220 | + }
|
|
| 221 | + Map<Class<? extends ReferentialDto>, Set<String>> toDisableIds = getToDisableIds();
|
|
| 222 | + if (toDisableIds != null) {
|
|
| 223 | + toDisableIds.forEach((k, v) -> log.info("Do disable references before save of type {} → {} id(s)", k, v.size()));
|
|
| 224 | + }
|
|
| 225 | + String replaceId = Optional.ofNullable(replaceReference).map(ToolkitIdBean::getId).orElse(null);
|
|
| 226 | + if (replaceId == null && toDisableIds == null) {
|
|
| 227 | + return null;
|
|
| 228 | + }
|
|
| 229 | + return new ExtraSaveRequest(replaceId, toDisableIds);
|
|
| 230 | + }
|
|
| 231 | + |
|
| 232 | + private Map<Class<? extends ReferentialDto>, Set<String>> getToDisableIds() {
|
|
| 233 | + Map<Class<? extends ReferentialDto>, Collection<ToolkitIdLabel>> secondLevelUsages = getSecondLevelUsages();
|
|
| 234 | + Map<Class<? extends ReferentialDto>, Set<String>> toDisableIds;
|
|
| 235 | + if (secondLevelUsages == null) {
|
|
| 236 | + toDisableIds = null;
|
|
| 237 | + } else {
|
|
| 238 | + toDisableIds = new LinkedHashMap<>(secondLevelUsages.size());
|
|
| 239 | + secondLevelUsages.forEach((k, v) -> toDisableIds.put(k, v.stream().map(ToolkitIdBean::getId).collect(Collectors.toSet())));
|
|
| 240 | + }
|
|
| 241 | + return toDisableIds;
|
|
| 242 | + }
|
|
| 243 | + |
|
| 197 | 244 | public Class<R> mainReferenceType() {
|
| 198 | 245 | return mainReferenceType;
|
| 199 | 246 | }
|
| 247 | + |
|
| 248 | + public Map<Class<? extends ReferentialDto>, Collection<ToolkitIdLabel>> getSecondLevelUsages() {
|
|
| 249 | + return secondLevelUsages;
|
|
| 250 | + }
|
|
| 251 | + |
|
| 252 | + public void setSecondLevelUsages(Map<Class<? extends ReferentialDto>, Collection<ToolkitIdLabel>> secondLevelUsages) {
|
|
| 253 | + this.secondLevelUsages = secondLevelUsages;
|
|
| 254 | + }
|
|
| 200 | 255 | } |
| ... | ... | @@ -24,16 +24,28 @@ package fr.ird.observe.client.datasource.editor.api.content.referential; |
| 24 | 24 | |
| 25 | 25 | import fr.ird.observe.client.datasource.editor.api.content.actions.save.SavePredicateSupport;
|
| 26 | 26 | import fr.ird.observe.client.datasource.editor.api.content.referential.usage.UsageForDesactivateUIHandler;
|
| 27 | +import fr.ird.observe.dto.BusinessDto;
|
|
| 27 | 28 | import fr.ird.observe.dto.ToolkitIdDtoBean;
|
| 28 | 29 | import fr.ird.observe.dto.ToolkitIdLabel;
|
| 29 | 30 | import fr.ird.observe.dto.reference.ReferentialDtoReference;
|
| 30 | 31 | import fr.ird.observe.dto.referential.ReferenceStatus;
|
| 31 | 32 | import fr.ird.observe.dto.referential.ReferentialDto;
|
| 32 | 33 | import fr.ird.observe.services.service.UsageCount;
|
| 34 | +import fr.ird.observe.services.service.UsageCountWithLabel;
|
|
| 33 | 35 | import fr.ird.observe.services.service.UsageService;
|
| 36 | +import fr.ird.observe.services.service.referential.ObserveReferentialSecondLevelDeepBehaviourModel;
|
|
| 37 | +import fr.ird.observe.spi.module.ObserveBusinessProject;
|
|
| 38 | +import io.ultreia.java4all.i18n.I18n;
|
|
| 34 | 39 | import org.apache.commons.lang3.tuple.Pair;
|
| 40 | +import org.apache.logging.log4j.LogManager;
|
|
| 41 | +import org.apache.logging.log4j.Logger;
|
|
| 35 | 42 | |
| 43 | +import java.util.Collection;
|
|
| 44 | +import java.util.LinkedHashMap;
|
|
| 36 | 45 | import java.util.List;
|
| 46 | +import java.util.Map;
|
|
| 47 | +import java.util.Objects;
|
|
| 48 | +import java.util.Set;
|
|
| 37 | 49 | import java.util.stream.Collectors;
|
| 38 | 50 | |
| 39 | 51 | /**
|
| ... | ... | @@ -43,7 +55,7 @@ import java.util.stream.Collectors; |
| 43 | 55 | * @since 9.0.0
|
| 44 | 56 | */
|
| 45 | 57 | public class ReferentialSavePredicate<D extends ReferentialDto, R extends ReferentialDtoReference, S extends ContentReferentialUIModelStates<D, R>> extends SavePredicateSupport<S,D> {
|
| 46 | - |
|
| 58 | + private static final Logger log = LogManager.getLogger(ReferentialSavePredicate.class);
|
|
| 47 | 59 | public ReferentialSavePredicate(S states) {
|
| 48 | 60 | super(states);
|
| 49 | 61 | }
|
| ... | ... | @@ -66,26 +78,51 @@ public class ReferentialSavePredicate<D extends ReferentialDto, R extends Refere |
| 66 | 78 | ToolkitIdDtoBean request = states.toUsageRequest(bean.getId());
|
| 67 | 79 | UsageCount usages = usageService.countReferential(request);
|
| 68 | 80 | |
| 69 | - if (!usages.isEmpty()) {
|
|
| 70 | - // some usages were found
|
|
| 81 | + if (usages.isEmpty()) {
|
|
| 82 | + return true;
|
|
| 83 | + }
|
|
| 71 | 84 | |
| 72 | - // get replacements
|
|
| 73 | - List<R> referentialReferences = states.getReferentialReferences();
|
|
| 74 | - List<R> referenceList = referentialReferences
|
|
| 75 | - .stream()
|
|
| 76 | - .filter(ReferentialDtoReference::isEnabled)
|
|
| 77 | - .filter(r -> !bean.getId().equals(r.getId()))
|
|
| 78 | - .collect(Collectors.toList());
|
|
| 85 | + // some usages were found
|
|
| 86 | + UsageCountWithLabel realUsages = new UsageCountWithLabel(I18n.getDefaultLocale(), ObserveBusinessProject.get(), usages);
|
|
| 87 | + UsageForDesactivateUIHandler.DisableReferentialUsagesGetter getter = new UsageForDesactivateUIHandler.DisableReferentialUsagesGetter(realUsages, usageService, request);
|
|
| 79 | 88 | |
| 80 | - ToolkitIdLabel dtoLabel = bean.toLabel();
|
|
| 81 | - states.getDecoratorService().installToolkitIdLabelDecorator(bean.getClass(), dtoLabel);
|
|
| 82 | - Pair<Boolean, ToolkitIdLabel> result = UsageForDesactivateUIHandler.showUsages(usageService, dtoLabel, request, usages, referenceList.stream().map(ReferentialDtoReference::toLabel).collect(Collectors.toList()));
|
|
| 83 | - boolean willSave = result.getLeft();
|
|
| 84 | - if (!willSave) {
|
|
| 85 | - return false;
|
|
| 89 | + ObserveReferentialSecondLevelDeepBehaviourModel referentialSecondLevelDeepBehaviourModel = ObserveReferentialSecondLevelDeepBehaviourModel.get();
|
|
| 90 | + Map<Class<? extends ReferentialDto>, Collection<ToolkitIdLabel>> secondLevelUsages = new LinkedHashMap<>();
|
|
| 91 | + Set<Class<? extends BusinessDto>> typesFound = usages.keySet();
|
|
| 92 | + for (Class<? extends BusinessDto> type : typesFound) {
|
|
| 93 | + if (!ReferentialDto.class.isAssignableFrom(type) ) {
|
|
| 94 | + continue;
|
|
| 86 | 95 | }
|
| 87 | - states.setReplaceReference(result.getRight());
|
|
| 96 | + @SuppressWarnings("unchecked") Class<? extends ReferentialDto> referentialType = (Class<? extends ReferentialDto>) type;
|
|
| 97 | + if (referentialSecondLevelDeepBehaviourModel.useSecondLevelDeepBehaviour(referentialType)) {
|
|
| 98 | + Collection<ToolkitIdLabel> toolkitIdLabels = getter.getUsages(type).get();
|
|
| 99 | + log.info("Found second level type: {} - {} occurrence(s).", referentialType, toolkitIdLabels.size());
|
|
| 100 | + secondLevelUsages.put(referentialType,toolkitIdLabels);
|
|
| 101 | + }
|
|
| 102 | + }
|
|
| 103 | + if (!secondLevelUsages.isEmpty()) {
|
|
| 104 | + states.setSecondLevelUsages(secondLevelUsages);
|
|
| 105 | + }
|
|
| 106 | + |
|
| 107 | + // get replacements
|
|
| 108 | + List<R> referentialReferences = states.getReferentialReferences();
|
|
| 109 | + List<ToolkitIdLabel> referenceList = referentialReferences
|
|
| 110 | + .stream()
|
|
| 111 | + .filter(ReferentialDtoReference::isEnabled)
|
|
| 112 | + .filter(r -> !Objects.equals(bean.getId(), r.getId()))
|
|
| 113 | + .map(ReferentialDtoReference::toLabel)
|
|
| 114 | + .collect(Collectors.toList());
|
|
| 115 | + |
|
| 116 | + ToolkitIdLabel dtoLabel = bean.toLabel();
|
|
| 117 | + states.getDecoratorService().installToolkitIdLabelDecorator(bean.getClass(), dtoLabel);
|
|
| 118 | + Pair<Boolean, ToolkitIdLabel> result = UsageForDesactivateUIHandler.showUsages(dtoLabel,
|
|
| 119 | + getter,
|
|
| 120 | + referenceList);
|
|
| 121 | + boolean willSave = result.getLeft();
|
|
| 122 | + if (!willSave) {
|
|
| 123 | + return false;
|
|
| 88 | 124 | }
|
| 125 | + states.setReplaceReference(result.getRight());
|
|
| 89 | 126 | }
|
| 90 | 127 | return true;
|
| 91 | 128 | }
|
| ... | ... | @@ -57,7 +57,7 @@ import static io.ultreia.java4all.i18n.I18n.n; |
| 57 | 57 | */
|
| 58 | 58 | public class ReplaceReferentialUsages<D extends ReferentialDto, R extends ReferentialDtoReference, U extends ContentReferentialUI<D, R, U>> extends ContentReferentialUIActionSupport<D, R, U> implements ConfigureMenuAction<U> {
|
| 59 | 59 | |
| 60 | - private static final Logger log = LogManager.getLogger(DeleteReferential.class);
|
|
| 60 | + private static final Logger log = LogManager.getLogger(ReplaceReferentialUsages.class);
|
|
| 61 | 61 | |
| 62 | 62 | private ToolkitIdLabel replaceReference;
|
| 63 | 63 |
| ... | ... | @@ -29,12 +29,10 @@ import fr.ird.observe.client.util.init.UIInitHelper; |
| 29 | 29 | import fr.ird.observe.dto.BusinessDto;
|
| 30 | 30 | import fr.ird.observe.dto.ToolkitIdDtoBean;
|
| 31 | 31 | import fr.ird.observe.dto.ToolkitIdLabel;
|
| 32 | -import fr.ird.observe.services.service.UsageCount;
|
|
| 33 | 32 | import fr.ird.observe.services.service.UsageCountWithLabel;
|
| 34 | 33 | import fr.ird.observe.services.service.UsageService;
|
| 35 | 34 | import fr.ird.observe.spi.module.ObserveBusinessProject;
|
| 36 | 35 | import io.ultreia.java4all.application.template.spi.GenerateTemplate;
|
| 37 | -import io.ultreia.java4all.i18n.I18n;
|
|
| 38 | 36 | import io.ultreia.java4all.jaxx.widgets.combobox.FilterableComboBox;
|
| 39 | 37 | import io.ultreia.java4all.jaxx.widgets.combobox.FilterableComboBoxModel;
|
| 40 | 38 | import io.ultreia.java4all.util.SingletonSupplier;
|
| ... | ... | @@ -85,15 +83,10 @@ public class UsageForDesactivateUIHandler extends UsageUIHandlerSupport<UsageFor |
| 85 | 83 | }
|
| 86 | 84 | }
|
| 87 | 85 | |
| 88 | - public static Pair<Boolean, ToolkitIdLabel> showUsages(UsageService usageService,
|
|
| 89 | - ToolkitIdLabel dto,
|
|
| 90 | - ToolkitIdDtoBean request,
|
|
| 91 | - UsageCount usages,
|
|
| 86 | + public static Pair<Boolean, ToolkitIdLabel> showUsages(ToolkitIdLabel dto,
|
|
| 87 | + DisableReferentialUsagesGetter getter,
|
|
| 92 | 88 | List<ToolkitIdLabel> referenceList) {
|
| 93 | 89 | ObserveBusinessProject businessProject = ObserveBusinessProject.get();
|
| 94 | - UsageCountWithLabel realUsages = new UsageCountWithLabel(I18n.getDefaultLocale(), businessProject, usages);
|
|
| 95 | - DisableReferentialUsagesGetter getter = new DisableReferentialUsagesGetter(realUsages, usageService, request);
|
|
| 96 | - |
|
| 97 | 90 | Class<? extends BusinessDto> dtoType = dto.getType();
|
| 98 | 91 | String type = businessProject.getLongTitle(dtoType);
|
| 99 | 92 | String message = t("observe.ui.message.show.usage.referential.disabled", type, dto);
|
| ... | ... | @@ -84,6 +84,11 @@ |
| 84 | 84 | <groupId>io.ultreia.java4all.http</groupId>
|
| 85 | 85 | <artifactId>http-api</artifactId>
|
| 86 | 86 | </dependency>
|
| 87 | + <dependency>
|
|
| 88 | + <groupId>junit</groupId>
|
|
| 89 | + <artifactId>junit</artifactId>
|
|
| 90 | + <scope>test</scope>
|
|
| 91 | + </dependency>
|
|
| 87 | 92 | <dependency>
|
| 88 | 93 | <groupId>io.ultreia.java4all.http</groupId>
|
| 89 | 94 | <artifactId>http-spi-processor</artifactId>
|
| 1 | +[
|
|
| 2 | + "fr.ird.observe.dto.referential.common.LengthLengthParameterDto",
|
|
| 3 | + "fr.ird.observe.dto.referential.common.LengthWeightParameterDto",
|
|
| 4 | + "fr.ird.observe.dto.referential.ps.common.WeightCategoryDto"
|
|
| 5 | +] |
|
| \ No newline at end of file |
| 1 | +package fr.ird.observe.services.service.referential;
|
|
| 2 | + |
|
| 3 | +/*-
|
|
| 4 | + * #%L
|
|
| 5 | + * ObServe Core :: API :: Services
|
|
| 6 | + * %%
|
|
| 7 | + * Copyright (C) 2008 - 2024 IRD, Ultreia.io
|
|
| 8 | + * %%
|
|
| 9 | + * This program is free software: you can redistribute it and/or modify
|
|
| 10 | + * it under the terms of the GNU General Public License as
|
|
| 11 | + * published by the Free Software Foundation, either version 3 of the
|
|
| 12 | + * License, or (at your option) any later version.
|
|
| 13 | + *
|
|
| 14 | + * This program is distributed in the hope that it will be useful,
|
|
| 15 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 16 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 17 | + * GNU General Public License for more details.
|
|
| 18 | + *
|
|
| 19 | + * You should have received a copy of the GNU General Public
|
|
| 20 | + * License along with this program. If not, see
|
|
| 21 | + * <http://www.gnu.org/licenses/gpl-3.0.html>.
|
|
| 22 | + * #L%
|
|
| 23 | + */
|
|
| 24 | + |
|
| 25 | +import fr.ird.observe.dto.referential.ReferentialDto;
|
|
| 26 | +import fr.ird.observe.dto.referential.common.LengthLengthParameterDto;
|
|
| 27 | +import fr.ird.observe.dto.referential.common.LengthWeightParameterDto;
|
|
| 28 | +import fr.ird.observe.dto.referential.common.SpeciesDto;
|
|
| 29 | +import fr.ird.observe.dto.referential.ps.common.WeightCategoryDto;
|
|
| 30 | +import org.junit.Test;
|
|
| 31 | + |
|
| 32 | +import java.util.Iterator;
|
|
| 33 | +import java.util.Set;
|
|
| 34 | + |
|
| 35 | +import static org.junit.Assert.assertEquals;
|
|
| 36 | +import static org.junit.Assert.assertFalse;
|
|
| 37 | +import static org.junit.Assert.assertNotNull;
|
|
| 38 | +import static org.junit.Assert.assertTrue;
|
|
| 39 | + |
|
| 40 | +/**
|
|
| 41 | + * Created at 30/09/2024.
|
|
| 42 | + *
|
|
| 43 | + * @author Tony Chemit - dev@tchemit.fr
|
|
| 44 | + * @since 9.4.0
|
|
| 45 | + */
|
|
| 46 | +public class ObserveReferentialSecondLevelDeepBehaviourModelTest {
|
|
| 47 | + |
|
| 48 | + @Test
|
|
| 49 | + public void get() {
|
|
| 50 | + ObserveReferentialSecondLevelDeepBehaviourModel model = ObserveReferentialSecondLevelDeepBehaviourModel.get();
|
|
| 51 | + assertNotNull(model);
|
|
| 52 | + Set<Class<? extends ReferentialDto>> entries = model.entries();
|
|
| 53 | + assertEquals(3, entries.size());
|
|
| 54 | + Iterator<Class<? extends ReferentialDto>> iterator = entries.iterator();
|
|
| 55 | + assertEquals(LengthLengthParameterDto.class, iterator.next());
|
|
| 56 | + assertEquals(LengthWeightParameterDto.class, iterator.next());
|
|
| 57 | + assertEquals(WeightCategoryDto.class, iterator.next());
|
|
| 58 | + }
|
|
| 59 | + |
|
| 60 | + @Test
|
|
| 61 | + public void useSecondLevelDeepBehaviour() {
|
|
| 62 | + ObserveReferentialSecondLevelDeepBehaviourModel model = ObserveReferentialSecondLevelDeepBehaviourModel.get();
|
|
| 63 | + assertNotNull(model);
|
|
| 64 | + assertTrue(model.useSecondLevelDeepBehaviour(LengthLengthParameterDto.class));
|
|
| 65 | + assertTrue(model.useSecondLevelDeepBehaviour(LengthWeightParameterDto.class));
|
|
| 66 | + assertTrue(model.useSecondLevelDeepBehaviour(WeightCategoryDto.class));
|
|
| 67 | + assertFalse(model.useSecondLevelDeepBehaviour(SpeciesDto.class));
|
|
| 68 | + }
|
|
| 69 | +} |
| ... | ... | @@ -28,6 +28,7 @@ import fr.ird.observe.dto.referential.common.SpeciesReference; |
| 28 | 28 | import fr.ird.observe.services.service.SaveResultDto;
|
| 29 | 29 | import fr.ird.observe.spi.result.AddEntityToUpdateStep;
|
| 30 | 30 | import fr.ird.observe.spi.service.ServiceContext;
|
| 31 | +import io.ultreia.java4all.util.sql.SqlScript;
|
|
| 31 | 32 | |
| 32 | 33 | import java.util.LinkedHashSet;
|
| 33 | 34 | import java.util.List;
|
| ... | ... | @@ -54,7 +55,7 @@ public class OceanSpi extends GeneratedOceanSpi { |
| 54 | 55 | }
|
| 55 | 56 | |
| 56 | 57 | @Override
|
| 57 | - public SaveResultDto saveEntity(ServiceContext context, Ocean entity, OceanDto dto) {
|
|
| 58 | + public SaveResultDto saveEntity(ServiceContext context, Ocean entity, OceanDto dto, List<SqlScript> extraScripts) {
|
|
| 58 | 59 | Set<Species> speciesToUpdate = new LinkedHashSet<>();
|
| 59 | 60 | Set<String> newSpeciesWithThisOceanIds = dto.getSpecies().stream().map(SpeciesReference::getId).collect(Collectors.toSet());
|
| 60 | 61 | if (dto.isPersisted()) {
|
| ... | ... | @@ -87,6 +88,6 @@ public class OceanSpi extends GeneratedOceanSpi { |
| 87 | 88 | for (Species species : speciesToUpdate) {
|
| 88 | 89 | update.update(Species.SPI, species);
|
| 89 | 90 | }
|
| 90 | - return update.build(entity);
|
|
| 91 | + return update.addExtraSqlScripts(extraScripts).build(entity);
|
|
| 91 | 92 | }
|
| 92 | -} //OceanSpi |
|
| 93 | +} |
| ... | ... | @@ -31,6 +31,7 @@ import fr.ird.observe.dto.reference.ReferentialDtoReferenceSet; |
| 31 | 31 | import fr.ird.observe.dto.referential.ReferentialDto;
|
| 32 | 32 | import fr.ird.observe.services.local.service.ObserveServiceLocal;
|
| 33 | 33 | import fr.ird.observe.services.service.SaveResultDto;
|
| 34 | +import fr.ird.observe.services.service.referential.ExtraSaveRequest;
|
|
| 34 | 35 | import fr.ird.observe.services.service.referential.ReferentialIds;
|
| 35 | 36 | import fr.ird.observe.services.service.referential.ReferentialService;
|
| 36 | 37 | import fr.ird.observe.spi.context.ReferentialDtoEntityContext;
|
| ... | ... | @@ -85,10 +86,10 @@ class ReferentialServiceLocalSupport extends ObserveServiceLocal implements Refe |
| 85 | 86 | }
|
| 86 | 87 | |
| 87 | 88 | @Override
|
| 88 | - public <D extends ReferentialDto> SaveResultDto save(D bean) throws ConcurrentModificationException {
|
|
| 89 | + public <D extends ReferentialDto> SaveResultDto save(D bean, ExtraSaveRequest extraSaveRequest) throws ConcurrentModificationException {
|
|
| 89 | 90 | @SuppressWarnings("unchecked") Class<D> type = (Class<D>) bean.getClass();
|
| 90 | 91 | ReferentialDtoEntityContext<D, ?, ?, ?> spi = fromReferentialDto(type);
|
| 91 | - return spi.save(this, bean);
|
|
| 92 | + return spi.save(this, bean, extraSaveRequest);
|
|
| 92 | 93 | }
|
| 93 | 94 | |
| 94 | 95 | @Override
|
| ... | ... | @@ -31,6 +31,7 @@ import fr.ird.observe.dto.referential.common.SpeciesDto; |
| 31 | 31 | import fr.ird.observe.services.local.LocalTestClassResource;
|
| 32 | 32 | import fr.ird.observe.services.local.LocalTestMethodResourceWrite;
|
| 33 | 33 | import fr.ird.observe.services.local.service.ServiceLocalTestSupportWrite;
|
| 34 | +import fr.ird.observe.services.service.referential.ExtraSaveRequest;
|
|
| 34 | 35 | import fr.ird.observe.services.service.referential.ReferentialService;
|
| 35 | 36 | import fr.ird.observe.services.service.referential.ReferentialServiceFixtures;
|
| 36 | 37 | import fr.ird.observe.services.service.referential.SynchronizeEngine;
|
| ... | ... | @@ -121,7 +122,7 @@ public class UnidirectionalResultIssue2208Test extends ServiceLocalTestSupportWr |
| 121 | 122 | centralSpecies.getOcean().add(ocean);
|
| 122 | 123 | TOPIA_TEST_CLASS_RESOURCE_CENTRAL.setGenerateNow(true);
|
| 123 | 124 | try {
|
| 124 | - centralReferentialService.save(centralSpecies);
|
|
| 125 | + centralReferentialService.save(centralSpecies, null);
|
|
| 125 | 126 | } finally {
|
| 126 | 127 | TOPIA_TEST_CLASS_RESOURCE_CENTRAL.setGenerateNow(false);
|
| 127 | 128 | }
|
| ... | ... | @@ -189,7 +189,7 @@ public class ReferentialServiceFixtures extends GeneratedReferentialServiceFixtu |
| 189 | 189 | fr.ird.observe.dto.referential.ps.common.ProgramDto actual = service.loadDto(fr.ird.observe.dto.referential.ps.common.ProgramDto.class, getProperty("loadDto.id"));
|
| 190 | 190 | Assert.assertNotNull(actual);
|
| 191 | 191 | try {
|
| 192 | - SaveResultDto actualResult = service.save(actual);
|
|
| 192 | + SaveResultDto actualResult = service.save(actual, null);
|
|
| 193 | 193 | Assert.assertNotNull(actualResult);
|
| 194 | 194 | } catch (ConcurrentModificationException e) {
|
| 195 | 195 | throw new RuntimeException(e);
|
| 1 | +###
|
|
| 2 | +# #%L
|
|
| 3 | +# ObServe :: Model
|
|
| 4 | +# %%
|
|
| 5 | +# Copyright (C) 2008 - 2024 IRD, Ultreia.io
|
|
| 6 | +# %%
|
|
| 7 | +# This program is free software: you can redistribute it and/or modify
|
|
| 8 | +# it under the terms of the GNU General Public License as
|
|
| 9 | +# published by the Free Software Foundation, either version 3 of the
|
|
| 10 | +# License, or (at your option) any later version.
|
|
| 11 | +#
|
|
| 12 | +# This program is distributed in the hope that it will be useful,
|
|
| 13 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 14 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 15 | +# GNU General Public License for more details.
|
|
| 16 | +#
|
|
| 17 | +# You should have received a copy of the GNU General Public
|
|
| 18 | +# License along with this program. If not, see
|
|
| 19 | +# <http://www.gnu.org/licenses/gpl-3.0.html>.
|
|
| 20 | +# #L%
|
|
| 21 | +###
|
|
| 22 | +referential.common.LengthLengthParameter=true
|
|
| 23 | +referential.common.LengthWeightParameter=true
|
|
| 24 | +referential.ps.common.WeightCategory=true |
| 1 | +package fr.ird.observe.services.service.referential;
|
|
| 2 | + |
|
| 3 | +/*-
|
|
| 4 | + * #%L
|
|
| 5 | + * ObServe Toolkit :: API :: Services
|
|
| 6 | + * %%
|
|
| 7 | + * Copyright (C) 2008 - 2024 IRD, Ultreia.io
|
|
| 8 | + * %%
|
|
| 9 | + * This program is free software: you can redistribute it and/or modify
|
|
| 10 | + * it under the terms of the GNU General Public License as
|
|
| 11 | + * published by the Free Software Foundation, either version 3 of the
|
|
| 12 | + * License, or (at your option) any later version.
|
|
| 13 | + *
|
|
| 14 | + * This program is distributed in the hope that it will be useful,
|
|
| 15 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 16 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 17 | + * GNU General Public License for more details.
|
|
| 18 | + *
|
|
| 19 | + * You should have received a copy of the GNU General Public
|
|
| 20 | + * License along with this program. If not, see
|
|
| 21 | + * <http://www.gnu.org/licenses/gpl-3.0.html>.
|
|
| 22 | + * #L%
|
|
| 23 | + */
|
|
| 24 | + |
|
| 25 | +import fr.ird.observe.dto.referential.ReferentialDto;
|
|
| 26 | +import io.ultreia.java4all.util.json.JsonAware;
|
|
| 27 | + |
|
| 28 | +import java.util.Map;
|
|
| 29 | +import java.util.Optional;
|
|
| 30 | +import java.util.Set;
|
|
| 31 | + |
|
| 32 | +/**
|
|
| 33 | + * An object to define all extra actions that can be done while trying to save a referential.
|
|
| 34 | + * <p>
|
|
| 35 | + * Created at 30/09/2024.
|
|
| 36 | + *
|
|
| 37 | + * @author Tony Chemit - dev@tchemit.fr
|
|
| 38 | + * @since 9.4.0
|
|
| 39 | + */
|
|
| 40 | +public class ExtraSaveRequest implements JsonAware {
|
|
| 41 | + /**
|
|
| 42 | + * Optional replace id to perform before save.
|
|
| 43 | + */
|
|
| 44 | + private final String replaceId;
|
|
| 45 | + /**
|
|
| 46 | + * Optional disable id to perform before save.
|
|
| 47 | + */
|
|
| 48 | + private final Map<Class<? extends ReferentialDto>, Set<String>> toDisableIds;
|
|
| 49 | + |
|
| 50 | + public ExtraSaveRequest(String replaceId, Map<Class<? extends ReferentialDto>, Set<String>> toDisableIds) {
|
|
| 51 | + this.replaceId = replaceId;
|
|
| 52 | + this.toDisableIds = toDisableIds;
|
|
| 53 | + }
|
|
| 54 | + |
|
| 55 | + public Optional<String> getReplaceId() {
|
|
| 56 | + return Optional.ofNullable(replaceId);
|
|
| 57 | + }
|
|
| 58 | + |
|
| 59 | + public Optional<Map<Class<? extends ReferentialDto>, Set<String>>> getToDisableIds() {
|
|
| 60 | + return Optional.ofNullable(toDisableIds);
|
|
| 61 | + }
|
|
| 62 | +} |
| 1 | +package fr.ird.observe.services.service.referential;
|
|
| 2 | + |
|
| 3 | +/*-
|
|
| 4 | + * #%L
|
|
| 5 | + * ObServe Toolkit :: API :: Services
|
|
| 6 | + * %%
|
|
| 7 | + * Copyright (C) 2008 - 2024 IRD, Ultreia.io
|
|
| 8 | + * %%
|
|
| 9 | + * This program is free software: you can redistribute it and/or modify
|
|
| 10 | + * it under the terms of the GNU General Public License as
|
|
| 11 | + * published by the Free Software Foundation, either version 3 of the
|
|
| 12 | + * License, or (at your option) any later version.
|
|
| 13 | + *
|
|
| 14 | + * This program is distributed in the hope that it will be useful,
|
|
| 15 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 16 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 17 | + * GNU General Public License for more details.
|
|
| 18 | + *
|
|
| 19 | + * You should have received a copy of the GNU General Public
|
|
| 20 | + * License along with this program. If not, see
|
|
| 21 | + * <http://www.gnu.org/licenses/gpl-3.0.html>.
|
|
| 22 | + * #L%
|
|
| 23 | + */
|
|
| 24 | + |
|
| 25 | +import com.google.gson.Gson;
|
|
| 26 | +import com.google.gson.GsonBuilder;
|
|
| 27 | +import com.google.gson.reflect.TypeToken;
|
|
| 28 | +import fr.ird.observe.dto.referential.ReferentialDto;
|
|
| 29 | +import io.ultreia.java4all.util.json.JsonAware;
|
|
| 30 | +import io.ultreia.java4all.util.json.adapters.ClassAdapter;
|
|
| 31 | + |
|
| 32 | +import java.io.BufferedReader;
|
|
| 33 | +import java.io.IOException;
|
|
| 34 | +import java.io.InputStreamReader;
|
|
| 35 | +import java.io.Reader;
|
|
| 36 | +import java.net.URL;
|
|
| 37 | +import java.nio.charset.StandardCharsets;
|
|
| 38 | +import java.util.Collections;
|
|
| 39 | +import java.util.LinkedHashSet;
|
|
| 40 | +import java.util.Objects;
|
|
| 41 | +import java.util.Set;
|
|
| 42 | + |
|
| 43 | +/**
|
|
| 44 | + * Created at 30/09/2024.
|
|
| 45 | + *
|
|
| 46 | + * @author Tony Chemit - dev@tchemit.fr
|
|
| 47 | + * @since 9.4.0
|
|
| 48 | + */
|
|
| 49 | +public class ReferentialSecondLevelDeepBehaviourModel implements JsonAware {
|
|
| 50 | + |
|
| 51 | + public static String toLocation(String modelName) {
|
|
| 52 | + return String.format("META-INF/services/%s/%s.json", Objects.requireNonNull(modelName), ReferentialSecondLevelDeepBehaviourModel.class.getSimpleName());
|
|
| 53 | + }
|
|
| 54 | + |
|
| 55 | + public static Gson newGson() {
|
|
| 56 | + return new GsonBuilder()
|
|
| 57 | + .disableHtmlEscaping()
|
|
| 58 | + .setPrettyPrinting()
|
|
| 59 | + .enableComplexMapKeySerialization()
|
|
| 60 | + .registerTypeAdapter(Class.class, new ClassAdapter())
|
|
| 61 | + .create();
|
|
| 62 | + }
|
|
| 63 | + |
|
| 64 | + private final Set<Class<? extends ReferentialDto>> entries;
|
|
| 65 | + |
|
| 66 | + public ReferentialSecondLevelDeepBehaviourModel(String modelName) {
|
|
| 67 | + String location = toLocation(Objects.requireNonNull(modelName));
|
|
| 68 | + URL resource = Objects.requireNonNull(ReferentialSecondLevelDeepBehaviourModel.class).getClassLoader().getResource(Objects.requireNonNull(location));
|
|
| 69 | + try (Reader reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(resource).openStream(), StandardCharsets.UTF_8))) {
|
|
| 70 | + this.entries = Collections.unmodifiableSet(newGson().fromJson(reader, new TypeToken<LinkedHashSet<Class<? extends ReferentialDto>>>() {
|
|
| 71 | + }.getType()));
|
|
| 72 | + } catch (IOException e) {
|
|
| 73 | + throw new IllegalStateException(String.format("Could not load resource: %s (for type: %s)", resource, getClass().getName()), e);
|
|
| 74 | + }
|
|
| 75 | + }
|
|
| 76 | + |
|
| 77 | + public Set<Class<? extends ReferentialDto>> entries() {
|
|
| 78 | + return entries;
|
|
| 79 | + }
|
|
| 80 | + |
|
| 81 | + public boolean useSecondLevelDeepBehaviour(Class<? extends ReferentialDto> type) {
|
|
| 82 | + return entries().contains(type);
|
|
| 83 | + }
|
|
| 84 | +} |
| ... | ... | @@ -96,9 +96,9 @@ public interface ReferentialService extends ObserveService { |
| 96 | 96 | <D extends ReferentialDto> Form<D> preCreate(Class<D> type);
|
| 97 | 97 | |
| 98 | 98 | @Write
|
| 99 | - @Post
|
|
| 99 | + @Post(useMultiPartForm = true)
|
|
| 100 | 100 | @MethodCredential(Permission.WRITE_REFERENTIAL)
|
| 101 | - <D extends ReferentialDto> SaveResultDto save(D bean) throws ConcurrentModificationException;
|
|
| 101 | + <D extends ReferentialDto> SaveResultDto save(D bean, @Nullable ExtraSaveRequest extraSaveRequest) throws ConcurrentModificationException;
|
|
| 102 | 102 | |
| 103 | 103 | @Write
|
| 104 | 104 | @Delete
|
| ... | ... | @@ -40,6 +40,7 @@ import fr.ird.observe.entities.referential.I18nReferentialEntity; |
| 40 | 40 | import fr.ird.observe.entities.referential.ReferentialEntity;
|
| 41 | 41 | import fr.ird.observe.services.service.SaveResultDto;
|
| 42 | 42 | import fr.ird.observe.services.service.UsageCount;
|
| 43 | +import fr.ird.observe.services.service.referential.ExtraSaveRequest;
|
|
| 43 | 44 | import fr.ird.observe.spi.referential.ReferentialExtraScripts;
|
| 44 | 45 | import fr.ird.observe.spi.service.ServiceContext;
|
| 45 | 46 | import fr.ird.observe.spi.usage.UsageHelper;
|
| ... | ... | @@ -52,10 +53,11 @@ import org.nuiton.topia.persistence.TopiaEntity; |
| 52 | 53 | import org.nuiton.topia.persistence.TopiaException;
|
| 53 | 54 | import org.nuiton.topia.service.sql.script.DeleteReferentialScript;
|
| 54 | 55 | import org.nuiton.topia.service.sql.script.DisableReferentialScript;
|
| 55 | -import org.nuiton.topia.service.sql.script.ReplaceReferentialInReferentialScript;
|
|
| 56 | 56 | import org.nuiton.topia.service.sql.script.ReplaceReferentialInDataScript;
|
| 57 | +import org.nuiton.topia.service.sql.script.ReplaceReferentialInReferentialScript;
|
|
| 57 | 58 | import org.nuiton.topia.service.sql.script.TopiaEntitySqlScript;
|
| 58 | 59 | |
| 60 | +import java.sql.Timestamp;
|
|
| 59 | 61 | import java.util.ArrayList;
|
| 60 | 62 | import java.util.Collection;
|
| 61 | 63 | import java.util.Comparator;
|
| ... | ... | @@ -83,23 +85,22 @@ public abstract class ReferentialDtoEntityContext< |
| 83 | 85 | |
| 84 | 86 | private final SingletonSupplier<ReferentialExtraScripts<E>> extraScripts;
|
| 85 | 87 | |
| 86 | - protected abstract TopiaEntitySqlScript loadSqlScript();
|
|
| 87 | - |
|
| 88 | - |
|
| 89 | - protected abstract ReferentialExtraScripts<E> loadExtraScripts();
|
|
| 90 | - |
|
| 91 | 88 | public ReferentialDtoEntityContext() {
|
| 92 | 89 | this.extraScripts = SingletonSupplier.of(this::loadExtraScripts);
|
| 93 | 90 | }
|
| 94 | 91 | |
| 92 | + protected abstract TopiaEntitySqlScript loadSqlScript();
|
|
| 93 | + |
|
| 94 | + protected abstract ReferentialExtraScripts<E> loadExtraScripts();
|
|
| 95 | + |
|
| 95 | 96 | @Override
|
| 96 | 97 | public Form<D> loadForm(ServiceContext context, String id) {
|
| 97 | 98 | E entity = loadEntity(context, id);
|
| 98 | 99 | return referentialEntityToForm(context.getReferentialLocale(), entity);
|
| 99 | 100 | }
|
| 100 | 101 | |
| 101 | - public SaveResultDto saveEntity(ServiceContext context, E entity, D dto) {
|
|
| 102 | - return saveEntity(context, entity);
|
|
| 102 | + public SaveResultDto saveEntity(ServiceContext context, E entity, D dto, List<SqlScript> extraScripts) {
|
|
| 103 | + return newSaveHelper(context).update(this, entity).addExtraSqlScripts(extraScripts).build(entity);
|
|
| 103 | 104 | }
|
| 104 | 105 | |
| 105 | 106 | public Optional<ReplaceReferentialInDataScript> getReplaceInDataScript() {
|
| ... | ... | @@ -261,6 +262,15 @@ public abstract class ReferentialDtoEntityContext< |
| 261 | 262 | sqlScript.ifPresent(s -> context.getTopiaPersistenceContext().executeSqlScript(s));
|
| 262 | 263 | }
|
| 263 | 264 | |
| 265 | + public final Optional<SqlScript> replaceReferenceScript(ServiceContext context, String idToReplace, String replaceId, boolean replaceInReferential, boolean replaceInData) {
|
|
| 266 | + if (!replaceInReferential && !replaceInData) {
|
|
| 267 | + throw new IllegalStateException("Need at least one of the parameters *replaceInReferential* or *replaceInData* to be on.");
|
|
| 268 | + }
|
|
| 269 | + checkEntityExists(context, idToReplace);
|
|
| 270 | + checkEntityExists(context, replaceId);
|
|
| 271 | + return generateReplaceScript(idToReplace, replaceId, context.now(), replaceInReferential, replaceInData);
|
|
| 272 | + }
|
|
| 273 | + |
|
| 264 | 274 | public final void changeId(ServiceContext context, String id, String newId) {
|
| 265 | 275 | checkEntityExists(context, id);
|
| 266 | 276 | log.info(String.format("will change id from %s to %s", id, newId));
|
| ... | ... | @@ -284,11 +294,31 @@ public abstract class ReferentialDtoEntityContext< |
| 284 | 294 | // delete(context, id);
|
| 285 | 295 | }
|
| 286 | 296 | |
| 287 | - public final SaveResultDto save(ServiceContext context, D dto) throws ConcurrentModificationException {
|
|
| 297 | + public final SaveResultDto save(ServiceContext context, D dto, ExtraSaveRequest extraSaveRequest) throws ConcurrentModificationException {
|
|
| 288 | 298 | E entity = loadOrCreateEntityFromReferentialDto(context, dto);
|
| 289 | 299 | checkLastUpdateDate(context, entity, dto);
|
| 300 | + // if replace id is here, let's do the replacement in data (never replace in referential)
|
|
| 301 | + List<SqlScript> extraScripts = new ArrayList<>();
|
|
| 302 | + extraSaveRequest.getReplaceId().ifPresent(replaceId -> {
|
|
| 303 | + Optional<SqlScript> sqlScript = replaceReferenceScript(context, dto.getId(), replaceId, false, true);
|
|
| 304 | + sqlScript.ifPresent(extraScripts::add);
|
|
| 305 | + });
|
|
| 306 | + Timestamp timestamp = context.timestampNow();
|
|
| 307 | + extraSaveRequest.getToDisableIds().ifPresent(toDisableIds -> {
|
|
| 308 | + // if toDisableIds is here, let's do the disable actions
|
|
| 309 | + List<String> sqlStatements = new ArrayList<>();
|
|
| 310 | + toDisableIds.forEach((k, v) -> {
|
|
| 311 | + ReferentialDtoEntityContext<? extends ReferentialDto, ?, ?, ?> spi2 = context.fromReferentialDto(k);
|
|
| 312 | + DisableReferentialScript disableScript = spi2.getDisableScript();
|
|
| 313 | + v.forEach(id -> sqlStatements.addAll(disableScript.generate(id, timestamp)));
|
|
| 314 | + sqlStatements.addAll(spi2.updateLastUpdateDateTable(context.getTopiaPersistenceContext(), timestamp));
|
|
| 315 | + });
|
|
| 316 | + if (!sqlStatements.isEmpty()) {
|
|
| 317 | + extraScripts.add(SqlScript.of(String.join("\n", sqlStatements)));
|
|
| 318 | + }
|
|
| 319 | + });
|
|
| 290 | 320 | fromDto(context.getReferentialLocale(), entity, dto);
|
| 291 | - return saveEntity(context, entity, dto);
|
|
| 321 | + return saveEntity(context, entity, dto, extraScripts);
|
|
| 292 | 322 | }
|
| 293 | 323 | |
| 294 | 324 | private SqlScript generateDuplicateScript(ServiceContext context, String id, String newId) {
|
| ... | ... | @@ -357,10 +387,6 @@ public abstract class ReferentialDtoEntityContext< |
| 357 | 387 | return toReference(context.getReferentialLocale(), entity);
|
| 358 | 388 | }
|
| 359 | 389 | |
| 360 | - public final SaveResultDto saveEntity(ServiceContext context, E entity) {
|
|
| 361 | - return newSaveHelper(context).update(this, entity).build(entity);
|
|
| 362 | - }
|
|
| 363 | - |
|
| 364 | 390 | public final UsageCount count(ServiceContext context, ToolkitIdDtoBean request) {
|
| 365 | 391 | ReferentialEntity entity = loadEntity(context, request.getId());
|
| 366 | 392 | return newUsageHelper(context).count(entity);
|
| 1 | +package fr.ird.observe.spi.result;
|
|
| 2 | + |
|
| 3 | +/*-
|
|
| 4 | + * #%L
|
|
| 5 | + * ObServe Toolkit :: Persistence
|
|
| 6 | + * %%
|
|
| 7 | + * Copyright (C) 2008 - 2023 IRD, Ultreia.io
|
|
| 8 | + * %%
|
|
| 9 | + * This program is free software: you can redistribute it and/or modify
|
|
| 10 | + * it under the terms of the GNU General Public License as
|
|
| 11 | + * published by the Free Software Foundation, either version 3 of the
|
|
| 12 | + * License, or (at your option) any later version.
|
|
| 13 | + *
|
|
| 14 | + * This program is distributed in the hope that it will be useful,
|
|
| 15 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 16 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 17 | + * GNU General Public License for more details.
|
|
| 18 | + *
|
|
| 19 | + * You should have received a copy of the GNU General Public
|
|
| 20 | + * License along with this program. If not, see
|
|
| 21 | + * <http://www.gnu.org/licenses/gpl-3.0.html>.
|
|
| 22 | + * #L%
|
|
| 23 | + */
|
|
| 24 | + |
|
| 25 | +import io.ultreia.java4all.util.sql.SqlScript;
|
|
| 26 | + |
|
| 27 | +import java.util.List;
|
|
| 28 | + |
|
| 29 | +/**
|
|
| 30 | + * Created at 30/09/2024.
|
|
| 31 | + *
|
|
| 32 | + * @author Tony Chemit - dev@tchemit.fr
|
|
| 33 | + * @since 9.4.0
|
|
| 34 | + */
|
|
| 35 | +public interface AddExtraSqlScripts extends BuildStep {
|
|
| 36 | + |
|
| 37 | + default AddExtraSqlScripts addExtraSqlScripts(List<SqlScript> sqlScripts) {
|
|
| 38 | + sqlScripts.forEach(this::addExtraSqlScript);
|
|
| 39 | + return this;
|
|
| 40 | + }
|
|
| 41 | + |
|
| 42 | + AddExtraSqlScripts addExtraSqlScript(SqlScript sqlScript);
|
|
| 43 | +} |
| ... | ... | @@ -30,7 +30,7 @@ import fr.ird.observe.spi.context.DtoEntityContext; |
| 30 | 30 | * @author Tony Chemit - dev@tchemit.fr
|
| 31 | 31 | * @since 6.0.4
|
| 32 | 32 | */
|
| 33 | -public interface AddUpdateLastUpdateDateTableStep extends BuildStep {
|
|
| 33 | +public interface AddUpdateLastUpdateDateTableStep extends AddExtraSqlScripts {
|
|
| 34 | 34 | |
| 35 | 35 | AddUpdateLastUpdateDateTableStep updateLastUpdateDateTable(DtoEntityContext<?, ?, ?, ?> spi);
|
| 36 | 36 | } |
| ... | ... | @@ -26,6 +26,7 @@ import fr.ird.observe.entities.Entity; |
| 26 | 26 | import fr.ird.observe.entities.ToolkitTopiaPersistenceContextSupport;
|
| 27 | 27 | import fr.ird.observe.services.service.SaveResultDto;
|
| 28 | 28 | import fr.ird.observe.spi.context.DtoEntityContext;
|
| 29 | +import io.ultreia.java4all.util.sql.SqlScript;
|
|
| 29 | 30 | |
| 30 | 31 | import java.util.Date;
|
| 31 | 32 | import java.util.LinkedList;
|
| ... | ... | @@ -41,12 +42,13 @@ import java.util.function.Supplier; |
| 41 | 42 | * @author Tony Chemit - dev@tchemit.fr
|
| 42 | 43 | * @since 5.0.10
|
| 43 | 44 | */
|
| 44 | -public class PersistenceResultBuilder implements AddEntityToUpdateStep, AddUpdateLastUpdateDateFieldStep, AddUpdateLastUpdateDateTableStep, BuildStep {
|
|
| 45 | +public class PersistenceResultBuilder implements AddEntityToUpdateStep, AddUpdateLastUpdateDateFieldStep, AddUpdateLastUpdateDateTableStep, AddExtraSqlScripts, BuildStep {
|
|
| 45 | 46 | private final List<EntityContext<?>> updates;
|
| 46 | 47 | private final List<EntityContext<?>> creates;
|
| 47 | 48 | private final List<EntityContext<?>> createsReal;
|
| 48 | 49 | private final List<EntityContext<?>> lastUpdateDateFields;
|
| 49 | 50 | private final List<DtoEntityContext<?, ?, ?, ?>> lastUpdateDateTables;
|
| 51 | + private final List<SqlScript> extraScriptScripts;
|
|
| 50 | 52 | private final ToolkitTopiaPersistenceContextSupport persistenceContext;
|
| 51 | 53 | private final Supplier<Date> lastUpdateDateSupplier;
|
| 52 | 54 | |
| ... | ... | @@ -62,6 +64,7 @@ public class PersistenceResultBuilder implements AddEntityToUpdateStep, AddUpdat |
| 62 | 64 | this.createsReal = new LinkedList<>();
|
| 63 | 65 | this.lastUpdateDateFields = new LinkedList<>();
|
| 64 | 66 | this.lastUpdateDateTables = new LinkedList<>();
|
| 67 | + this.extraScriptScripts = new LinkedList<>();
|
|
| 65 | 68 | }
|
| 66 | 69 | |
| 67 | 70 | @Override
|
| ... | ... | @@ -111,6 +114,12 @@ public class PersistenceResultBuilder implements AddEntityToUpdateStep, AddUpdat |
| 111 | 114 | return this;
|
| 112 | 115 | }
|
| 113 | 116 | |
| 117 | + @Override
|
|
| 118 | + public AddExtraSqlScripts addExtraSqlScript(SqlScript sqlScript) {
|
|
| 119 | + extraScriptScripts.add(Objects.requireNonNull(sqlScript));
|
|
| 120 | + return this;
|
|
| 121 | + }
|
|
| 122 | + |
|
| 114 | 123 | @Override
|
| 115 | 124 | public Date build() {
|
| 116 | 125 | Date lastUpdateDate = lastUpdateDateSupplier.get();
|
| ... | ... | @@ -137,6 +146,7 @@ public class PersistenceResultBuilder implements AddEntityToUpdateStep, AddUpdat |
| 137 | 146 | for (DtoEntityContext<?, ?, ?, ?> spi : lastUpdateDateTables) {
|
| 138 | 147 | spi.updateLastUpdateDateTable(persistenceContext, lastUpdateDate);
|
| 139 | 148 | }
|
| 149 | + extraScriptScripts.forEach(persistenceContext::executeSqlScript);
|
|
| 140 | 150 | return lastUpdateDate;
|
| 141 | 151 | }
|
| 142 | 152 |
| ... | ... | @@ -24,6 +24,8 @@ package fr.ird.observe.toolkit.templates; |
| 24 | 24 | |
| 25 | 25 | import fr.ird.observe.toolkit.templates.services.GenerateDifferentialMetaModelClass;
|
| 26 | 26 | import fr.ird.observe.toolkit.templates.services.GenerateDifferentialMetaModelFile;
|
| 27 | +import fr.ird.observe.toolkit.templates.services.GenerateReferentialSecondLevelDeepBehaviourClass;
|
|
| 28 | +import fr.ird.observe.toolkit.templates.services.GenerateReferentialSecondLevelDeepBehaviourFile;
|
|
| 27 | 29 | import org.codehaus.plexus.component.annotations.Component;
|
| 28 | 30 | import org.nuiton.eugene.Template;
|
| 29 | 31 | |
| ... | ... | @@ -38,7 +40,9 @@ public class GenerateServices extends DtoMetaTransformer { |
| 38 | 40 | public GenerateServices() {
|
| 39 | 41 | setTemplateTypes(
|
| 40 | 42 | GenerateDifferentialMetaModelFile.class,
|
| 41 | - GenerateDifferentialMetaModelClass.class
|
|
| 43 | + GenerateDifferentialMetaModelClass.class,
|
|
| 44 | + GenerateReferentialSecondLevelDeepBehaviourFile.class,
|
|
| 45 | + GenerateReferentialSecondLevelDeepBehaviourClass.class
|
|
| 42 | 46 | );
|
| 43 | 47 | }
|
| 44 | 48 | } |
| ... | ... | @@ -145,6 +145,19 @@ public class ToolkitTagValues extends DefaultTagValueMetadatasProvider { |
| 145 | 145 | return store.findAttributeBooleanTagValue(Store.skipCopyToEntity, clazz, attribute);
|
| 146 | 146 | }
|
| 147 | 147 | |
| 148 | + /**
|
|
| 149 | + * Obtain the value of the {@link Store#referentialSecondLevelDeepBehaviour} tag value on the given classifier.
|
|
| 150 | + *
|
|
| 151 | + * @param store tag-values store
|
|
| 152 | + * @param clazz attribute classifier
|
|
| 153 | + * @return the none empty value of the found tag value or {@code null} if not found nor empty.
|
|
| 154 | + * @see Store#referentialSecondLevelDeepBehaviour
|
|
| 155 | + * @see <a href="https://gitlab.com/ultreiaio/ird-observe/-/issues/2846">issue 2846</a>
|
|
| 156 | + */
|
|
| 157 | + public boolean isReferentialSecondLevelDeepBehaviour(ObjectModelTagValuesStore store, ObjectModelClass clazz) {
|
|
| 158 | + return store.findClassifierBooleanTagValue(Store.referentialSecondLevelDeepBehaviour, clazz);
|
|
| 159 | + }
|
|
| 160 | + |
|
| 148 | 161 | public String notSkip(String tagValue) {
|
| 149 | 162 | return Objects.equals("skip", tagValue) ? null : tagValue;
|
| 150 | 163 | }
|
| ... | ... | @@ -174,6 +187,11 @@ public class ToolkitTagValues extends DefaultTagValueMetadatasProvider { |
| 174 | 187 | |
| 175 | 188 | form("Pour qualifier les propriétés à générer", String.class, null, ObjectModelClassifier.class, ObjectModelPackage.class),
|
| 176 | 189 | |
| 190 | + /**
|
|
| 191 | + * @see <a href="https://gitlab.com/ultreiaio/ird-observe/-/issues/2846">issue 2846</a>
|
|
| 192 | + */
|
|
| 193 | + referentialSecondLevelDeepBehaviour("Pour qualifier le comportement à utiliser pour les référentiels lors d'une suppression, désactivation ou remplacement (false = comportement par défaut, true = pour effectuer une opération en profondeur sur les référentiels utilisés de second niveau", boolean.class, "false", ObjectModelClassifier.class),
|
|
| 194 | + |
|
| 177 | 195 | packagePriority("Pour prioriser les paquetages", int.class, null, ObjectModelPackage.class);
|
| 178 | 196 | |
| 179 | 197 | private final Set<Class<?>> targets;
|
| 1 | +package fr.ird.observe.toolkit.templates.services;
|
|
| 2 | + |
|
| 3 | +/*-
|
|
| 4 | + * #%L
|
|
| 5 | + * ObServe Toolkit :: Templates
|
|
| 6 | + * %%
|
|
| 7 | + * Copyright (C) 2008 - 2024 IRD, Ultreia.io
|
|
| 8 | + * %%
|
|
| 9 | + * This program is free software: you can redistribute it and/or modify
|
|
| 10 | + * it under the terms of the GNU General Public License as
|
|
| 11 | + * published by the Free Software Foundation, either version 3 of the
|
|
| 12 | + * License, or (at your option) any later version.
|
|
| 13 | + *
|
|
| 14 | + * This program is distributed in the hope that it will be useful,
|
|
| 15 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 16 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 17 | + * GNU General Public License for more details.
|
|
| 18 | + *
|
|
| 19 | + * You should have received a copy of the GNU General Public
|
|
| 20 | + * License along with this program. If not, see
|
|
| 21 | + * <http://www.gnu.org/licenses/gpl-3.0.html>.
|
|
| 22 | + * #L%
|
|
| 23 | + */
|
|
| 24 | + |
|
| 25 | +import fr.ird.observe.services.service.referential.ReferentialSecondLevelDeepBehaviourModel;
|
|
| 26 | +import fr.ird.observe.toolkit.templates.TemplateContract;
|
|
| 27 | +import org.nuiton.eugene.java.ObjectModelTransformerToJava;
|
|
| 28 | +import org.nuiton.eugene.models.object.ObjectModel;
|
|
| 29 | +import org.nuiton.eugene.models.object.ObjectModelClass;
|
|
| 30 | +import org.nuiton.eugene.models.object.ObjectModelJavaModifier;
|
|
| 31 | +import org.nuiton.eugene.models.object.ObjectModelOperation;
|
|
| 32 | + |
|
| 33 | +/**
|
|
| 34 | + * Created at 30/09/2024.
|
|
| 35 | + *
|
|
| 36 | + * @author Tony Chemit - dev@tchemit.fr
|
|
| 37 | + * @since 9.4.0
|
|
| 38 | + */
|
|
| 39 | +public class GenerateReferentialSecondLevelDeepBehaviourClass extends ObjectModelTransformerToJava implements TemplateContract {
|
|
| 40 | + |
|
| 41 | + @Override
|
|
| 42 | + public void transformFromModel(ObjectModel model) {
|
|
| 43 | + String packageName = getDefaultPackageName().replace(".dto", ".services.service.referential");
|
|
| 44 | + String modelName = model.getName();
|
|
| 45 | + ObjectModelClass modelSupport = createClass(modelName + ReferentialSecondLevelDeepBehaviourModel.class.getSimpleName(), packageName);
|
|
| 46 | + setSuperClass(modelSupport, ReferentialSecondLevelDeepBehaviourModel.class);
|
|
| 47 | + addStaticFactory(modelSupport, modelSupport.getQualifiedName());
|
|
| 48 | + ObjectModelOperation constructor = addConstructor(modelSupport, ObjectModelJavaModifier.PUBLIC);
|
|
| 49 | + setOperationBody(constructor, ""/*{
|
|
| 50 | + super("<%=modelName%>");
|
|
| 51 | + }*/);
|
|
| 52 | + }
|
|
| 53 | + |
|
| 54 | +} |
| 1 | +package fr.ird.observe.toolkit.templates.services;
|
|
| 2 | + |
|
| 3 | +/*-
|
|
| 4 | + * #%L
|
|
| 5 | + * ObServe Toolkit :: Templates
|
|
| 6 | + * %%
|
|
| 7 | + * Copyright (C) 2008 - 2024 IRD, Ultreia.io
|
|
| 8 | + * %%
|
|
| 9 | + * This program is free software: you can redistribute it and/or modify
|
|
| 10 | + * it under the terms of the GNU General Public License as
|
|
| 11 | + * published by the Free Software Foundation, either version 3 of the
|
|
| 12 | + * License, or (at your option) any later version.
|
|
| 13 | + *
|
|
| 14 | + * This program is distributed in the hope that it will be useful,
|
|
| 15 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 16 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 17 | + * GNU General Public License for more details.
|
|
| 18 | + *
|
|
| 19 | + * You should have received a copy of the GNU General Public
|
|
| 20 | + * License along with this program. If not, see
|
|
| 21 | + * <http://www.gnu.org/licenses/gpl-3.0.html>.
|
|
| 22 | + * #L%
|
|
| 23 | + */
|
|
| 24 | + |
|
| 25 | +import fr.ird.observe.services.service.referential.ReferentialSecondLevelDeepBehaviourModel;
|
|
| 26 | +import fr.ird.observe.toolkit.templates.ToolkitTagValues;
|
|
| 27 | +import org.nuiton.eugene.models.object.ObjectModel;
|
|
| 28 | +import org.nuiton.eugene.models.object.ObjectModelClass;
|
|
| 29 | +import org.nuiton.eugene.models.object.ObjectModelGenerator;
|
|
| 30 | +import org.nuiton.eugene.models.object.ObjectModelType;
|
|
| 31 | +import org.nuiton.topia.templates.sql.TopiaMetadataModelGeneratorSupport;
|
|
| 32 | + |
|
| 33 | +import java.io.File;
|
|
| 34 | +import java.io.Writer;
|
|
| 35 | +import java.util.Collections;
|
|
| 36 | +import java.util.LinkedList;
|
|
| 37 | +import java.util.List;
|
|
| 38 | + |
|
| 39 | +/**
|
|
| 40 | + * Created at 30/09/2024.
|
|
| 41 | + *
|
|
| 42 | + * @author Tony Chemit - dev@tchemit.fr
|
|
| 43 | + * @since 9.4.0
|
|
| 44 | + */
|
|
| 45 | +public class GenerateReferentialSecondLevelDeepBehaviourFile extends ObjectModelGenerator {
|
|
| 46 | + |
|
| 47 | + @Override
|
|
| 48 | + public void applyTemplate(ObjectModel model, File destDir) {
|
|
| 49 | + this.model = model;
|
|
| 50 | + |
|
| 51 | + File realTarget = TopiaMetadataModelGeneratorSupport.getNotGeneratedResourceDirector(destDir);
|
|
| 52 | + String filename = getFilenameForModel(model);
|
|
| 53 | + generateFromElement(model, realTarget, filename, ObjectModelType.OBJECT_MODEL);
|
|
| 54 | + }
|
|
| 55 | + |
|
| 56 | + @Override
|
|
| 57 | + public String getFilenameForModel(ObjectModel model) {
|
|
| 58 | + return ReferentialSecondLevelDeepBehaviourModel.toLocation(super.getFilenameForModel(model));
|
|
| 59 | + }
|
|
| 60 | + |
|
| 61 | + @Override
|
|
| 62 | + protected void generateFromElement(Object element, File destDir, String filename, ObjectModelType type) {
|
|
| 63 | + if (ObjectModelType.OBJECT_MODEL != type) {
|
|
| 64 | + // only generate on model
|
|
| 65 | + return;
|
|
| 66 | + }
|
|
| 67 | + super.generateFromElement(element, destDir, filename, type);
|
|
| 68 | + }
|
|
| 69 | + |
|
| 70 | + @Override
|
|
| 71 | + public void generateFromModel(Writer output, ObjectModel input) {
|
|
| 72 | + List<String> referentialTypes = new LinkedList<>();
|
|
| 73 | + |
|
| 74 | + ToolkitTagValues tagValues = new ToolkitTagValues();
|
|
| 75 | + for (ObjectModelClass classifier : model.getClasses()) {
|
|
| 76 | + if (tagValues.isReferentialSecondLevelDeepBehaviour(model.getTagValuesStore(), classifier)) {
|
|
| 77 | + referentialTypes.add(classifier.getQualifiedName() + "Dto");
|
|
| 78 | + }
|
|
| 79 | + }
|
|
| 80 | + Collections.sort(referentialTypes);
|
|
| 81 | + ReferentialSecondLevelDeepBehaviourModel.newGson().toJson(referentialTypes, output);
|
|
| 82 | + }
|
|
| 83 | + |
|
| 84 | + @Override
|
|
| 85 | + protected File getDestinationFile(File destDir, String filename) {
|
|
| 86 | + return destDir.toPath().resolve(filename).toFile();
|
|
| 87 | + }
|
|
| 88 | +} |