This is an automated email from the ASF dual-hosted git repository. jayblanc pushed a commit to branch UNOMI-973-file-endpoint-containment in repository https://gitbox.apache.org/repos/asf/unomi.git
commit 3c7f42b681ce68884dc0ea8696cc6754f1179232 Author: Jérôme Blanchard <[email protected]> AuthorDate: Tue Aug 11 16:20:10 2026 +0200 UNOMI-973: Record on a configuration whether its endpoint can be honoured The permitted directories are an operational setting and the configurations are user data, so the two drift apart: a configuration that was legitimate when it was created is refused once the deployment is reconfigured. Refusing it silently left its owner with a configuration that looks fine and does nothing, the only trace being a log line. A configuration whose endpoint is refused while its route is built is now marked INVALID_ENDPOINT and saved, so it can be seen and dealt with. Nothing is deleted and no restart is blocked: correcting or removing it belongs to whoever owns it. Restoring the permitted directories clears the mark on its own at the next rebuild, so operations can undo a change without anyone touching the configurations. The status is its own value rather than one of the execution statuses: those report on a run that happened, this one says no run can. Keeping them apart is what makes the mark safe to clear -- the record of a run that genuinely failed is left alone. Saving is done without asking for a route refresh, which would rebuild the route, refuse it again and save it again, without end. --- .../apache/unomi/router/api/RouterConstants.java | 7 + .../router/core/context/RouterCamelContext.java | 2 + .../route/ProfileExportCollectRouteBuilder.java | 7 + .../route/ProfileImportFromSourceRouteBuilder.java | 1 + .../core/route/RouterAbstractRouteBuilder.java | 33 +++ .../core/route/FileEndpointContainmentTest.java | 15 + .../core/route/RefusedConfigurationStatusTest.java | 305 +++++++++++++++++++++ 7 files changed, 370 insertions(+) diff --git a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/RouterConstants.java b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/RouterConstants.java index 03d84bcb2..9df7a2752 100644 --- a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/RouterConstants.java +++ b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/RouterConstants.java @@ -32,6 +32,13 @@ public interface RouterConstants { String CONFIG_STATUS_COMPLETE_ERRORS = "ERRORS"; String CONFIG_STATUS_COMPLETE_SUCCESS = "SUCCESS"; String CONFIG_STATUS_COMPLETE_WITH_ERRORS = "WITH_ERRORS"; + /** + * The configuration names an endpoint that cannot be honoured, so no route carries it. Kept apart + * from the execution statuses above: those report on a run that happened, this one says no run can. + * It is set and cleared by the route builders alone, so that restoring the deployment's permitted + * directories brings the configuration back on its own. + */ + String CONFIG_STATUS_INVALID_ENDPOINT = "INVALID_ENDPOINT"; String IMPORT_EXPORT_CONFIG_TYPE_RECURRENT = "recurrent"; String IMPORT_EXPORT_CONFIG_TYPE_ONESHOT = "oneshot"; diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/context/RouterCamelContext.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/context/RouterCamelContext.java index 15f401880..d0e8dcdae 100644 --- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/context/RouterCamelContext.java +++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/context/RouterCamelContext.java @@ -211,6 +211,7 @@ public class RouterCamelContext implements IRouterCamelContext { //Profiles collect ProfileExportCollectRouteBuilder profileExportCollectRouteBuilder = new ProfileExportCollectRouteBuilder(kafkaProps, configType); profileExportCollectRouteBuilder.setExportConfigurationList(exportConfigurationService.getAll()); + profileExportCollectRouteBuilder.setExportConfigurationService(exportConfigurationService); profileExportCollectRouteBuilder.setPersistenceService(persistenceService); profileExportCollectRouteBuilder.setAllowedEndpoints(allowedEndpoints); profileExportCollectRouteBuilder.setPermittedExportBaseDirs(permittedExportBaseDirs); @@ -276,6 +277,7 @@ public class RouterCamelContext implements IRouterCamelContext { if (RouterConstants.IMPORT_EXPORT_CONFIG_TYPE_RECURRENT.equals(exportConfiguration.getConfigType())) { ProfileExportCollectRouteBuilder profileExportCollectRouteBuilder = new ProfileExportCollectRouteBuilder(kafkaProps, configType); profileExportCollectRouteBuilder.setExportConfigurationList(Collections.singletonList(exportConfiguration)); + profileExportCollectRouteBuilder.setExportConfigurationService(exportConfigurationService); profileExportCollectRouteBuilder.setPersistenceService(persistenceService); profileExportCollectRouteBuilder.setAllowedEndpoints(allowedEndpoints); profileExportCollectRouteBuilder.setPermittedExportBaseDirs(permittedExportBaseDirs); diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileExportCollectRouteBuilder.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileExportCollectRouteBuilder.java index d0f075f1c..f59b46edb 100644 --- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileExportCollectRouteBuilder.java +++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileExportCollectRouteBuilder.java @@ -23,6 +23,7 @@ import org.apache.unomi.persistence.spi.PersistenceService; import org.apache.unomi.router.api.EndpointValidator; import org.apache.unomi.router.api.ExportConfiguration; import org.apache.unomi.router.api.RouterConstants; +import org.apache.unomi.router.api.services.ImportExportConfigurationService; import org.apache.unomi.router.core.bean.CollectProfileBean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,6 +39,7 @@ public class ProfileExportCollectRouteBuilder extends RouterAbstractRouteBuilder private static final Logger LOGGER = LoggerFactory.getLogger(ProfileExportCollectRouteBuilder.class); private List<ExportConfiguration> exportConfigurationList; + private ImportExportConfigurationService<ExportConfiguration> exportConfigurationService; private PersistenceService persistenceService; public ProfileExportCollectRouteBuilder(Map<String, String> kafkaProps, String configType) { @@ -63,6 +65,7 @@ public class ProfileExportCollectRouteBuilder extends RouterAbstractRouteBuilder if ((Map<String, String>) exportConfiguration.getProperties().get("mapping") != null) { String destinationEndpoint = (String) exportConfiguration.getProperties().get("destination"); String refusal = EndpointValidator.validate(destinationEndpoint, allowedEndpoints, permittedBaseDirs); + recordEndpointOutcome(exportConfiguration, exportConfigurationService, refusal); if (refusal == null) { String timerString = "timer://collectProfile?fixedRate=true&period=" + (String) exportConfiguration.getProperties().get("period"); if ((String) exportConfiguration.getProperties().get("delay") != null) { @@ -103,6 +106,10 @@ public class ProfileExportCollectRouteBuilder extends RouterAbstractRouteBuilder this.permittedBaseDirs = permittedExportBaseDirs; } + public void setExportConfigurationService(ImportExportConfigurationService<ExportConfiguration> exportConfigurationService) { + this.exportConfigurationService = exportConfigurationService; + } + public void setExportConfigurationList(List<ExportConfiguration> exportConfigurationList) { this.exportConfigurationList = exportConfigurationList; } diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportFromSourceRouteBuilder.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportFromSourceRouteBuilder.java index 54b9109fc..4c468a659 100644 --- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportFromSourceRouteBuilder.java +++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportFromSourceRouteBuilder.java @@ -98,6 +98,7 @@ public class ProfileImportFromSourceRouteBuilder extends RouterAbstractRouteBuil } String refusal = EndpointValidator.validate(endpoint, allowedEndpoints, permittedBaseDirs); + recordEndpointOutcome(importConfiguration, importConfigurationService, refusal); if (refusal == null) { ProcessorDefinition prDef = from(endpoint) .routeId(importConfiguration.getItemId())// This allow identification of the route for manual start/stop diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/RouterAbstractRouteBuilder.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/RouterAbstractRouteBuilder.java index 00ae70204..fa4998998 100644 --- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/RouterAbstractRouteBuilder.java +++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/RouterAbstractRouteBuilder.java @@ -23,7 +23,9 @@ import org.apache.camel.component.kafka.KafkaConfiguration; import org.apache.camel.component.kafka.KafkaEndpoint; import org.apache.commons.lang3.StringUtils; import org.apache.unomi.api.services.ProfileService; +import org.apache.unomi.router.api.ImportExportConfiguration; import org.apache.unomi.router.api.RouterConstants; +import org.apache.unomi.router.api.services.ImportExportConfigurationService; import java.util.Map; @@ -61,6 +63,37 @@ public abstract class RouterAbstractRouteBuilder extends RouteBuilder { this.configType = configType; } + /** + * Records, on the configuration itself, whether the endpoint it names can be honoured. + * + * <p>The permitted directories are an operational setting and the configurations are user data, so + * the two drift apart: a configuration that was legitimate when it was created can be refused after + * the deployment is reconfigured. Refusing it silently leaves the owner with a configuration that + * looks fine and does nothing, so the refusal is written where they will see it. It is theirs to + * correct or remove — nothing is deleted here. + * + * <p>The other way round matters just as much: restoring the permitted directories must bring the + * configuration back on its own, without anyone having to touch it. Only the status this method + * sets is cleared, so the record of a run that genuinely failed survives. + * + * <p>The configuration is saved without asking for its running route to be refreshed: the refresh + * would rebuild the route, refuse it again and save it again, without end. + * + * @param configuration the configuration whose endpoint was examined + * @param service the service holding that kind of configuration + * @param refusal the reason the endpoint was refused, or {@code null} if it can be honoured + */ + protected <T extends ImportExportConfiguration> void recordEndpointOutcome( + T configuration, ImportExportConfigurationService<T> service, String refusal) { + if (refusal != null) { + configuration.setStatus(RouterConstants.CONFIG_STATUS_INVALID_ENDPOINT); + service.save(configuration, false); + } else if (RouterConstants.CONFIG_STATUS_INVALID_ENDPOINT.equals(configuration.getStatus())) { + configuration.setStatus(null); + service.save(configuration, false); + } + } + public Object getEndpointURI(String direction, String operationDepositBuffer) { Object endpoint; if (RouterConstants.CONFIG_TYPE_KAFKA.equals(configType)) { diff --git a/extensions/router/router-core/src/test/java/org/apache/unomi/router/core/route/FileEndpointContainmentTest.java b/extensions/router/router-core/src/test/java/org/apache/unomi/router/core/route/FileEndpointContainmentTest.java index e10d3b0c6..78287c6f8 100644 --- a/extensions/router/router-core/src/test/java/org/apache/unomi/router/core/route/FileEndpointContainmentTest.java +++ b/extensions/router/router-core/src/test/java/org/apache/unomi/router/core/route/FileEndpointContainmentTest.java @@ -23,6 +23,7 @@ import org.apache.unomi.router.api.ExportConfiguration; import org.apache.unomi.router.api.ImportConfiguration; import org.apache.unomi.router.api.ProfileToImport; import org.apache.unomi.router.api.RouterConstants; +import org.apache.unomi.router.api.services.ImportExportConfigurationService; import org.junit.After; import org.junit.Assume; import org.junit.Before; @@ -382,6 +383,7 @@ public class FileEndpointContainmentTest { ProfileImportFromSourceRouteBuilder builder = new ProfileImportFromSourceRouteBuilder(NO_KAFKA, RouterConstants.CONFIG_TYPE_NOBROKER); builder.setImportConfigurationList(Arrays.asList(configurations)); + builder.setImportConfigurationService(discardingConfigurationService()); builder.setProfileService(noOpProfileService()); builder.setJacksonDataFormat(new JacksonDataFormat(ProfileToImport.class)); builder.setAllowedEndpoints(allowedEndpoints); @@ -394,6 +396,7 @@ public class FileEndpointContainmentTest { ProfileExportCollectRouteBuilder builder = new ProfileExportCollectRouteBuilder(NO_KAFKA, RouterConstants.CONFIG_TYPE_NOBROKER); builder.setExportConfigurationList(Arrays.asList(configurations)); + builder.setExportConfigurationService(discardingConfigurationService()); builder.setJacksonDataFormat(new JacksonDataFormat(ProfileToImport.class)); builder.setAllowedEndpoints(DEFAULT_ALLOWED_ENDPOINTS); builder.setPermittedExportBaseDirs(permittedExportDir.getAbsolutePath()); @@ -401,6 +404,18 @@ public class FileEndpointContainmentTest { camelContext.addRoutes(builder); } + /** + * A refused configuration is recorded, and these tests are not about that: what they observe is + * whether the route was built. + */ + @SuppressWarnings("unchecked") + private static <T> ImportExportConfigurationService<T> discardingConfigurationService() { + return (ImportExportConfigurationService<T>) Proxy.newProxyInstance( + ImportExportConfigurationService.class.getClassLoader(), + new Class<?>[]{ImportExportConfigurationService.class}, + (proxy, method, args) -> "save".equals(method.getName()) ? args[0] : null); + } + /** * The route builders ask the profile service for the profile property types while they build. * Nothing in these tests depends on what it answers. diff --git a/extensions/router/router-core/src/test/java/org/apache/unomi/router/core/route/RefusedConfigurationStatusTest.java b/extensions/router/router-core/src/test/java/org/apache/unomi/router/core/route/RefusedConfigurationStatusTest.java new file mode 100644 index 000000000..d0daf23e4 --- /dev/null +++ b/extensions/router/router-core/src/test/java/org/apache/unomi/router/core/route/RefusedConfigurationStatusTest.java @@ -0,0 +1,305 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.unomi.router.core.route; + +import org.apache.camel.component.jackson.JacksonDataFormat; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.unomi.api.services.ProfileService; +import org.apache.unomi.router.api.ExportConfiguration; +import org.apache.unomi.router.api.ImportConfiguration; +import org.apache.unomi.router.api.ProfileToImport; +import org.apache.unomi.router.api.RouterConstants; +import org.apache.unomi.router.api.services.ImportExportConfigurationService; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * The permitted base directories are an operational setting, and the configurations are user data: + * the two drift apart. A configuration that was legitimate when it was created can find itself + * outside the permitted directories after the deployment is reconfigured, and its route then stops + * being built. + * + * <p>Leaving that silent is what makes it painful — the screen keeps showing the configuration as + * running, and the only trace is a log line nobody reads. So a configuration whose endpoint is + * refused while its route is being built is recorded as failed, and stays in the store: it is for + * whoever owns it to correct it or remove it, and they can only do that if they can see it. + * + * <p>Recording it must not schedule the configuration for a route refresh — that would have the + * refresh rebuild the route, refuse it again, and save it again, indefinitely. + */ +public class RefusedConfigurationStatusTest { + + private static final String DEFAULT_ALLOWED_ENDPOINTS = "file,ftp,sftp,ftps"; + + private static final Map<String, String> NO_KAFKA = new HashMap<>(); + + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + private DefaultCamelContext camelContext; + + private File permittedImportDir; + private File permittedExportDir; + private File arbitraryDir; + + private RecordingConfigurationService<ImportConfiguration> importConfigurations; + private RecordingConfigurationService<ExportConfiguration> exportConfigurations; + + @Before + public void setUp() throws Exception { + permittedImportDir = tmp.newFolder("permitted-import"); + permittedExportDir = tmp.newFolder("permitted-export"); + arbitraryDir = tmp.newFolder("arbitrary"); + camelContext = new DefaultCamelContext(); + importConfigurations = new RecordingConfigurationService<>(); + exportConfigurations = new RecordingConfigurationService<>(); + } + + @After + public void tearDown() throws Exception { + camelContext.stop(); + } + + @Test + public void aRefusedImportConfigurationIsRecordedAsFailed() throws Exception { + ImportConfiguration configuration = recurrentImport(fileUri(arbitraryDir, "?fileName=profiles.csv")); + + addImportRoutes(configuration); + + assertNull("no route should have been built", camelContext.getRouteDefinition("out-of-bounds")); + assertEquals("the configuration should be recorded as failed", + RouterConstants.CONFIG_STATUS_INVALID_ENDPOINT, configuration.getStatus()); + assertTrue("the configuration should have been saved so the failure is visible", + importConfigurations.contains("out-of-bounds")); + } + + @Test + public void aRefusedExportConfigurationIsRecordedAsFailed() throws Exception { + ExportConfiguration configuration = recurrentExport(fileUri(arbitraryDir, "?fileName=profiles.csv")); + + addExportRoutes(configuration); + + assertNull("no route should have been built", camelContext.getRouteDefinition("out-of-bounds")); + assertEquals("the configuration should be recorded as failed", + RouterConstants.CONFIG_STATUS_INVALID_ENDPOINT, configuration.getStatus()); + assertTrue("the configuration should have been saved so the failure is visible", + exportConfigurations.contains("out-of-bounds")); + } + + @Test + public void recordingARefusedImportConfigurationDoesNotScheduleARouteRefresh() throws Exception { + addImportRoutes(recurrentImport(fileUri(arbitraryDir, "?fileName=profiles.csv"))); + + assertFalse("refreshing the route would refuse and save it again, without end", + importConfigurations.lastSaveAskedForARouteRefresh); + } + + @Test + public void recordingARefusedExportConfigurationDoesNotScheduleARouteRefresh() throws Exception { + addExportRoutes(recurrentExport(fileUri(arbitraryDir, "?fileName=profiles.csv"))); + + assertFalse("refreshing the route would refuse and save it again, without end", + exportConfigurations.lastSaveAskedForARouteRefresh); + } + + @Test + public void anImportConfigurationRecoversWhenItsEndpointBecomesAcceptableAgain() throws Exception { + ImportConfiguration configuration = recurrentImport(fileUri(permittedImportDir, "?fileName=profiles.csv")); + configuration.setStatus(RouterConstants.CONFIG_STATUS_INVALID_ENDPOINT); + + addImportRoutes(configuration); + + assertNull("restoring the permitted directories must bring the configuration back on its own", + configuration.getStatus()); + assertTrue("the recovery must be persisted", importConfigurations.contains("out-of-bounds")); + } + + @Test + public void anExportConfigurationRecoversWhenItsEndpointBecomesAcceptableAgain() throws Exception { + ExportConfiguration configuration = recurrentExport(fileUri(permittedExportDir, "?fileName=profiles.csv")); + configuration.setStatus(RouterConstants.CONFIG_STATUS_INVALID_ENDPOINT); + + addExportRoutes(configuration); + + assertNull("restoring the permitted directories must bring the configuration back on its own", + configuration.getStatus()); + assertTrue("the recovery must be persisted", exportConfigurations.contains("out-of-bounds")); + } + + @Test + public void aConfigurationThatFailedItsLastRunKeepsThatStatusWhenItsRouteIsRebuilt() throws Exception { + ImportConfiguration configuration = recurrentImport(fileUri(permittedImportDir, "?fileName=profiles.csv")); + configuration.setStatus(RouterConstants.CONFIG_STATUS_COMPLETE_ERRORS); + + addImportRoutes(configuration); + + assertEquals("a failed run is a different matter, and its record must survive", + RouterConstants.CONFIG_STATUS_COMPLETE_ERRORS, configuration.getStatus()); + } + + @Test + public void anAcceptedImportConfigurationIsLeftAlone() throws Exception { + ImportConfiguration configuration = recurrentImport(fileUri(permittedImportDir, "?fileName=profiles.csv")); + configuration.setItemId("in-bounds"); + + addImportRoutes(configuration); + + assertNull("an accepted configuration keeps the status it had", configuration.getStatus()); + assertFalse("an accepted configuration is not saved while its route is built", + importConfigurations.contains("in-bounds")); + } + + @Test + public void anAcceptedExportConfigurationIsLeftAlone() throws Exception { + ExportConfiguration configuration = recurrentExport(fileUri(permittedExportDir, "?fileName=profiles.csv")); + configuration.setItemId("in-bounds"); + + addExportRoutes(configuration); + + assertNull("an accepted configuration keeps the status it had", configuration.getStatus()); + assertFalse("an accepted configuration is not saved while its route is built", + exportConfigurations.contains("in-bounds")); + } + + // --------------------------------------------------------------------------------------------- + // Fixtures + // --------------------------------------------------------------------------------------------- + + private String fileUri(File directory, String suffix) { + return "file://" + directory.getAbsolutePath() + suffix; + } + + private ImportConfiguration recurrentImport(String source) { + ImportConfiguration configuration = new ImportConfiguration(); + configuration.setItemId("out-of-bounds"); + configuration.setConfigType(RouterConstants.IMPORT_EXPORT_CONFIG_TYPE_RECURRENT); + configuration.setActive(true); + configuration.getProperties().put("source", source); + configuration.getProperties().put("mapping", Collections.singletonMap("0", 0)); + return configuration; + } + + private ExportConfiguration recurrentExport(String destination) { + ExportConfiguration configuration = new ExportConfiguration(); + configuration.setItemId("out-of-bounds"); + configuration.setConfigType(RouterConstants.IMPORT_EXPORT_CONFIG_TYPE_RECURRENT); + configuration.setActive(true); + configuration.getProperties().put("destination", destination); + configuration.getProperties().put("mapping", Collections.singletonMap("0", "firstName")); + configuration.getProperties().put("segment", "exportSegment"); + configuration.getProperties().put("period", "1m"); + return configuration; + } + + private void addImportRoutes(ImportConfiguration... configurations) throws Exception { + ProfileImportFromSourceRouteBuilder builder = + new ProfileImportFromSourceRouteBuilder(NO_KAFKA, RouterConstants.CONFIG_TYPE_NOBROKER); + builder.setImportConfigurationList(java.util.Arrays.asList(configurations)); + builder.setImportConfigurationService(importConfigurations); + builder.setProfileService(noOpProfileService()); + builder.setJacksonDataFormat(new JacksonDataFormat(ProfileToImport.class)); + builder.setAllowedEndpoints(DEFAULT_ALLOWED_ENDPOINTS); + builder.setPermittedImportBaseDirs(permittedImportDir.getAbsolutePath()); + builder.setContext(camelContext); + camelContext.addRoutes(builder); + } + + private void addExportRoutes(ExportConfiguration... configurations) throws Exception { + ProfileExportCollectRouteBuilder builder = + new ProfileExportCollectRouteBuilder(NO_KAFKA, RouterConstants.CONFIG_TYPE_NOBROKER); + builder.setExportConfigurationList(java.util.Arrays.asList(configurations)); + builder.setExportConfigurationService(exportConfigurations); + builder.setJacksonDataFormat(new JacksonDataFormat(ProfileToImport.class)); + builder.setAllowedEndpoints(DEFAULT_ALLOWED_ENDPOINTS); + builder.setPermittedExportBaseDirs(permittedExportDir.getAbsolutePath()); + builder.setContext(camelContext); + camelContext.addRoutes(builder); + } + + private static ProfileService noOpProfileService() { + return (ProfileService) Proxy.newProxyInstance( + ProfileService.class.getClassLoader(), + new Class<?>[]{ProfileService.class}, + (proxy, method, args) -> java.util.Collection.class.isAssignableFrom(method.getReturnType()) + ? Collections.emptyList() : null); + } + + /** + * Stores what it is given, and remembers whether the last save asked for the running route to be + * refreshed — a refused configuration must not, or the refresh loops. + */ + private static final class RecordingConfigurationService<T> implements ImportExportConfigurationService<T> { + + private final Map<String, T> stored = new LinkedHashMap<>(); + + private boolean lastSaveAskedForARouteRefresh; + + boolean contains(String configId) { + return stored.containsKey(configId); + } + + @Override + public List<T> getAll() { + return new ArrayList<>(stored.values()); + } + + @Override + public T load(String configId) { + return stored.get(configId); + } + + @Override + public T save(T configuration, boolean updateRunningRoute) { + lastSaveAskedForARouteRefresh = updateRunningRoute; + stored.put(itemIdOf(configuration), configuration); + return configuration; + } + + @Override + public void delete(String configId) { + stored.remove(configId); + } + + @Override + public Map<String, RouterConstants.CONFIG_CAMEL_REFRESH> consumeConfigsToBeRefresh() { + return Collections.emptyMap(); + } + + private String itemIdOf(T configuration) { + return configuration instanceof ImportConfiguration + ? ((ImportConfiguration) configuration).getItemId() + : ((ExportConfiguration) configuration).getItemId(); + } + } +}
