This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 0ec3f6909c26 CAMEL-26124: camel-jt400 - Fix program call without
outputFieldsIdx and with a List body (#28900)
0ec3f6909c26 is described below
commit 0ec3f6909c2685678a289c6abd7f528561f52dff
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 2 20:38:50 2026 +0100
CAMEL-26124: camel-jt400 - Fix program call without outputFieldsIdx and
with a List body (#28900)
* CAMEL-26124: camel-jt400 - Fix program call without outputFieldsIdx a…
…nd with a List body
A program call without outputFieldsIdx failed with a NullPointerException,
because isFieldIdxForOuput did a binary search on a null array. A missing
outputFieldsIdx now means no output fields (all parameters are input).
The producer cast the body to Object[], so a List body failed with a
ClassCastException. It now uses getMandatoryBody(Object[].class): a List
is converted, and other bodies fail with an InvalidPayloadException.
* CAMEL-26124: camel-jt400 - Fix outputFieldsIdx order and missing fiel…
…dsLength in program call
isFieldIdxForOuput used a binary search, so outputFieldsIdx only worked
in ascending order (2,0 did not find 0). It now checks the indexes in any
order.
The producer read the field length for every parameter, so a call
without fieldsLength failed with a NullPointerException (and a too short
fieldsLength with an ArrayIndexOutOfBoundsException). The length is now
only read where it is needed (output, text and byte[] parameters), and
a missing length fails with an IllegalArgumentException naming fieldsLength.
* CAMEL-26124: camel-jt400 - Update the producer body type: a List is c…
…onverted
The program call now accepts a List body and converts it to an Object[],
so the @BodyTypes description no longer says that a List fails.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../org/apache/camel/catalog/components/jt400.json | 2 +-
.../apache/camel/catalog/docs/jt400-component.adoc | 8 +-
.../org/apache/camel/component/jt400/jt400.json | 2 +-
.../camel-jt400/src/main/docs/jt400-component.adoc | 8 +-
.../camel/component/jt400/Jt400Endpoint.java | 23 +++-
.../camel/component/jt400/Jt400PgmProducer.java | 31 +++--
.../component/jt400/Jt400PgmProducerTest.java | 130 +++++++++++++++++++++
7 files changed, 178 insertions(+), 26 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jt400.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jt400.json
index 1332a1618e71..2240668e95c8 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jt400.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jt400.json
@@ -44,7 +44,7 @@
},
"bodyTypes": {
"consumerOut": { "carriers": [ "text" ], "dependsOn": [ "objectPath" ],
"byOption": [ { "option": "format", "value": "binary", "carriers": [ "bytes" ]
}, { "option": "sendEmptyMessageWhenIdle", "value": "true", "carriers": [
"text", "bytes", "null" ] } ], "description": "The data queue entry as a
String, or a byte array with format=binary; a message queue (.MSGQ) always
gives the message text" },
- "producerIn": { "carriers": [ "any" ], "dependsOn": [ "objectPath",
"format" ], "description": "A queue writes the body converted to a String, or
to a byte array with format=binary (text, bytes or a stream). A program call
(.PGM, .SRVPGM) needs an Object[] of the parameters: a List fails" },
+ "producerIn": { "carriers": [ "any" ], "dependsOn": [ "objectPath",
"format" ], "description": "A queue writes the body converted to a String, or
to a byte array with format=binary (text, bytes or a stream). A program call
(.PGM, .SRVPGM) needs an Object[] of the parameters (a List is converted to an
array)" },
"producerOut": { "carriers": [ "unchanged" ], "dependsOn": [ "objectPath"
], "description": "Unchanged for a queue. A program call sets an Object[] of
the parameters, the output ones replaced by what the program returned: Strings,
or byte arrays with format=binary" }
},
"properties": {
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jt400-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jt400-component.adoc
index 047b2ba226e7..a136d92ce39b 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jt400-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jt400-component.adoc
@@ -93,12 +93,16 @@ on the endpoint.
=== Program call
-This endpoint expects the input to be an `Object[]`, whose object types are
+This endpoint expects the input to be an `Object[]` (a `List` is converted to
an array), whose object types are
`int`, `long`, `CharSequence` (such as `String`), or `byte[]`. All other
data types in the input array will be converted to `String`. For character
inputs, CCSID handling is performed through the native jt400 library
mechanisms. A parameter can be _omitted_ by passing null as the value in
-its position (the program has to support it). After the program execution,
+its position (the program has to support it). The `outputFieldsIdx` option
lists the
+output parameters (in any order); when it is not set, all parameters are
input-only.
+The `fieldsLength` option gives the length of each parameter. A length is
needed for
+output parameters and for text and `byte[]` input parameters, but not for
`int` and `long` inputs.
+After the program execution,
the endpoint returns an `Object[]` in the message body. Depending on
_format_, the returned array will be populated with `byte[]` or `String`
objects representing the values as they were returned by the program.
diff --git
a/components/camel-jt400/src/generated/resources/META-INF/org/apache/camel/component/jt400/jt400.json
b/components/camel-jt400/src/generated/resources/META-INF/org/apache/camel/component/jt400/jt400.json
index 1332a1618e71..2240668e95c8 100644
---
a/components/camel-jt400/src/generated/resources/META-INF/org/apache/camel/component/jt400/jt400.json
+++
b/components/camel-jt400/src/generated/resources/META-INF/org/apache/camel/component/jt400/jt400.json
@@ -44,7 +44,7 @@
},
"bodyTypes": {
"consumerOut": { "carriers": [ "text" ], "dependsOn": [ "objectPath" ],
"byOption": [ { "option": "format", "value": "binary", "carriers": [ "bytes" ]
}, { "option": "sendEmptyMessageWhenIdle", "value": "true", "carriers": [
"text", "bytes", "null" ] } ], "description": "The data queue entry as a
String, or a byte array with format=binary; a message queue (.MSGQ) always
gives the message text" },
- "producerIn": { "carriers": [ "any" ], "dependsOn": [ "objectPath",
"format" ], "description": "A queue writes the body converted to a String, or
to a byte array with format=binary (text, bytes or a stream). A program call
(.PGM, .SRVPGM) needs an Object[] of the parameters: a List fails" },
+ "producerIn": { "carriers": [ "any" ], "dependsOn": [ "objectPath",
"format" ], "description": "A queue writes the body converted to a String, or
to a byte array with format=binary (text, bytes or a stream). A program call
(.PGM, .SRVPGM) needs an Object[] of the parameters (a List is converted to an
array)" },
"producerOut": { "carriers": [ "unchanged" ], "dependsOn": [ "objectPath"
], "description": "Unchanged for a queue. A program call sets an Object[] of
the parameters, the output ones replaced by what the program returned: Strings,
or byte arrays with format=binary" }
},
"properties": {
diff --git a/components/camel-jt400/src/main/docs/jt400-component.adoc
b/components/camel-jt400/src/main/docs/jt400-component.adoc
index 047b2ba226e7..a136d92ce39b 100644
--- a/components/camel-jt400/src/main/docs/jt400-component.adoc
+++ b/components/camel-jt400/src/main/docs/jt400-component.adoc
@@ -93,12 +93,16 @@ on the endpoint.
=== Program call
-This endpoint expects the input to be an `Object[]`, whose object types are
+This endpoint expects the input to be an `Object[]` (a `List` is converted to
an array), whose object types are
`int`, `long`, `CharSequence` (such as `String`), or `byte[]`. All other
data types in the input array will be converted to `String`. For character
inputs, CCSID handling is performed through the native jt400 library
mechanisms. A parameter can be _omitted_ by passing null as the value in
-its position (the program has to support it). After the program execution,
+its position (the program has to support it). The `outputFieldsIdx` option
lists the
+output parameters (in any order); when it is not set, all parameters are
input-only.
+The `fieldsLength` option gives the length of each parameter. A length is
needed for
+output parameters and for text and `byte[]` input parameters, but not for
`int` and `long` inputs.
+After the program execution,
the endpoint returns an `Object[]` in the message body. Depending on
_format_, the returned array will be populated with `byte[]` or `String`
objects representing the values as they were returned by the program.
diff --git
a/components/camel-jt400/src/main/java/org/apache/camel/component/jt400/Jt400Endpoint.java
b/components/camel-jt400/src/main/java/org/apache/camel/component/jt400/Jt400Endpoint.java
index 45a58baee4d2..784cf6d8692d 100644
---
a/components/camel-jt400/src/main/java/org/apache/camel/component/jt400/Jt400Endpoint.java
+++
b/components/camel-jt400/src/main/java/org/apache/camel/component/jt400/Jt400Endpoint.java
@@ -17,7 +17,6 @@
package org.apache.camel.component.jt400;
import java.net.URISyntaxException;
-import java.util.Arrays;
import java.util.Map;
import javax.naming.OperationNotSupportedException;
@@ -57,7 +56,7 @@ import org.apache.camel.util.URISupport;
producerIn = @BodyType(value = Carrier.ANY, dependsOn = {
"objectPath", "format" },
description = "A queue writes the body
converted to a String, or to a byte array with"
+ " format=binary (text, bytes
or a stream). A program call (.PGM, .SRVPGM)"
- + " needs an Object[] of the
parameters: a List fails"),
+ + " needs an Object[] of the
parameters (a List is converted to an array)"),
producerOut = @BodyType(value = Carrier.UNCHANGED, dependsOn =
"objectPath",
description = "Unchanged for a queue. A
program call sets an Object[] of the parameters,"
+ " the output ones replaced
by what the program returned: Strings, or byte"
@@ -266,11 +265,27 @@ public class Jt400Endpoint extends ScheduledPollEndpoint
implements MultipleCons
}
public boolean isFieldIdxForOuput(int idx) {
- return Arrays.binarySearch(getOutputFieldsIdxArray(), idx) >= 0;
+ Integer[] outputFieldsIdxArray = getOutputFieldsIdxArray();
+ // no outputFieldsIdx configured means no output fields
+ if (outputFieldsIdxArray == null) {
+ return false;
+ }
+ // the indexes can be listed in any order
+ for (Integer outputIdx : outputFieldsIdxArray) {
+ if (outputIdx == idx) {
+ return true;
+ }
+ }
+ return false;
}
public int getOutputFieldLength(int idx) {
- return configuration.getOutputFieldsLengthArray()[idx];
+ Integer[] fieldsLengthArray =
configuration.getOutputFieldsLengthArray();
+ if (fieldsLengthArray == null || idx >= fieldsLengthArray.length) {
+ throw new IllegalArgumentException(
+ "The fieldsLength option must specify the length of the
field (program parameter) at index " + idx);
+ }
+ return fieldsLengthArray[idx];
}
public void setObjectPath(String objectPath) {
diff --git
a/components/camel-jt400/src/main/java/org/apache/camel/component/jt400/Jt400PgmProducer.java
b/components/camel-jt400/src/main/java/org/apache/camel/component/jt400/Jt400PgmProducer.java
index bd9a02f888aa..ccfbb1c3f951 100644
---
a/components/camel-jt400/src/main/java/org/apache/camel/component/jt400/Jt400PgmProducer.java
+++
b/components/camel-jt400/src/main/java/org/apache/camel/component/jt400/Jt400PgmProducer.java
@@ -99,12 +99,11 @@ public class Jt400PgmProducer extends DefaultProducer {
}
}
- private ProgramParameter[] getParameterList(Exchange exchange, AS400
iSeries)
+ ProgramParameter[] getParameterList(Exchange exchange, AS400 iSeries)
throws InvalidPayloadException, PropertyVetoException {
- Object body = exchange.getIn().getMandatoryBody();
-
- Object[] params = (Object[]) body;
+ // the body must be an array of parameters (a List is converted to an
array)
+ Object[] params = exchange.getIn().getMandatoryBody(Object[].class);
ProgramParameter[] parameterList = new ProgramParameter[params.length];
for (int i = 0; i < params.length; i++) {
@@ -122,27 +121,24 @@ public class Jt400PgmProducer extends DefaultProducer {
byte[] inputData = null;
- // XXX Actually, returns any field length, not just output.
- int length = getISeriesEndpoint().getOutputFieldLength(i);
-
if (input) {
if (param != null) {
AS400DataType typeConverter;
if (param instanceof CharSequence) {
param = param.toString();
- typeConverter = new AS400Text(length, iSeries);
+ typeConverter = new
AS400Text(getISeriesEndpoint().getOutputFieldLength(i), iSeries);
} else if (param instanceof char[]) {
param = new String((char[]) param);
- typeConverter = new AS400Text(length, iSeries);
+ typeConverter = new
AS400Text(getISeriesEndpoint().getOutputFieldLength(i), iSeries);
} else if (param instanceof Integer) {
typeConverter = new AS400Bin4();
} else if (param instanceof Long) {
typeConverter = new AS400Bin8();
} else if (param instanceof byte[]) {
- typeConverter = new AS400ByteArray(length);
+ typeConverter = new
AS400ByteArray(getISeriesEndpoint().getOutputFieldLength(i));
} else {
param = param.toString(); // must be a String for
AS400Text class
- typeConverter = new AS400Text(length, iSeries);
+ typeConverter = new
AS400Text(getISeriesEndpoint().getOutputFieldLength(i), iSeries);
}
inputData = typeConverter.toBytes(param);
}
@@ -152,9 +148,13 @@ public class Jt400PgmProducer extends DefaultProducer {
if (input && output) {
LOG.trace("Parameter {} is both input and output.", i);
if (getISeriesEndpoint().getType() == Jt400Type.PGM) {
- parameterList[i] = new ProgramParameter(inputData, length);
+ parameterList[i] = new ProgramParameter(
+ inputData,
+ getISeriesEndpoint().getOutputFieldLength(i));
} else {
- parameterList[i] = new
ProgramParameter(ProgramParameter.PASS_BY_REFERENCE, inputData, length);
+ parameterList[i] = new ProgramParameter(
+ ProgramParameter.PASS_BY_REFERENCE, inputData,
+ getISeriesEndpoint().getOutputFieldLength(i));
}
} else if (input) {
LOG.trace("Parameter {} is input.", i);
@@ -168,7 +168,7 @@ public class Jt400PgmProducer extends DefaultProducer {
} else {
// output
LOG.trace("Parameter {} is output.", i);
- parameterList[i] = new ProgramParameter(length);
+ parameterList[i] = new
ProgramParameter(getISeriesEndpoint().getOutputFieldLength(i));
}
}
@@ -178,8 +178,7 @@ public class Jt400PgmProducer extends DefaultProducer {
private void handlePGMOutput(Exchange exchange, ProgramCall pgmCall, AS400
iSeries)
throws InvalidPayloadException {
- Object body = exchange.getIn().getMandatoryBody();
- Object[] params = (Object[]) body;
+ Object[] params = exchange.getIn().getMandatoryBody(Object[].class);
List<Object> results = new ArrayList<>();
diff --git
a/components/camel-jt400/src/test/java/org/apache/camel/component/jt400/Jt400PgmProducerTest.java
b/components/camel-jt400/src/test/java/org/apache/camel/component/jt400/Jt400PgmProducerTest.java
new file mode 100644
index 000000000000..416443d37fd3
--- /dev/null
+++
b/components/camel-jt400/src/test/java/org/apache/camel/component/jt400/Jt400PgmProducerTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.component.jt400;
+
+import java.util.List;
+
+import com.ibm.as400.access.AS400;
+import com.ibm.as400.access.ProgramParameter;
+import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Builds the program parameter list of {@link Jt400PgmProducer} without
connecting to an IBM i system.
+ */
+public class Jt400PgmProducerTest extends Jt400TestSupport {
+
+ private static final String PGM_URI =
"jt400://USER:password@host/qsys.lib/library.lib/prog.pgm"
+ +
"?connectionPool=#mockPool&fieldsLength=4,8";
+ private static final String PGM_URI_NO_LENGTH =
"jt400://USER:password@host/qsys.lib/library.lib/prog.pgm"
+ +
"?connectionPool=#mockPool";
+
+ private final AS400 iSeries = new AS400("host", "USER", "password");
+
+ @Test
+ public void testNoOutputFieldsIdxMeansInputOnly() throws Exception {
+ Jt400Endpoint endpoint = context.getEndpoint(PGM_URI,
Jt400Endpoint.class);
+ assertNull(endpoint.getOutputFieldsIdxArray());
+ assertFalse(endpoint.isFieldIdxForOuput(0));
+
+ ProgramParameter[] parameters = createParameterList(endpoint, new
Object[] { 1, 2L });
+
+ assertEquals(2, parameters.length);
+ assertArrayEquals(new byte[] { 0, 0, 0, 1 },
parameters[0].getInputData());
+ assertEquals(0, parameters[0].getOutputDataLength());
+ assertArrayEquals(new byte[] { 0, 0, 0, 0, 0, 0, 0, 2 },
parameters[1].getInputData());
+ assertEquals(0, parameters[1].getOutputDataLength());
+ }
+
+ @Test
+ public void testListBody() throws Exception {
+ Jt400Endpoint endpoint = context.getEndpoint(PGM_URI +
"&outputFieldsIdx=1", Jt400Endpoint.class);
+
+ ProgramParameter[] parameters = createParameterList(endpoint,
List.of(1, new byte[0]));
+
+ assertEquals(2, parameters.length);
+ assertArrayEquals(new byte[] { 0, 0, 0, 1 },
parameters[0].getInputData());
+ assertEquals(0, parameters[0].getOutputDataLength());
+ assertEquals(8, parameters[1].getOutputDataLength());
+ }
+
+ @Test
+ public void testOutputFieldsIdxInAnyOrder() throws Exception {
+ Jt400Endpoint endpoint = context.getEndpoint(PGM_URI +
",16&outputFieldsIdx=2,0", Jt400Endpoint.class);
+ assertTrue(endpoint.isFieldIdxForOuput(0));
+ assertFalse(endpoint.isFieldIdxForOuput(1));
+ assertTrue(endpoint.isFieldIdxForOuput(2));
+
+ ProgramParameter[] parameters = createParameterList(endpoint, new
Object[] { null, 1, null });
+
+ assertEquals(4, parameters[0].getOutputDataLength());
+ assertEquals(0, parameters[1].getOutputDataLength());
+ assertEquals(16, parameters[2].getOutputDataLength());
+ }
+
+ @Test
+ public void testNoFieldsLengthForNumbers() throws Exception {
+ Jt400Endpoint endpoint = context.getEndpoint(PGM_URI_NO_LENGTH,
Jt400Endpoint.class);
+
+ ProgramParameter[] parameters = createParameterList(endpoint, new
Object[] { 1, 2L });
+
+ assertEquals(2, parameters.length);
+ assertArrayEquals(new byte[] { 0, 0, 0, 1 },
parameters[0].getInputData());
+ assertArrayEquals(new byte[] { 0, 0, 0, 0, 0, 0, 0, 2 },
parameters[1].getInputData());
+ }
+
+ @Test
+ public void testNoFieldsLengthForOutputField() {
+ Jt400Endpoint endpoint = context.getEndpoint(PGM_URI_NO_LENGTH +
"&outputFieldsIdx=0", Jt400Endpoint.class);
+
+ IllegalArgumentException e =
assertThrows(IllegalArgumentException.class,
+ () -> createParameterList(endpoint, new Object[] { null }));
+ assertTrue(e.getMessage().contains("fieldsLength"), e.getMessage());
+ }
+
+ @Test
+ public void testFieldsLengthTooShort() {
+ Jt400Endpoint endpoint = context.getEndpoint(PGM_URI +
"&outputFieldsIdx=2", Jt400Endpoint.class);
+
+ IllegalArgumentException e =
assertThrows(IllegalArgumentException.class,
+ () -> createParameterList(endpoint, new Object[] { 1, 2L, null
}));
+ assertTrue(e.getMessage().contains("fieldsLength"), e.getMessage());
+ }
+
+ @Test
+ public void testInvalidBody() {
+ Jt400Endpoint endpoint = context.getEndpoint(PGM_URI,
Jt400Endpoint.class);
+
+ assertThrows(InvalidPayloadException.class, () ->
createParameterList(endpoint, new Object()));
+ }
+
+ private ProgramParameter[] createParameterList(Jt400Endpoint endpoint,
Object body) throws Exception {
+ Exchange exchange = new DefaultExchange(context);
+ exchange.getIn().setBody(body);
+ return new Jt400PgmProducer(endpoint).getParameterList(exchange,
iSeries);
+ }
+
+}