Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand Down Expand Up @@ -79,6 +80,7 @@
*
* @author Michael J. Simons
* @author Gerrit Meier
* @author Francesco Chicchiriccò
* @since 6.0
*/
@API(status = API.Status.STABLE, since = "6.0")
Expand Down Expand Up @@ -444,6 +446,7 @@ private <T extends Neo4jPersistentPropertyConverterFactory> T getOrCreateConvert
}));
}

@SuppressWarnings("rawtypes")
@Nullable Neo4jPersistentPropertyConverter<?> getOptionalCustomConversionsFor(Neo4jPersistentProperty persistentProperty) {

// Is the annotation present at all?
Expand All @@ -468,21 +471,12 @@ private <T extends Neo4jPersistentPropertyConverterFactory> T getOrCreateConvert
else {
converterClass = customConverter.getClass();
}
Map<String, Type> typeVariableMap = (converterClass != null)
? GenericTypeResolver.getTypeVariableMap(converterClass)
.entrySet()
.stream()
.collect(Collectors.toMap(e -> e.getKey().getName(), Map.Entry::getValue))
: Map.of();
Type propertyType = null;
if (typeVariableMap.containsKey("T")) {
propertyType = typeVariableMap.get("T");
}
else if (typeVariableMap.containsKey("P")) {
propertyType = typeVariableMap.get("P");
}
forCollection = propertyType instanceof ParameterizedType
&& persistentProperty.getType().equals(((ParameterizedType) propertyType).getRawType());
Map<TypeVariable, Type> typeVariableMap = (converterClass != null)
? GenericTypeResolver.getTypeVariableMap(converterClass) : Map.of();
forCollection = typeVariableMap.values()
.stream()
.anyMatch(propertyType -> propertyType instanceof ParameterizedType
&& persistentProperty.getType().equals(((ParameterizedType) propertyType).getRawType()));
}

return new NullSafeNeo4jPersistentPropertyConverter<>(customConverter, persistentProperty.isComposite(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@
import org.springframework.data.neo4j.integration.issues.gh3092.DynamicLabelChild;
import org.springframework.data.neo4j.integration.issues.gh3092.DynamicLabelChildRepository;
import org.springframework.data.neo4j.integration.issues.gh3092.DynamicLabelRoot;
import org.springframework.data.neo4j.integration.issues.gh3109.NodeWithValueList;
import org.springframework.data.neo4j.integration.issues.gh3109.NodeWithValueListRepository;
import org.springframework.data.neo4j.integration.issues.qbe.A;
import org.springframework.data.neo4j.integration.issues.qbe.ARepository;
import org.springframework.data.neo4j.integration.issues.qbe.B;
Expand All @@ -228,6 +230,7 @@

/**
* @author Michael J. Simons
* @author Francesco Chicchiriccò
*/
@Neo4jIntegrationTest
@DisplayNameGeneration(SimpleDisplayNameGeneratorWithTags.class)
Expand Down Expand Up @@ -1899,6 +1902,17 @@ void instantiateCorrectEntitiesIfDynamicLabelsAreUsed(@Autowired VehicleReposito

}

@Tag("GH-3109")
@Test
void customConverterForGenericCollection(@Autowired NodeWithValueListRepository repository) {
NodeWithValueList entity = new NodeWithValueList();
entity.getValueList().add("value1");
entity.getValueList().add("value2");

entity = repository.save(entity);
assertThat(entity.getValueList()).containsExactly("value1", "value2");
}

@Configuration
@EnableTransactionManagement
@EnableNeo4jRepositories(namedQueriesLocation = "more-custom-queries.properties")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2011-present the original author or authors.
*
* 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
*
* 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.springframework.data.neo4j.integration.issues.gh3109;

import java.util.ArrayList;
import java.util.List;

import org.springframework.data.neo4j.core.convert.ConvertWith;
import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;

/**
* @author Francesco Chicchiriccò
*/
@Node
public class NodeWithValueList {

@Id
@GeneratedValue
private Long id;

@ConvertWith(converter = StringListConverter.class)
private List<String> valueList = new ArrayList<>();

public Long getId() {
return this.id;
}

public List<String> getValueList() {
return this.valueList;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2011-present the original author or authors.
*
* 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
*
* 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.springframework.data.neo4j.integration.issues.gh3109;

import org.springframework.data.neo4j.repository.Neo4jRepository;

/**
* @author Francesco Chicchiriccò
*/
public interface NodeWithValueListRepository extends Neo4jRepository<NodeWithValueList, Long> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2011-present the original author or authors.
*
* 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
*
* 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.springframework.data.neo4j.integration.issues.gh3109;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.neo4j.driver.Value;
import org.neo4j.driver.Values;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.data.neo4j.core.convert.Neo4jPersistentPropertyConverter;

/**
* @author Francesco Chicchiriccò
*/
abstract class SerializableListConverter<T extends Serializable> implements Neo4jPersistentPropertyConverter<List<T>> {

private static final Logger LOG = LoggerFactory.getLogger(SerializableListConverter.class);

private static final JsonMapper MAPPER = JsonMapper.builder().findAndAddModules().build();

private static String serialize(final Object object) {
String result = null;

try {
result = MAPPER.writeValueAsString(object);
}
catch (Exception ex) {
LOG.error("During serialization", ex);
}

return result;
}

private static <O extends Object> O deserialize(final String serialized, final TypeReference<O> reference) {
O result = null;

try {
result = MAPPER.readValue(serialized, reference);
}
catch (Exception ex) {
LOG.error("During deserialization", ex);
}

return result;
}

protected abstract TypeReference<List<T>> typeRef();

@Override
public Value write(final List<T> source) {
return Optional.ofNullable(source)
.map(SerializableListConverter::serialize)
.map(Values::value)
.orElse(Values.value(List.of()));
}

@Override
public List<T> read(final Value source) {
return Optional.ofNullable(source)
.map(data -> deserialize(source.asString(), typeRef()))
.orElseGet(ArrayList::new);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2011-present the original author or authors.
*
* 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
*
* 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.springframework.data.neo4j.integration.issues.gh3109;

import java.util.List;

import com.fasterxml.jackson.core.type.TypeReference;

/**
* @author Francesco Chicchiriccò
*/
public class StringListConverter extends SerializableListConverter<String> {

protected static final TypeReference<List<String>> TYPEREF = new TypeReference<List<String>>() {
};

@Override
protected TypeReference<List<String>> typeRef() {
return TYPEREF;
}

}
Loading