Skip to content

Commit

Permalink
feat: Introduce ConverterInfo so that individual converters can get…
Browse files Browse the repository at this point in the history
… more info about the fields to be converted.

Closes #1190.
  • Loading branch information
michael-simons committed Nov 8, 2024
1 parent dcdabb9 commit ce0a0ab
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 14 deletions.
23 changes: 15 additions & 8 deletions core/src/main/java/org/neo4j/ogm/metadata/FieldInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import org.neo4j.ogm.session.Utils;
import org.neo4j.ogm.typeconversion.AttributeConverter;
import org.neo4j.ogm.typeconversion.CompositeAttributeConverter;
import org.neo4j.ogm.typeconversion.ConverterInfo;
import org.neo4j.ogm.typeconversion.MapCompositeConverter;
import org.neo4j.ogm.utils.RelationshipUtils;
import org.neo4j.ogm.utils.StringUtils;
Expand Down Expand Up @@ -114,7 +115,7 @@ public class FieldInfo {
this.annotations = annotations;
this.isSupportedNativeType = isSupportedNativeType.test(DescriptorMappings.getType(getTypeDescriptor()));
if (!this.annotations.isEmpty()) {
Object converter = getAnnotations().getConverter(this.fieldType);
Object converter = getAnnotations().getConverter(new ConverterInfo(field, this.fieldType, this.property0()));
if (converter instanceof AttributeConverter) {
setPropertyConverter((AttributeConverter<?, ?>) converter);
} else if (converter instanceof CompositeAttributeConverter) {
Expand Down Expand Up @@ -163,17 +164,23 @@ public String getName() {
// should these two methods be on PropertyReader, RelationshipReader respectively?
public String property() {
if (persistableAsProperty()) {
if (annotations != null) {
AnnotationInfo propertyAnnotation = annotations.get(Property.class);
if (propertyAnnotation != null) {
return propertyAnnotation.get(Property.NAME, getName());
}
}
return getName();
return property0();
}
return null;
}

// extracted here, so that it can be used in the constructor before the (composite) converters are
// assigned and #persistableAsProperty becomes possibly true
private String property0() {
if (annotations != null) {
AnnotationInfo propertyAnnotation = annotations.get(Property.class);
if (propertyAnnotation != null) {
return propertyAnnotation.get(Property.NAME, getName());
}
}
return getName();
}

public String relationship() {
Optional<String> localRelationshipType = relationshipType;
if (!localRelationshipType.isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.neo4j.ogm.annotation.typeconversion.NumberString;
import org.neo4j.ogm.config.Configuration;
import org.neo4j.ogm.exception.core.MappingException;
import org.neo4j.ogm.typeconversion.ConverterInfo;
import org.neo4j.ogm.typeconversion.DateLongConverter;
import org.neo4j.ogm.typeconversion.DateStringConverter;
import org.neo4j.ogm.typeconversion.EnumStringConverter;
Expand Down Expand Up @@ -74,7 +75,9 @@ public boolean isEmpty() {
return annotations.isEmpty();
}

Object getConverter(Class<?> fieldType) {
Object getConverter(ConverterInfo converterInfo) {

Class<?> fieldType = converterInfo.fieldType();

// try to get a custom type converter
AnnotationInfo customType = get(Convert.class);
Expand All @@ -86,6 +89,11 @@ Object getConverter(Class<?> fieldType) {

try {
Class<?> clazz = Class.forName(classDescriptor, false, Configuration.getDefaultClassLoader());
try {
return clazz.getDeclaredConstructor(ConverterInfo.class).newInstance(converterInfo);
} catch (NoSuchMethodException e) {
// Well, ok…
}
return clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
Expand Down
32 changes: 32 additions & 0 deletions core/src/main/java/org/neo4j/ogm/typeconversion/ConverterInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2002-2024 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed 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.neo4j.ogm.typeconversion;

import java.lang.reflect.Field;

/**
* Converters registered via {@link org.neo4j.ogm.annotation.typeconversion.Convert @Convert} can require this information
* via a non-default, public constructor taking in one single {@link ConverterInfo argument}.
*
* @param field The field on which the converter was registered
* @param fieldType The field type that OGM does assume, might be different of what the raw field would give you
* @param defaultPropertyName The property that would be assumed for this field by OGM
*/
public record ConverterInfo(Field field, Class<?> fieldType, String defaultPropertyName) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2002-2024 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed 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.neo4j.ogm.domain.gh1190;

import java.util.UUID;

import org.neo4j.ogm.annotation.GeneratedValue;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.typeconversion.Convert;
import org.neo4j.ogm.id.UuidStrategy;

@NodeEntity
public class MyEntity {
@Id
@GeneratedValue(strategy = UuidStrategy.class)
@Convert(Uuid2LongConverter.class)
private UUID uuid;

@Convert(Uuid2LongConverter.class)
private UUID otherUuid;

public UUID getOtherUuid() {
return otherUuid;
}

public void setOtherUuid(UUID otherUuid) {
this.otherUuid = otherUuid;
}

public UUID getUuid() {
return uuid;
}

public void setUuid(UUID uuid) {
this.uuid = uuid;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2002-2024 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed 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.neo4j.ogm.domain.gh1190;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.neo4j.ogm.typeconversion.CompositeAttributeConverter;
import org.neo4j.ogm.typeconversion.ConverterInfo;

public class Uuid2LongConverter implements CompositeAttributeConverter<UUID> {

private final ConverterInfo converterInfo;
private final String fieldName;

public Uuid2LongConverter(ConverterInfo converterInfo) {
this.converterInfo = converterInfo;
this.fieldName = converterInfo.field().getName();
}

@Override
public Map<String, ?> toGraphProperties(UUID value) {

var properties = new HashMap<String, Long>();
if (value != null) {
properties.put(fieldName + "_most", value.getMostSignificantBits());
properties.put(fieldName + "_least", value.getLeastSignificantBits());
}

return properties;
}

@Override
public UUID toEntityAttribute(Map<String, ?> value) {
var most = (Long) value.get(fieldName + "_most");
var least = (Long) value.get(fieldName + "_least");

if (most != null && least != null) {
return new UUID(most, least);
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
Expand All @@ -42,6 +43,7 @@
import org.neo4j.driver.Values;
import org.neo4j.ogm.domain.convertible.numbers.Account;
import org.neo4j.ogm.domain.convertible.numbers.Foobar;
import org.neo4j.ogm.domain.gh1190.MyEntity;
import org.neo4j.ogm.domain.music.Album;
import org.neo4j.ogm.domain.music.Artist;
import org.neo4j.ogm.domain.music.TimeHolder;
Expand All @@ -62,7 +64,7 @@ public class TypeConversionIntegrationTest extends TestContainersTestBase {
@BeforeAll
public static void oneTimeSetUp() {
sessionFactory = new SessionFactory(getDriver(), "org.neo4j.ogm.domain.music",
"org.neo4j.ogm.domain.convertible.numbers");
"org.neo4j.ogm.domain.convertible.numbers", "org.neo4j.ogm.domain.gh1190");
}

@BeforeEach
Expand Down Expand Up @@ -187,7 +189,8 @@ void savedTimestampAsParameterToSimpleCreateIsReadBackAsIs() {
verify(timeHolder.getGraphId(), someTime, someLocalDateTime, someLocalDate);
}

private void verify(Long graphId, OffsetDateTime expectedOffsetDateTime, LocalDateTime expectedLocalDateTime, LocalDate expectedLocalDate) {
private void verify(Long graphId, OffsetDateTime expectedOffsetDateTime, LocalDateTime expectedLocalDateTime,
LocalDate expectedLocalDate) {

// opening a new Session to prevent shared data
TimeHolder reloaded = sessionFactory.openSession().load(TimeHolder.class, graphId);
Expand All @@ -200,7 +203,7 @@ private void verify(Long graphId, OffsetDateTime expectedOffsetDateTime, LocalDa
String localDateTimeValue = null;
String localDateValue = null;

try(Driver driver = getNewBoltConnection()) {
try (Driver driver = getNewBoltConnection()) {
try (org.neo4j.driver.Session driverSession = driver.session()) {
Record record = driverSession
.run("MATCH (n) WHERE id(n) = $id RETURN n", Values.parameters("id", graphId)).single();
Expand Down Expand Up @@ -243,8 +246,8 @@ void converterShouldBeAppliedBothWaysCorrectly() {
localSession = sessionFactory.openSession();

Iterable<Map<String, Object>> result = localSession.query(""
+ "MATCH (a:Account) WHERE id(a) = $id "
+ "RETURN a.valueA AS va, a.valueB as vb, a.listOfFoobars as listOfFoobars, a.anotherListOfFoobars as anotherListOfFoobars, a.foobar as foobar, a.notConverter as notConverter",
+ "MATCH (a:Account) WHERE id(a) = $id "
+ "RETURN a.valueA AS va, a.valueB as vb, a.listOfFoobars as listOfFoobars, a.anotherListOfFoobars as anotherListOfFoobars, a.foobar as foobar, a.notConverter as notConverter",
Collections.singletonMap("id", account.getId())
);
assertThat(result).hasSize(1);
Expand All @@ -269,4 +272,28 @@ void converterShouldBeAppliedBothWaysCorrectly() {
.containsExactlyInAnyOrder("C", "D");
assertThat(account.getFoobar().getValue()).isEqualTo("Foobar");
}

@Test // GH-1190
void convertersShouldGetAllTheInfo() {

var other = UUID.randomUUID();

var myEntity = new MyEntity();
myEntity.setOtherUuid(other);

session.save(myEntity);
session.clear();

var loaded = session.load(MyEntity.class, myEntity.getUuid());
assertThat(loaded).isNotNull();
assertThat(loaded.getOtherUuid()).isEqualTo(other);

try (Driver driver = getNewBoltConnection()) {
try (org.neo4j.driver.Session driverSession = driver.session()) {
Map<String, Object> record = driverSession
.run("MATCH (n:MyEntity) RETURN n").single().get("n").asMap();
assertThat(record).containsOnlyKeys("uuid_most", "uuid_least", "otherUuid_most", "otherUuid_least");
}
}
}
}

0 comments on commit ce0a0ab

Please sign in to comment.