diff --git a/model/src/main/java/org/alfresco/transform/registry/CombinedTransformConfig.java b/model/src/main/java/org/alfresco/transform/registry/CombinedTransformConfig.java index 56128bcb..3e4373b8 100644 --- a/model/src/main/java/org/alfresco/transform/registry/CombinedTransformConfig.java +++ b/model/src/main/java/org/alfresco/transform/registry/CombinedTransformConfig.java @@ -30,11 +30,14 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.StringJoiner; import java.util.function.Function; import java.util.stream.Collectors; +import org.springframework.util.CollectionUtils; + import org.alfresco.transform.config.AddSupported; import org.alfresco.transform.config.OverrideSupported; import org.alfresco.transform.config.RemoveSupported; @@ -71,6 +74,7 @@ public class CombinedTransformConfig private final Map> combinedTransformOptions = new HashMap<>(); private List> combinedTransformers = new ArrayList<>(); private final Defaults defaults = new Defaults(); + private final List deferredOverrides = new ArrayList<>(); public static void combineAndRegister(TransformConfig transformConfig, String readFrom, String baseUrl, AbstractTransformRegistry registry) @@ -86,6 +90,7 @@ public class CombinedTransformConfig combinedTransformOptions.clear(); combinedTransformers.clear(); defaults.clear(); + deferredOverrides.clear(); } public void addTransformConfig(List> transformConfigList, AbstractTransformRegistry registry) @@ -101,7 +106,7 @@ public class CombinedTransformConfig removeSupported(transformConfig.getRemoveSupported(), readFrom, registry); addSupported(transformConfig.getAddSupported(), readFrom, registry); - overrideSupported(transformConfig.getOverrideSupported(), readFrom, registry); + storeOverridesForDeferredProcessing(transformConfig.getOverrideSupported(), readFrom); // Add transform options and transformers from the new transformConfig transformConfig.getTransformOptions().forEach(combinedTransformOptions::put); @@ -218,23 +223,77 @@ public class CombinedTransformConfig })); } - private void overrideSupported(Set overrideSupportedSet, String readFrom, AbstractTransformRegistry registry) + /** + * Store overrides for deferred processing. Overrides are applied AFTER wildcard generation in the combineTransformerConfig() method, ensuring that pipeline transformers have their supportedSourceAndTargetList populated before overrides are applied. + * + * @param overrideSupportedSet + * the set of overrides to store + * @param readFrom + * where the overrides were read from + */ + private void storeOverridesForDeferredProcessing(Set overrideSupportedSet, String readFrom) { - processSupported(overrideSupportedSet, readFrom, registry, "overrideSupported", - (leftOver, overrideSupported) -> combinedTransformers.stream().map(Origin::get).filter(transformer -> transformer.getTransformerName().equals(overrideSupported.getTransformerName())).forEach(transformerWithName -> { - Set supportedSourceAndTargetList = transformerWithName.getSupportedSourceAndTargetList(); - SupportedSourceAndTarget existingSupported = getExistingSupported( - supportedSourceAndTargetList, - overrideSupported.getSourceMediaType(), overrideSupported.getTargetMediaType()); - if (existingSupported != null) - { - supportedSourceAndTargetList.remove(existingSupported); - existingSupported.setMaxSourceSizeBytes(overrideSupported.getMaxSourceSizeBytes()); - existingSupported.setPriority(overrideSupported.getPriority()); - supportedSourceAndTargetList.add(existingSupported); - leftOver.remove(overrideSupported); - } - })); + if (!CollectionUtils.isEmpty(overrideSupportedSet)) + { + overrideSupportedSet.forEach(override -> deferredOverrides.add(new DeferredOverride(override, readFrom))); + } + } + + /** + * Apply all stored overrides AFTER wildcard generation. This ensures that pipeline and failover transformers have their supportedSourceAndTargetList populated before overrides are applied. + * + * @param registry + * used for logging + */ + private void applyDeferredOverrides(AbstractTransformRegistry registry) + { + if (CollectionUtils.isEmpty(deferredOverrides)) + { + return; + } + + Map> leftoverBySource = new HashMap<>(); + for (DeferredOverride deferredOverride : deferredOverrides) + { + OverrideSupported override = deferredOverride.getOverrideSupported(); + String readFrom = deferredOverride.getReadFrom(); + + List matchedTransformers = combinedTransformers.stream() + .map(Origin::get) + .filter(transformer -> transformer.getTransformerName().equals(override.getTransformerName())) + .collect(Collectors.toList()); + if (matchedTransformers.isEmpty()) + { + leftoverBySource.computeIfAbsent(readFrom, k -> new HashSet<>()).add(override); + continue; + } + if (matchedTransformers.size() > 1) + { + throw new IllegalStateException("Multiple transformers found for " + readFrom + " with name: " + override.getTransformerName() + ". This should not be possible as removeInvalidTransformers should have removed duplicates."); + } + + Set supportedList = matchedTransformers.get(0).getSupportedSourceAndTargetList(); + Optional existingSupportedOpt = supportedList.stream() + .filter(supported -> supported.getSourceMediaType().equals(override.getSourceMediaType()) && + supported.getTargetMediaType().equals(override.getTargetMediaType())) + .findFirst(); + + if (existingSupportedOpt.isPresent()) + { + SupportedSourceAndTarget existingSupported = existingSupportedOpt.get(); + supportedList.remove(existingSupported); + existingSupported.setMaxSourceSizeBytes(override.getMaxSourceSizeBytes()); + existingSupported.setPriority(override.getPriority()); + supportedList.add(existingSupported); + } + else + { + leftoverBySource.computeIfAbsent(readFrom, k -> new HashSet<>()).add(override); + } + } + // Warn about overrides that didn't match anything + leftoverBySource.forEach((readFrom, leftOvers) -> logWarn(leftOvers, readFrom, registry, "overrideSupported")); + deferredOverrides.clear(); } private SupportedSourceAndTarget getExistingSupported(Set supportedSourceAndTargetList, @@ -250,8 +309,9 @@ public class CombinedTransformConfig { removeInvalidTransformers(registry); sortTransformers(registry); - applyDefaults(); addWildcardSupportedSourceAndTarget(registry); + applyDefaults(); + applyDeferredOverrides(registry); removePipelinesWithUnsupportedTransforms(registry); setCoreVersionOnCombinedMultiStepTransformers(); } @@ -584,28 +644,51 @@ public class CombinedTransformConfig } /** - * Applies priority and size defaults. Must be called before {@link #addWildcardSupportedSourceAndTarget(AbstractTransformRegistry)} as it uses the priority value. + * Applies priority and size defaults to a SupportedSourceAndTarget entry. + */ + private SupportedSourceAndTarget applyDefaultsToSupportedSourceAndTarget( + SupportedSourceAndTarget supportedSourceAndTarget, + String transformerName, + Set supportedDefaultTransformerNames, + Defaults defaults) + { + Integer priority = supportedSourceAndTarget.getPriority(); + Long maxSourceSizeBytes = supportedSourceAndTarget.getMaxSourceSizeBytes(); + String sourceMediaType = supportedSourceAndTarget.getSourceMediaType(); + if (defaults.valuesUnset(priority, maxSourceSizeBytes)) + { + supportedSourceAndTarget.setPriority(defaults.getPriority(transformerName, sourceMediaType, priority)); + supportedSourceAndTarget.setMaxSourceSizeBytes(defaults.getMaxSourceSizeBytes(transformerName, sourceMediaType, maxSourceSizeBytes)); + } + if (supportedDefaultTransformerNames.contains(transformerName)) + { + supportedSourceAndTarget.setPriority(defaults.getPriority(transformerName, sourceMediaType, null)); + supportedSourceAndTarget.setMaxSourceSizeBytes(defaults.getMaxSourceSizeBytes(transformerName, sourceMediaType, null)); + } + return supportedSourceAndTarget; + } + + /** + * Applies priority and size defaults to supported source/target entries. + * + * Previously, this method was called before {@link #addWildcardSupportedSourceAndTarget(AbstractTransformRegistry)} because it relied on the priority value. As of MNT-25426, it is now called after wildcard generation, ensuring that pipeline transformers also receive the correct defaults. */ private void applyDefaults() { + Set supportedDefaultTransformerNames = defaults.getSupportedDefaults() + .stream() + .map(SupportedDefaults::getTransformerName) + .collect(toSet()); + combinedTransformers.stream() .map(Origin::get) .forEach(transformer -> { transformer.setSupportedSourceAndTargetList( - transformer.getSupportedSourceAndTargetList().stream().map(supportedSourceAndTarget -> { - Integer priority = supportedSourceAndTarget.getPriority(); - Long maxSourceSizeBytes = supportedSourceAndTarget.getMaxSourceSizeBytes(); - if (defaults.valuesUnset(priority, maxSourceSizeBytes)) - { - String transformerName = transformer.getTransformerName(); - String sourceMediaType = supportedSourceAndTarget.getSourceMediaType(); - supportedSourceAndTarget.setPriority(defaults.getPriority(transformerName, sourceMediaType, priority)); - supportedSourceAndTarget.setMaxSourceSizeBytes(defaults.getMaxSourceSizeBytes(transformerName, sourceMediaType, maxSourceSizeBytes)); - } - return supportedSourceAndTarget; - }).collect(toSet())); + transformer.getSupportedSourceAndTargetList() + .stream() + .map(supportedSourceAndTarget -> applyDefaultsToSupportedSourceAndTarget(supportedSourceAndTarget, transformer.getTransformerName(), supportedDefaultTransformerNames, defaults)) + .collect(toSet())); }); - defaults.clear(); } @@ -865,4 +948,9 @@ public class CombinedTransformConfig { return combinedTransformers.stream().collect(Collectors.toMap(origin -> origin.get().getTransformerName(), origin -> origin)); } + + List getDeferredOverrides() + { + return deferredOverrides; + } } diff --git a/model/src/main/java/org/alfresco/transform/registry/DeferredOverride.java b/model/src/main/java/org/alfresco/transform/registry/DeferredOverride.java new file mode 100644 index 00000000..bcf1eb74 --- /dev/null +++ b/model/src/main/java/org/alfresco/transform/registry/DeferredOverride.java @@ -0,0 +1,49 @@ +/* + * #%L + * Alfresco Transform Model + * %% + * Copyright (C) 2026 Alfresco Software Limited + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * . + * #L% + */ +package org.alfresco.transform.registry; + +import org.alfresco.transform.config.OverrideSupported; + +/** + * Holds override information for deferred processing after wildcard generation. + */ +class DeferredOverride +{ + private final OverrideSupported overrideSupported; + private final String readFrom; + + public DeferredOverride(OverrideSupported overrideSupported, String readFrom) + { + this.overrideSupported = overrideSupported; + this.readFrom = readFrom; + } + + public OverrideSupported getOverrideSupported() + { + return overrideSupported; + } + + public String getReadFrom() + { + return readFrom; + } +} diff --git a/model/src/test/java/org/alfresco/transform/registry/CombinedTransformConfigTest.java b/model/src/test/java/org/alfresco/transform/registry/CombinedTransformConfigTest.java index 2f6b019c..c037d206 100644 --- a/model/src/test/java/org/alfresco/transform/registry/CombinedTransformConfigTest.java +++ b/model/src/test/java/org/alfresco/transform/registry/CombinedTransformConfigTest.java @@ -39,6 +39,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.junit.jupiter.api.Test; +import org.alfresco.transform.config.OverrideSupported; import org.alfresco.transform.config.SupportedSourceAndTarget; import org.alfresco.transform.config.TransformConfig; import org.alfresco.transform.config.TransformStep; @@ -300,6 +301,26 @@ public class CombinedTransformConfigTest assertEquals(0, config.buildTransformConfig().getTransformOptions().size()); } + @Test + public void testClearAlsoRemovesDeferredOverrides() + { + // Add a config with an overrideSupported entry to populate deferredOverrides + TransformConfig overrideConfig = TransformConfig.builder() + .withOverrideSupported(ImmutableSet.of( + OverrideSupported.builder() + .withTransformerName("pipeline1") + .withSourceMediaType("mimetype/a") + .withTargetMediaType("mimetype/b") + .withPriority(99) + .build())) + .build(); + config.addTransformConfig(overrideConfig, READ_FROM_B, BASE_URL_B, registry); + + assertEquals(1, config.getDeferredOverrides().size()); + config.clear(); + assertEquals(0, config.getDeferredOverrides().size()); + } + @Test public void testCombineTransformerConfigNoOp() { diff --git a/model/src/test/java/org/alfresco/transform/registry/OverrideTransformConfigTests.java b/model/src/test/java/org/alfresco/transform/registry/OverrideTransformConfigTests.java index 14050189..7e0c9a91 100644 --- a/model/src/test/java/org/alfresco/transform/registry/OverrideTransformConfigTests.java +++ b/model/src/test/java/org/alfresco/transform/registry/OverrideTransformConfigTests.java @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.HashSet; +import java.util.List; import java.util.Set; import com.google.common.collect.ImmutableList; @@ -37,6 +38,7 @@ import org.alfresco.transform.config.RemoveSupported; import org.alfresco.transform.config.SupportedDefaults; import org.alfresco.transform.config.SupportedSourceAndTarget; import org.alfresco.transform.config.TransformConfig; +import org.alfresco.transform.config.TransformStep; import org.alfresco.transform.config.Transformer; /** @@ -431,7 +433,83 @@ public class OverrideTransformConfigTests "{\"sourceMediaType\": \"mimetype/x\", \"targetMediaType\": \"mimetype/y\", \"maxSourceSizeBytes\": \"200\"}" + "]"; - addTransformConfig(secondConfig, expectedWarnMessage, expectedSupported, expectedToString); + config.addTransformConfig(secondConfig, READ_FROM_B, BASE_URL_B, registry); + config.combineTransformerConfig(registry); + + assertEquals(1, registry.warnMessages.size()); + assertEquals(expectedWarnMessage, registry.warnMessages.get(0)); + + Set supportedSourceAndTargetList = config.buildTransformConfig().getTransformers().get(0).getSupportedSourceAndTargetList(); + assertEquals(expectedSupported, supportedSourceAndTargetList); + assertEquals(expectedToString, supportedSourceAndTargetList.toString()); + } + + @Test + public void testDeferredOverrideForPipelineTransformer() + { + // Add step transformers first + Transformer step1 = Transformer.builder() + .withTransformerName("step1") + .withSupportedSourceAndTargetList(Set.of( + SupportedSourceAndTarget.builder() + .withSourceMediaType("mimetype/document") + .withTargetMediaType("mimetype/pdf") + .build())) + .build(); + + Transformer step2 = Transformer.builder() + .withTransformerName("step2") + .withSupportedSourceAndTargetList(Set.of( + SupportedSourceAndTarget.builder() + .withSourceMediaType("mimetype/pdf") + .withTargetMediaType("mimetype/image") + .build())) + .build(); + + // Add pipeline transformer + Transformer pipelineTransformer = Transformer.builder() + .withTransformerName("pipeline1") + .withTransformerPipeline(List.of( + new TransformStep("step1", "mimetype/pdf"), + new TransformStep("step2", null))) + .build(); + + TransformConfig pipelineConfig = TransformConfig.builder() + .withTransformers(List.of(step1, step2, pipelineTransformer)) + .build(); + + config.addTransformConfig(pipelineConfig, READ_FROM_A, BASE_URL_A, registry); + + // Add override for pipeline transformer + OverrideSupported override = OverrideSupported.builder() + .withTransformerName("pipeline1") + .withSourceMediaType("mimetype/document") + .withTargetMediaType("mimetype/image") + .withPriority(40) + .build(); + + TransformConfig overrideConfig = TransformConfig.builder() + .withOverrideSupported(Set.of(override)) + .build(); + + config.addTransformConfig(overrideConfig, READ_FROM_B, BASE_URL_B, registry); + + // Combine configs + config.combineTransformerConfig(registry); + + // Assert override applied + List transformers = config.buildTransformConfig().getTransformers(); + assertTrue(!transformers.isEmpty(), "Pipeline transformer should exist after valid setup"); + Set supportedList = transformers.stream() + .filter(t -> "pipeline1".equals(t.getTransformerName())) + .findFirst() + .orElseThrow() + .getSupportedSourceAndTargetList(); + + boolean found = supportedList.stream().anyMatch(s -> "mimetype/document".equals(s.getSourceMediaType()) && + "mimetype/image".equals(s.getTargetMediaType()) && + s.getPriority() == 40); + assertTrue(found, "Deferred override for pipeline transformer should be applied after wildcard generation"); } private void addTransformConfig_A2B_X2Y_100_23()