/*
* Copyright (c) [2011-2017] "Pivotal Software, Inc." / "Neo Technology" / "Graph Aware Ltd."
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product may include a number of subcomponents with
* separate copyright notices and license terms. Your use of the source
* code for these subcomponents is subject to the terms and
* conditions of the subcomponent's license, as noted in the LICENSE file.
*
*/
package org.springframework.data.neo4j.integration.conversion;
import static org.junit.Assert.*;
import java.lang.annotation.ElementType;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.Result;
import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory;
import org.neo4j.ogm.testutil.MultiDriverTestClass;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.neo4j.conversion.MetaDataDrivenConversionService;
import org.springframework.data.neo4j.integration.conversion.domain.JavaElement;
import org.springframework.data.neo4j.integration.conversion.domain.MonetaryAmount;
import org.springframework.data.neo4j.integration.conversion.domain.PensionPlan;
import org.springframework.data.neo4j.integration.conversion.domain.SiteMember;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.transaction.Neo4jTransactionManager;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.support.TransactionTemplate;
/**
* @author Adam George
* @author Luanne Misquitta
* @author Vince Bickers
* @author Mark Angrish
* @author Mark Paluch
* @author Jens Schauder
* @see DATAGRAPH-624
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {ConversionServiceTests.ConversionServicePersistenceContext.class})
public class ConversionServiceTests extends MultiDriverTestClass {
@Autowired
PlatformTransactionManager platformTransactionManager;
@Autowired
private PensionRepository pensionRepository;
@Autowired
private JavaElementRepository javaElementRepository;
@Autowired
private SiteMemberRepository siteMemberRepository;
@Autowired
private GenericConversionService conversionService;
@Autowired Session session;
private TransactionTemplate transactionTemplate;
@Before
public void setUp() {
transactionTemplate = new TransactionTemplate(platformTransactionManager);
getGraphDatabaseService().execute("MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE r, n");
}
/**
* This should work by virtue of the fact that there's an OGM-level converter defined on a class we've scanned.
*/
@Test
public void shouldBeAbleToConvertBetweenBytesAndBase64EncodedDataViaSpringConversionService() {
String base64Representation = "YmNkZWY=";
byte[] binaryData = new byte[]{98, 99, 100, 101, 102};
assertTrue(this.conversionService.canConvert(byte[].class, String.class));
assertEquals(base64Representation, this.conversionService.convert(binaryData, String.class));
assertTrue(this.conversionService.canConvert(String.class, byte[].class));
assertTrue(Arrays.equals(binaryData, this.conversionService.convert(base64Representation, byte[].class)));
}
@Test
public void shouldConvertBase64StringOutOfGraphDatabaseBackIntoByteArray() {
Result rs = getGraphDatabaseService().execute(
"CREATE (u:SiteMember {profilePictureData:'MTIzNDU2Nzg5'}) RETURN id(u) AS userId");
Long userId = (Long) rs.columnAs("userId").next();
byte[] expectedData = "123456789".getBytes();
SiteMember siteMember = this.siteMemberRepository.findById(userId).get();
assertTrue("The data wasn't converted correctly", Arrays.equals(expectedData, siteMember.getProfilePictureData()));
}
@Test
public void shouldConvertFieldsUsingSpringConvertersAddedDirectlyToConversionService() {
PensionPlan pensionToSave = transactionTemplate.execute(status -> {
conversionService.addConverter(new SpringMonetaryAmountToIntegerConverter());
conversionService.addConverter(new SpringIntegerToMonetaryAmountConverter());
conversionService.addConverter(new SpringMonetaryAmountToLongConverter());
conversionService.addConverter(new SpringLongToMonetaryAmountConverter());
PensionPlan pensionToSave1 = new PensionPlan(new MonetaryAmount(16472, 81), "Tightfist Asset Management Ltd");
pensionRepository.save(pensionToSave1);
return pensionToSave1;
});
Result result = getGraphDatabaseService().execute("MATCH (p:PensionPlan) RETURN p.fundValue AS fv");
assertTrue("Nothing was saved", result.hasNext());
assertEquals("The amount wasn't converted and persisted correctly", "1647281", String.valueOf(result.next().get("fv")));
result.close();
PensionPlan reloadedPension = this.pensionRepository.findById(pensionToSave.getPensionPlanId()).get();
assertEquals("The amount was converted incorrectly", pensionToSave.getFundValue(), reloadedPension.getFundValue());
}
/**
* If target graph type is set to Integer but we have a converter for a Number then it should still save to the graph.
*/
@Test
public void shouldConvertFieldsUsingAnAvailableSupertypeConverterIfExactTypesDoNotMatch() {
this.conversionService.addConverterFactory(new SpringMonetaryAmountToNumberConverterFactory());
//this.conversionService.addConverter(new SpringIntegerToMonetaryAmountConverter());
PensionPlan pension = new PensionPlan(new MonetaryAmount(20_000, 00), "Ashes Assets LLP");
this.pensionRepository.save(pension);
Result result = getGraphDatabaseService()
.execute("MATCH (p:PensionPlan) RETURN p.fundValue AS fv");
assertTrue("Nothing was saved", result.hasNext());
assertEquals("The amount wasn't converted and persisted correctly", "2000000", String.valueOf(result.next().get("fv")));
result.close();
}
/**
* This should fix DATAGRAPH-659 too.
*/
@Test
public void shouldOperateWithDefaultSpringConvertersToConvertObjectTypesNotInMetaData() {
DefaultConversionService.addDefaultConverters(this.conversionService);
JavaElement method = new JavaElement();
method.setName("toString");
method.setElementType(ElementType.METHOD);
this.javaElementRepository.save(method);
Result result = getGraphDatabaseService()
.execute("MATCH (e:JavaElement) RETURN e.elementType AS type");
assertTrue("Nothing was saved", result.hasNext());
assertEquals("The element type wasn't converted and persisted correctly", "METHOD", result.next().get("type"));
result.close();
JavaElement loadedObject = this.javaElementRepository.findAll().iterator().next();
assertEquals("The element type wasn't loaded and converted correctly", ElementType.METHOD, loadedObject.getElementType());
}
@Test(expected = ConverterNotFoundException.class)
public void shouldThrowExceptionIfSuitableConverterIsNotFound() {
this.conversionService.addConverterFactory(new SpringMonetaryAmountToNumberConverterFactory());
PensionPlan pension = new PensionPlan(new MonetaryAmount(20_000, 00), "Ashes Assets LLP");
pension.setJavaElement(new JavaElement());
this.pensionRepository.save(pension);
}
@Test
public void shouldUseSpecifiedAttributeConverterInsteadOfSprings() {
//We're registering Spring converters as well
this.conversionService.addConverter(new SpringIntegerToByteArrayConverter());
this.conversionService.addConverter(new SpringByteArrayToIntegerConverter());
String base64Representation = "YmNkZWY=";
byte[] binaryData = new byte[]{98, 99, 100, 101, 102};
assertTrue(this.conversionService.canConvert(byte[].class, String.class));
assertEquals(base64Representation, this.conversionService.convert(binaryData, String.class));
assertTrue(this.conversionService.canConvert(String.class, byte[].class));
assertTrue(Arrays.equals(binaryData, this.conversionService.convert(base64Representation, byte[].class)));
SiteMember siteMember = new SiteMember();
siteMember.setProfilePictureData(binaryData);
this.siteMemberRepository.save(siteMember);
siteMember = session.loadAll(SiteMember.class).iterator().next();
assertArrayEquals(binaryData, siteMember.getProfilePictureData());
}
@Test
public void shouldUseDefaultAttributeConverterInsteadOfSprings() {
//We're registering Spring converters which should not override the default ogm BigInteger converter
this.conversionService.addConverter(new SpringBigIntegerToBooleanConverter());
this.conversionService.addConverter(new SpringBooleanToBigIntegerConverter());
byte[] binaryData = new byte[]{98, 99, 100, 101, 102};
SiteMember siteMember = new SiteMember();
siteMember.setProfilePictureData(binaryData);
siteMember.setYears(BigInteger.valueOf(50));
this.siteMemberRepository.save(siteMember);
siteMember = session.loadAll(SiteMember.class).iterator().next();
assertArrayEquals(binaryData, siteMember.getProfilePictureData());
assertEquals(50, siteMember.getYears().intValue());
}
/**
* @see DATAGRAPH-659
*/
@Test
public void shouldRecognizeJavaEnums() {
SiteMember siteMember = new SiteMember();
siteMember.setRoundingModes(Arrays.asList(RoundingMode.DOWN, RoundingMode.FLOOR));
this.siteMemberRepository.save(siteMember);
siteMember = session.loadAll(SiteMember.class).iterator().next();
assertEquals(2, siteMember.getRoundingModes().size());
assertTrue(siteMember.getRoundingModes().contains(RoundingMode.DOWN));
assertTrue(siteMember.getRoundingModes().contains(RoundingMode.FLOOR));
}
@Configuration
@EnableNeo4jRepositories(basePackageClasses = {SiteMemberRepository.class, PensionRepository.class, JavaElementRepository.class})
@EnableTransactionManagement
static class ConversionServicePersistenceContext {
@Bean
public ConversionService conversionService() {
return new MetaDataDrivenConversionService(sessionFactory().metaData());
}
@Bean
public PlatformTransactionManager transactionManager() {
return new Neo4jTransactionManager(sessionFactory());
}
@Bean
public SessionFactory sessionFactory() {
return new SessionFactory(getBaseConfiguration().build(), "org.springframework.data.neo4j.integration.conversion.domain");
}
}
}