This is an automated email from the ASF dual-hosted git repository.
apkhmv pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new b1c2969dd0 IGNITE-23093 Support nested objects automatic conversion in
Compute API (#4556)
b1c2969dd0 is described below
commit b1c2969dd0d309b7e8c2b06b49ec016521330c71
Author: Maksim Myskov <[email protected]>
AuthorDate: Wed Oct 16 16:13:15 2024 +0300
IGNITE-23093 Support nested objects automatic conversion in Compute API
(#4556)
---
.../internal/client/proto/pojo/PojoConverter.java | 46 ++++++++++++++++++----
.../proto/ClientComputeJobPackerUnpackerTest.java | 25 ++++--------
.../internal/client/proto/pojo/ChildPojo.java | 23 -----------
.../internal/client/proto/pojo/ParentPojo.java | 23 -----------
.../ignite/internal/client/proto/pojo/Pojo.java | 35 +++++++++++++++-
.../client/proto/pojo/PojoConverterTest.java | 29 +++++---------
.../apache/ignite/internal/runner/app/Jobs.java | 21 +++++++++-
.../ItThinClientPojoComputeMarshallingTest.java | 17 ++++++++
8 files changed, 126 insertions(+), 93 deletions(-)
diff --git
a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/pojo/PojoConverter.java
b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/pojo/PojoConverter.java
index 121e612805..5589edc432 100644
---
a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/pojo/PojoConverter.java
+++
b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/pojo/PojoConverter.java
@@ -20,13 +20,21 @@ package org.apache.ignite.internal.client.proto.pojo;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.ignite.sql.ColumnType;
import org.apache.ignite.table.Tuple;
import org.apache.ignite.table.mapper.Mapper;
import org.apache.ignite.table.mapper.PojoMapper;
/** Converts POJO to Tuple and back. */
public class PojoConverter {
+ private static final Set<Class<?>> NATIVE_TYPES =
Arrays.stream(ColumnType.values())
+ .map(ColumnType::javaClass)
+ .collect(Collectors.toUnmodifiableSet());
/**
* Converts POJO to Tuple. Supports public and private non-static fields.
@@ -38,11 +46,6 @@ public class PojoConverter {
public static Tuple toTuple(Object obj) throws PojoConversionException {
Class<?> clazz = obj.getClass();
- // TODO https://issues.apache.org/jira/browse/IGNITE-23092
- if (clazz.getSuperclass() != Object.class) {
- throw new PojoConversionException("Can't convert subclasses");
- }
-
PojoMapper<?> mapper;
try {
mapper = (PojoMapper<?>) Mapper.of(clazz);
@@ -61,7 +64,7 @@ public class PojoConverter {
// TODO https://issues.apache.org/jira/browse/IGNITE-23261
MethodHandles.Lookup lookup =
MethodHandles.privateLookupIn(clazz, MethodHandles.lookup());
VarHandle varHandle = lookup.unreflectVarHandle(field);
- tuple.set(columnName, varHandle.get(obj));
+ tuple.set(columnName, convertToTupleIfNeeded(field,
varHandle.get(obj)));
} catch (IllegalAccessException e) {
throw new PojoConversionException("Cannot access field `" +
fieldName + "`", e);
} catch (NoSuchFieldException e) {
@@ -92,7 +95,7 @@ public class PojoConverter {
// TODO https://issues.apache.org/jira/browse/IGNITE-23261
MethodHandles.Lookup lookup =
MethodHandles.privateLookupIn(clazz, MethodHandles.lookup());
VarHandle varHandle = lookup.unreflectVarHandle(field);
- varHandle.set(obj, value);
+ varHandle.set(obj, convertFromNestedTupleIfNeeded(field,
value));
} catch (UnsupportedOperationException e) {
throw new PojoConversionException("Field for the column `" +
fieldName + "` is final", e);
} catch (ClassCastException e) {
@@ -104,6 +107,32 @@ public class PojoConverter {
}
}
+ private static Object convertToTupleIfNeeded(Field field, Object obj) {
+ if (isNativeType(field.getType())) {
+ return obj;
+ }
+ if (obj == null) {
+ return null;
+ }
+ return toTuple(obj);
+ }
+
+ private static Object convertFromNestedTupleIfNeeded(Field field, Object
value) throws IllegalAccessException {
+ if (isNativeType(field.getType())) {
+ return value;
+ }
+ if (value == null) {
+ return null;
+ }
+ try {
+ Object obj =
field.getType().getDeclaredConstructor().newInstance();
+ fromTuple(obj, (Tuple) value);
+ return obj;
+ } catch (NoSuchMethodException | InvocationTargetException |
InstantiationException e) {
+ throw new PojoConversionException("Field for the column `" +
field.getName() + "` cannot be converted", e);
+ }
+ }
+
private static Field getField(Class<?> clazz, String fieldName) {
try {
return clazz.getDeclaredField(fieldName);
@@ -112,4 +141,7 @@ public class PojoConverter {
}
}
+ private static boolean isNativeType(Class<?> clazz) {
+ return NATIVE_TYPES.contains(clazz) || clazz.isPrimitive();
+ }
}
diff --git
a/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/ClientComputeJobPackerUnpackerTest.java
b/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/ClientComputeJobPackerUnpackerTest.java
index 3e446d33fc..428aece0be 100644
---
a/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/ClientComputeJobPackerUnpackerTest.java
+++
b/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/ClientComputeJobPackerUnpackerTest.java
@@ -41,7 +41,6 @@ import
org.apache.ignite.internal.client.proto.pojo.StaticFieldPojo;
import org.apache.ignite.marshalling.Marshaller;
import org.apache.ignite.marshalling.MarshallingException;
import org.apache.ignite.marshalling.UnmarshallingException;
-import org.apache.ignite.marshalling.UnsupportedObjectTypeMarshallingException;
import org.apache.ignite.table.Tuple;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -76,11 +75,7 @@ class ClientComputeJobPackerUnpackerTest {
}
private static List<Object> pojo() {
- return List.of(new Pojo(
- true, (byte) 4, (short) 8, 15, 16L, 23.0f, 42.0d,
"TEST_STRING", UUID.randomUUID(), new byte[]{1, 2, 3},
- LocalTime.now(), LocalDate.now(), LocalDateTime.now(),
Instant.now(),
- Period.of(1, 2, 3), Duration.of(1, ChronoUnit.DAYS)
- ));
+ return List.of(Pojo.generateTestPojo());
}
private ClientMessagePacker messagePacker;
@@ -192,26 +187,20 @@ class ClientComputeJobPackerUnpackerTest {
}
}
- /** Pojo with unsupported type. */
- public static class UnsupportedType {
- // Unsupported type
- Object obj = new Object();
- }
-
@Test
- void packUnsupportedType() {
+ void packInvalidPojoStatic() {
assertThrows(
- UnsupportedObjectTypeMarshallingException.class,
- () -> packJobResult(new UnsupportedType(), null,
messagePacker),
- "Tuple field is of unsupported type: class java.lang.Object"
+ MarshallingException.class,
+ () -> packJobResult(new StaticFieldPojo(), null,
messagePacker),
+ "Can't pack object"
);
}
@Test
- void packInvalidPojo() {
+ void packInvalidPojoEmpty() {
assertThrows(
MarshallingException.class,
- () -> packJobResult(new StaticFieldPojo(), null,
messagePacker),
+ () -> packJobResult(new Object(), null, messagePacker),
"Can't pack object"
);
}
diff --git
a/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/pojo/ChildPojo.java
b/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/pojo/ChildPojo.java
deleted file mode 100644
index 484136f5c3..0000000000
---
a/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/pojo/ChildPojo.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.ignite.internal.client.proto.pojo;
-
-/** Child POJO. */
-public class ChildPojo extends ParentPojo {
- public int childField;
-}
diff --git
a/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/pojo/ParentPojo.java
b/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/pojo/ParentPojo.java
deleted file mode 100644
index b0b977e2e1..0000000000
---
a/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/pojo/ParentPojo.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.ignite.internal.client.proto.pojo;
-
-/** Parent POJO. */
-public class ParentPojo {
- public int parentField;
-}
diff --git
a/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/pojo/Pojo.java
b/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/pojo/Pojo.java
index 4e0bd4a5ae..6cb93afb65 100644
---
a/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/pojo/Pojo.java
+++
b/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/pojo/Pojo.java
@@ -23,6 +23,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;
+import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;
@@ -47,12 +48,14 @@ public class Pojo {
private Instant instant;
private Period period;
private Duration duration;
+ private Pojo childPojo;
public Pojo() {
}
/** Constructor. */
public Pojo(
+ boolean nested,
boolean bool, byte b, short shortField, int intField, long
longField, float floatField, double doubleField, String string,
UUID uuid, byte[] byteArray, LocalTime localTime, LocalDate
localDate, LocalDateTime localDateTime,
Instant instant, Period period, Duration duration
@@ -73,6 +76,10 @@ public class Pojo {
this.instant = instant;
this.period = period;
this.duration = duration;
+ if (nested) {
+ this.childPojo = new Pojo(false, bool, b, shortField, intField,
longField, floatField, doubleField, string, uuid,
+ byteArray, localTime, localDate, localDateTime, instant,
period, duration);
+ }
}
@Override
@@ -92,7 +99,8 @@ public class Pojo {
&& Objects.equals(uuid, pojo.uuid) && Arrays.equals(byteArray,
pojo.byteArray)
&& Objects.equals(localTime, pojo.localTime) &&
Objects.equals(localDate, pojo.localDate)
&& Objects.equals(localDateTime, pojo.localDateTime) &&
Objects.equals(instant, pojo.instant)
- && Objects.equals(period, pojo.period) &&
Objects.equals(duration, pojo.duration);
+ && Objects.equals(period, pojo.period) &&
Objects.equals(duration, pojo.duration)
+ && Objects.equals(childPojo, pojo.childPojo);
}
@Override
@@ -113,6 +121,7 @@ public class Pojo {
result = 31 * result + Objects.hashCode(instant);
result = 31 * result + Objects.hashCode(period);
result = 31 * result + Objects.hashCode(duration);
+ result = 31 * result + Objects.hashCode(childPojo);
return result;
}
@@ -121,6 +130,28 @@ public class Pojo {
return "Pojo{" + "bool=" + bool + ", b=" + byteField + ", s=" +
shortField + ", i=" + intField + ", l=" + longField
+ ", f=" + floatField + ", d=" + doubleField + ", str='" +
string + '\'' + ", uuid=" + uuid
+ ", byteArray=" + Arrays.toString(byteArray) + ", localTime="
+ localTime + ", localDate=" + localDate
- + ", localDateTime=" + localDateTime + ", instant=" + instant
+ ", period=" + period + ", duration=" + duration + '}';
+ + ", localDateTime=" + localDateTime + ", instant=" + instant
+ ", period=" + period + ", duration=" + duration
+ + ", childPojo=" + childPojo
+ + '}';
+ }
+
+ /**
+ * Creates instance filled with test data.
+ */
+ public static Pojo generateTestPojo() {
+ return generateTestPojo(false);
+ }
+
+ /**
+ * Creates instance filled with test data.
+ *
+ * @param nested {@code true} if childPojo field needs to be filled
+ */
+ public static Pojo generateTestPojo(boolean nested) {
+ return new Pojo(nested,
+ true, (byte) 4, (short) 8, 15, 16L, 23.0f, 42.0d,
"TEST_STRING", UUID.randomUUID(), new byte[]{1, 2, 3},
+ LocalTime.now(), LocalDate.now(), LocalDateTime.now(),
Instant.now(),
+ Period.of(1, 2, 3), Duration.of(1, ChronoUnit.DAYS)
+ );
}
}
diff --git
a/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/pojo/PojoConverterTest.java
b/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/pojo/PojoConverterTest.java
index 3b256e3d5e..d555d98052 100644
---
a/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/pojo/PojoConverterTest.java
+++
b/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/pojo/PojoConverterTest.java
@@ -23,14 +23,6 @@ import static
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThr
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
-import java.time.Duration;
-import java.time.Instant;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.LocalTime;
-import java.time.Period;
-import java.time.temporal.ChronoUnit;
-import java.util.UUID;
import org.apache.ignite.table.Tuple;
import org.junit.jupiter.api.Test;
@@ -38,24 +30,23 @@ import org.junit.jupiter.api.Test;
class PojoConverterTest {
@Test
void allTypes() {
- Pojo src = new Pojo(
- true, (byte) 4, (short) 8, 15, 16L, 23.0f, 42.0d,
"TEST_STRING", UUID.randomUUID(), new byte[]{1, 2, 3},
- LocalTime.now(), LocalDate.now(), LocalDateTime.now(),
Instant.now(),
- Period.of(1, 2, 3), Duration.of(1, ChronoUnit.DAYS)
- );
+ Pojo src = Pojo.generateTestPojo(true);
Tuple tuple = toTuple(src);
Pojo dst = new Pojo();
fromTuple(dst, tuple);
assertThat(dst, is(src));
}
+ /*
+ * Nested Pojo is null in this case.
+ */
@Test
- void nestedPojo() {
- assertThrows(
- PojoConversionException.class,
- () -> toTuple(new ChildPojo()),
- "Can't convert subclasses"
- );
+ void allTypesWithoutNested() {
+ Pojo src = Pojo.generateTestPojo(false);
+ Tuple tuple = toTuple(src);
+ Pojo dst = new Pojo();
+ fromTuple(dst, tuple);
+ assertThat(dst, is(src));
}
@Test
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/Jobs.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/Jobs.java
index 2ad5631055..6fa5803b77 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/Jobs.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/Jobs.java
@@ -192,8 +192,17 @@ public class Jobs {
public static class PojoJob implements ComputeJob<PojoArg, PojoResult> {
@Override
public CompletableFuture<PojoResult> executeAsync(JobExecutionContext
context, @Nullable PojoArg arg) {
+ return completedFuture(new PojoResult().setLongValue(getSum(arg)));
+ }
+
+ private static long getSum(@Nullable PojoArg arg) {
var numberFromStr = Integer.parseInt(arg.strValue);
- return completedFuture(new PojoResult().setLongValue(arg.intValue
+ numberFromStr));
+ var sum = arg.intValue + numberFromStr;
+ if (arg.childPojo != null) {
+ return sum + getSum(arg.getChildPojo());
+ } else {
+ return sum;
+ }
}
}
@@ -201,6 +210,7 @@ public class Jobs {
public static class PojoArg {
String strValue;
int intValue;
+ PojoArg childPojo;
public PojoArg() {
}
@@ -223,6 +233,15 @@ public class Jobs {
return this;
}
+ public PojoArg getChildPojo() {
+ return childPojo;
+ }
+
+ public PojoArg setChildPojo(PojoArg value) {
+ this.childPojo = value;
+ return this;
+ }
+
@Override
public boolean equals(Object obj) {
if (this == obj) {
diff --git
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientPojoComputeMarshallingTest.java
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientPojoComputeMarshallingTest.java
index 7f4b6ca72c..902d0b0d6c 100644
---
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientPojoComputeMarshallingTest.java
+++
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientPojoComputeMarshallingTest.java
@@ -59,6 +59,23 @@ public class ItThinClientPojoComputeMarshallingTest extends
ItAbstractThinClient
assertThat(result.getLongValue(), is(3L));
}
+ @ParameterizedTest
+ @ValueSource(ints = {0, 1})
+ void childPojoJob(int targetNodeIdx) {
+ // Given target node.
+ var targetNode = node(targetNodeIdx);
+
+ // When run job with provided pojo result class.
+ PojoResult result = client().compute().execute(
+ JobTarget.node(targetNode),
+
JobDescriptor.builder(PojoJob.class).resultClass(PojoResult.class).build(),
+ new PojoArg().setStrValue("1").setChildPojo(new
PojoArg().setStrValue("1"))
+ );
+
+ // Then the job returns the expected result.
+ assertThat(result.getLongValue(), is(2L));
+ }
+
@ParameterizedTest
@ValueSource(ints = {0, 1})
void pojoJobWithDifferentClass(int targetNodeIdx) {