Skip to content
Draft
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
2 changes: 1 addition & 1 deletion backend-core-business-impl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>com.flowingcode.backend-core</groupId>
<artifactId>backend-core</artifactId>
<version>1.1.1-SNAPSHOT</version>
<version>1.2.0-SNAPSHOT</version>
</parent>

<artifactId>backend-core-business-impl</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion backend-core-business-spring-impl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>com.flowingcode.backend-core</groupId>
<artifactId>backend-core</artifactId>
<version>1.1.1-SNAPSHOT</version>
<version>1.2.0-SNAPSHOT</version>
</parent>

<artifactId>backend-core-business-spring-impl</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion backend-core-business/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>com.flowingcode.backend-core</groupId>
<artifactId>backend-core</artifactId>
<version>1.1.1-SNAPSHOT</version>
<version>1.2.0-SNAPSHOT</version>
</parent>

<artifactId>backend-core-business</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion backend-core-data-impl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>com.flowingcode.backend-core</groupId>
<artifactId>backend-core</artifactId>
<version>1.1.1-SNAPSHOT</version>
<version>1.2.0-SNAPSHOT</version>
</parent>

<artifactId>backend-core-data-impl</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* #%L
* Commons Backend - Data Access Layer Implementations
* %%
* Copyright (C) 2020 - 2021 Flowing Code
* Copyright (C) 2020 - 2026 Flowing Code
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -31,6 +31,7 @@
import com.flowingcode.backendcore.model.constraints.AttributeLikeConstraint;
import com.flowingcode.backendcore.model.constraints.AttributeNullConstraint;
import com.flowingcode.backendcore.model.constraints.AttributeRelationalConstraint;
import com.flowingcode.backendcore.model.constraints.DisjunctionConstraint;
import com.flowingcode.backendcore.model.constraints.NegatedConstraint;
import com.flowingcode.backendcore.model.constraints.RelationalConstraint;

Expand All @@ -44,6 +45,11 @@
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

/**
* JPA/Criteria implementation of {@link ConstraintTransformer}.
*
* <p><b>Instances are not thread-safe.</b> A new instance must be created for each query.
*/
@RequiredArgsConstructor
public class ConstraintTransformerJpaImpl extends ConstraintTransformer<Predicate> {

Expand Down Expand Up @@ -80,10 +86,12 @@ private From<?,?> join(From<?,?> root, String[] path) {
return from;
}

private JoinType currentJoinType = JoinType.INNER;

@SuppressWarnings("rawtypes")
private From<?,?> join(From<?,?> source, String attributeName) {
Optional<Join> existingJoin = source.getJoins().stream().filter(join->join.getAttribute().getName().equals(attributeName)).map(join->(Join)join).findFirst();
return existingJoin.orElseGet(()->source.join(attributeName, JoinType.INNER));
return existingJoin.orElseGet(()->source.join(attributeName, currentJoinType));
}

private static Class<?> boxed(Class<?> type) {
Expand Down Expand Up @@ -166,4 +174,18 @@ protected Predicate transformNullConstraint(AttributeNullConstraint c) {
protected Predicate transformILikeConstraint(AttributeILikeConstraint c) {
return criteriaBuilder.like(criteriaBuilder.lower(getExpression(c, String.class)), c.getPattern().toLowerCase());
}

@Override
protected Predicate transformDisjunctionConstraint(DisjunctionConstraint c) {
JoinType saved = currentJoinType;
currentJoinType = JoinType.LEFT;
try {
Predicate[] predicates = c.getConstraints().stream()
.map(this::apply)
.toArray(Predicate[]::new);
return criteriaBuilder.or(predicates);
} finally {
currentJoinType = saved;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* #%L
* Commons Backend - Data Access Layer Implementations
* %%
* Copyright (C) 2020 - 2021 Flowing Code
* Copyright (C) 2020 - 2026 Flowing Code
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -147,6 +147,27 @@ void testFilterByState() {
assertEquals(5, count);
}

@Test
void testFilterWithOrConstraint() {
// OR of both city ids must match all 10 persons that have a city assigned
PersonFilter pf = new PersonFilter();
pf.addConstraint(
ConstraintBuilder.of("city", "id").equal(cities.get(0).getId())
.or(ConstraintBuilder.of("city", "id").equal(cities.get(1).getId())));
assertEquals(10, dao.count(pf));
}

@Test
void testFilterWithOrConstraintPartialMatch() {
// city.id branch matches 5 persons; id branch matches persistedPerson (who has no city).
// LEFT JOIN on city must keep persistedPerson in the result set so the OR can match them.
PersonFilter pf = new PersonFilter();
pf.addConstraint(
ConstraintBuilder.of("city", "id").equal(cities.get(0).getId())
.or(ConstraintBuilder.of("id").equal(persistedPerson.getId())));
assertEquals(6, dao.count(pf));
}

@Test
@Disabled
void testDelete() {
Expand Down
2 changes: 1 addition & 1 deletion backend-core-data/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>com.flowingcode.backend-core</groupId>
<artifactId>backend-core</artifactId>
<version>1.1.1-SNAPSHOT</version>
<version>1.2.0-SNAPSHOT</version>
</parent>

<artifactId>backend-core-data</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion backend-core-model/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>com.flowingcode.backend-core</groupId>
<artifactId>backend-core</artifactId>
<version>1.1.1-SNAPSHOT</version>
<version>1.2.0-SNAPSHOT</version>
</parent>

<artifactId>backend-core-model</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* #%L
* Commons Backend - Model
* %%
* Copyright (C) 2020 - 2021 Flowing Code
* Copyright (C) 2020 - 2026 Flowing Code
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,12 +19,32 @@
*/
package com.flowingcode.backendcore.model;

import com.flowingcode.backendcore.model.constraints.DisjunctionConstraint;
import com.flowingcode.backendcore.model.constraints.NegatedConstraint;

public interface Constraint {

default Constraint not() {
return new NegatedConstraint(this);
}


/**
* Returns a constraint that is satisfied when this constraint or any of the given constraints is
* satisfied (logical OR).
*
* @param first the first additional constraint
* @param rest optional additional constraints
* @return a {@link DisjunctionConstraint} combining this and the given constraints
*/
default Constraint or(Constraint first, Constraint... rest) {
return DisjunctionConstraint.of(this, prepend(first, rest));
}

private static Constraint[] prepend(Constraint first, Constraint[] rest) {
Constraint[] result = new Constraint[1 + rest.length];
result[0] = first;
System.arraycopy(rest, 0, result, 1, rest.length);
return result;
Comment on lines +39 to +47
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle explicit null varargs defensively in or(...).

Line 43 assumes rest is non-null. A call like or(x, (Constraint[]) null) throws an NPE in prepend(...) before constraint-level null validation runs.

Suggested fix
 import com.flowingcode.backendcore.model.constraints.DisjunctionConstraint;
 import com.flowingcode.backendcore.model.constraints.NegatedConstraint;
+import java.util.Objects;
@@
   default Constraint or(Constraint first, Constraint... rest) {
-    return DisjunctionConstraint.of(this, prepend(first, rest));
+    return DisjunctionConstraint.of(this, prepend(first, Objects.requireNonNull(rest, "rest must not be null")));
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
default Constraint or(Constraint first, Constraint... rest) {
return DisjunctionConstraint.of(this, prepend(first, rest));
}
private static Constraint[] prepend(Constraint first, Constraint[] rest) {
Constraint[] result = new Constraint[1 + rest.length];
result[0] = first;
System.arraycopy(rest, 0, result, 1, rest.length);
return result;
default Constraint or(Constraint first, Constraint... rest) {
return DisjunctionConstraint.of(this, prepend(first, Objects.requireNonNull(rest, "rest must not be null")));
}
private static Constraint[] prepend(Constraint first, Constraint[] rest) {
Constraint[] result = new Constraint[1 + rest.length];
result[0] = first;
System.arraycopy(rest, 0, result, 1, rest.length);
return result;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend-core-model/src/main/java/com/flowingcode/backendcore/model/Constraint.java`
around lines 39 - 47, The or(Constraint first, Constraint... rest) path assumes
rest is non-null and will NPE in prepend(...) for calls like or(x,
(Constraint[]) null); modify prepend(Constraint first, Constraint[] rest) (or
add a null-guard in or()) to treat a null rest as an empty array before creating
the result array and calling System.arraycopy — e.g., normalize rest to an empty
Constraint[] when null so the array creation and copy logic work safely and
subsequent constraint-level null validations can run.

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* #%L
* Commons Backend - Model
* %%
* Copyright (C) 2020 - 2021 Flowing Code
* Copyright (C) 2020 - 2026 Flowing Code
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -28,6 +28,7 @@
import com.flowingcode.backendcore.model.constraints.AttributeLikeConstraint;
import com.flowingcode.backendcore.model.constraints.AttributeNullConstraint;
import com.flowingcode.backendcore.model.constraints.AttributeRelationalConstraint;
import com.flowingcode.backendcore.model.constraints.DisjunctionConstraint;
import com.flowingcode.backendcore.model.constraints.NegatedConstraint;

/**
Expand Down Expand Up @@ -80,6 +81,10 @@
return transformILikeConstraint((AttributeILikeConstraint) c);
}

if (c instanceof DisjunctionConstraint) {

Check warning on line 84 in backend-core-model/src/main/java/com/flowingcode/backendcore/model/ConstraintTransformer.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this instanceof check and cast with 'instanceof DisjunctionConstraint disjunctionconstraint'

See more on https://sonarcloud.io/project/issues?id=FlowingCode_backend-core&issues=AZz3-zsR2UZ2OVrkOAs-&open=AZz3-zsR2UZ2OVrkOAs-&pullRequest=108
return transformDisjunctionConstraint((DisjunctionConstraint) c);
}

return null;
}

Expand Down Expand Up @@ -125,4 +130,10 @@
protected T transformILikeConstraint(AttributeILikeConstraint c) {
return null;
}

/** Return an implementation-specific representation of a {@code DisjunctionConstraint} constraint.
* @return an implementation-specific representation of the constraint, or {@code null} if it cannot be transformed.*/
protected T transformDisjunctionConstraint(DisjunctionConstraint c) {

Check warning on line 136 in backend-core-model/src/main/java/com/flowingcode/backendcore/model/ConstraintTransformer.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused method parameter "c".

See more on https://sonarcloud.io/project/issues?id=FlowingCode_backend-core&issues=AZz3-zsR2UZ2OVrkOAs_&open=AZz3-zsR2UZ2OVrkOAs_&pullRequest=108
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*-
* #%L
* Commons Backend - Model
* %%
* Copyright (C) 2020 - 2026 Flowing Code
* %%
* 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.
* #L%
*/
package com.flowingcode.backendcore.model.constraints;

import com.flowingcode.backendcore.model.Constraint;
import java.util.List;
import java.util.Objects;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;

/** A constraint that is satisfied when any of its member constraints is satisfied (logical OR). */
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public final class DisjunctionConstraint implements Constraint {

@NonNull List<Constraint> constraints;

public static DisjunctionConstraint of(Constraint first, Constraint... rest) {
List<Constraint> list = new java.util.ArrayList<>();
list.add(Objects.requireNonNull(first, "constraint must not be null"));
for (Constraint c : rest) {
list.add(Objects.requireNonNull(c, "constraint must not be null"));
}
Comment on lines +39 to +44
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add an explicit null check for varargs container.

of(first, (Constraint[]) null) currently throws an implicit NPE in the loop before element-level checks run. Please validate rest itself first for predictable API behavior.

Suggested patch
 public static DisjunctionConstraint of(Constraint first, Constraint... rest) {
+  Objects.requireNonNull(rest, "constraints must not be null");
   List<Constraint> list = new java.util.ArrayList<>();
   list.add(Objects.requireNonNull(first, "constraint must not be null"));
   for (Constraint c : rest) {
     list.add(Objects.requireNonNull(c, "constraint must not be null"));
   }
   return new DisjunctionConstraint(List.copyOf(list));
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static DisjunctionConstraint of(Constraint first, Constraint... rest) {
List<Constraint> list = new java.util.ArrayList<>();
list.add(Objects.requireNonNull(first, "constraint must not be null"));
for (Constraint c : rest) {
list.add(Objects.requireNonNull(c, "constraint must not be null"));
}
public static DisjunctionConstraint of(Constraint first, Constraint... rest) {
Objects.requireNonNull(rest, "constraints must not be null");
List<Constraint> list = new java.util.ArrayList<>();
list.add(Objects.requireNonNull(first, "constraint must not be null"));
for (Constraint c : rest) {
list.add(Objects.requireNonNull(c, "constraint must not be null"));
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend-core-model/src/main/java/com/flowingcode/backendcore/model/constraints/DisjunctionConstraint.java`
around lines 39 - 44, DisjunctionConstraint.of currently assumes the varargs
array 'rest' is non-null and will throw an implicit NPE in the for-loop; add an
explicit null check for the varargs container before iterating (e.g.,
Objects.requireNonNull(rest, "constraints must not be null")) so callers calling
of(first, (Constraint[]) null) get a predictable error, then continue to
validate each element in 'rest' with Objects.requireNonNull as already done.

return new DisjunctionConstraint(List.copyOf(list));
}

}
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<groupId>com.flowingcode.backend-core</groupId>
<artifactId>backend-core</artifactId>
<packaging>pom</packaging>
<version>1.1.1-SNAPSHOT</version>
<version>1.2.0-SNAPSHOT</version>
<name>Backend Core</name>
<description>Common utilities for backend enterprise application development</description>
<url>https://www.flowingcode.com/en/open-source/</url>
Expand Down
Loading