Skip to content
Closed
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 .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# These are supported funding model platforms

github: perplexhub # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class RSQLCommonSupport {
private @Getter static final Map<Class, Class> valueTypeMap = new ConcurrentHashMap<>();
private @Getter static final Map<Class<?>, List<String>> propertyWhitelist = new ConcurrentHashMap<>();
private @Getter static final Map<Class<?>, List<String>> propertyBlacklist = new ConcurrentHashMap<>();
private @Getter static final Map<Class<?>, Map<String, Function<String, ?>>> fieldTransformers = new ConcurrentHashMap<>();
private @Getter static final ConfigurableConversionService conversionService = new DefaultConversionService();

public RSQLCommonSupport() {
Expand All @@ -51,6 +52,7 @@ protected void init() {
RSQLVisitorBase.setPropertyRemapping(getPropertyRemapping());
RSQLVisitorBase.setGlobalPropertyWhitelist(getPropertyWhitelist());
RSQLVisitorBase.setGlobalPropertyBlacklist(getPropertyBlacklist());
RSQLVisitorBase.setFieldTransformers(getFieldTransformers());
RSQLVisitorBase.setDefaultConversionService(getConversionService());
log.info("RSQLCommonSupport {} is initialized", getVersion());
}
Expand Down Expand Up @@ -127,6 +129,14 @@ public static void addEntityAttributeTypeMap(Class valueClass, Class mappedClass
}
}

public static <T> void addFieldTransformer(Class<?> entityClass, String fieldName, Function<String, ?> transformer) {
log.info("Adding field transformer for {}.{}", entityClass, fieldName);
if (entityClass != null && fieldName != null && transformer != null) {
fieldTransformers.computeIfAbsent(entityClass, k -> new ConcurrentHashMap<>())
.put(fieldName, transformer);
}
}

protected String getVersion() {
try {
Properties prop = new Properties();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import java.lang.reflect.*;
import java.sql.Timestamp;
import java.time.*;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Function;

import jakarta.persistence.EntityManager;
import jakarta.persistence.metamodel.Attribute;
Expand All @@ -16,7 +16,6 @@
import lombok.Getter;
import org.hibernate.metamodel.model.domain.ManagedDomainType;
import org.hibernate.metamodel.model.domain.PersistentAttribute;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.util.StringUtils;
Expand All @@ -37,6 +36,7 @@ public abstract class RSQLVisitorBase<R, A> implements RSQLVisitor<R, A> {
protected static volatile @Setter Map<Class<?>, Map<String, String>> propertyRemapping;
protected static volatile @Setter Map<Class<?>, List<String>> globalPropertyWhitelist;
protected static volatile @Setter Map<Class<?>, List<String>> globalPropertyBlacklist;
protected static volatile @Setter Map<Class<?>, Map<String, Function<String, ?>>> fieldTransformers;
protected static volatile @Setter ConfigurableConversionService defaultConversionService;

protected @Setter Map<Class<?>, List<String>> propertyWhitelist;
Expand Down Expand Up @@ -77,6 +77,25 @@ public Map<Class<?>, Map<String, String>> getPropertyRemapping() {
protected Object convert(String source, Class targetType) {
log.debug("convert(source:{},targetType:{})", source, targetType);

if (source == null) {
return null;
}

// Check for field-specific transformer first
if (fieldTransformers != null) {
Map<String, Function<String, ?>> entityTransformers = fieldTransformers.get(targetType);
if (entityTransformers != null) {
Function<String, ?> transformer = entityTransformers.get(source);
if (transformer != null) {
try {
return transformer.apply(source);
} catch (Exception e) {
log.warn("Failed to apply field transformer for {}.{}", targetType, source, e);
}
}
}
}

Object object = null;
try {
if (defaultConversionService.canConvert(String.class, targetType)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ public class User {

private String name;

private String number;

private String email;

private String phone;

@ManyToOne
@JoinColumn(name = "companyId", referencedColumnName = "id")
private Company company;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.verify;

import java.sql.Timestamp;
import java.text.ParseException;
Expand All @@ -17,6 +20,8 @@
import java.time.ZoneOffset;
import java.util.*;

import cz.jirutka.rsql.parser.RSQLParser;
import cz.jirutka.rsql.parser.ast.Node;
import io.github.perplexhub.rsql.custom.CustomType;
import io.github.perplexhub.rsql.model.Project;
import io.github.perplexhub.rsql.model.AdminProject;
Expand All @@ -28,6 +33,7 @@
import io.github.perplexhub.rsql.repository.jpa.ProjectRepository;
import io.github.perplexhub.rsql.repository.jpa.custom.CustomTypeRepository;
import io.github.perplexhub.rsql.custom.EntityWithCustomType;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Predicate;
Expand Down Expand Up @@ -1463,6 +1469,52 @@ void testSearchBySubtypeAttribute() {
Assertions.assertThat(result).hasSize(1);
}

@Test
void testNumberFieldTransformer() {
RSQLCommonSupport.addFieldTransformer(User.class, "number", value -> value.replaceAll("[^0-9]", ""));
String rsql = "number=like='123????'";
List<User> users = userRepository.findAll(toSpecification(rsql));
for (User user : users)
System.out.println(user);
}

@Test
void testEmailFieldTransformer() {
RSQLCommonSupport.addFieldTransformer(User.class, "email", value -> value.toLowerCase());
String rsql = "email=like='[email protected]'";
List<User> users = userRepository.findAll(toSpecification(rsql));
for (User user : users)
System.out.println(user);
}

@Test
void testPhoneFieldTransformer() {
RSQLCommonSupport.addFieldTransformer(User.class, "phone", value -> value.replaceAll("[^0-9+]", ""));
String rsql = "phone=like='+1 (555) 123-4567'";
List<User> users = userRepository.findAll(toSpecification(rsql));
for (User user : users)
System.out.println(user);
}

@Test
void testMultipleTransformers() {
RSQLCommonSupport.addFieldTransformer(User.class, "number", value -> value.replaceAll("[^0-9]", ""));
RSQLCommonSupport.addFieldTransformer(User.class, "email", value -> value.toLowerCase());
String rsql = "number=like='123????' and email=like='[email protected]'";
List<User> users = userRepository.findAll(toSpecification(rsql));
for (User user : users)
System.out.println(user);
}

@Test
void testTransformerWithInvalidValue() {
String rsql = "number=like='abc'";
RSQLCommonSupport.addFieldTransformer(User.class, "number", value -> value.replaceAll("[^0-9]", ""));
List<User> users = userRepository.findAll(toSpecification(rsql));
for (User user : users)
System.out.println(user);
}

@BeforeEach
void setUp() {
getPropertyWhitelist().clear();
Expand Down