http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/examples/README.adoc ---------------------------------------------------------------------- diff --git a/examples/README.adoc b/examples/README.adoc index 9eeae55..08e5ff5 100644 --- a/examples/README.adoc +++ b/examples/README.adoc @@ -93,7 +93,9 @@ Number of Examples: 84 | link:camel-example-transformer-cdi/README.md[Transformer CDI] (camel-example-transformer-cdi) | Input/Output Type Contract | An example demonstrating declarative transformation along data type declaration using Java DSL and CDI -| link:camel-example-transformer-demo/README.md[Transformer Spring XML] (camel-example-transformer-demo) | Input/Output Type Contract | An example demonstrating declarative transformation along data type declaration using Spring DSL +| link:camel-example-transformer-demo/README.md[Transformer and Validator Spring XML] (camel-example-transformer-demo) | Input/Output Type Contract | An example demonstrating declarative transformation and validation along data type declaration using Spring DSL + +| link:camel-example-validator-spring-boot/README.md[Validator Spring Boot] (camel-example-validator-spring-boot) | Input/Output Type Contract | An example demonstrating declarative validation along data type declaration using Spring Boot | link:camel-example-bam/README.md[BAM (deprecated)] (camel-example-bam) | Management and Monitoring | *deprecated* An example showing how to use Camel as a Business Activity Monitoring tool
http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/examples/camel-example-transformer-demo/README.md ---------------------------------------------------------------------- diff --git a/examples/camel-example-transformer-demo/README.md b/examples/camel-example-transformer-demo/README.md index 8f203f8..11123d9 100644 --- a/examples/camel-example-transformer-demo/README.md +++ b/examples/camel-example-transformer-demo/README.md @@ -1,9 +1,9 @@ -# Declarative Transformer Demo using Spring XML +# Declarative Transformer and Validator Demo using Spring XML ### Introduction -This example shows how to work with declarative transformation by declaring data types. +This example shows how to work with declarative transformation and validation by declaring data types. ### Build @@ -20,6 +20,10 @@ To run the example type You can see the routing rules by looking at the Spring XML configuration lives in `src/main/resources/META-INF/spring` +If you enable DEBUG level log for org.apache.camel.processor, you can see the details +of when/which transformers & validators are applied. Check the +`src/main/resources/log4j2.properties` + ### Forum, Help, etc http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/examples/camel-example-transformer-demo/pom.xml ---------------------------------------------------------------------- diff --git a/examples/camel-example-transformer-demo/pom.xml b/examples/camel-example-transformer-demo/pom.xml index af3759c..d81a437 100644 --- a/examples/camel-example-transformer-demo/pom.xml +++ b/examples/camel-example-transformer-demo/pom.xml @@ -27,12 +27,12 @@ <artifactId>camel-example-transformer-demo</artifactId> <name>Camel :: Example :: Transformer :: Demo</name> - <description>An example demonstrating declarative transformation along data type declaration using Spring DSL</description> + <description>An example demonstrating declarative transformation and validation along data type declaration using Spring DSL</description> <packaging>jar</packaging> <properties> <category>Input/Output Type Contract</category> - <title>Transformer Spring XML</title> + <title>Transformer and Validator Spring XML</title> </properties> <dependencies> http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/examples/camel-example-transformer-demo/src/main/java/org/apache/camel/example/transformer/demo/OrderResponseValidator.java ---------------------------------------------------------------------- diff --git a/examples/camel-example-transformer-demo/src/main/java/org/apache/camel/example/transformer/demo/OrderResponseValidator.java b/examples/camel-example-transformer-demo/src/main/java/org/apache/camel/example/transformer/demo/OrderResponseValidator.java new file mode 100644 index 0000000..2ebf9f3 --- /dev/null +++ b/examples/camel-example-transformer-demo/src/main/java/org/apache/camel/example/transformer/demo/OrderResponseValidator.java @@ -0,0 +1,26 @@ +package org.apache.camel.example.transformer.demo; + +import org.apache.camel.Message; +import org.apache.camel.ValidationException; +import org.apache.camel.spi.DataType; +import org.apache.camel.spi.Validator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OrderResponseValidator extends Validator { + private static final Logger LOG = LoggerFactory.getLogger(OrderResponseValidator.class); + + @Override + public void validate(Message message, DataType type) throws ValidationException { + Object body = message.getBody(); + LOG.info("Validating message body: {}", body); + if (!(body instanceof OrderResponse)) { + throw new ValidationException(message.getExchange(), "Expected OrderResponse, but was " + body.getClass()); + } + OrderResponse r = (OrderResponse)body; + if (!r.isAccepted()) { + throw new ValidationException(message.getExchange(), "Order was not accepted:" + r); + } + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/examples/camel-example-transformer-demo/src/main/resources/META-INF/spring/camel-context.xml ---------------------------------------------------------------------- diff --git a/examples/camel-example-transformer-demo/src/main/resources/META-INF/spring/camel-context.xml b/examples/camel-example-transformer-demo/src/main/resources/META-INF/spring/camel-context.xml index 09c3b72..fa22fe0 100644 --- a/examples/camel-example-transformer-demo/src/main/resources/META-INF/spring/camel-context.xml +++ b/examples/camel-example-transformer-demo/src/main/resources/META-INF/spring/camel-context.xml @@ -51,13 +51,23 @@ <camel:dataFormatTransformer ref="jsondf" scheme="json"/> </camel:transformers> <!-- END SNIPPET: e3 --> + <!-- START SNIPPET: e8 --> + <camel:validators> + <camel:endpointValidator type="xml" uri="validator:xsd/schema.xsd"/> + <camel:predicateValidator type="json"> + <camel:simple>${body} contains 'orderId' and ${body} not contains 'accepted'</camel:simple> + </camel:predicateValidator> + <camel:customValidator type="java:org.apache.camel.example.transformer.demo.OrderResponse" + className="org.apache.camel.example.transformer.demo.OrderResponseValidator"/> + </camel:validators> + <!-- END SNIPPET: e8 --> <!-- START SNIPPET: e4 --> <camel:route id="xml"> <camel:from uri="direct:xml"/> <!-- This route expects XML as an input/output type --> - <camel:inputType urn="xml:XMLOrder"/> - <camel:outputType urn="xml:XMLOrderResponse"/> + <camel:inputType urn="xml:XMLOrder" validate="true"/> + <camel:outputType urn="xml:XMLOrderResponse" validate="true"/> <camel:to uri="direct:java"/> </camel:route> <!-- END SNIPPET: e4 --> @@ -67,7 +77,7 @@ <camel:from uri="direct:json"/> <!-- This route expects JSON as an input/output type --> <!-- Only scheme 'json' is specified for the type, which means it handles arbitrary JSON content --> - <camel:inputType urn="json"/> + <camel:inputType urn="json" validate="true"/> <camel:outputType urn="json"/> <camel:to uri="direct:java"/> </camel:route> @@ -81,7 +91,7 @@ <!-- If it comes from json route, the JSON-Java transformer is applied. --> <!-- If it's sent via ProducerTemplate directly without specifying input/output type, no transformer is applied. --> <camel:inputType urn="java:org.apache.camel.example.transformer.demo.Order"/> - <camel:outputType urn="java:org.apache.camel.example.transformer.demo.OrderResponse"/> + <camel:outputType urn="java:org.apache.camel.example.transformer.demo.OrderResponse" validate="true"/> <camel:wireTap uri="direct:csv"/> <camel:process ref="orderProcessor"/> </camel:route> http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/examples/camel-example-transformer-demo/src/main/resources/xsd/schema.xsd ---------------------------------------------------------------------- diff --git a/examples/camel-example-transformer-demo/src/main/resources/xsd/schema.xsd b/examples/camel-example-transformer-demo/src/main/resources/xsd/schema.xsd new file mode 100644 index 0000000..cd1455e --- /dev/null +++ b/examples/camel-example-transformer-demo/src/main/resources/xsd/schema.xsd @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"> + + <xs:element name="order" type="order"/> + + <xs:element name="orderResponse" type="orderResponse"/> + + <xs:complexType name="order"> + <xs:sequence/> + <xs:attribute name="orderId" type="xs:string"/> + <xs:attribute name="itemId" type="xs:string"/> + <xs:attribute name="quantity" type="xs:int" use="required"/> + </xs:complexType> + + <xs:complexType name="orderResponse"> + <xs:sequence/> + <xs:attribute name="orderId" type="xs:string"/> + <xs:attribute name="accepted" type="xs:boolean" use="required"/> + <xs:attribute name="description" type="xs:string"/> + </xs:complexType> +</xs:schema> + http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/examples/camel-example-validator-spring-boot/pom.xml ---------------------------------------------------------------------- diff --git a/examples/camel-example-validator-spring-boot/pom.xml b/examples/camel-example-validator-spring-boot/pom.xml new file mode 100644 index 0000000..91ebba2 --- /dev/null +++ b/examples/camel-example-validator-spring-boot/pom.xml @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.camel</groupId> + <artifactId>examples</artifactId> + <version>2.19.0-SNAPSHOT</version> + </parent> + + <artifactId>camel-example-validator-spring-boot</artifactId> + <name>Camel :: Example :: Validator :: Spring Boot</name> + <description>An example showing how to work with declarative validation and Spring Boot</description> + + <properties> + <category>Input/Output Type Contract</category> + <title>Validator Spring Boot</title> + + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <spring.boot-version>${spring-boot-version}</spring.boot-version> + </properties> + + <dependencyManagement> + <dependencies> + <!-- Spring Boot BOM --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-dependencies</artifactId> + <version>${spring.boot-version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + <!-- Camel BOM --> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-spring-boot-dependencies</artifactId> + <version>${project.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <dependencies> + + <!-- Spring Boot --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-undertow</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-actuator</artifactId> + </dependency> + + <!-- Camel --> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-spring-boot-starter</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-stream-starter</artifactId> + </dependency> + + <!-- test --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test-spring</artifactId> + <scope>test</scope> + </dependency> + + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <version>${spring-boot-version}</version> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/examples/camel-example-validator-spring-boot/readme.adoc ---------------------------------------------------------------------- diff --git a/examples/camel-example-validator-spring-boot/readme.adoc b/examples/camel-example-validator-spring-boot/readme.adoc new file mode 100644 index 0000000..1bc83e4 --- /dev/null +++ b/examples/camel-example-validator-spring-boot/readme.adoc @@ -0,0 +1,28 @@ +# Camel Example Validator Spring Boot + +This example shows how to work with a simple Apache Camel application using Spring Boot with declarative content validation enabled. + +The example generates messages using timer trigger, writes them to standard output. The output type is declared as `greeting`, which has corresponding validator defined. + +## Camel routes + +The Camel route is located in the `SampleCamelRouter` class. In this class the route +starts from a timer, that triggers every 2nd second and calls a Spring Bean `SampleBean` +which returns a message, that is routed to a stream endpoint which writes to standard output. +The output type is declared as `greeting`, and the validator `GreetingValidator` is registered +to be triggered for `greeting` output message right after the routing. + +## Using Camel components + +Apache Camel provides 200+ components which you can use to integrate and route messages between many systems +and data formats. To use any of these Camel components, add the component as a dependency to your project. + +## How to run + +You can run this example using + + mvn spring-boot:run + +## More information + +You can find more information about Apache Camel at the website: http://camel.apache.org/ http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/examples/camel-example-validator-spring-boot/src/main/java/sample/camel/GreetingValidator.java ---------------------------------------------------------------------- diff --git a/examples/camel-example-validator-spring-boot/src/main/java/sample/camel/GreetingValidator.java b/examples/camel-example-validator-spring-boot/src/main/java/sample/camel/GreetingValidator.java new file mode 100644 index 0000000..79129ff --- /dev/null +++ b/examples/camel-example-validator-spring-boot/src/main/java/sample/camel/GreetingValidator.java @@ -0,0 +1,31 @@ +package sample.camel; + +import org.apache.camel.Message; +import org.apache.camel.ValidationException; +import org.apache.camel.spi.DataType; +import org.apache.camel.spi.Validator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component("greetingValidator") +public class GreetingValidator extends Validator { + + private static final Logger LOG = LoggerFactory.getLogger(GreetingValidator.class); + + @Value("${greeting}") + private String greeting; + + @Override + public void validate(Message message, DataType type) throws ValidationException { + Object body = message.getBody(); + LOG.info("Validating : [{}]", body); + if (body instanceof String && body.equals(greeting)) { + LOG.info("OK"); + } else { + throw new ValidationException(message.getExchange(), "Wrong content"); + } + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/examples/camel-example-validator-spring-boot/src/main/java/sample/camel/SampleBean.java ---------------------------------------------------------------------- diff --git a/examples/camel-example-validator-spring-boot/src/main/java/sample/camel/SampleBean.java b/examples/camel-example-validator-spring-boot/src/main/java/sample/camel/SampleBean.java new file mode 100644 index 0000000..b60ef69 --- /dev/null +++ b/examples/camel-example-validator-spring-boot/src/main/java/sample/camel/SampleBean.java @@ -0,0 +1,38 @@ +/** + * 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 sample.camel; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * A bean that returns a message when you call the {@link #saySomething()} method. + * <p/> + * Uses <tt>@Component("myBean")</tt> to register this bean with the name <tt>myBean</tt> + * that we use in the Camel route to lookup this bean. + */ +@Component("myBean") +public class SampleBean { + + @Value("${greeting}") + private String say; + + public String saySomething() { + return say; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/examples/camel-example-validator-spring-boot/src/main/java/sample/camel/SampleCamelApplication.java ---------------------------------------------------------------------- diff --git a/examples/camel-example-validator-spring-boot/src/main/java/sample/camel/SampleCamelApplication.java b/examples/camel-example-validator-spring-boot/src/main/java/sample/camel/SampleCamelApplication.java new file mode 100644 index 0000000..5d9304a --- /dev/null +++ b/examples/camel-example-validator-spring-boot/src/main/java/sample/camel/SampleCamelApplication.java @@ -0,0 +1,37 @@ +/** + * 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 sample.camel; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +//CHECKSTYLE:OFF +/** + * A sample Spring Boot application that starts the Camel routes. + */ +@SpringBootApplication +public class SampleCamelApplication { + + /** + * A main method to start this application. + */ + public static void main(String[] args) { + SpringApplication.run(SampleCamelApplication.class, args); + } + +} +//CHECKSTYLE:ON http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/examples/camel-example-validator-spring-boot/src/main/java/sample/camel/SampleCamelRouter.java ---------------------------------------------------------------------- diff --git a/examples/camel-example-validator-spring-boot/src/main/java/sample/camel/SampleCamelRouter.java b/examples/camel-example-validator-spring-boot/src/main/java/sample/camel/SampleCamelRouter.java new file mode 100644 index 0000000..b7535ea --- /dev/null +++ b/examples/camel-example-validator-spring-boot/src/main/java/sample/camel/SampleCamelRouter.java @@ -0,0 +1,42 @@ +/** + * 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 sample.camel; + +import org.apache.camel.builder.RouteBuilder; +import org.springframework.stereotype.Component; + +/** + * A simple Camel route that triggers from a timer and calls a bean and prints to system out. + * <p/> + * Use <tt>@Component</tt> to make Camel auto detect this route when starting. + */ +@Component +public class SampleCamelRouter extends RouteBuilder { + + @Override + public void configure() throws Exception { + validator() + .type("greeting") + .withBean("greetingValidator"); + + from("timer:hello?period={{timer.period}}") + .outputType("greeting", true) + .transform(method("myBean", "saySomething")) + .to("stream:out"); + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/examples/camel-example-validator-spring-boot/src/main/resources/application.properties ---------------------------------------------------------------------- diff --git a/examples/camel-example-validator-spring-boot/src/main/resources/application.properties b/examples/camel-example-validator-spring-boot/src/main/resources/application.properties new file mode 100644 index 0000000..1d32a64 --- /dev/null +++ b/examples/camel-example-validator-spring-boot/src/main/resources/application.properties @@ -0,0 +1,28 @@ +# +# 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. +# + +# the name of Camel +camel.springboot.name = SampleCamel + +# properties used in the Camel route and beans +# -------------------------------------------- + +# what to say +greeting = Hello World + +# how often to trigger the timer +timer.period = 2000 http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/examples/camel-example-validator-spring-boot/src/test/java/sample/camel/SampleCamelApplicationTest.java ---------------------------------------------------------------------- diff --git a/examples/camel-example-validator-spring-boot/src/test/java/sample/camel/SampleCamelApplicationTest.java b/examples/camel-example-validator-spring-boot/src/test/java/sample/camel/SampleCamelApplicationTest.java new file mode 100644 index 0000000..7d53276 --- /dev/null +++ b/examples/camel-example-validator-spring-boot/src/test/java/sample/camel/SampleCamelApplicationTest.java @@ -0,0 +1,47 @@ +/** + * 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 sample.camel; + +import java.util.concurrent.TimeUnit; + +import org.apache.camel.CamelContext; +import org.apache.camel.builder.NotifyBuilder; +import org.apache.camel.test.spring.CamelSpringBootRunner; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.Assert.assertTrue; + +@RunWith(CamelSpringBootRunner.class) +@SpringBootTest(classes = SampleCamelApplication.class) +public class SampleCamelApplicationTest { + + @Autowired + private CamelContext camelContext; + + @Test + public void shouldProduceMessages() throws Exception { + // we expect that one or more messages is automatic done by the Camel + // route as it uses a timer to trigger + NotifyBuilder notify = new NotifyBuilder(camelContext).whenDone(1).create(); + + assertTrue(notify.matches(10, TimeUnit.SECONDS)); + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/examples/pom.xml ---------------------------------------------------------------------- diff --git a/examples/pom.xml b/examples/pom.xml index a0c31a1..05a2e98 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -110,6 +110,7 @@ <module>camel-example-twitter-salesforce</module> <module>camel-example-twitter-websocket</module> <module>camel-example-twitter-websocket-blueprint</module> + <module>camel-example-validator-spring-boot</module> <module>camel-example-widget-gadget-cdi</module> <module>camel-example-widget-gadget-java</module> <module>camel-example-widget-gadget-xml</module> http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractLocalCamelController.java ---------------------------------------------------------------------- diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractLocalCamelController.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractLocalCamelController.java index a4d33d4..f37d85b 100644 --- a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractLocalCamelController.java +++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractLocalCamelController.java @@ -47,6 +47,7 @@ import org.apache.camel.spi.ManagementAgent; import org.apache.camel.spi.RestRegistry; import org.apache.camel.spi.RuntimeEndpointRegistry; import org.apache.camel.spi.Transformer; +import org.apache.camel.spi.Validator; import org.apache.camel.util.JsonSchemaHelper; /** @@ -646,6 +647,27 @@ public abstract class AbstractLocalCamelController extends AbstractCamelControll return answer; } + @Override + public List<Map<String, String>> getValidators(String camelContextName) throws Exception { + List<Map<String, String>> answer = new ArrayList<Map<String, String>>(); + + if (camelContextName != null) { + CamelContext context = this.getLocalCamelContext(camelContextName); + if (context != null) { + List<Validator> validators = new ArrayList<Validator>(context.getValidatorRegistry().values()); + for (Validator validator : validators) { + Map<String, String> row = new LinkedHashMap<String, String>(); + row.put("camelContextName", context.getName()); + row.put("type", validator.getType().toString()); + row.put("state", validator.getStatus().toString()); + row.put("description", validator.toString()); + answer.add(row); + } + } + } + return answer; + } + private static String getEndpointState(Endpoint endpoint) { // must use String type to be sure remote JMX can read the attribute without requiring Camel classes. if (endpoint instanceof StatefulService) { http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelController.java ---------------------------------------------------------------------- diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelController.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelController.java index 2cf70a4..67de587 100644 --- a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelController.java +++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelController.java @@ -376,4 +376,13 @@ public interface CamelController { * @throws java.lang.Exception can be thrown */ List<Map<String, String>> getTransformers(String camelContextName) throws Exception; + + /** + * Return the validators + * + * @param camelContextName the Camel context. + * @return a list of key/value pairs with validator information + * @throws java.lang.Exception can be thrown + */ + List<Map<String, String>> getValidators(String camelContextName) throws Exception; } http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ValidatorListCommand.java ---------------------------------------------------------------------- diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ValidatorListCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ValidatorListCommand.java new file mode 100644 index 0000000..07608f0 --- /dev/null +++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ValidatorListCommand.java @@ -0,0 +1,185 @@ +/** + * 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.commands; + +import java.io.PrintStream; +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +import org.apache.camel.util.JsonSchemaHelper; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.URISupport; + +/** + * List the Camel validators available in the JVM. + */ +public class ValidatorListCommand extends AbstractCamelCommand { + + private static final String CONTEXT_NAME_COLUMN_LABEL = "Context"; + private static final String TYPE_COLUMN_LABEL = "Type"; + private static final String STATE_COLUMN_LABEL = "State"; + private static final String DESCRIPTION_COLUMN_LABEL = "Description"; + + private static final int DEFAULT_COLUMN_WIDTH_INCREMENT = 0; + private static final String DEFAULT_FIELD_PREAMBLE = " "; + private static final String DEFAULT_FIELD_POSTAMBLE = " "; + private static final String DEFAULT_HEADER_PREAMBLE = " "; + private static final String DEFAULT_HEADER_POSTAMBLE = " "; + private static final int DEFAULT_FORMAT_BUFFER_LENGTH = 24; + // endpoint uris can be very long so clip by default after 120 chars + private static final int MAX_COLUMN_WIDTH = 120; + private static final int MIN_COLUMN_WIDTH = 12; + + boolean decode = true; + boolean verbose; + boolean explain; + private final String context; + + public ValidatorListCommand(String context, boolean decode, boolean verbose, boolean explain) { + this.decode = decode; + this.verbose = verbose; + this.explain = explain; + this.context = context; + } + + @Override + public Object execute(CamelController camelController, PrintStream out, PrintStream err) throws Exception { + final List<Map<String, String>> camelContextInfos = camelController.getCamelContexts(this.context); + final Map<String, List<Map<String, String>>> contextsToValidators = new HashMap<>(); + + for (Map<String, String> camelContextInfo : camelContextInfos) { + String camelContextName = camelContextInfo.get("name"); + final List<Map<String, String>> validators = camelController.getValidators(camelContextName); + if (validators.isEmpty()) { + continue; + } + contextsToValidators.put(camelContextName, validators); + } + + final Map<String, Integer> columnWidths = computeColumnWidths(contextsToValidators); + final String headerFormat = buildFormatString(columnWidths, true); + final String rowFormat = buildFormatString(columnWidths, false); + + for (Map.Entry<String, List<Map<String, String>>> stringListEntry : contextsToValidators.entrySet()) { + final String camelContextName = stringListEntry.getKey(); + final List<Map<String, String>> validators = stringListEntry.getValue(); + + if (verbose) { + out.println(String.format(headerFormat, CONTEXT_NAME_COLUMN_LABEL, TYPE_COLUMN_LABEL, STATE_COLUMN_LABEL, DESCRIPTION_COLUMN_LABEL)); + out.println(String.format(headerFormat, "-------", "----", "-----", "-----------")); + } else { + out.println(String.format(headerFormat, CONTEXT_NAME_COLUMN_LABEL, TYPE_COLUMN_LABEL, STATE_COLUMN_LABEL)); + out.println(String.format(headerFormat, "-------", "----", "-----")); + } + for (Map<String, String> row : validators) { + String type = row.get("type"); + String state = row.get("state"); + if (verbose) { + String desc = row.get("description"); + out.println(String.format(rowFormat, camelContextName, type, state, desc)); + } else { + out.println(String.format(rowFormat, camelContextName, type, state)); + } + } + } + return null; + } + + private Map<String, Integer> computeColumnWidths(final Map<String, List<Map<String, String>>> contextsToValidators) throws Exception { + int maxCamelContextLen = 0; + int maxTypeLen = 0; + int maxStatusLen = 0; + int maxDescLen = 0; + + for (Map.Entry<String, List<Map<String, String>>> stringListEntry : contextsToValidators.entrySet()) { + final String camelContextName = stringListEntry.getKey(); + + maxCamelContextLen = java.lang.Math.max(maxCamelContextLen, camelContextName.length()); + + final List<Map<String, String>> validators = stringListEntry.getValue(); + + + for (Map<String, String> row : validators) { + String type = row.get("type"); + maxTypeLen = java.lang.Math.max(maxTypeLen, type == null ? 0 : type.length()); + String status = row.get("state"); + maxStatusLen = java.lang.Math.max(maxStatusLen, status == null ? 0 : status.length()); + if (verbose) { + String desc = row.get("description"); + maxDescLen = java.lang.Math.max(maxDescLen, desc == null ? 0 : desc.length()); + } + } + } + + final Map<String, Integer> retval = new Hashtable<>(); + retval.put(CONTEXT_NAME_COLUMN_LABEL, maxCamelContextLen); + retval.put(TYPE_COLUMN_LABEL, maxTypeLen); + retval.put(STATE_COLUMN_LABEL, maxStatusLen); + if (verbose) { + retval.put(DESCRIPTION_COLUMN_LABEL, maxDescLen); + } + + return retval; + } + + private String buildFormatString(final Map<String, Integer> columnWidths, final boolean isHeader) { + final String fieldPreamble; + final String fieldPostamble; + final int columnWidthIncrement; + + if (isHeader) { + fieldPreamble = DEFAULT_HEADER_PREAMBLE; + fieldPostamble = DEFAULT_HEADER_POSTAMBLE; + } else { + fieldPreamble = DEFAULT_FIELD_PREAMBLE; + fieldPostamble = DEFAULT_FIELD_POSTAMBLE; + } + columnWidthIncrement = DEFAULT_COLUMN_WIDTH_INCREMENT; + + int ctxLen = java.lang.Math.min(columnWidths.get(CONTEXT_NAME_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth()); + ctxLen = Math.max(MIN_COLUMN_WIDTH, ctxLen); + int typeLen = java.lang.Math.min(columnWidths.get(TYPE_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth()); + typeLen = Math.max(MIN_COLUMN_WIDTH, typeLen); + int stateLen = -1; + if (verbose) { + stateLen = java.lang.Math.min(columnWidths.get(STATE_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth()); + stateLen = Math.max(MIN_COLUMN_WIDTH, stateLen); + } + // last row does not have min width + + final StringBuilder retval = new StringBuilder(DEFAULT_FORMAT_BUFFER_LENGTH); + retval.append(fieldPreamble).append("%-").append(ctxLen).append('.').append(ctxLen).append('s').append(fieldPostamble).append(' '); + retval.append(fieldPreamble).append("%-").append(typeLen).append('.').append(typeLen).append('s').append(fieldPostamble).append(' '); + if (verbose) { + retval.append(fieldPreamble).append("%-").append(stateLen).append('.').append(stateLen).append('s').append(fieldPostamble).append(' '); + } + retval.append(fieldPreamble).append("%s").append(fieldPostamble).append(' '); + + return retval.toString(); + } + + private int getMaxColumnWidth() { + if (verbose) { + return Integer.MAX_VALUE; + } else { + return MAX_COLUMN_WIDTH; + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/AbstractLocalCamelControllerTest.java ---------------------------------------------------------------------- diff --git a/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/AbstractLocalCamelControllerTest.java b/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/AbstractLocalCamelControllerTest.java index 682ac0d..896dd46 100644 --- a/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/AbstractLocalCamelControllerTest.java +++ b/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/AbstractLocalCamelControllerTest.java @@ -21,11 +21,13 @@ import java.util.Map; import org.apache.camel.CamelContext; import org.apache.camel.Message; +import org.apache.camel.ValidationException; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.impl.DefaultCamelContext; import org.apache.camel.impl.ExplicitCamelContextNameStrategy; import org.apache.camel.spi.DataType; import org.apache.camel.spi.Transformer; +import org.apache.camel.spi.Validator; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -50,6 +52,9 @@ public class AbstractLocalCamelControllerTest { .fromType("xml:foo") .toType("json:bar") .withJava(DummyTransformer.class); + validator() + .type("xml:foo") + .withJava(DummyValidator.class); from("direct:start1").id("route1").delay(100).to("mock:result1"); from("direct:start2").id("route2").delay(100).to("mock:result2"); from("direct:start3").id("route3").delay(100).to("mock:result3"); @@ -117,7 +122,6 @@ public class AbstractLocalCamelControllerTest { @Test public void testTransformer() throws Exception { - context.resolveTransformer(new DataType("xml:foo"), new DataType("json:bar")); List<Map<String, String>> transformers = localCamelController.getTransformers("context1"); assertEquals(1, transformers.size()); Map<String, String> dummyTransformer = transformers.get(0); @@ -129,9 +133,26 @@ public class AbstractLocalCamelControllerTest { assertEquals("Started", dummyTransformer.get("state")); } + @Test + public void testValidator() throws Exception { + List<Map<String, String>> validators = localCamelController.getValidators("context1"); + assertEquals(1, validators.size()); + Map<String, String> dummyValidator = validators.get(0); + assertEquals("context1", dummyValidator.get("camelContextName")); + assertEquals("DummyValidator[type='xml:foo']", dummyValidator.get("description")); + assertEquals("xml:foo", dummyValidator.get("type")); + assertEquals("Started", dummyValidator.get("state")); + } + public static class DummyTransformer extends Transformer { @Override public void transform(Message message, DataType from, DataType to) throws Exception { } } + + public static class DummyValidator extends Validator { + @Override + public void validate(Message message, DataType type) throws ValidationException { + } + } } http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/TransformerListCommandTest.java ---------------------------------------------------------------------- diff --git a/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/TransformerListCommandTest.java b/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/TransformerListCommandTest.java index 5f71849..0acd9e5 100644 --- a/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/TransformerListCommandTest.java +++ b/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/TransformerListCommandTest.java @@ -78,18 +78,15 @@ public class TransformerListCommandTest { etd.setToType("json:bar"); etd.setUri("direct:transformer"); context.getTransformers().add(etd); - context.resolveTransformer(new DataType("xml:foo"), new DataType("json:bar")); DataFormatTransformerDefinition dftd = new DataFormatTransformerDefinition(); dftd.setFromType(this.getClass()); dftd.setToType("xml:test"); dftd.setDataFormatType(new StringDataFormat()); context.getTransformers().add(dftd); - context.resolveTransformer(new DataType(this.getClass()), new DataType("xml:test")); CustomTransformerDefinition ctd = new CustomTransformerDefinition(); ctd.setScheme("custom"); - ctd.setType(MyTransformer.class.getName()); + ctd.setClassName(MyTransformer.class.getName()); context.getTransformers().add(ctd); - context.resolveTransformer("custom"); context.setNameStrategy(new ExplicitCamelContextNameStrategy("foobar")); context.start(); http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/ValidatorListCommandTest.java ---------------------------------------------------------------------- diff --git a/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/ValidatorListCommandTest.java b/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/ValidatorListCommandTest.java new file mode 100644 index 0000000..3457520 --- /dev/null +++ b/platforms/commands/commands-core/src/test/java/org/apache/camel/commands/ValidatorListCommandTest.java @@ -0,0 +1,121 @@ +/** + * 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.commands; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; + +import org.apache.camel.CamelContext; +import org.apache.camel.Message; +import org.apache.camel.ValidationException; +import org.apache.camel.builder.ExpressionBuilder; +import org.apache.camel.builder.PredicateBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.builder.SimpleBuilder; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.impl.ExplicitCamelContextNameStrategy; +import org.apache.camel.model.dataformat.StringDataFormat; +import org.apache.camel.model.language.ExpressionDefinition; +import org.apache.camel.model.transformer.CustomTransformerDefinition; +import org.apache.camel.model.transformer.DataFormatTransformerDefinition; +import org.apache.camel.model.transformer.EndpointTransformerDefinition; +import org.apache.camel.model.validator.CustomValidatorDefinition; +import org.apache.camel.model.validator.EndpointValidatorDefinition; +import org.apache.camel.model.validator.PredicateValidatorDefinition; +import org.apache.camel.spi.DataType; +import org.apache.camel.spi.Transformer; +import org.apache.camel.spi.Validator; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class ValidatorListCommandTest { + + private static final Logger LOG = LoggerFactory.getLogger(ValidatorListCommandTest.class); + + @Test + public void testValidatorList() throws Exception { + String out = doTest(false); + assertTrue(out.contains("xml:foo")); + assertTrue(out.contains("java:" + this.getClass().getName())); + assertTrue(out.contains("custom")); + assertTrue(out.contains("Started")); + assertFalse(out.contains("ProcessorValidator[")); + assertFalse(out.contains("processor='validate(body)'")); + assertFalse(out.contains("processor='sendTo(direct://validator)'")); + assertFalse(out.contains("MyValidator[")); + } + + @Test + public void testValidatorListVerbose() throws Exception { + String out = doTest(true); + assertTrue(out.contains("xml:foo")); + assertTrue(out.contains("java:" + this.getClass().getName())); + assertTrue(out.contains("custom")); + assertTrue(out.contains("Started")); + assertTrue(out.contains("ProcessorValidator[")); + assertTrue(out.contains("processor='validate(body)'")); + assertTrue(out.contains("processor='sendTo(direct://validator)'")); + assertTrue(out.contains("MyValidator[")); + } + + private String doTest(boolean verbose) throws Exception { + CamelContext context = new DefaultCamelContext(); + EndpointValidatorDefinition evd = new EndpointValidatorDefinition(); + evd.setType("xml:foo"); + evd.setUri("direct:validator"); + context.getValidators().add(evd); + PredicateValidatorDefinition pvd = new PredicateValidatorDefinition(); + pvd.setType(this.getClass()); + pvd.setExpression(new ExpressionDefinition(ExpressionBuilder.bodyExpression())); + context.getValidators().add(pvd); + CustomValidatorDefinition cvd = new CustomValidatorDefinition(); + cvd.setType("custom"); + cvd.setClassName(MyValidator.class.getName()); + context.getValidators().add(cvd); + context.setNameStrategy(new ExplicitCamelContextNameStrategy("foobar")); + context.start(); + + CamelController controller = new DummyCamelController(context); + + OutputStream os = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(os); + + ValidatorListCommand command = new ValidatorListCommand(null, false, verbose, false); + command.execute(controller, ps, null); + + String out = os.toString(); + assertNotNull(out); + LOG.info("\n\n{}\n", out); + + context.stop(); + return out; + } + + public static class MyValidator extends Validator { + @Override + public void validate(Message message, DataType type) throws ValidationException { + return; + } + } +} + http://git-wip-us.apache.org/repos/asf/camel/blob/f9946b2e/platforms/commands/commands-jolokia/src/main/java/org/apache/camel/commands/jolokia/DefaultJolokiaCamelController.java ---------------------------------------------------------------------- diff --git a/platforms/commands/commands-jolokia/src/main/java/org/apache/camel/commands/jolokia/DefaultJolokiaCamelController.java b/platforms/commands/commands-jolokia/src/main/java/org/apache/camel/commands/jolokia/DefaultJolokiaCamelController.java index e342840..cf30c39 100644 --- a/platforms/commands/commands-jolokia/src/main/java/org/apache/camel/commands/jolokia/DefaultJolokiaCamelController.java +++ b/platforms/commands/commands-jolokia/src/main/java/org/apache/camel/commands/jolokia/DefaultJolokiaCamelController.java @@ -820,6 +820,50 @@ public class DefaultJolokiaCamelController extends AbstractCamelController imple return answer; } + @Override + public List<Map<String, String>> getValidators(String camelContextName) throws Exception { + if (jolokia == null) { + throw new IllegalStateException("Need to connect to remote jolokia first"); + } + + List<Map<String, String>> answer = new ArrayList<Map<String, String>>(); + + ObjectName found = lookupCamelContext(camelContextName); + if (found != null) { + String pattern = String.format("%s:context=%s,type=services,name=DefaultValidatorRegistry", found.getDomain(), found.getKeyProperty("context")); + ObjectName on = ObjectName.getInstance(pattern); + + J4pExecResponse response = jolokia.execute(new J4pExecRequest(on, "listValidators()")); + if (response != null) { + JSONObject data = response.getValue(); + if (data != null) { + for (Object obj : data.values()) { + JSONObject data2 = (JSONObject) obj; + JSONObject service = (JSONObject) data2.values().iterator().next(); + + Map<String, String> row = new LinkedHashMap<String, String>(); + row.put("type", asString(service.get("type"))); + row.put("static", asString(service.get("static"))); + row.put("dynamic", asString(service.get("dynamic"))); + row.put("description", asString(service.get("description"))); + answer.add(row); + } + } + } + + // sort the list + Collections.sort(answer, new Comparator<Map<String, String>>() { + @Override + public int compare(Map<String, String> service1, Map<String, String> service2) { + String type1 = service1.get("type"); + String type2 = service2.get("type"); + return type1.compareTo(type2); + } + }); + } + return answer; + } + private static String asKey(String attributeKey) { char ch = Character.toLowerCase(attributeKey.charAt(0)); return ch + attributeKey.substring(1);