From b7b6d34938bf2d2a27d33d1ddb8a0e11d6411d86 Mon Sep 17 00:00:00 2001 From: Andrus Adamchik Date: Fri, 7 Jun 2024 15:32:59 -0400 Subject: [PATCH] Multi-column ObjectMapper #676 --- RELEASE-NOTES.md | 1 + .../update/stage/CayenneMapUpdateStage.java | 5 +- .../java/io/agrest/cayenne/PUT/MapperIT.java | 118 ++++++++++++++++++ .../agrest/cayenne/PUT/Parent_MapperIT.java | 17 ++- .../java/io/agrest/ObjectMapperFactory.java | 36 +++++- .../main/java/io/agrest/UpdateBuilder.java | 9 +- .../processor/update/ByIdObjectMapper.java | 2 +- .../update/ByIdObjectMapperFactory.java | 3 +- .../update/ByKeyObjectMapperFactory.java | 9 +- .../update/ByPropertiesObjectMapper.java | 61 +++++++++ .../ByPropertiesObjectMapperFactory.java | 32 +++++ ...apper.java => ByPropertyObjectMapper.java} | 27 ++-- .../update/ByPropertyObjectMapperFactory.java | 28 +++++ 13 files changed, 307 insertions(+), 41 deletions(-) create mode 100644 agrest-cayenne/src/test/java/io/agrest/cayenne/PUT/MapperIT.java create mode 100644 agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByPropertiesObjectMapper.java create mode 100644 agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByPropertiesObjectMapperFactory.java rename agrest-engine/src/main/java/io/agrest/runtime/processor/update/{ByKeyObjectMapper.java => ByPropertyObjectMapper.java} (62%) create mode 100644 agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByPropertyObjectMapperFactory.java diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 29757f193..23c3ba5de 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -10,6 +10,7 @@ * #660 Builders: auto-detect multi-column IDs * #662 Update expression parser with JavaCC 7.0.13 * #673 OpenAPI should have an "array of string" schema for "include" and "exclude" +* #676 Multi-column ObjectMapper * #677 Upgrading JUnit to 5.10.2 * #678 Upgrading Swagger to 2.2.2 * #679 Upgrading Jackson to 2.15.4 diff --git a/agrest-cayenne/src/main/java/io/agrest/cayenne/processor/update/stage/CayenneMapUpdateStage.java b/agrest-cayenne/src/main/java/io/agrest/cayenne/processor/update/stage/CayenneMapUpdateStage.java index 7dda122ef..96dbb734f 100644 --- a/agrest-cayenne/src/main/java/io/agrest/cayenne/processor/update/stage/CayenneMapUpdateStage.java +++ b/agrest-cayenne/src/main/java/io/agrest/cayenne/processor/update/stage/CayenneMapUpdateStage.java @@ -1,7 +1,6 @@ package io.agrest.cayenne.processor.update.stage; import io.agrest.AgException; -import io.agrest.id.AgObjectId; import io.agrest.EntityUpdate; import io.agrest.ObjectMapper; import io.agrest.ObjectMapperFactory; @@ -13,9 +12,9 @@ import io.agrest.cayenne.processor.CayenneProcessor; import io.agrest.cayenne.processor.CayenneRelatedResourceEntityExt; import io.agrest.cayenne.processor.ICayenneQueryAssembler; +import io.agrest.id.AgObjectId; import io.agrest.meta.AgIdPart; import io.agrest.protocol.Exp; -import io.agrest.runtime.processor.update.ByIdObjectMapperFactory; import io.agrest.runtime.processor.update.ChangeOperation; import io.agrest.runtime.processor.update.ChangeOperationType; import io.agrest.runtime.processor.update.UpdateContext; @@ -112,7 +111,7 @@ protected void collectCreateOps( protected ObjectMapper createObjectMapper(UpdateContext context) { ObjectMapperFactory mapper = context.getMapper() != null ? context.getMapper() - : ByIdObjectMapperFactory.mapper(); + : ObjectMapperFactory.matchById(); return mapper.createMapper(context); } diff --git a/agrest-cayenne/src/test/java/io/agrest/cayenne/PUT/MapperIT.java b/agrest-cayenne/src/test/java/io/agrest/cayenne/PUT/MapperIT.java new file mode 100644 index 000000000..6ebe7233e --- /dev/null +++ b/agrest-cayenne/src/test/java/io/agrest/cayenne/PUT/MapperIT.java @@ -0,0 +1,118 @@ +package io.agrest.cayenne.PUT; + +import io.agrest.DataResponse; +import io.agrest.cayenne.cayenne.main.E1; +import io.agrest.cayenne.cayenne.main.E23; +import io.agrest.cayenne.unit.main.MainDbTest; +import io.agrest.cayenne.unit.main.MainModelTester; +import io.agrest.jaxrs3.AgJaxrs; +import io.bootique.junit5.BQTestTool; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.Context; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class MapperIT extends MainDbTest { + + @BQTestTool + static final MainModelTester tester = tester(Resource.class) + .entitiesAndDependencies(E1.class, E23.class) + .build(); + + + @Test + public void implicitIdMapper() { + + tester.e23().insertColumns("id", "name") + .values(56, "N1") + .values(54, "N2").exec(); + + tester.target("/id") + .queryParam("include", "exposedId", "name") + .put("[ {\"exposedId\":58,\"name\":\"N4\"}, {\"exposedId\":56,\"name\":\"N3\"} ]") + .wasOk() + .bodyEquals(2, "{\"exposedId\":58,\"name\":\"N4\"},{\"exposedId\":56,\"name\":\"N3\"}"); + + tester.e23().matcher().assertMatches(3); + tester.e23().matcher().eq("id", 58).andEq("name", "N4").assertOneMatch(); + tester.e23().matcher().eq("id", 56).andEq("name", "N3").assertOneMatch(); + } + + @Test + public void propertyMapper() { + + tester.e1().insertColumns("id", "name", "description") + .values(56, "N1", "D1") + .values(54, "N2", "D2").exec(); + + tester.target("/prop") + .queryParam("include", "name", "description") + .put("[ {\"name\":\"N4\",\"description\":\"D4\"}, {\"name\":\"N1\",\"description\":\"D3\"} ]") + .wasOk() + .bodyEquals(2, "{\"description\":\"D4\",\"name\":\"N4\"},{\"description\":\"D3\",\"name\":\"N1\"}"); + + tester.e1().matcher().assertMatches(3); + tester.e1().matcher().eq("name", "N1").andEq("description", "D3").assertOneMatch(); + tester.e1().matcher().eq("name", "N2").andEq("description", "D2").assertOneMatch(); + tester.e1().matcher().eq("name", "N4").andEq("description", "D4").assertOneMatch(); + } + + @Test + public void propertiesMapper() { + + tester.e1().insertColumns("id", "name", "age", "description") + .values(56, "N1", 1, "D1") + .values(55, "N1", 2, "D2") + .values(54, "N2", 2, "D3").exec(); + + tester.target("/props") + .queryParam("include", "name", "age", "description") + .put("[{\"age\":3,\"description\":\"D4\",\"name\":\"N1\"}, {\"age\":2,\"description\":\"D5\",\"name\":\"N1\"}]") + .wasOk() + .bodyEquals(2, "{\"age\":3,\"description\":\"D4\",\"name\":\"N1\"},{\"age\":2,\"description\":\"D5\",\"name\":\"N1\"}"); + + tester.e1().matcher().assertMatches(4); + tester.e1().matcher().eq("name", "N1").andEq("age", 1).andEq("description", "D1").assertOneMatch(); + tester.e1().matcher().eq("name", "N1").andEq("age", 2).andEq("description", "D5").assertOneMatch(); + tester.e1().matcher().eq("name", "N2").andEq("age", 2).andEq("description", "D3").assertOneMatch(); + tester.e1().matcher().eq("name", "N1").andEq("age", 3).andEq("description", "D4").assertOneMatch(); + + } + + @Path("") + public static class Resource { + + @Context + private Configuration config; + + @PUT + @Path("id") + public DataResponse implicitIdMapper(@QueryParam("include") List includes, String entityData) { + return AgJaxrs.idempotentCreateOrUpdate(E23.class, config) + .request(AgJaxrs.request(config).addIncludes(includes).build()) + .syncAndSelect(entityData); + } + + @PUT + @Path("prop") + public DataResponse propertyMapper(@QueryParam("include") List includes, String entityData) { + return AgJaxrs.idempotentCreateOrUpdate(E1.class, config) + .mapper(E1.NAME.getName()) + .request(AgJaxrs.request(config).addIncludes(includes).build()) + .syncAndSelect(entityData); + } + + @PUT + @Path("props") + public DataResponse propertiesMapper(@QueryParam("include") List includes, String entityData) { + return AgJaxrs.idempotentCreateOrUpdate(E1.class, config) + .mapper(E1.NAME.getName(), E1.AGE.getName()) + .request(AgJaxrs.request(config).addIncludes(includes).build()) + .syncAndSelect(entityData); + } + } +} diff --git a/agrest-cayenne/src/test/java/io/agrest/cayenne/PUT/Parent_MapperIT.java b/agrest-cayenne/src/test/java/io/agrest/cayenne/PUT/Parent_MapperIT.java index e77205171..16fcdef07 100644 --- a/agrest-cayenne/src/test/java/io/agrest/cayenne/PUT/Parent_MapperIT.java +++ b/agrest-cayenne/src/test/java/io/agrest/cayenne/PUT/Parent_MapperIT.java @@ -1,6 +1,7 @@ package io.agrest.cayenne.PUT; import io.agrest.DataResponse; +import io.agrest.ObjectMapperFactory; import io.agrest.cayenne.cayenne.main.E14; import io.agrest.cayenne.cayenne.main.E15; import io.agrest.cayenne.cayenne.main.E3; @@ -9,15 +10,13 @@ import io.agrest.cayenne.unit.main.MainDbTest; import io.agrest.cayenne.unit.main.MainModelTester; import io.agrest.jaxrs3.AgJaxrs; -import io.agrest.runtime.processor.update.ByKeyObjectMapperFactory; import io.bootique.junit5.BQTestTool; -import org.junit.jupiter.api.Test; - import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.core.Configuration; import jakarta.ws.rs.core.Context; +import org.junit.jupiter.api.Test; public class Parent_MapperIT extends MainDbTest { @@ -38,7 +37,7 @@ public void relate_ToMany_MixedCollection() { .values(8, "yyy", 15) .values(9, "aaa", 15).exec(); - tester.target("/e8/bykey/15/e7s") + tester.target("/e8/custommapper/15/e7s") .put("[ {\"name\":\"newname\"}, {\"name\":\"aaa\"} ]") .wasOk() .replaceId("XID") @@ -48,7 +47,7 @@ public void relate_ToMany_MixedCollection() { // testing idempotency - tester.target("/e8/bykey/15/e7s") + tester.target("/e8/custommapper/15/e7s") .put("[ {\"name\":\"newname\"}, {\"name\":\"aaa\"} ]") .wasOk().replaceId("XID") .bodyEquals(2, @@ -70,7 +69,7 @@ public void relate_ToMany_PropertyMapper() { .values(8, "yyy", 15) .values(9, "aaa", 15).exec(); - tester.target("/e8/bypropkey/15/e7s") + tester.target("/e8/propmapper/15/e7s") .put("[ {\"name\":\"newname\"}, {\"name\":\"aaa\"} ]") .wasOk().replaceId("XID") .bodyEquals(2, "{\"id\":XID,\"name\":\"newname\"},{\"id\":9,\"name\":\"aaa\"}"); @@ -110,16 +109,16 @@ public static class Resource { private Configuration config; @PUT - @Path("e8/bykey/{id}/e7s") + @Path("e8/custommapper/{id}/e7s") public DataResponse e8CreateOrUpdateE7sByKey_Idempotent(@PathParam("id") int id, String entityData) { return AgJaxrs.idempotentCreateOrUpdate(E7.class, config) - .mapper(ByKeyObjectMapperFactory.byKey(E7.NAME.getName())) + .mapper(ObjectMapperFactory.matchByProperties(E7.NAME.getName())) .parent(E8.class, id, E8.E7S.getName()) .syncAndSelect(entityData); } @PUT - @Path("e8/bypropkey/{id}/e7s") + @Path("e8/propmapper/{id}/e7s") public DataResponse e8CreateOrUpdateE7sByPropKey_Idempotent(@PathParam("id") int id, String entityData) { return AgJaxrs.idempotentCreateOrUpdate(E7.class, config) .mapper(E7.NAME.getName()) diff --git a/agrest-engine/src/main/java/io/agrest/ObjectMapperFactory.java b/agrest-engine/src/main/java/io/agrest/ObjectMapperFactory.java index 8c0ee2328..ef8c05199 100644 --- a/agrest-engine/src/main/java/io/agrest/ObjectMapperFactory.java +++ b/agrest-engine/src/main/java/io/agrest/ObjectMapperFactory.java @@ -1,16 +1,40 @@ package io.agrest; +import io.agrest.runtime.processor.update.ByIdObjectMapperFactory; +import io.agrest.runtime.processor.update.ByPropertiesObjectMapperFactory; +import io.agrest.runtime.processor.update.ByPropertyObjectMapperFactory; import io.agrest.runtime.processor.update.UpdateContext; /** - * A strategy for mapping update operations to existing objects. - * + * A factory of a strategy for mapping update operations to existing objects. + * * @since 1.7 */ public interface ObjectMapperFactory { - /** - * Returns a mapper to handle objects of a given response. - */ - ObjectMapper createMapper(UpdateContext context); + /** + * @since 5.0 + */ + static ObjectMapperFactory matchById() { + return ByIdObjectMapperFactory.mapper(); + } + + /** + * @since 5.0 + */ + static ObjectMapperFactory matchByProperties(String... properties) { + switch (properties.length) { + case 0: + throw new IllegalArgumentException("No properties specified for ObjectMapperFactory"); + case 1: + return new ByPropertyObjectMapperFactory(properties[0]); + default: + return new ByPropertiesObjectMapperFactory(properties); + } + } + + /** + * Returns a mapper to handle objects of a given response. + */ + ObjectMapper createMapper(UpdateContext context); } \ No newline at end of file diff --git a/agrest-engine/src/main/java/io/agrest/UpdateBuilder.java b/agrest-engine/src/main/java/io/agrest/UpdateBuilder.java index 1313a4998..6dbebc446 100644 --- a/agrest-engine/src/main/java/io/agrest/UpdateBuilder.java +++ b/agrest-engine/src/main/java/io/agrest/UpdateBuilder.java @@ -10,7 +10,6 @@ import io.agrest.processor.Processor; import io.agrest.processor.ProcessorOutcome; import io.agrest.protocol.ControlParams; -import io.agrest.runtime.processor.update.ByKeyObjectMapperFactory; import io.agrest.runtime.processor.update.UpdateContext; import java.util.Collection; @@ -147,8 +146,8 @@ default UpdateBuilder deleteAuthorizer(Class entityType, DeleteAuthori UpdateBuilder entityOverlay(AgEntityOverlay overlay); /** - * Sets a custom mapper that locates existing objects based on request data. - * If not set, objects will be located by their IDs. + * Sets a custom mapper that locates existing objects based on request data. If not set explicitly, objects will + * be matched by their IDs. */ UpdateBuilder mapper(ObjectMapperFactory mapper); @@ -158,8 +157,8 @@ default UpdateBuilder deleteAuthorizer(Class entityType, DeleteAuthori * * @since 1.20 */ - default UpdateBuilder mapper(String propertyName) { - return mapper(ByKeyObjectMapperFactory.byKey(propertyName)); + default UpdateBuilder mapper(String... propertyNames) { + return mapper(ObjectMapperFactory.matchByProperties(propertyNames)); } /** diff --git a/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByIdObjectMapper.java b/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByIdObjectMapper.java index b2abdec95..b6ae02ce1 100644 --- a/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByIdObjectMapper.java +++ b/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByIdObjectMapper.java @@ -11,7 +11,7 @@ import java.util.Map; import java.util.Objects; -class ByIdObjectMapper implements ObjectMapper { +public class ByIdObjectMapper implements ObjectMapper { private final AgEntity entity; diff --git a/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByIdObjectMapperFactory.java b/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByIdObjectMapperFactory.java index ca97a6623..6c7f7f27f 100644 --- a/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByIdObjectMapperFactory.java +++ b/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByIdObjectMapperFactory.java @@ -4,8 +4,7 @@ import io.agrest.ObjectMapperFactory; /** - * A default singleton implementation of the {@link ObjectMapperFactory} that - * looks up objects based on their IDs. + * A default implementation of the {@link ObjectMapperFactory} that looks up objects based on their IDs. * * @since 1.4 */ diff --git a/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByKeyObjectMapperFactory.java b/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByKeyObjectMapperFactory.java index c951ca79d..9567311b2 100644 --- a/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByKeyObjectMapperFactory.java +++ b/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByKeyObjectMapperFactory.java @@ -11,11 +11,16 @@ * that have a unique property within parent. * * @since 1.4 + * @deprecated in favor of {@link ByPropertyObjectMapperFactory} */ +@Deprecated(since = "5.0", forRemoval = true) public class ByKeyObjectMapperFactory implements ObjectMapperFactory { - private String property; + private final String property; + /** + * @deprecated in favor of {@link ObjectMapperFactory#matchByProperties(String...)} + */ public static ByKeyObjectMapperFactory byKey(String key) { return new ByKeyObjectMapperFactory(key); } @@ -30,6 +35,6 @@ public ObjectMapper createMapper(UpdateContext context) { // TODO: should we account for "id" attributes here? AgAttribute attribute = entity.getAttribute(property); - return new ByKeyObjectMapper<>(attribute); + return new ByPropertyObjectMapper<>(attribute); } } diff --git a/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByPropertiesObjectMapper.java b/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByPropertiesObjectMapper.java new file mode 100644 index 000000000..52ff3df94 --- /dev/null +++ b/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByPropertiesObjectMapper.java @@ -0,0 +1,61 @@ +package io.agrest.runtime.processor.update; + +import io.agrest.EntityUpdate; +import io.agrest.ObjectMapper; +import io.agrest.meta.AgAttribute; +import io.agrest.protocol.Exp; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * @since 5.0 + */ +public class ByPropertiesObjectMapper implements ObjectMapper { + + private final AgAttribute[] attributes; + private final int keyMapCapacity; + + public ByPropertiesObjectMapper(AgAttribute[] attributes) { + this.attributes = attributes; + this.keyMapCapacity = 1 + (int) (attributes.length / 0.75); + } + + @Override + public Exp expressionForKey(Object key) { + + // allowing null values in the map, but not the null key map + Map map = (Map) Objects.requireNonNull(key); + + int len = attributes.length; + Exp[] exps = new Exp[len]; + + for (int i = 0; i < len; i++) { + String n = attributes[i].getName(); + exps[i] = Exp.equal(n, map.get(n)); + } + + return Exp.and(exps); + } + + @Override + public Object keyForObject(T object) { + Map key = new HashMap<>(keyMapCapacity); + for (AgAttribute a : attributes) { + key.put(a.getName(), a.getDataReader().read(object)); + } + return key; + } + + @Override + public Object keyForUpdate(EntityUpdate update) { + Map key = new HashMap<>(keyMapCapacity); + for (AgAttribute a : attributes) { + String n = a.getName(); + key.put(n, update.getAttribute(n)); + } + + return key; + } +} diff --git a/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByPropertiesObjectMapperFactory.java b/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByPropertiesObjectMapperFactory.java new file mode 100644 index 000000000..44d6134db --- /dev/null +++ b/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByPropertiesObjectMapperFactory.java @@ -0,0 +1,32 @@ +package io.agrest.runtime.processor.update; + +import io.agrest.ObjectMapper; +import io.agrest.ObjectMapperFactory; +import io.agrest.meta.AgAttribute; +import io.agrest.meta.AgEntity; + +/** + * @since 5.0 + */ +public class ByPropertiesObjectMapperFactory implements ObjectMapperFactory { + + private final String[] properties; + + public ByPropertiesObjectMapperFactory(String... properties) { + this.properties = properties; + } + + @Override + public ObjectMapper createMapper(UpdateContext context) { + AgEntity entity = context.getEntity().getAgEntity(); + + int len = properties.length; + AgAttribute[] attributes = new AgAttribute[len]; + for (int i = 0; i < len; i++) { + // TODO: should we account for "id" attributes here? + attributes[i] = entity.getAttribute(properties[i]); + } + + return new ByPropertiesObjectMapper<>(attributes); + } +} diff --git a/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByKeyObjectMapper.java b/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByPropertyObjectMapper.java similarity index 62% rename from agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByKeyObjectMapper.java rename to agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByPropertyObjectMapper.java index b586c3c9d..03bc72ec0 100644 --- a/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByKeyObjectMapper.java +++ b/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByPropertyObjectMapper.java @@ -2,32 +2,33 @@ import io.agrest.EntityUpdate; import io.agrest.ObjectMapper; -import io.agrest.protocol.Exp; import io.agrest.meta.AgAttribute; +import io.agrest.protocol.Exp; -import java.util.Objects; - -class ByKeyObjectMapper implements ObjectMapper { +/** + * @since 5.0 + */ +public class ByPropertyObjectMapper implements ObjectMapper { private final AgAttribute attribute; - public ByKeyObjectMapper(AgAttribute attribute) { - this.attribute = Objects.requireNonNull(attribute); + public ByPropertyObjectMapper(AgAttribute attribute) { + this.attribute = attribute; } @Override - public Object keyForObject(T object) { - return attribute.getDataReader().read(object); + public Exp expressionForKey(Object key) { + // allowing nulls here + return Exp.equal(attribute.getName(), key); } @Override - public Object keyForUpdate(EntityUpdate u) { - return u.getAttributes().get(attribute.getName()); + public Object keyForObject(T object) { + return attribute.getDataReader().read(object); } @Override - public Exp expressionForKey(Object key) { - // allowing nulls here - return Exp.equal(attribute.getName(), key); + public Object keyForUpdate(EntityUpdate update) { + return update.getAttributes().get(attribute.getName()); } } diff --git a/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByPropertyObjectMapperFactory.java b/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByPropertyObjectMapperFactory.java new file mode 100644 index 000000000..be057aa5a --- /dev/null +++ b/agrest-engine/src/main/java/io/agrest/runtime/processor/update/ByPropertyObjectMapperFactory.java @@ -0,0 +1,28 @@ +package io.agrest.runtime.processor.update; + +import io.agrest.ObjectMapper; +import io.agrest.ObjectMapperFactory; +import io.agrest.meta.AgAttribute; +import io.agrest.meta.AgEntity; + +/** + * @since 5.0 + */ +public class ByPropertyObjectMapperFactory implements ObjectMapperFactory { + + private final String property; + + public ByPropertyObjectMapperFactory(String property) { + this.property = property; + } + + @Override + public ObjectMapper createMapper(UpdateContext context) { + AgEntity entity = context.getEntity().getAgEntity(); + + // TODO: should we account for "id" attributes here? + AgAttribute attribute = entity.getAttribute(property); + + return new ByPropertyObjectMapper<>(attribute); + } +}