Skip to content
Merged
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
1 change: 0 additions & 1 deletion cxbox-base/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@
<!-- dependencies versions -->
<hibernate-commons-annotations.version>6.0.6.Final</hibernate-commons-annotations.version>
<javapoet.version>1.13.0</javapoet.version>
<hibernate.version>6.4.10.Final</hibernate.version> <!--Do not change. BaseEntity redesign needed due to 6.5.* annotation checks changes -->

<!-- plugins versions -->
<jacoco-maven-plugin.version>0.8.11</jacoco-maven-plugin.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@
/**
* Enables filtration by the annotated field of {@link org.cxbox.api.data.dto.DataResponseDTO DataResponseDTO}.
* Configures the rules and parameters for filtering by field.
* When an {@link Enum} or {@link org.cxbox.dictionary.Dictionary Dictionary} field is used through a
* {@link org.cxbox.core.dto.multivalue.MultivalueField MultivalueField}, the DTO field must be annotated so the
* target type can be resolved:
* <ul>
* <li>entity field is an {@link Enum} ; annotate the DTO field with
* {@link org.cxbox.core.util.filter.provider.impl.EnumValueProvider.BaseEnum @BaseEnum}</li>
* Example of usage: {@link org.cxbox.core.util.filter.provider.impl.EnumValueProvider EnumValueProvider} </li>
* <li>entity field extends {@link org.cxbox.dictionary.Dictionary Dictionary}; annotate the DTO field with
* {@link org.cxbox.core.util.filter.provider.impl.DictionaryValueProvider.BaseDictionary @BaseDictionary}
* Example of usage: {@link org.cxbox.core.util.filter.provider.impl.DictionaryValueProvider DictionaryValueProvider} </li>
* </ul>
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
Expand Down Expand Up @@ -73,7 +84,6 @@
/**
* Get a provider for defining of classify data parameter in sorting or searching cases.
* Necessary to correctly type string representation of filtering parameter.
*
* @return ClassifyDataProvider
*/
Class<? extends ClassifyDataProvider> provider() default StringValueProvider.class;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,74 @@

package org.cxbox.core.util.filter.provider.impl;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static org.cxbox.core.controller.param.SearchOperation.CONTAINS_ONE_OF;
import static org.cxbox.core.controller.param.SearchOperation.EQUALS_ONE_OF;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import org.cxbox.api.config.CxboxBeanProperties;
import org.cxbox.api.exception.ServerException;
import org.cxbox.core.controller.param.FilterParameter;
import org.cxbox.core.dao.ClassifyDataParameter;
import org.cxbox.core.util.filter.SearchParameter;
import org.cxbox.core.util.filter.provider.ClassifyDataProvider;
import org.cxbox.dictionary.Dictionary;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
* {@link ClassifyDataProvider} for dictionary-typed DTO fields. Converts string filter values into the
* dictionary type (resolved from the field type or its {@link BaseDictionary @BaseDictionary} annotation)
* <p>
* For use with {@link org.cxbox.core.dto.multivalue.MultivalueField MultivalueField} the field must be annotated with
* {@link BaseDictionary @BaseDictionary}.
* <p>
* Example:
* <pre>{@code
* // Dictionary
* public record TestDict(String key) implements Dictionary {
* public static final TestDict VALUE_1 = new TestDict("value 1");
* public static final TestDict VALUE_2 = new TestDict("value 2");
* }
*
* // Entity
* public class Entity extends BaseEntity {
*
* @Column(name = "TEST_DICT")
* public TestDict testDict;
*
* @ElementCollection(targetClass = TestDict.class)
* @CollectionTable(name = "TEST_DICT_TO_ENTITY", joinColumns = @JoinColumn(name = "ENTITY_ID"))
* @Column(name = "TEST_DICT", nullable = false)
* public Set<TestDict> testDicts = new HashSet<>();
* }
*
* // DTO
* public class EntityDTO extends DataResponseDTO {
* //!WARN. Must be annotated BaseDictionary
* @BaseDictionary(TestDict.class)
* @SearchParameter(name = "testDictCollection", provider = MultiFieldValueProvider.class, multiFieldKey = DictionaryValueProvider.class)
* private MultivalueField testDictCollection;
*
* @SearchParameter(name = "testDict", provider = DictionaryValueProvider.class)
* private TestDict testDict = TestDict.VALUE_1;
* }
* }</pre>
*
* @see BaseDictionary
* @see AbstractClassifyDataProvider
*
*/
@Component
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = false)
Expand All @@ -42,6 +92,14 @@ public class DictionaryValueProvider extends AbstractClassifyDataProvider implem
@Qualifier(CxboxBeanProperties.OBJECT_MAPPER)
private final ObjectMapper objectMapper;

/**
* Converts the filter's string value(s) into the resolved dictionary type and stores them on {@code dataParameter}.
*
* @param dtoField the field being filtered
* @param dataParameter parameter populated with the converted value(s)
* @param filterParam source of the raw string value(s)
* @return singleton list containing {@code dataParameter}
*/
@Override
protected List<ClassifyDataParameter> getProviderParameterValues(Field dtoField, ClassifyDataParameter dataParameter,
FilterParameter filterParam, SearchParameter searchParam,
Expand All @@ -59,15 +117,51 @@ protected List<ClassifyDataParameter> getProviderParameterValues(Field dtoField,
return result;
}


/**
* Extracts the dictionary class declared by the field's {@link BaseDictionary} annotation, if present.
*
* @return the annotated dictionary class, or empty if the field is not annotated with {@link BaseDictionary}.
*/
private Optional<Class<? extends Dictionary>> getType(Field field) {
BaseDictionary annotation = field.getAnnotation(BaseDictionary.class);
if (annotation != null) {
return Optional.of(annotation.value());
}
return Optional.empty();
}


public Object convertDictToTargetType(Object value) {
return value;
}

private Class<?> getDictType(Field dtoField) {
Class<?> dtoFieldType = dtoField.getType();
Class<?> type;
type = dtoFieldType;
if (Dictionary.class.isAssignableFrom(dtoFieldType)) {
type = dtoFieldType;
} else {
type = getType(dtoField).orElseThrow(() -> new ServerException(
"DictionaryValueProvider must be used with Dictionary dto field or field annotated with @BaseDictionary"));
}
return type;
}

/**
* Used when a JPA entity enum field is mapped to
* a {@link org.cxbox.api.data.dto.DataResponseDTO DataResponseDTO} field which type is not dictionary.
* Necessary to define the type of field by which filtering will be performed.
*/
@Target(FIELD)
@Retention(RUNTIME)
public @interface BaseDictionary {

/**
* @return Class of the corresponding dictionary field in JPA entity.
*/
Class<? extends Dictionary> value();

}
}

Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,57 @@
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
{@link ClassifyDataProvider} for enum-typed DTO fields. Converts string filter values into the
* enum type (resolved from the field type or its {@link BaseEnum @BaseEnum}).
* <p>
* For use with {@link org.cxbox.core.dto.multivalue.MultivalueField MultivalueField} the field must be annotated with
* {@link BaseEnum}.
* <p>
* Example:
* <pre>{@code
* // Enum
* @Getter
* @AllArgsConstructor
* public enum TestEnum {
* VALUE_1("value 1"),
* VALUE_2("value 2"),
* VALUE_3("value 3");
*
* @JsonValue
* private final String value;
* }
*
* // Entity
* public class Entity extends BaseEntity {
*
* @Column(name = "TEST_ENUM")
* @Enumerated(EnumType.STRING)
* public TestEnum testEnum;
*
* @ElementCollection(targetClass = TestEnum.class)
* @CollectionTable(name = "TEST_ENUM_TO_ENTITY", joinColumns = @JoinColumn(name = "ENTITY_ID"))
* @Column(name = "TEST_ENUM", nullable = false)
* @Enumerated(EnumType.STRING)
* public Set<TestEnum> testEnums = new HashSet<>();
* }
*
* // DTO
* public class EntityDTO extends DataResponseDTO {
* //!WARN. Must be annotated BaseEnum
* @BaseEnum(TestEnum.class)
* @SearchParameter(name = "testEnumCollection", provider = MultiFieldValueProvider.class, multiFieldKey = EnumValueProvider.class)
* private MultivalueField testEnumCollection;
*
* @SearchParameter(name = "testEnum", provider = EnumValueProvider.class)
* private TestEnum testEnum = TestEnum.VALUE_1;
* }
* }</pre>
*
* @see BaseEnum
* @see AbstractClassifyDataProvider
*
*/
@Component
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = false)
Expand All @@ -49,6 +100,14 @@ public class EnumValueProvider extends AbstractClassifyDataProvider implements C
@Qualifier(CxboxBeanProperties.OBJECT_MAPPER)
private final ObjectMapper objectMapper;

/**
* Converts the filter's string value(s) into the resolved enum type and stores them on {@code dataParameter}.
*
* @param dtoField the field being filtered
* @param dataParameter parameter populated with the converted value(s)
* @param filterParam source of the raw string value(s)
* @return singleton list containing {@code dataParameter}
*/
@Override
protected List<ClassifyDataParameter> getProviderParameterValues(Field dtoField, ClassifyDataParameter dataParameter,
FilterParameter filterParam, SearchParameter searchParam,
Expand All @@ -70,6 +129,11 @@ public Object convertEnumToTargetType(Object value) {
return value;
}

/**
* Resolves the enum class for the field: its own type if it is an enum, otherwise the {@link BaseEnum} value.
*
* @throws ServerException if the field is neither an enum nor annotated with {@link BaseEnum}
*/
private Class<?> getEnumType(Field dtoField) {
Class<?> dtoFieldType = dtoField.getType();
Class<?> type;
Expand All @@ -82,6 +146,7 @@ private Class<?> getEnumType(Field dtoField) {
return type;
}

/** @return the enum class declared by the field's {@link BaseEnum} annotation, or empty if absent. */
private Optional<Class<? extends Enum<?>>> getType(Field field) {
BaseEnum annotation = field.getAnnotation(BaseEnum.class);
if (annotation != null) {
Expand All @@ -94,6 +159,7 @@ private Optional<Class<? extends Enum<?>>> getType(Field field) {
* Used when a JPA entity enum field is mapped to
* a {@link org.cxbox.api.data.dto.DataResponseDTO DataResponseDTO} field which type is not enum.
* Necessary to define the type of field by which filtering will be performed.
* see example on {@link EnumValueProvider} class javadoc
*/
@Target(FIELD)
@Retention(RUNTIME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.cxbox.core.util.filter.SearchParameter;
import org.cxbox.core.util.filter.provider.ClassifyDataProvider;
import org.cxbox.model.core.dao.impl.DialectName;
import org.hibernate.query.criteria.JpaExpression;
import org.springframework.stereotype.Component;

@Component
Expand Down Expand Up @@ -88,7 +89,7 @@ public Expression<?> getSortExpression(@NonNull final SearchParameter searchPara
if (dialect.equals(DialectName.ORACLE)) {
return builder.function("TO_CHAR", String.class, fieldPath, builder.literal("HH24:MI:SS"));
}
return fieldPath.as(LocalTime.class);
return ((JpaExpression<?>) fieldPath).cast(LocalTime.class);
}
return null;
}
Expand Down Expand Up @@ -124,7 +125,7 @@ private static Expression getExpressionByTimePart(@NonNull Path field, @NonNull
return cb.function("TO_CHAR", String.class, field, cb.literal("HH24:MI:SS"));

}
return field.as(LocalTime.class);
return ((JpaExpression<?>) field).cast(LocalTime.class);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* © OOO "SI IKS LAB", 2022-2026
*
* 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.
*/

package org.cxbox.dictionary.hibernate;

import org.cxbox.dictionary.Dictionary;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.AbstractClassJavaType;
import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;

/**
* JavaType for Dicitionary
* <a href="https://docs.hibernate.org/orm/6.6/userguide/html_single/#basic-legacy">doc</a>
*/
public class DictionaryJavaType<T extends Dictionary>
extends AbstractClassJavaType<T> {

public DictionaryJavaType(Class<T> clazz) {
super(clazz, ImmutableMutabilityPlan.instance());
}

@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
return indicators.getTypeConfiguration()
.getJdbcTypeRegistry()
.getDescriptor(SqlTypes.VARCHAR);
}

@Override
public T fromString(CharSequence key) {
if (key == null) {
return null;
}
return Dictionary.of(getJavaTypeClass(), key.toString());
}

@Override
public <X> X unwrap(T value, Class<X> type, WrapperOptions options) {
if (value == null) {
return null;
}
if (type.isInstance(value)) {
return (X) value;
}
if (type.isAssignableFrom(String.class)) {
return type.cast(value.key());
}
throw unknownUnwrap(type);
}

@Override
public <X> T wrap(X value, WrapperOptions options) {
if (value == null) {
return null;
}
var javaTypeClass = getJavaTypeClass();
if (javaTypeClass.isInstance(value)) {
return (T) value;
}
if (value instanceof String s) {
return fromString(s);
}
throw unknownWrap(value.getClass());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public void contribute(TypeContributions typeContributions, ServiceRegistry serv
try {
var dictClass = provider.getDictionaryType();
typeContributions.contributeType(new DictionaryType(dictClass), dictClass.getName());
typeContributions.contributeJavaType(new DictionaryJavaType<>(dictClass));
} catch (NoClassDefFoundError e) {
//
}
Expand Down
Loading
Loading