Skip to content

Setting most generic class for source of relationship #2527

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 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 @@ -24,6 +24,7 @@
/**
* @author Michael J. Simons
* @author Gerrit Meier
* @author Andreas Berger
* @since 6.0
*/
final class DefaultRelationshipDescription extends Association<Neo4jPersistentProperty> implements RelationshipDescription {
Expand Down Expand Up @@ -55,13 +56,30 @@ final class DefaultRelationshipDescription extends Association<Neo4jPersistentPr
this.relationshipObverse = relationshipObverse;
this.type = type;
this.dynamic = dynamic;
this.source = source;
this.source = getSourceForField(source, type, dynamic, target, direction, relationshipProperties);
this.fieldName = fieldName;
this.target = target;
this.direction = direction;
this.relationshipPropertiesClass = relationshipProperties;
}

private static NodeDescription<?> getSourceForField(NodeDescription<?> source, String type, boolean dynamic,
NodeDescription<?> target, Relationship.Direction direction,
@Nullable NodeDescription<?> relationshipProperties) {
NodeDescription<?> parent = source.getParentNodeDescription();
if (parent == null) {
return source;
}
for (RelationshipDescription relationship : parent.getRelationships()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This solution does not seem to work consistently. I could detect invocations where parent.getRelationships() returns an empty list.

@michael-simons / @meistermeier do you have any idea how to get a consistent behavior?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

making source lazy (Lazy<NodeDescription<?>>) seems to solve this issue

if (Objects.equals(relationship.getTarget(), target) && Objects.equals(relationship.getDirection(), direction)
&& Objects.equals(relationship.isDynamic(), dynamic) && Objects.equals(relationship.getType(), type)
&& Objects.equals(relationship.getRelationshipPropertiesEntity(), relationshipProperties)) {
return getSourceForField(relationship.getSource(), type, dynamic, target, direction, relationshipProperties);
}
}
return source;
}

@Override
public String getType() {
return type;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2011-2022 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.gh2526;

import static org.springframework.data.neo4j.core.schema.Relationship.Direction.*;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;

import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.Relationship;

/**
* @author Andreas Berger
*/
@Node
@Data
@Setter(AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
@SuperBuilder(toBuilder = true)
public class AccountingMeasurementMeta extends MeasurementMeta {

private String formula;

@Relationship(type = "WEIGHTS", direction = OUTGOING) private MeasurementMeta baseMeasurement;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2011-2022 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.gh2526;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.NonFinal;
import lombok.experimental.SuperBuilder;

import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.support.UUIDStringGenerator;

/**
* @author Andreas Berger
*/
@org.springframework.data.neo4j.core.schema.Node
@NonFinal
@Data
@Setter(AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@SuperBuilder(toBuilder = true)
public class BaseNodeEntity {

@Id
@GeneratedValue(UUIDStringGenerator.class)
@EqualsAndHashCode.Include private String nodeId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2011-2022 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.gh2526;

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

/**
* @author Andreas Berger
*/
public interface BaseNodeRepository extends Neo4jRepository<BaseNodeEntity, String> {

<R> R findByNodeId(String nodeIds, Class<R> clazz);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2011-2022 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.gh2526;

import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.With;

import org.springframework.data.annotation.Immutable;
import org.springframework.data.neo4j.core.schema.RelationshipId;
import org.springframework.data.neo4j.core.schema.RelationshipProperties;
import org.springframework.data.neo4j.core.schema.TargetNode;

/**
* @author Andreas Berger
*/
@RelationshipProperties
@Value
@With
@AllArgsConstructor
@Immutable
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class DataPoint {

@RelationshipId Long id;

boolean manual;

@TargetNode
@EqualsAndHashCode.Include Measurand measurand;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright 2011-2022 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.gh2526;

import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.InstanceOfAssertFactories.*;

import java.util.Set;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.neo4j.driver.Driver;
import org.neo4j.driver.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.config.AbstractNeo4jConfig;
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.test.BookmarkCapture;
import org.springframework.data.neo4j.test.Neo4jExtension;
import org.springframework.data.neo4j.test.Neo4jIntegrationTest;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
* @author Andreas Berger
*/
@Neo4jIntegrationTest
public class GH2526IT {

protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport;

@BeforeEach
void setupData(@Autowired Driver driver, @Autowired BookmarkCapture bookmarkCapture) {

try (Session session = driver.session()) {
session.run("MATCH (n) DETACH DELETE n").consume();
session.run("CREATE (o1:Measurand {measurandId: 'o1'})"
+ "CREATE (acc1:AccountingMeasurementMeta:BaseNodeEntity {nodeId: 'acc1'})"
+ "CREATE (o1)-[:IS_MEASURED_BY{ manual: true }]->(acc1)").consume();
bookmarkCapture.seedWith(session.lastBookmark());
}
}

@Test
// GH-2526
void testRichRelationWithInheritance(@Autowired BaseNodeRepository repository) {
Projection m = repository.findByNodeId("acc1", Projection.class);
assertThat(m).isNotNull();
assertThat(m).extracting(Projection::getDataPoints, collection(DataPoint.class))
.extracting(DataPoint::isManual, DataPoint::getMeasurand).contains(tuple(true, new Measurand("o1")));
}

interface Projection {
String getNodeId();

Set<DataPoint> getDataPoints();
}

@Configuration
@EnableTransactionManagement
@EnableNeo4jRepositories
static class Config extends AbstractNeo4jConfig {

@Bean
public BookmarkCapture bookmarkCapture() {
return new BookmarkCapture();
}

@Override
public PlatformTransactionManager transactionManager(Driver driver,
DatabaseSelectionProvider databaseNameProvider) {

BookmarkCapture bookmarkCapture = bookmarkCapture();
return new Neo4jTransactionManager(driver, databaseNameProvider, Neo4jBookmarkManager.create(bookmarkCapture));
}

@Bean
public Driver driver() {

return neo4jConnectionSupport.getDriver();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2011-2022 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.gh2526;

import lombok.AllArgsConstructor;
import lombok.Value;

import org.springframework.data.annotation.Immutable;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;

/**
* @author Andreas Berger
*/
@Node
@Value
@AllArgsConstructor
@Immutable
public class Measurand {

@Id String measurandId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2011-2022 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.gh2526;

import static org.springframework.data.neo4j.core.schema.Relationship.Direction.*;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;

import java.util.Set;

import org.springframework.data.neo4j.core.schema.Relationship;

/**
* @author Andreas Berger
*/
@org.springframework.data.neo4j.core.schema.Node
@Data
@Setter(AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
@SuperBuilder(toBuilder = true)
public class MeasurementMeta extends BaseNodeEntity {

@Relationship(type = "IS_MEASURED_BY", direction = INCOMING) private Set<DataPoint> dataPoints;
}