This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch var in repository https://gitbox.apache.org/repos/asf/camel.git
commit c5f19f53fe35bb12024ceb8bb8522a2ef0151e7a Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Fri Dec 29 18:30:12 2023 +0100 CAMEL-19749: Add variables as concept to Camel --- .../main/java/org/apache/camel/CamelContext.java | 19 ++++ .../src/main/java/org/apache/camel/Exchange.java | 6 +- .../java/org/apache/camel/ExchangeExtension.java | 1 + .../org/apache/camel/spi/VariableRepository.java | 55 ++++++++++ .../camel/spi/VariableRepositoryFactory.java | 32 ++++++ .../camel/impl/engine/AbstractCamelContext.java | 26 +++++ .../engine/DefaultVariableRepositoryFactory.java | 75 ++++++++++++++ .../camel/impl/engine/SimpleCamelContext.java | 6 ++ .../camel/processor/SetVariableProcessor.java | 40 +++++++- .../camel/processor/CustomGlobalVariableTest.java | 112 +++++++++++++++++++++ .../camel/processor/SetGlobalVariableTest.java | 74 ++++++++++++++ .../mbean/ManagedVariableRepositoryMBean.java | 26 +++++ .../management/JmxManagementLifecycleStrategy.java | 4 + .../mbean/ManagedVariableRepository.java | 41 ++++++++ .../management/ManagedNonManagedServiceTest.java | 2 +- ...edProducerRouteAddRemoveRegisterAlwaysTest.java | 2 +- .../management/ManagedRouteAddRemoveTest.java | 2 +- .../org/apache/camel/support/AbstractExchange.java | 86 ++++++++-------- .../camel/support/DefaultPooledExchange.java | 3 - .../camel/support/ExchangeVariableRepository.java | 87 ++++++++++++++++ .../camel/support/ExtendedExchangeExtension.java | 3 +- .../camel/support/GlobalVariableRepository.java | 58 +++++++++++ .../camel/support/builder/ExpressionBuilder.java | 2 +- .../processor/DefaultExchangeFormatter.java | 8 +- .../java/org/apache/camel/util/StringHelper.java | 18 ++++ 25 files changed, 728 insertions(+), 60 deletions(-) diff --git a/core/camel-api/src/main/java/org/apache/camel/CamelContext.java b/core/camel-api/src/main/java/org/apache/camel/CamelContext.java index 22cfc3ed793..8fadd42919f 100644 --- a/core/camel-api/src/main/java/org/apache/camel/CamelContext.java +++ b/core/camel-api/src/main/java/org/apache/camel/CamelContext.java @@ -806,6 +806,25 @@ public interface CamelContext extends CamelContextLifecycle, RuntimeConfiguratio */ String resolvePropertyPlaceholders(String text); + /** + * To get a variable by name. + * + * @param name the variable name. Can be prefixed with repo-id:name to lookup the variable from a specific + * repository. If no repo-id is provided, then global repository will be used. + * @return the variable, or <tt>null</tt> if not found. + */ + Object getVariable(String name); + + /** + * To get a variable by name and covert to the given type. + * + * @param name the variable name. Can be prefixed with repo-id:name to lookup the variable from a specific + * repository. If no repo-id is provided, then global repository will be used. + * @param type the type to convert the variable to + * @return the variable, or <tt>null</tt> if not found. + */ + <T> T getVariable(String name, Class<T> type); + /** * Returns the configured properties component or create one if none has been configured. * diff --git a/core/camel-api/src/main/java/org/apache/camel/Exchange.java b/core/camel-api/src/main/java/org/apache/camel/Exchange.java index a91f9865b6c..1946492717d 100644 --- a/core/camel-api/src/main/java/org/apache/camel/Exchange.java +++ b/core/camel-api/src/main/java/org/apache/camel/Exchange.java @@ -449,7 +449,7 @@ public interface Exchange { <T> T getVariable(String name, Object defaultValue, Class<T> type); /** - * Sets a varialbe on the exchange + * Sets a variable on the exchange * * @param name of the variable * @param value the value of the variable @@ -460,7 +460,7 @@ public interface Exchange { * Removes the given variable * * @param name of the variable - * @return the old value of the variable + * @return the old value of the variable, or <tt>null</tt> if there was no variable for the given name */ Object removeVariable(String name); @@ -485,7 +485,7 @@ public interface Exchange { /** * Returns the variables * - * @return the variables in a Map + * @return the variables in a Map. */ Map<String, Object> getVariables(); diff --git a/core/camel-api/src/main/java/org/apache/camel/ExchangeExtension.java b/core/camel-api/src/main/java/org/apache/camel/ExchangeExtension.java index 7ea4a144193..f076041c70c 100644 --- a/core/camel-api/src/main/java/org/apache/camel/ExchangeExtension.java +++ b/core/camel-api/src/main/java/org/apache/camel/ExchangeExtension.java @@ -272,4 +272,5 @@ public interface ExchangeExtension { * @return A new Exchange instance */ Exchange createCopyWithProperties(CamelContext context); + } diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepository.java b/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepository.java new file mode 100644 index 00000000000..91d78bfa52f --- /dev/null +++ b/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepository.java @@ -0,0 +1,55 @@ +/* + * 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.camel.spi; + +import org.apache.camel.StaticService; + +/** + * Repository for storing and accessing variables. + */ +public interface VariableRepository extends StaticService { + + /** + * The id of this repository. + */ + String getId(); + + /** + * Returns a variable by name + * + * @param name the name of the variable + * @return the value of the given variable or <tt>null</tt> if there is no variable for the given name + */ + Object getVariable(String name); + + /** + * Sets a variable + * + * @param name of the variable + * @param value the value of the variable + */ + void setVariable(String name, Object value); + + /** + * Removes the given variable + * + * @param name of the variable + * @return the old value of the variable, or <tt>null</tt> if there was no variable for the given name + */ + Object removeVariable(String name); + +} diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepositoryFactory.java b/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepositoryFactory.java new file mode 100644 index 00000000000..ba7aefeb473 --- /dev/null +++ b/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepositoryFactory.java @@ -0,0 +1,32 @@ +/* + * 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.camel.spi; + +/** + * Factory for {@link VariableRepository}. + */ +public interface VariableRepositoryFactory { + + /** + * Gets the {@link VariableRepository} for the given id + * + * @param id the repository id + * @return the repository or <tt>null</tt> if none found + */ + VariableRepository getVariableRepository(String id); + +} diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java index 5f2060182d7..461444c6590 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java @@ -166,6 +166,8 @@ import org.apache.camel.spi.UriFactoryResolver; import org.apache.camel.spi.UuidGenerator; import org.apache.camel.spi.Validator; import org.apache.camel.spi.ValidatorRegistry; +import org.apache.camel.spi.VariableRepository; +import org.apache.camel.spi.VariableRepositoryFactory; import org.apache.camel.support.CamelContextHelper; import org.apache.camel.support.EndpointHelper; import org.apache.camel.support.EventHelper; @@ -340,6 +342,7 @@ public abstract class AbstractCamelContext extends BaseService camelContextExtension.addContextPlugin(FactoryFinderResolver.class, createFactoryFinderResolver()); camelContextExtension.addContextPlugin(PackageScanClassResolver.class, createPackageScanClassResolver()); camelContextExtension.addContextPlugin(PackageScanResourceResolver.class, createPackageScanResourceResolver()); + camelContextExtension.addContextPlugin(VariableRepositoryFactory.class, createVariableRepositoryFactory()); camelContextExtension.lazyAddContextPlugin(ModelineFactory.class, this::createModelineFactory); camelContextExtension.lazyAddContextPlugin(ModelJAXBContextFactory.class, this::createModelJAXBContextFactory); camelContextExtension.addContextPlugin(DataFormatResolver.class, createDataFormatResolver()); @@ -1557,6 +1560,27 @@ public abstract class AbstractCamelContext extends BaseService return camelContextExtension.resolvePropertyPlaceholders(text, false); } + @Override + public Object getVariable(String name) { + String id = StringHelper.before(name, ":", "global"); + name = StringHelper.after(name, ":", name); + VariableRepository repo + = camelContextExtension.getContextPlugin(VariableRepositoryFactory.class).getVariableRepository(id); + if (repo != null) { + return repo.getVariable(name); + } + return null; + } + + @Override + public <T> T getVariable(String name, Class<T> type) { + Object value = getVariable(name); + if (value != null) { + return getTypeConverter().convertTo(type, value); + } + return null; + } + @Override public TypeConverter getTypeConverter() { return camelContextExtension.getTypeConverter(); @@ -3996,6 +4020,8 @@ public abstract class AbstractCamelContext extends BaseService protected abstract ValidatorRegistry<ValidatorKey> createValidatorRegistry(); + protected abstract VariableRepositoryFactory createVariableRepositoryFactory(); + protected RestConfiguration createRestConfiguration() { // lookup a global which may have been on a container such spring-boot / CDI / etc. RestConfiguration conf diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultVariableRepositoryFactory.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultVariableRepositoryFactory.java new file mode 100644 index 00000000000..524192b909f --- /dev/null +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultVariableRepositoryFactory.java @@ -0,0 +1,75 @@ +/* + * 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.camel.impl.engine; + +import org.apache.camel.CamelContext; +import org.apache.camel.StaticService; +import org.apache.camel.spi.VariableRepository; +import org.apache.camel.spi.VariableRepositoryFactory; +import org.apache.camel.support.CamelContextHelper; +import org.apache.camel.support.GlobalVariableRepository; +import org.apache.camel.support.service.ServiceSupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Default {@link VariableRepositoryFactory}. + */ +public class DefaultVariableRepositoryFactory extends ServiceSupport implements VariableRepositoryFactory, StaticService { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultVariableRepositoryFactory.class); + + private final CamelContext camelContext; + private VariableRepository global; + + public DefaultVariableRepositoryFactory(CamelContext camelContext) { + this.camelContext = camelContext; + } + + @Override + public VariableRepository getVariableRepository(String id) { + if ("global".equals(id)) { + return global; + } + + // otherwise lookup in registry if the repo exists + VariableRepository repo = CamelContextHelper.lookup(camelContext, id, VariableRepository.class); + if (repo != null) { + return repo; + } + + return null; + } + + @Override + protected void doStart() throws Exception { + super.doStart(); + + VariableRepository repo = CamelContextHelper.findSingleByType(camelContext, VariableRepository.class); + if (repo != null) { + LOG.info("Using VariableRepository: {} as global repository", repo.getId()); + global = repo; + } else { + global = new GlobalVariableRepository(); + } + + if (!camelContext.hasService(global)) { + camelContext.addService(global); + } + } + +} diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/SimpleCamelContext.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/SimpleCamelContext.java index 04bc7c475f0..4b013e81e81 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/SimpleCamelContext.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/SimpleCamelContext.java @@ -91,6 +91,7 @@ import org.apache.camel.spi.UnitOfWorkFactory; import org.apache.camel.spi.UriFactoryResolver; import org.apache.camel.spi.UuidGenerator; import org.apache.camel.spi.ValidatorRegistry; +import org.apache.camel.spi.VariableRepositoryFactory; import org.apache.camel.support.DefaultRegistry; import org.apache.camel.support.DefaultUuidGenerator; import org.apache.camel.support.NormalizedUri; @@ -720,6 +721,11 @@ public class SimpleCamelContext extends AbstractCamelContext { return new DefaultValidatorRegistry(getCamelContextReference()); } + @Override + protected VariableRepositoryFactory createVariableRepositoryFactory() { + return new DefaultVariableRepositoryFactory(getCamelContextReference()); + } + @Override protected TransformerRegistry<TransformerKey> createTransformerRegistry() { return new DefaultTransformerRegistry(getCamelContextReference()); diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SetVariableProcessor.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SetVariableProcessor.java index 0f8bb726253..f0690902e27 100644 --- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SetVariableProcessor.java +++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SetVariableProcessor.java @@ -17,22 +17,30 @@ package org.apache.camel.processor; import org.apache.camel.AsyncCallback; +import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; import org.apache.camel.Exchange; import org.apache.camel.Expression; import org.apache.camel.Traceable; import org.apache.camel.spi.IdAware; import org.apache.camel.spi.RouteIdAware; +import org.apache.camel.spi.VariableRepository; +import org.apache.camel.spi.VariableRepositoryFactory; import org.apache.camel.support.AsyncProcessorSupport; import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.StringHelper; /** * A processor which sets the variable with an {@link Expression} */ -public class SetVariableProcessor extends AsyncProcessorSupport implements Traceable, IdAware, RouteIdAware { +public class SetVariableProcessor extends AsyncProcessorSupport implements Traceable, IdAware, RouteIdAware, CamelContextAware { + + private CamelContext camelContext; private String id; private String routeId; private final Expression variableName; private final Expression expression; + private VariableRepositoryFactory factory; public SetVariableProcessor(Expression variableName, Expression expression) { this.variableName = variableName; @@ -41,6 +49,16 @@ public class SetVariableProcessor extends AsyncProcessorSupport implements Trace ObjectHelper.notNull(expression, "expression"); } + @Override + public CamelContext getCamelContext() { + return camelContext; + } + + @Override + public void setCamelContext(CamelContext camelContext) { + this.camelContext = camelContext; + } + @Override public boolean process(Exchange exchange, AsyncCallback callback) { try { @@ -53,7 +71,19 @@ public class SetVariableProcessor extends AsyncProcessorSupport implements Trace } String key = variableName.evaluate(exchange, String.class); - exchange.setVariable(key, newVariable); + String id = StringHelper.before(key, ":"); + if (id != null) { + VariableRepository repo = factory.getVariableRepository(id); + if (repo != null) { + key = StringHelper.after(key, ":"); + repo.setVariable(key, newVariable); + } else { + exchange.setException( + new IllegalArgumentException("VariableRepository with id: " + id + " does not exists")); + } + } else { + exchange.setVariable(key, newVariable); + } } catch (Exception e) { exchange.setException(e); } @@ -62,6 +92,12 @@ public class SetVariableProcessor extends AsyncProcessorSupport implements Trace return true; } + @Override + protected void doBuild() throws Exception { + super.doBuild(); + factory = getCamelContext().getCamelContextExtension().getContextPlugin(VariableRepositoryFactory.class); + } + @Override public String toString() { return id; diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/CustomGlobalVariableTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/CustomGlobalVariableTest.java new file mode 100644 index 00000000000..82192e1efbf --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/processor/CustomGlobalVariableTest.java @@ -0,0 +1,112 @@ +/* + * 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.camel.processor; + +import java.util.List; + +import org.apache.camel.CamelContext; +import org.apache.camel.ContextTestSupport; +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.spi.VariableRepository; +import org.apache.camel.support.service.ServiceSupport; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class CustomGlobalVariableTest extends ContextTestSupport { + private MockEndpoint end; + private String variableName = "foo"; + private String expectedVariableValue = "bar"; + + @Override + protected CamelContext createCamelContext() throws Exception { + CamelContext context = super.createCamelContext(); + context.getRegistry().bind("myGlobal", new MyGlobalRepo()); + return context; + } + + @Test + public void testSetExchangeVariableMidRoute() throws Exception { + assertNull(context.getVariable(variableName)); + + end.expectedMessageCount(1); + + template.sendBody("direct:start", "<blah/>"); + + // make sure we got the message + assertMockEndpointsSatisfied(); + + // lets get the variable value + List<Exchange> exchanges = end.getExchanges(); + Exchange exchange = exchanges.get(0); + String actualVariableValue = exchange.getVariable(variableName, String.class); + // should be stored on global so null + assertNull(actualVariableValue); + + // should be stored as global variable + assertEquals("!" + expectedVariableValue + "!", context.getVariable(variableName)); + } + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + end = getMockEndpoint("mock:end"); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + public void configure() { + // stored as global variable + from("direct:start").setVariable("global:" + variableName).constant(expectedVariableValue).to("mock:end"); + } + }; + } + + private static class MyGlobalRepo extends ServiceSupport implements VariableRepository { + + private Object value; + + @Override + public String getId() { + return "myGlobal"; + } + + @Override + public Object getVariable(String name) { + if (value != null) { + return "!" + value + "!"; + } + return null; + } + + @Override + public void setVariable(String name, Object value) { + this.value = value; + } + + @Override + public Object removeVariable(String name) { + return null; + } + } +} diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/SetGlobalVariableTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/SetGlobalVariableTest.java new file mode 100644 index 00000000000..7870bd292f1 --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/processor/SetGlobalVariableTest.java @@ -0,0 +1,74 @@ +/* + * 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.camel.processor; + +import java.util.List; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class SetGlobalVariableTest extends ContextTestSupport { + private MockEndpoint end; + private String variableName = "foo"; + private String expectedVariableValue = "bar"; + + @Test + public void testSetExchangeVariableMidRoute() throws Exception { + assertNull(context.getVariable(variableName)); + + end.expectedMessageCount(1); + + template.sendBody("direct:start", "<blah/>"); + + // make sure we got the message + assertMockEndpointsSatisfied(); + + // lets get the variable value + List<Exchange> exchanges = end.getExchanges(); + Exchange exchange = exchanges.get(0); + String actualVariableValue = exchange.getVariable(variableName, String.class); + // should be stored on global so null + assertNull(actualVariableValue); + + // should be stored as global variable + assertEquals(expectedVariableValue, context.getVariable(variableName)); + } + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + end = getMockEndpoint("mock:end"); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + public void configure() { + // stored as global variable + from("direct:start").setVariable("global:" + variableName).constant(expectedVariableValue).to("mock:end"); + } + }; + } +} diff --git a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedVariableRepositoryMBean.java b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedVariableRepositoryMBean.java new file mode 100644 index 00000000000..d9e4ea37583 --- /dev/null +++ b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedVariableRepositoryMBean.java @@ -0,0 +1,26 @@ +/* + * 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.camel.api.management.mbean; + +import org.apache.camel.api.management.ManagedAttribute; + +public interface ManagedVariableRepositoryMBean extends ManagedServiceMBean { + + @ManagedAttribute(description = "Repository ID") + String getId(); + +} diff --git a/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java b/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java index ec17575664e..83192a799f7 100644 --- a/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java +++ b/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java @@ -74,6 +74,7 @@ import org.apache.camel.management.mbean.ManagedTracer; import org.apache.camel.management.mbean.ManagedTransformerRegistry; import org.apache.camel.management.mbean.ManagedTypeConverterRegistry; import org.apache.camel.management.mbean.ManagedValidatorRegistry; +import org.apache.camel.management.mbean.ManagedVariableRepository; import org.apache.camel.model.InterceptDefinition; import org.apache.camel.model.OnCompletionDefinition; import org.apache.camel.model.OnExceptionDefinition; @@ -106,6 +107,7 @@ import org.apache.camel.spi.TransformerRegistry; import org.apache.camel.spi.TypeConverterRegistry; import org.apache.camel.spi.UnitOfWork; import org.apache.camel.spi.ValidatorRegistry; +import org.apache.camel.spi.VariableRepository; import org.apache.camel.support.TimerListenerManager; import org.apache.camel.support.service.ServiceSupport; import org.apache.camel.throttling.ThrottlingExceptionRoutePolicy; @@ -565,6 +567,8 @@ public class JmxManagementLifecycleStrategy extends ServiceSupport implements Li answer = new ManagedTransformerRegistry(context, transformerRegistry); } else if (service instanceof ValidatorRegistry<?> validatorRegistry) { answer = new ManagedValidatorRegistry(context, validatorRegistry); + } else if (service instanceof VariableRepository variableRepository) { + answer = new ManagedVariableRepository(context, variableRepository); } else if (service instanceof CamelClusterService) { answer = getManagementObjectStrategy().getManagedObjectForClusterService(context, (CamelClusterService) service); } else if (service != null) { diff --git a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedVariableRepository.java b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedVariableRepository.java new file mode 100644 index 00000000000..75c9569ff3a --- /dev/null +++ b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedVariableRepository.java @@ -0,0 +1,41 @@ +/* + * 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.camel.management.mbean; + +import org.apache.camel.CamelContext; +import org.apache.camel.api.management.ManagedResource; +import org.apache.camel.api.management.mbean.ManagedVariableRepositoryMBean; +import org.apache.camel.spi.VariableRepository; + +@ManagedResource(description = "Managed VariableRepository") +public class ManagedVariableRepository extends ManagedService implements ManagedVariableRepositoryMBean { + private final VariableRepository variableRepository; + + public ManagedVariableRepository(CamelContext context, VariableRepository variableRepository) { + super(context, variableRepository); + this.variableRepository = variableRepository; + } + + public VariableRepository getVariableRepository() { + return variableRepository; + } + + @Override + public String getId() { + return variableRepository.getId(); + } +} diff --git a/core/camel-management/src/test/java/org/apache/camel/management/ManagedNonManagedServiceTest.java b/core/camel-management/src/test/java/org/apache/camel/management/ManagedNonManagedServiceTest.java index 4450b8511fa..29e6e24dcf8 100644 --- a/core/camel-management/src/test/java/org/apache/camel/management/ManagedNonManagedServiceTest.java +++ b/core/camel-management/src/test/java/org/apache/camel/management/ManagedNonManagedServiceTest.java @@ -34,7 +34,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; @DisabledOnOs(OS.AIX) public class ManagedNonManagedServiceTest extends ManagementTestSupport { - private static final int SERVICES = 14; + private static final int SERVICES = 15; @Test public void testService() throws Exception { diff --git a/core/camel-management/src/test/java/org/apache/camel/management/ManagedProducerRouteAddRemoveRegisterAlwaysTest.java b/core/camel-management/src/test/java/org/apache/camel/management/ManagedProducerRouteAddRemoveRegisterAlwaysTest.java index 57a0b4c33d5..56b9275cc96 100644 --- a/core/camel-management/src/test/java/org/apache/camel/management/ManagedProducerRouteAddRemoveRegisterAlwaysTest.java +++ b/core/camel-management/src/test/java/org/apache/camel/management/ManagedProducerRouteAddRemoveRegisterAlwaysTest.java @@ -36,7 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; @DisabledOnOs(OS.AIX) public class ManagedProducerRouteAddRemoveRegisterAlwaysTest extends ManagementTestSupport { - private static final int SERVICES = 14; + private static final int SERVICES = 15; @Override protected CamelContext createCamelContext() throws Exception { diff --git a/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteAddRemoveTest.java b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteAddRemoveTest.java index abeda66326b..69c52be028b 100644 --- a/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteAddRemoveTest.java +++ b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteAddRemoveTest.java @@ -40,7 +40,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; @DisabledOnOs(OS.AIX) public class ManagedRouteAddRemoveTest extends ManagementTestSupport { - private static final int SERVICES = 14; + private static final int SERVICES = 15; @Override protected RouteBuilder createRouteBuilder() throws Exception { diff --git a/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java b/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java index b0e4ddbcffc..362336c3292 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java @@ -53,7 +53,6 @@ abstract class AbstractExchange implements Exchange { protected final CamelContext context; protected Map<String, Object> properties; // create properties on-demand as we use internal properties mostly - protected Map<String, Object> variables; // create variables on-demand protected Message in; protected Message out; protected Exception exception; @@ -63,10 +62,10 @@ abstract class AbstractExchange implements Exchange { protected boolean rollbackOnly; protected boolean rollbackOnlyLast; protected Map<String, SafeCopyProperty> safeCopyProperties; + protected ExchangeVariableRepository variableRepository; private final ExtendedExchangeExtension privateExtension; private RedeliveryTraitPayload externalRedelivered = RedeliveryTraitPayload.UNDEFINED_REDELIVERY; - // TODO: variables ? protected AbstractExchange(CamelContext context, EnumMap<ExchangePropertyKey, Object> internalProperties, Map<String, Object> properties) { this.context = context; @@ -127,7 +126,11 @@ abstract class AbstractExchange implements Exchange { privateExtension.setStreamCacheDisabled(parent.getExchangeExtension().isStreamCacheDisabled()); if (parent.hasVariables()) { - this.variables = safeCopyProperties(parent.variables); + if (this.variableRepository == null) { + this.variableRepository = new ExchangeVariableRepository(); + } + this.variableRepository.setVariables(parent.getVariables()); + } if (parent.hasProperties()) { this.properties = safeCopyProperties(parent.properties); @@ -391,8 +394,8 @@ abstract class AbstractExchange implements Exchange { @Override public Object getVariable(String name) { - if (variables != null) { - return variables.get(name); + if (variableRepository != null) { + return variableRepository.getVariable(name); } return null; } @@ -411,24 +414,18 @@ abstract class AbstractExchange implements Exchange { @Override public void setVariable(String name, Object value) { - if (value != null) { - // avoid the NullPointException - if (variables == null) { - this.variables = new ConcurrentHashMap<>(8); - } - variables.put(name, value); - } else if (variables != null) { - // if the value is null, we just remove the key from the map - variables.remove(name); + if (variableRepository == null) { + variableRepository = new ExchangeVariableRepository(); } + variableRepository.setVariable(name, value); } @Override public Object removeVariable(String name) { - if (!hasVariables()) { - return null; + if (variableRepository != null) { + return variableRepository.removeVariable(name); } - return variables.remove(name); + return null; } @Override @@ -438,40 +435,37 @@ abstract class AbstractExchange implements Exchange { @Override public boolean removeVariables(String pattern, String... excludePatterns) { + if (variableRepository == null) { + return false; + } + // special optimized if (excludePatterns == null && "*".equals(pattern)) { - if (variables != null) { - variables.clear(); - } + variableRepository.clear(); return true; } boolean matches = false; // store keys to be removed as we cannot loop and remove at the same time in implementations such as HashMap - if (variables != null) { - Set<String> toBeRemoved = null; - for (String key : variables.keySet()) { + if (variableRepository.hasVariables()) { + Set<String> toBeRemoved = new HashSet<>(); + variableRepository.names().forEach(key -> { if (PatternHelper.matchPattern(key, pattern)) { - if (excludePatterns != null && PatternHelper.isExcludePatternMatch(key, excludePatterns)) { - continue; + boolean excluded = excludePatterns != null && PatternHelper.isExcludePatternMatch(key, excludePatterns); + if (!excluded) { + toBeRemoved.add(key); } - matches = true; - if (toBeRemoved == null) { - toBeRemoved = new HashSet<>(); - } - toBeRemoved.add(key); } - } - - if (matches) { - if (toBeRemoved.size() == variables.size()) { - // special optimization when all should be removed - variables.clear(); - } else { - for (String key : toBeRemoved) { - variables.remove(key); - } + }); + + matches = !toBeRemoved.isEmpty(); + if (toBeRemoved.size() == variableRepository.size()) { + // special optimization when all should be removed + variableRepository.clear(); + } else { + for (String key : toBeRemoved) { + variableRepository.removeVariable(key); } } } @@ -481,15 +475,19 @@ abstract class AbstractExchange implements Exchange { @Override public Map<String, Object> getVariables() { - if (variables == null) { - this.variables = new ConcurrentHashMap<>(8); + if (variableRepository == null) { + // force creating variables + variableRepository = new ExchangeVariableRepository(); } - return variables; + return variableRepository.getVariables(); } @Override public boolean hasVariables() { - return variables != null && !variables.isEmpty(); + if (variableRepository != null) { + return variableRepository.hasVariables(); + } + return false; } @Override diff --git a/core/camel-support/src/main/java/org/apache/camel/support/DefaultPooledExchange.java b/core/camel-support/src/main/java/org/apache/camel/support/DefaultPooledExchange.java index 75fa2d70e42..620a7af2623 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/DefaultPooledExchange.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/DefaultPooledExchange.java @@ -41,7 +41,6 @@ public final class DefaultPooledExchange extends AbstractExchange implements Poo super(context); this.originalPattern = getPattern(); this.properties = new ConcurrentHashMap<>(8); - this.clock = new ResetableClock(); } @@ -184,9 +183,7 @@ public final class DefaultPooledExchange extends AbstractExchange implements Poo public static DefaultPooledExchange newFromEndpoint(Endpoint fromEndpoint, ExchangePattern exchangePattern) { DefaultPooledExchange exchange = new DefaultPooledExchange(fromEndpoint.getCamelContext(), exchangePattern); - exchange.getExchangeExtension().setFromEndpoint(fromEndpoint); - return exchange; } } diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java new file mode 100644 index 00000000000..e422155282f --- /dev/null +++ b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java @@ -0,0 +1,87 @@ +/* + * 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.camel.support; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +import org.apache.camel.Exchange; +import org.apache.camel.NonManagedService; +import org.apache.camel.spi.VariableRepository; +import org.apache.camel.support.service.ServiceSupport; + +/** + * {@link VariableRepository} which is local per {@link Exchange} to hold request-scoped variables. + */ +class ExchangeVariableRepository extends ServiceSupport implements VariableRepository, NonManagedService { + + private final Map<String, Object> variables = new ConcurrentHashMap<>(8); + + @Override + public String getId() { + return "exchange"; + } + + @Override + public Object getVariable(String name) { + return variables.get(name); + } + + @Override + public void setVariable(String name, Object value) { + if (value != null) { + // avoid the NullPointException + variables.put(name, value); + } else { + // if the value is null, we just remove the key from the map + variables.remove(name); + } + } + + public boolean hasVariables() { + return !variables.isEmpty(); + } + + public int size() { + return variables.size(); + } + + public Stream<String> names() { + return variables.keySet().stream(); + } + + public Map<String, Object> getVariables() { + return variables; + } + + public void setVariables(Map<String, Object> map) { + variables.putAll(map); + } + + public void clear() { + variables.clear(); + } + + @Override + public Object removeVariable(String name) { + if (!hasVariables()) { + return null; + } + return variables.remove(name); + } +} diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ExtendedExchangeExtension.java b/core/camel-support/src/main/java/org/apache/camel/support/ExtendedExchangeExtension.java index 42cb778526b..9f35486dd0b 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/ExtendedExchangeExtension.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/ExtendedExchangeExtension.java @@ -328,14 +328,13 @@ public class ExtendedExchangeExtension implements ExchangeExtension { setRedeliveryExhausted(false); setErrorHandlerHandled(null); setStreamCacheDisabled(false); + this.exchange.variableRepository = null; } @Override public Exchange createCopyWithProperties(CamelContext context) { DefaultExchange answer = new DefaultExchange(context, exchange.internalProperties, exchange.properties); - answer.setPattern(exchange.pattern); - return answer; } } diff --git a/core/camel-support/src/main/java/org/apache/camel/support/GlobalVariableRepository.java b/core/camel-support/src/main/java/org/apache/camel/support/GlobalVariableRepository.java new file mode 100644 index 00000000000..a026f759c8b --- /dev/null +++ b/core/camel-support/src/main/java/org/apache/camel/support/GlobalVariableRepository.java @@ -0,0 +1,58 @@ +/* + * 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.camel.support; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.camel.spi.VariableRepository; +import org.apache.camel.support.service.ServiceSupport; + +/** + * Global {@link VariableRepository} which stores variables in-memory in a {@link Map}. + */ +public class GlobalVariableRepository extends ServiceSupport implements VariableRepository { + + private final ConcurrentMap<String, Object> variables = new ConcurrentHashMap<>(); + + @Override + public String getId() { + return "global"; + } + + @Override + public Object getVariable(String name) { + return variables.get(name); + } + + @Override + public void setVariable(String name, Object value) { + if (value != null) { + // avoid the NullPointException + variables.put(name, value); + } else { + // if the value is null, we just remove the key from the map + variables.remove(name); + } + } + + @Override + public Object removeVariable(String name) { + return variables.remove(name); + } +} diff --git a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java index b476a8c0e96..2d105ed02f8 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java @@ -294,7 +294,7 @@ public class ExpressionBuilder { } /** - * Returns an expression for variables + * Returns an expression for the {@link Exchange} variables * * @return an expression object which will return the variables */ diff --git a/core/camel-support/src/main/java/org/apache/camel/support/processor/DefaultExchangeFormatter.java b/core/camel-support/src/main/java/org/apache/camel/support/processor/DefaultExchangeFormatter.java index ffd911aacf4..e9f919d5b45 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/processor/DefaultExchangeFormatter.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/processor/DefaultExchangeFormatter.java @@ -185,7 +185,9 @@ public class DefaultExchangeFormatter implements ExchangeFormatter { if (multiline) { sb.append(SEPARATOR); } - style(sb, "Variables").append(sortMap(filterHeaderAndProperties(exchange.getVariables()))); + if (exchange.hasVariables()) { + style(sb, "Variables").append(sortMap(filterHeaderAndProperties(exchange.getVariables()))); + } } if (showAll || showHeaders) { if (multiline) { @@ -574,7 +576,9 @@ public class DefaultExchangeFormatter implements ExchangeFormatter { private static Map<String, Object> sortMap(Map<String, Object> map) { Map<String, Object> answer = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - answer.putAll(map); + if (map != null && !map.isEmpty()) { + answer.putAll(map); + } return answer; } diff --git a/core/camel-util/src/main/java/org/apache/camel/util/StringHelper.java b/core/camel-util/src/main/java/org/apache/camel/util/StringHelper.java index c5611e3ee33..8771410df5f 100644 --- a/core/camel-util/src/main/java/org/apache/camel/util/StringHelper.java +++ b/core/camel-util/src/main/java/org/apache/camel/util/StringHelper.java @@ -569,6 +569,9 @@ public final class StringHelper { * @return the text after the token, or <tt>null</tt> if text does not contain the token */ public static String after(String text, String after) { + if (text == null) { + return null; + } int pos = text.indexOf(after); if (pos == -1) { return null; @@ -614,6 +617,9 @@ public final class StringHelper { * @return the text after the token, or <tt>null</tt> if text does not contain the token */ public static String afterLast(String text, String after) { + if (text == null) { + return null; + } int pos = text.lastIndexOf(after); if (pos == -1) { return null; @@ -642,6 +648,9 @@ public final class StringHelper { * @return the text before the token, or <tt>null</tt> if text does not contain the token */ public static String before(String text, String before) { + if (text == null) { + return null; + } int pos = text.indexOf(before); return pos == -1 ? null : text.substring(0, pos); } @@ -655,6 +664,9 @@ public final class StringHelper { * @return the text before the token, or the supplied defaultValue if text does not contain the token */ public static String before(String text, String before, String defaultValue) { + if (text == null) { + return defaultValue; + } int pos = text.indexOf(before); return pos == -1 ? defaultValue : text.substring(0, pos); } @@ -668,6 +680,9 @@ public final class StringHelper { * @return the text before the token, or the supplied defaultValue if text does not contain the token */ public static String before(String text, char before, String defaultValue) { + if (text == null) { + return defaultValue; + } int pos = text.indexOf(before); return pos == -1 ? defaultValue : text.substring(0, pos); } @@ -697,6 +712,9 @@ public final class StringHelper { * @return the text before the token, or <tt>null</tt> if text does not contain the token */ public static String beforeLast(String text, String before) { + if (text == null) { + return null; + } int pos = text.lastIndexOf(before); return pos == -1 ? null : text.substring(0, pos); }