This is an automated email from the ASF dual-hosted git repository. acosentino pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 0c181d0a123df7c132c7f656fd644c413d2e5926 Author: Christofer Dutz <[email protected]> AuthorDate: Wed Oct 26 16:05:47 2022 +0200 Added the Apache PLC4X Camel Integation Module --- components/camel-plc4x/pom.xml | 173 ++++++++++++++++++++ components/camel-plc4x/src/main/docs/PLC4X.adoc | 64 ++++++++ .../java/org/apache/plc4x/camel/Constants.java | 30 ++++ .../org/apache/plc4x/camel/Plc4XComponent.java | 75 +++++++++ .../java/org/apache/plc4x/camel/Plc4XConsumer.java | 157 ++++++++++++++++++ .../java/org/apache/plc4x/camel/Plc4XEndpoint.java | 178 +++++++++++++++++++++ .../java/org/apache/plc4x/camel/Plc4XProducer.java | 111 +++++++++++++ .../main/java/org/apache/plc4x/camel/TagData.java | 129 +++++++++++++++ .../services/org/apache/camel/component/plc4x | 19 +++ .../java/org/apache/plc4x/camel/ConstantsTest.java | 42 +++++ .../java/org/apache/plc4x/camel/ManualTest.java | 73 +++++++++ .../java/org/apache/plc4x/camel/MockDriver.java | 101 ++++++++++++ .../org/apache/plc4x/camel/Plc4XComponentTest.java | 67 ++++++++ .../org/apache/plc4x/camel/Plc4XConsumerTest.java | 35 ++++ .../org/apache/plc4x/camel/Plc4XEndpointTest.java | 59 +++++++ .../org/apache/plc4x/camel/Plc4XProducerTest.java | 111 +++++++++++++ .../services/org.apache.plc4x.java.api.PlcDriver | 19 +++ .../src/test/resources/log4j2.properties | 24 +++ .../src/test/resources/logback-test.xml | 36 +++++ 19 files changed, 1503 insertions(+) diff --git a/components/camel-plc4x/pom.xml b/components/camel-plc4x/pom.xml new file mode 100644 index 00000000000..0c86170b193 --- /dev/null +++ b/components/camel-plc4x/pom.xml @@ -0,0 +1,173 @@ +<?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>components</artifactId> + <version>3.20.0-SNAPSHOT</version> + </parent> + + <artifactId>camel-plc4x</artifactId> + <packaging>jar</packaging> + <name>Camel :: PLC4X</name> + <description>Camel PLC4X support</description> + + <properties> + <plc4x.version>0.10.0</plc4x.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-core-engine</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-core-model</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-api</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-mock</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-support</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-util</artifactId> + </dependency> + + <!-- PLC4J dependencies --> + + <dependency> + <groupId>org.apache.plc4x</groupId> + <artifactId>plc4j-api</artifactId> + <version>${plc4x.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.plc4x</groupId> + <artifactId>plc4j-connection-pool</artifactId> + <version>${plc4x.version}</version> + </dependency> + <dependency> + <groupId>org.apache.plc4x</groupId> + <artifactId>plc4j-scraper</artifactId> + <version>${plc4x.version}</version> + </dependency> + + <!-- Include all drivers --> + <dependency> + <groupId>org.apache.plc4x</groupId> + <artifactId>plc4j-driver-ab-eth</artifactId> + <version>${plc4x.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.plc4x</groupId> + <artifactId>plc4j-driver-ads</artifactId> + <version>${plc4x.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.plc4x</groupId> + <artifactId>plc4j-driver-canopen</artifactId> + <version>${plc4x.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.plc4x</groupId> + <artifactId>plc4j-driver-eip</artifactId> + <version>${plc4x.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.plc4x</groupId> + <artifactId>plc4j-driver-firmata</artifactId> + <version>${plc4x.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.plc4x</groupId> + <artifactId>plc4j-driver-knxnetip</artifactId> + <version>${plc4x.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.plc4x</groupId> + <artifactId>plc4j-driver-modbus</artifactId> + <version>${plc4x.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.plc4x</groupId> + <artifactId>plc4j-driver-opcua</artifactId> + <version>${plc4x.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.plc4x</groupId> + <artifactId>plc4j-driver-s7</artifactId> + <version>${plc4x.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.plc4x</groupId> + <artifactId>plc4j-driver-simulated</artifactId> + <version>${plc4x.version}</version> + <scope>runtime</scope> + </dependency> + + <!-- Testing --> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test-junit5</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.apache.plc4x</groupId> + <artifactId>plc4j-spi</artifactId> + <version>${plc4x.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <!-- avoids java.lang.NoClassDefFoundError: javax/activation/DataHandler in Plc4XProducerTest on Java 11--> + <groupId>javax.activation</groupId> + <artifactId>javax.activation-api</artifactId> + <version>1.2.0</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-main</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/components/camel-plc4x/src/main/docs/PLC4X.adoc b/components/camel-plc4x/src/main/docs/PLC4X.adoc new file mode 100644 index 00000000000..5e6901d1e48 --- /dev/null +++ b/components/camel-plc4x/src/main/docs/PLC4X.adoc @@ -0,0 +1,64 @@ +// +// 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 +// +// https://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. +// + +:icons: font + +== Camel-PLC4X Component +The Camel Component for PLC4X allows you to create routes using the PLC4X API to read from a PLC device or write to it. + +=== Maven dependency +To use the Camel component, just add following dependency to your project +---- +<dependency> + <groupId>org.apache.plc4x</groupId> + <artifactId>plc4j-apache-camel</artifactId> + <version>{current-last-released-version}</version> +</dependency> +---- + +== Endpoint +[cols="2"] +|=== +|Name |Value + +|*Tags* | The tags to read as `Map<String,String>` containing the tagname associated to its query +|*Trigger*|(*Consumer*) Query to a trigger. On a rising edge of the trigger, the tags will be read once +|*Period* |(*Consumer*) Interval on which the Trigger should be checked +|*Driver parameters* | Every Parameter unknown to the Component will be passed to the driver +|=== +=== URI Format +---- +plc4x:[driver-code]://[IP|host][?parameters] +---- +Note that sometimes you want to add the `Transport` code after the `Driver` code: + +---- +plc4x:[driver-code]:[transport-code]://[IP|host][?parameters] +---- +== Consumer +The consumer supports one-time reading or Triggered Reading. (_Schedulded Reading using Period only soon_).To read from +the PLC, use a `Map<String,String>` containing the Alias and Queries for the Data you want. + +The Body create by the Consumer will be a `Map<String,Object>` containing the Aliases and there associated value +read from the PLC. + +== Producer +To write data to the PLC, we also use a `Map`. The difference with the Producer is that the `Value` of the Map has also to +be a Map. Also, this `Map` has to be set into the `Body` of the `Message` + +The used `Map` would be a `Map<String,Map<String,Object>` where the `Map<String,Object>` represent the Query and the +data we want to write to it. diff --git a/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/Constants.java b/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/Constants.java new file mode 100644 index 00000000000..805b59b6622 --- /dev/null +++ b/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/Constants.java @@ -0,0 +1,30 @@ +/* + * 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 + * + * https://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.plc4x.camel; + +public class Constants { + + public static final String FIELD_NAME_HEADER = "fieldName"; + public static final String FIELD_QUERY_HEADER = "fieldQuery"; + public final static String TRIGGER = "TRIGGER_VAR"; + public final static String PLC_NAME = "PLC"; + private Constants() { + throw new IllegalStateException("Utility class!"); + } +} diff --git a/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/Plc4XComponent.java b/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/Plc4XComponent.java new file mode 100644 index 00000000000..7d84c4672d3 --- /dev/null +++ b/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/Plc4XComponent.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 + * + * https://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.plc4x.camel; + +import org.apache.camel.Endpoint; +import org.apache.camel.support.DefaultComponent; +import org.apache.camel.util.PropertiesHelper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +public class Plc4XComponent extends DefaultComponent { + private static final Logger LOGGER = LoggerFactory.getLogger(Plc4XComponent.class); + + @Override + protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { + Plc4XEndpoint endpoint = new Plc4XEndpoint(uri, this); + //Tags have a Name, a query and an optional value (for writing) + //Reading --> Map<String,String> + //Writing --> Map<String,Map.Entry<String,Object>> + Map<String, Object> tags = getAndRemoveOrResolveReferenceParameter(parameters, "tags", Map.class); + if (tags != null) { + endpoint.setTags(tags); + } + String trigger = getAndRemoveOrResolveReferenceParameter(parameters, "trigger", String.class); + if (trigger != null) { + endpoint.setTrigger(trigger); + } + Integer period = getAndRemoveOrResolveReferenceParameter(parameters, "period", Integer.class); + if (period != null) { + endpoint.setPeriod(period); + } + setProperties(endpoint, parameters); + return endpoint; + } + + @Override + protected void afterConfiguration(String uri, String remaining, Endpoint endpoint, Map<String, Object> parameters) { + Plc4XEndpoint plc4XEndpoint = (Plc4XEndpoint) endpoint; + plc4XEndpoint.setDriver(remaining.split(":")[0]); + } + + @Override + protected void validateParameters(String uri, Map<String, Object> parameters, String optionPrefix) { + if (parameters != null && !parameters.isEmpty()) { + Map<String, Object> param = parameters; + if (optionPrefix != null) { + param = PropertiesHelper.extractProperties(parameters, optionPrefix); + } + + if (parameters.size() > 0) { + LOGGER.info("{} parameters will be passed to the PLC Driver", param); + } + } + } + +} \ No newline at end of file diff --git a/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/Plc4XConsumer.java b/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/Plc4XConsumer.java new file mode 100644 index 00000000000..45f4a41f3e1 --- /dev/null +++ b/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/Plc4XConsumer.java @@ -0,0 +1,157 @@ +/* + * 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 + * + * https://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.plc4x.camel; + +import org.apache.camel.Endpoint; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.support.DefaultConsumer; +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.exceptions.PlcIncompatibleDatatypeException; +import org.apache.plc4x.java.api.messages.PlcReadRequest; +import org.apache.plc4x.java.scraper.config.JobConfigurationImpl; +import org.apache.plc4x.java.scraper.config.ScraperConfiguration; +import org.apache.plc4x.java.scraper.config.triggeredscraper.ScraperConfigurationTriggeredImpl; +import org.apache.plc4x.java.scraper.exception.ScraperException; +import org.apache.plc4x.java.scraper.triggeredscraper.TriggeredScraperImpl; +import org.apache.plc4x.java.scraper.triggeredscraper.triggerhandler.collector.TriggerCollector; +import org.apache.plc4x.java.scraper.triggeredscraper.triggerhandler.collector.TriggerCollectorImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class Plc4XConsumer extends DefaultConsumer { + private static final Logger LOGGER = LoggerFactory.getLogger(Plc4XConsumer.class); + + private final PlcConnection plcConnection; + private final Map<String, Object> tags; + private final String trigger; + private final Plc4XEndpoint plc4XEndpoint; + + private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + private ScheduledFuture<?> future; + + public Plc4XConsumer(Plc4XEndpoint endpoint, Processor processor) { + super(endpoint, processor); + this.plc4XEndpoint = endpoint; + this.plcConnection = endpoint.getConnection(); + this.tags = endpoint.getTags(); + this.trigger = endpoint.getTrigger(); + } + + @Override + public String toString() { + return "Plc4XConsumer[" + plc4XEndpoint + "]"; + } + + @Override + public Endpoint getEndpoint() { + return plc4XEndpoint; + } + + @Override + protected void doStart() throws ScraperException { + if (trigger == null) { + startUnTriggered(); + } else { + startTriggered(); + } + } + + private void startUnTriggered() { + PlcReadRequest.Builder builder = plcConnection.readRequestBuilder(); + for (Map.Entry<String, Object> tag : tags.entrySet()) { + try { + builder.addItem(tag.getKey(), (String) tag.getValue()); + } catch (PlcIncompatibleDatatypeException e) { + LOGGER.error("For consumer, please use Map<String,String>, currently using {}", tags.getClass().getSimpleName()); + } + } + PlcReadRequest request = builder.build(); + future = executorService.schedule(() -> + request.execute().thenAccept(response -> { + try { + Exchange exchange = plc4XEndpoint.createExchange(); + Map<String, Object> rsp = new HashMap<>(); + for (String field : response.getFieldNames()) { + rsp.put(field, response.getObject(field)); + } + exchange.getIn().setBody(rsp); + getProcessor().process(exchange); + } catch (Exception e) { + getExceptionHandler().handleException(e); + } + }) + , 500, TimeUnit.MILLISECONDS); + } + + private void startTriggered() throws ScraperException { + ScraperConfiguration configuration = getScraperConfig(validateTags()); + TriggerCollector collector = new TriggerCollectorImpl(plc4XEndpoint.getPlcDriverManager()); + + TriggeredScraperImpl scraper = new TriggeredScraperImpl(configuration, (job, alias, response) -> { + try { + Exchange exchange = plc4XEndpoint.createExchange(); + exchange.getIn().setBody(response); + getProcessor().process(exchange); + } catch (Exception e) { + getExceptionHandler().handleException(e); + } + }, collector); + scraper.start(); + collector.start(); + } + + private Map<String, String> validateTags() { + Map<String, String> map = new HashMap<>(); + for (Map.Entry<String, Object> tag : tags.entrySet()) { + if (tag.getValue() instanceof String) { + map.put(tag.getKey(), (String) tag.getValue()); + } + } + if (map.size() != tags.size()) { + LOGGER.error("At least one entry does not match the format : Map.Entry<String,String> "); + return null; + } else return map; + } + + private ScraperConfigurationTriggeredImpl getScraperConfig(Map<String, String> tagList) { + String config = "(TRIGGER_VAR," + plc4XEndpoint.getPeriod() + ",(" + plc4XEndpoint.getTrigger() + ")==(true))"; + List<JobConfigurationImpl> job = Collections.singletonList(new JobConfigurationImpl("PLC4X-Camel", config, 0, Collections.singletonList(Constants.PLC_NAME), tagList)); + Map<String, String> source = Collections.singletonMap(Constants.PLC_NAME, plc4XEndpoint.getUri()); + return new ScraperConfigurationTriggeredImpl(source, job); + } + + @Override + protected void doStop() { + // First stop the polling process + if (future != null) { + future.cancel(true); + } + } + +} \ No newline at end of file diff --git a/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/Plc4XEndpoint.java b/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/Plc4XEndpoint.java new file mode 100644 index 00000000000..262a0184668 --- /dev/null +++ b/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/Plc4XEndpoint.java @@ -0,0 +1,178 @@ +/* + * 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 + * + * https://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.plc4x.camel; + +import org.apache.camel.*; +import org.apache.camel.support.DefaultEndpoint; +import org.apache.camel.spi.Metadata; +import org.apache.camel.spi.UriEndpoint; +import org.apache.camel.spi.UriParam; +import org.apache.camel.spi.UriPath; +import org.apache.commons.math3.util.Pair; +import org.apache.plc4x.java.PlcDriverManager; +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.exceptions.PlcConnectionException; +import org.apache.plc4x.java.api.exceptions.PlcRuntimeException; +import org.apache.plc4x.java.utils.connectionpool.PooledPlcDriverManager; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@UriEndpoint(scheme = "plc4x", title = "PLC4X", syntax = "plc4x:driver", label = "plc4x") +public class Plc4XEndpoint extends DefaultEndpoint { + + @UriPath + @Metadata(required = true) + private String driver; + + @UriParam + private Map<String, Object> tags; + + @UriParam + private String trigger; + + @UriParam + private int period; + + public int getPeriod() { + return period; + } + + public void setPeriod(int period) { + this.period = period; + } + + private PlcDriverManager plcDriverManager; + private PlcConnection connection; + private String uri; + + public String getUri() { + return uri; + } + + public String getTrigger() { + return trigger; + } + + public void setTrigger(String trigger) { + this.trigger = trigger; + plcDriverManager = new PooledPlcDriverManager(); + String plc4xURI = uri.replaceFirst("plc4x:/?/?", ""); + // TODO: is this mutation really intentional + uri = plc4xURI; + try { + connection = plcDriverManager.getConnection(plc4xURI); + } catch (PlcConnectionException e) { + throw new PlcRuntimeException(e); + } + } + + public Plc4XEndpoint(String endpointUri, Component component) throws PlcConnectionException { + super(endpointUri, component); + this.plcDriverManager = new PlcDriverManager(); + //Here we establish the connection in the endpoint, as it is created once during the context + // to avoid disconnecting and reconnecting for every request + this.uri = endpointUri.replaceFirst("plc4x:/?/?", ""); + this.connection = plcDriverManager.getConnection(this.uri); + } + + public PlcConnection getConnection() { + return connection; + } + + @Override + public void setProperties(Object bean, Map<String, Object> parameters) { + + } + + @Override + public Producer createProducer() throws Exception { + //Checking if connection is still up and reconnecting if not + if (!connection.isConnected()) { + connection = plcDriverManager.getConnection(uri.replaceFirst("plc4x:/?/?", "")); + } + return new Plc4XProducer(this); + } + + @Override + public Consumer createConsumer(Processor processor) throws Exception { + //Checking if connection is still up and reconnecting if not + if (!connection.isConnected()) { + connection = plcDriverManager.getConnection(uri.replaceFirst("plc4x:/?/?", "")); + } + return new Plc4XConsumer(this, processor); + } + + @Override + public boolean isSingleton() { + return true; + } + + public PlcDriverManager getPlcDriverManager() { + return plcDriverManager; + } + + public String getDriver() { + return driver; + } + + public void setDriver(String driver) { + this.driver = driver; + } + + public Map<String, Object> getTags() { + return tags; + } + + public void setTags(Map<String, Object> tags) { + this.tags = tags; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Plc4XEndpoint)) { + return false; + } + if (!super.equals(o)) { + return false; + } + Plc4XEndpoint that = (Plc4XEndpoint) o; + return Objects.equals(getDriver(), that.getDriver()) && + Objects.equals(getTags(), that.getTags()) && + Objects.equals(getPlcDriverManager(), that.getPlcDriverManager()); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), getDriver(), getTags(), getPlcDriverManager()); + } + + @Override + public void doStop() throws Exception { + //Shutting down the connection when leaving the Context + if (connection != null && connection.isConnected()) { + connection.close(); + } + } + +} diff --git a/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/Plc4XProducer.java b/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/Plc4XProducer.java new file mode 100644 index 00000000000..6adfab3700c --- /dev/null +++ b/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/Plc4XProducer.java @@ -0,0 +1,111 @@ +/* + * 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 + * + * https://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.plc4x.camel; + +import org.apache.camel.AsyncCallback; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.support.DefaultAsyncProducer; +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.exceptions.PlcException; +import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException; +import org.apache.plc4x.java.api.messages.PlcWriteRequest; +import org.apache.plc4x.java.api.messages.PlcWriteResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; + +public class Plc4XProducer extends DefaultAsyncProducer { + private final Logger log = LoggerFactory.getLogger(Plc4XProducer.class); + private PlcConnection plcConnection; + private AtomicInteger openRequests; + + public Plc4XProducer(Plc4XEndpoint endpoint) throws PlcException { + super(endpoint); + String plc4xURI = endpoint.getEndpointUri().replaceFirst("plc4x:/?/?", ""); + this.plcConnection = endpoint.getConnection(); + if (!plcConnection.getMetadata().canWrite()) { + throw new PlcException("This connection (" + plc4xURI + ") doesn't support writing."); + } + openRequests = new AtomicInteger(); + } + + @Override + public void process(Exchange exchange) throws Exception { + Message in = exchange.getIn(); + Object body = in.getBody(); + PlcWriteRequest.Builder builder = plcConnection.writeRequestBuilder(); + if (body instanceof Map) { //Check if we have a Map + Map<String, Map<String, Object>> tags = (Map<String, Map<String, Object>>) body; + for (Map.Entry<String, Map<String, Object>> entry : tags.entrySet()) { + //Tags are stored like this --> Map<Tagname,Map<Query,Value>> for writing + String name = entry.getKey(); + String query = entry.getValue().keySet().iterator().next(); + Object value = entry.getValue().get(query); + builder.addItem(name,query,value); + } + } else { + throw new PlcInvalidFieldException("The body must contain a Map<String,Map<String,Object>"); + } + + CompletableFuture<? extends PlcWriteResponse> completableFuture = builder.build().execute(); + int currentlyOpenRequests = openRequests.incrementAndGet(); + try { + log.debug("Currently open requests including {}:{}", exchange, currentlyOpenRequests); + Object plcWriteResponse = completableFuture.get(); + if (exchange.getPattern().isOutCapable()) { + Message out = exchange.getOut(); + out.copyFrom(exchange.getIn()); + out.setBody(plcWriteResponse); + } else { + in.setBody(plcWriteResponse); + } + } finally { + int openRequestsAfterFinish = openRequests.decrementAndGet(); + log.trace("Open Requests after {}:{}", exchange, openRequestsAfterFinish); + } + } + + @Override + public boolean process(Exchange exchange, AsyncCallback callback) { + try { + process(exchange); + Message out = exchange.getOut(); + out.copyFrom(exchange.getIn()); + } catch (Exception e) { + exchange.setOut(null); + exchange.setException(e); + } + callback.done(true); + return true; + } + + @Override + protected void doStop() throws Exception { + int openRequestsAtStop = openRequests.get(); + log.debug("Stopping with {} open requests", openRequestsAtStop); + if (openRequestsAtStop > 0) { + log.warn("There are still {} open requests", openRequestsAtStop); + } + } + +} diff --git a/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/TagData.java b/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/TagData.java new file mode 100644 index 00000000000..6974ead775b --- /dev/null +++ b/components/camel-plc4x/src/main/java/org/apache/plc4x/camel/TagData.java @@ -0,0 +1,129 @@ +/* + * 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 + * + * https://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.plc4x.camel; + +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +public class TagData { + private String tagName; + private String query; + private Object value; + + public TagData(String alias, String query, Object value) { + this.tagName = alias; + this.query = query; + this.value = value; + setType(); + } + + public TagData(String tagName, String query) { + this.tagName = tagName; + this.query = query; + } + + public String getTagName() { + return tagName; + } + + public void setTagName(String tagName) { + this.tagName = tagName; + } + + public String getQuery() { + return query; + } + + public void setQuery(String query) { + this.query = query; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + + private void setType(){ + if(value!=null && value instanceof String){ + String val = (String)value; + if(canParse.get(Boolean.TYPE).test(val)){ + value = Boolean.parseBoolean(val); + } + if(canParse.get(Short.TYPE).test(val)){ + value = Short.parseShort(val); + } + else if(canParse.get(Integer.TYPE).test(val)){ + value = Integer.parseInt(val); + } + else if(canParse.get(Long.TYPE).test(val)){ + value = Long.parseLong(val); + } + else if(canParse.get(Double.TYPE).test(val)){ + value = Double.parseDouble(val); + } + else if(canParse.get(Float.TYPE).test(val)){ + value = Float.parseFloat(val); + } + + } + } + + private Map<Class<?>, Predicate<String>> canParse = new HashMap<>(); + { + canParse.put(Integer.TYPE, s -> {try {Integer.parseInt(s); return true;} catch(Exception e) {return false;}}); + canParse.put(Long.TYPE, s -> {try {Long.parseLong(s); return true;} catch(Exception e) {return false;}}); + canParse.put(Short.TYPE, s -> {try {Short.parseShort(s); return true;} catch(Exception e) {return false;}}); + canParse.put(Boolean.TYPE, s -> {try {Boolean.parseBoolean(s); return true;} catch(Exception e) {return false;}}); + canParse.put(Double.TYPE, s -> {try {Double.parseDouble(s); return true;} catch(Exception e) {return false;}}); + canParse.put(Float.TYPE, s -> {try {Float.parseFloat(s); return true;} catch(Exception e) {return false;}}); + }; + + @Override + public String toString(){ + return "("+tagName+") : "+value; + } + + @Override + public boolean equals(Object tag){ + return value!= null?((TagData)tag).getValue().equals(value) + && ((TagData)tag).getTagName().equals(tagName) + && ((TagData)tag).getQuery().equals(query) : + ((TagData)tag).getTagName().equals(tagName) + && ((TagData)tag).getQuery().equals(query); + + + } + + public static Map<String,String> toMap(List<TagData> tags){ + Map<String,String> map = new HashMap<>(); + LoggerFactory.getLogger(TagData.class).info("Classloader {} ", Thread.currentThread().getContextClassLoader()); + for(TagData tag : tags){ + map.put(tag.getTagName(),tag.getQuery()); + } + return map; + } +} diff --git a/components/camel-plc4x/src/main/resources/META-INF/services/org/apache/camel/component/plc4x b/components/camel-plc4x/src/main/resources/META-INF/services/org/apache/camel/component/plc4x new file mode 100644 index 00000000000..5fd13a9798b --- /dev/null +++ b/components/camel-plc4x/src/main/resources/META-INF/services/org/apache/camel/component/plc4x @@ -0,0 +1,19 @@ +# +# 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 +# +# https://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. +# +class=org.apache.plc4x.camel.Plc4XComponent \ No newline at end of file diff --git a/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/ConstantsTest.java b/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/ConstantsTest.java new file mode 100644 index 00000000000..68d36bfc5b1 --- /dev/null +++ b/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/ConstantsTest.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 + * + * https://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.plc4x.camel; + +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Constructor; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class ConstantsTest { + + @Test + public void testConstantsNotInstanceable() { + assertThrows(IllegalStateException.class, () -> { + try { + Constructor<Constants> constructor = Constants.class.getDeclaredConstructor(); + constructor.setAccessible(true); + constructor.newInstance(); + } catch (Exception e) { + throw e.getCause(); + } + }); + } + +} \ No newline at end of file diff --git a/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/ManualTest.java b/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/ManualTest.java new file mode 100644 index 00000000000..46f27041aa7 --- /dev/null +++ b/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/ManualTest.java @@ -0,0 +1,73 @@ +/* + * 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 + * + * https://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.plc4x.camel; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.main.Main; +import org.apache.camel.main.MainListenerSupport; + +import java.util.Date; + +public class ManualTest { + + private Main main; + + public static void main(String[] args) throws Exception { + ManualTest example = new ManualTest(); + example.boot(); + } + + public void boot() throws Exception { + // create a Main instance + main = new Main(); + // bind MyBean into the registry + main.bind("foo", new MyBean()); + // add routes + main.getCamelContext().addRoutes(new MyRouteBuilder()); + // add event listener + main.addMainListener(new Events()); + // set the properties from a file + main.setPropertyPlaceholderLocations("example.properties"); + // run until you terminate the JVM + System.out.println("Starting Camel. Use ctrl + c to terminate the JVM.\n"); + main.run(); + } + + private static class MyRouteBuilder extends RouteBuilder { + @Override + public void configure() { + from("plc4x:ads:tcp://10.10.64.40/10.10.64.40.1.1:851/192.168.113.3.1.1:30000?dataType=java.lang.Integer&address=Allgemein_S2.Station") + .process(exchange -> System.out.println("Invoked timer at " + new Date())) + .bean("foo") + .log("Received ${body}"); + } + } + + public static class MyBean { + public void callMe() { + System.out.println("MyBean.callMe method has been called"); + } + } + + public static class Events extends MainListenerSupport { + + + + } +} diff --git a/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/MockDriver.java b/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/MockDriver.java new file mode 100644 index 00000000000..542b1132f8a --- /dev/null +++ b/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/MockDriver.java @@ -0,0 +1,101 @@ +/* + * 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 + * + * https://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.plc4x.camel; + +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.authentication.PlcAuthentication; +import org.apache.plc4x.java.api.exceptions.PlcConnectionException; +import org.apache.plc4x.java.api.messages.*; +import org.apache.plc4x.java.spi.messages.DefaultPlcSubscriptionResponse; +import org.apache.plc4x.java.spi.messages.PlcSubscriber; +import org.apache.plc4x.java.api.PlcDriver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.mockito.Mockito.*; + +public class MockDriver implements PlcDriver { + + public static final Logger LOGGER = LoggerFactory.getLogger(MockDriver.class); + + ExecutorService executorService = Executors.newFixedThreadPool(10); + + @Override + public String getProtocolCode() { + return "mock"; + } + + @Override + public String getProtocolName() { + return "Mock Protocol Implementation"; + } + + @Override + public PlcConnection getConnection(String url) throws PlcConnectionException { + // Mock a connection. + PlcConnection plcConnectionMock = mock(PlcConnection.class, RETURNS_DEEP_STUBS); + when(plcConnectionMock.getMetadata().canRead()).thenReturn(true); + when(plcConnectionMock.getMetadata().canWrite()).thenReturn(true); + when(plcConnectionMock.readRequestBuilder()).thenReturn(mock(PlcReadRequest.Builder.class, RETURNS_DEEP_STUBS)); + when(plcConnectionMock.writeRequestBuilder()).thenReturn(mock(PlcWriteRequest.Builder.class, RETURNS_DEEP_STUBS)); + when(plcConnectionMock.subscriptionRequestBuilder()).thenReturn(mock(PlcSubscriptionRequest.Builder.class, RETURNS_DEEP_STUBS)); + when(plcConnectionMock.unsubscriptionRequestBuilder()).thenReturn(mock(PlcUnsubscriptionRequest.Builder.class, RETURNS_DEEP_STUBS)); + + // Mock a typical subscriber. + PlcSubscriber plcSubscriber = mock(PlcSubscriber.class, RETURNS_DEEP_STUBS); + when(plcSubscriber.subscribe(any(PlcSubscriptionRequest.class))).thenAnswer(invocation -> { + LOGGER.info("Received {}", invocation); + // TODO: Translate this so it actually does something ... + /*PlcSubscriptionRequest subscriptionRequest = invocation.getArgument(0); + List<PlcSubscriptionResponse> responseItems = + subscriptionRequest.getFieldNames().stream().map( + fieldName -> subscriptionRequest.getField(fieldName)).map(field -> { + Consumer consumer = subscriptionRequestItem.getConsumer(); + executorService.submit(() -> { + while (!Thread.currentThread().isInterrupted()) { + consumer.accept(new SubscriptionEventItem<>(null, Calendar.getInstance(), Collections.singletonList("HelloWorld"))); + try { + TimeUnit.MILLISECONDS.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + } + }); + return new SubscriptionResponseItem<>(subscriptionRequestItem, + mock(PlcSubscriptionHandle.class, RETURNS_DEEP_STUBS), PlcResponseCode.OK); + }).collect(Collectors.toList()); + PlcSubscriptionResponse response = new PlcSubscriptionResponse(subscriptionRequest, responseItems);*/ + PlcSubscriptionResponse response = new DefaultPlcSubscriptionResponse(mock(PlcSubscriptionRequest.class), new HashMap<>()); + return CompletableFuture.completedFuture(response); + }); + return plcConnectionMock; + } + + @Override + public PlcConnection getConnection(String url, PlcAuthentication authentication) throws PlcConnectionException { + return getConnection(null); + } + +} diff --git a/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/Plc4XComponentTest.java b/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/Plc4XComponentTest.java new file mode 100644 index 00000000000..bc66944b3fb --- /dev/null +++ b/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/Plc4XComponentTest.java @@ -0,0 +1,67 @@ +/* + * 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 + * + * https://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.plc4x.camel; + +import org.apache.camel.Expression; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.apache.plc4x.java.api.model.PlcField; +import org.junit.jupiter.api.Test; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +public class Plc4XComponentTest extends CamelTestSupport { + + @Test + public void testSimpleRouting() throws Exception { + MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedMinimumMessageCount(1); + mock.expectedMessageCount(2); + + template.asyncSendBody("direct:plc4x", Collections.singletonList("irrelevant")); + template.asyncSendBody("direct:plc4x2", Collections.singletonList("irrelevant")); + + assertMockEndpointsSatisfied(2, TimeUnit.SECONDS); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + public void configure() { + Map<String,Object> tags = new HashMap<>(); + tags.put("Test1","%TestQuery"); + Plc4XEndpoint producer = getContext().getEndpoint("plc4x:mock:10.10.10.1/1/1", Plc4XEndpoint.class); + producer.setTags(tags); + from("direct:plc4x") + .setBody(constant(Collections.singletonMap("test",Collections.singletonMap("testAddress",false)))) + .to("plc4x:mock:10.10.10.1/1/1") + .to("mock:result"); + from("direct:plc4x2") + .setBody(constant(Collections.singletonMap("test2",Collections.singletonMap("testAddress2",0x05)))) + .to("plc4x:mock:10.10.10.1/1/1") + .to("mock:result"); + from(producer) + .log("Got ${body}"); + } + }; + } + +} \ No newline at end of file diff --git a/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/Plc4XConsumerTest.java b/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/Plc4XConsumerTest.java new file mode 100644 index 00000000000..8611cf08061 --- /dev/null +++ b/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/Plc4XConsumerTest.java @@ -0,0 +1,35 @@ +/* + * 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 + * + * https://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.plc4x.camel; + +import org.junit.jupiter.api.Test; + +// TODO: implement me +public class Plc4XConsumerTest { + + + @Test + public void doStart() { + } + + @Test + public void doStop() { + } + +} \ No newline at end of file diff --git a/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/Plc4XEndpointTest.java b/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/Plc4XEndpointTest.java new file mode 100644 index 00000000000..94e120f6de3 --- /dev/null +++ b/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/Plc4XEndpointTest.java @@ -0,0 +1,59 @@ +/* + * 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 + * + * https://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.plc4x.camel; + +import org.apache.camel.Component; +import org.apache.camel.Processor; +import org.apache.camel.impl.DefaultCamelContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.*; + +public class Plc4XEndpointTest { + + Plc4XEndpoint SUT; + + @BeforeEach + public void setUp() throws Exception { + Component mockComponent = mock(Component.class, RETURNS_DEEP_STUBS); + when(mockComponent.getCamelContext()).thenReturn(new DefaultCamelContext()); + SUT = new Plc4XEndpoint("plc4x:mock:10.10.10.1/1/1", mockComponent); + } + + // TODO: figure out what this is + @Test + public void createProducer() throws Exception { + assertThat(SUT.createProducer(), notNullValue()); + } + + @Test + public void createConsumer() throws Exception { + assertThat(SUT.createConsumer(mock(Processor.class)), notNullValue()); + } + + @Test + public void isSingleton() { + assertThat(SUT.isSingleton(), is(true)); + } + +} \ No newline at end of file diff --git a/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/Plc4XProducerTest.java b/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/Plc4XProducerTest.java new file mode 100644 index 00000000000..a201fd36729 --- /dev/null +++ b/components/camel-plc4x/src/test/java/org/apache/plc4x/camel/Plc4XProducerTest.java @@ -0,0 +1,111 @@ +/* + * 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 + * + * https://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.plc4x.camel; + +import org.apache.camel.Exchange; +import org.apache.camel.ExchangePattern; +import org.apache.plc4x.java.api.PlcConnection; +import org.apache.plc4x.java.api.messages.PlcWriteRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.mockito.Mockito.*; + +public class Plc4XProducerTest { + + private Plc4XProducer SUT; + + private Exchange testExchange; + + @BeforeEach + public void setUp() throws Exception { + Plc4XEndpoint endpointMock = mock(Plc4XEndpoint.class, RETURNS_DEEP_STUBS); + when(endpointMock.getEndpointUri()).thenReturn("plc4x:mock:10.10.10.1/1/1"); + PlcConnection mockConnection = mock(PlcConnection.class, RETURNS_DEEP_STUBS); + + when(mockConnection.getMetadata().canRead()).thenReturn(true); + when(mockConnection.getMetadata().canWrite()).thenReturn(true); + when(mockConnection.writeRequestBuilder()) + .thenReturn(mock(PlcWriteRequest.Builder.class, RETURNS_DEEP_STUBS)); + + when(endpointMock.getConnection()).thenReturn(mockConnection); + SUT = new Plc4XProducer(endpointMock); + testExchange = mock(Exchange.class, RETURNS_DEEP_STUBS); + Map<String, Map<String,Object>> tags = new HashMap(); + tags.put("test1", Collections.singletonMap("testAddress1",0)); + tags.put("test1", Collections.singletonMap("testAddress2",true)); + tags.put("test1", Collections.singletonMap("testAddress3","TestString")); + when(testExchange.getIn().getBody()) + .thenReturn(tags); + } + + @Test + public void process() throws Exception { + when(testExchange.getPattern()).thenReturn(ExchangePattern.InOnly); + SUT.process(testExchange); + when(testExchange.getPattern()).thenReturn(ExchangePattern.InOut); + SUT.process(testExchange); + when(testExchange.getIn().getBody()).thenReturn(2); + + } + + @Test + public void process_Async() { + SUT.process(testExchange, doneSync -> { + }); + when(testExchange.getPattern()).thenReturn(ExchangePattern.InOnly); + SUT.process(testExchange, doneSync -> { + }); + when(testExchange.getPattern()).thenReturn(ExchangePattern.InOut); + SUT.process(testExchange, doneSync -> { + }); + } + + @Test + public void doStop() throws Exception { + SUT.doStop(); + } + + @Test + public void doStopOpenRequest() throws Exception { + Field openRequests = SUT.getClass().getDeclaredField("openRequests"); + openRequests.setAccessible(true); + AtomicInteger atomicInteger = (AtomicInteger) openRequests.get(SUT); + atomicInteger.incrementAndGet(); + SUT.doStop(); + } + + @Test + public void doStopBadConnection() throws Exception { + Field openRequests = SUT.getClass().getDeclaredField("plcConnection"); + openRequests.setAccessible(true); + PlcConnection plcConnectionMock = mock(PlcConnection.class); + doThrow(new RuntimeException("oh noes")).when(plcConnectionMock).close(); + openRequests.set(SUT, plcConnectionMock); + SUT.doStop(); + } + +} \ No newline at end of file diff --git a/components/camel-plc4x/src/test/resources/META-INF/services/org.apache.plc4x.java.api.PlcDriver b/components/camel-plc4x/src/test/resources/META-INF/services/org.apache.plc4x.java.api.PlcDriver new file mode 100644 index 00000000000..981a9f1938d --- /dev/null +++ b/components/camel-plc4x/src/test/resources/META-INF/services/org.apache.plc4x.java.api.PlcDriver @@ -0,0 +1,19 @@ +# +# 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 +# +# https://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. +# +org.apache.plc4x.camel.MockDriver \ No newline at end of file diff --git a/components/camel-plc4x/src/test/resources/log4j2.properties b/components/camel-plc4x/src/test/resources/log4j2.properties new file mode 100644 index 00000000000..bd94d40a452 --- /dev/null +++ b/components/camel-plc4x/src/test/resources/log4j2.properties @@ -0,0 +1,24 @@ +# +# 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 +# +# https://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. +# +appender.out.type=Console +appender.out.name=out +appender.out.layout.type=PatternLayout +appender.out.layout.pattern=[%30.30t] %-30.30c{1} %-5p %m%n +rootLogger.level=INFO +rootLogger.appenderRef.out.ref=out diff --git a/components/camel-plc4x/src/test/resources/logback-test.xml b/components/camel-plc4x/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..2b9cea25dc8 --- /dev/null +++ b/components/camel-plc4x/src/test/resources/logback-test.xml @@ -0,0 +1,36 @@ +<?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 + + https://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. + --> +<configuration xmlns="http://ch.qos.logback/xml/ns/logback" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation=" + http://ch.qos.logback/xml/ns/logback + https://raw.githubusercontent.com/enricopulatzo/logback-XSD/master/src/main/xsd/logback.xsd"> + + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> + </encoder> + </appender> + + <root level="error"> + <appender-ref ref="STDOUT" /> + </root> + +</configuration> \ No newline at end of file
