Spring string to enum converter accepts empty strings - java

I have the following string to enum converter factory:
public final class StringToEnumConverterFactory implements ConverterFactory<String, Enum<?>> {
public <T extends Enum<?>> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnumConverter(targetType);
}
#RequiredArgsConstructor
private static final class StringToEnumConverter<T extends Enum<T>> implements Converter<String, T> {
private final Class<T> enumType;
public T convert(String source) {
try {
return Enum.valueOf(this.enumType, source.toUpperCase().trim());
} catch (IllegalArgumentException e) {
throw new RuntimeException("Argument invalid " + source);
}
}
}
}
And I've implemented the following controller:
public interface GetGraphsController {
#GetMapping(value = "/graphs", produces = MediaType.APPLICATION_JSON_VALUE)
Graphs getGraphs(#RequestParam GraphType graphType);
}
GraphType corresponds to the following enum:
public enum GraphType {
A,
B;
}
Since graphType is required, I expect Spring to throw an exception when requesting /graphs?graphType= (note no graphType is included). However, passing no graphType is allowed, and no error is thrown.
I've also tried adding the following condition to convert, but the result is the same:
if (source.isBlank()) {
throw new RuntimeException("Argument invalid " + source);
}

I was finally able to solve it by adding #Validated to the controller interface and #NotNull to graphType. Apparently without those annotations validation is not made. #RequestParam has a required field (true by default) which only checks if the query parameter is included in the request and does not care about its value

Related

How allow case-insensitive mapping of enums in jackson/Spring boot?

NOTE: This is not a duplicate. That other question is not about auto-marshalling of Spring request params. It has a solution where you manually marshall objects with jackson.
I want to allow devs to create request objects with enums that can match with case-insensitivity. Other fields/properties may need case-sensitive matching, but the enums should be case-insensitive.
The only way I've found so far (initBinding) requires you to specify the exact enum class at compile time. I am looking for a more generic way to marshall the strings in the JSON request into enums.
The only current way I've found:
#RestController
public class TestController
{
//...elided...
#InitBinder
public void initBinder(final WebDataBinder webdataBinder)
{
webdataBinder.registerCustomEditor( MyEnum.class, new CaseInsensitiveEnumConverter() );
}
}
But this requires compiling with the enums pre-known.
you can see the class org.springframework.core.convert.support.StringToEnumConverterFactory, so you can customize yourself converterFactory like this.
public class MyStringToEnumConverterFactory implements ConverterFactory<String, Enum> {
#Override
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnum(getEnumType(targetType));
}
private class StringToEnum<T extends Enum> implements Converter<String, T> {
private final Class<T> enumType;
public StringToEnum(Class<T> enumType) {
this.enumType = enumType;
}
#Override
public T convert(String source) {
if (source.isEmpty()) {
// It's an empty enum identifier: reset the enum value to null.
return null;
}
return (T) Enum.valueOf(this.enumType, source.trim().toUpperCase());
}
}
private static Class<?> getEnumType(Class targetType) {
Class<?> enumType = targetType;
while (enumType != null && !enumType.isEnum()) {
enumType = enumType.getSuperclass();
}
if (enumType == null) {
throw new IllegalArgumentException(
"The target type " + targetType.getName() + " does not refer to an enum");
}
return enumType;
}
}
and add to ConverterRegistry .
#Configuration
public class MyConfiguration {
#Bean
public ConverterRegistry initConverter(ConverterRegistry registry) {
registry.addConverterFactory(new MyStringToEnumConverterFactory());
return registry;
}
}
Hope to help you!
Starting with spring 2.0 it should be enough to set the following in your application.properties:
spring.jackson.mapper.accept-case-insensitive-enums = true

Unable to MockUp a generic interface in JMockit

I want to mock a generic interface:
public interface IModel<T, S> {
public S classify(T entity);
}
This interface is sub-classed by 3 concrete classes: TextModel, ImageModel, ScoringModel. Each of these concrete classes have different T and S parameters.
I wrote a generic method that receives the concrete model class as an argument and generates a mocked version of the model:
private <T extends IModel<?, ?>> T mockModel(Class<T> modelClass) {
return new MockUp<T>() {
#Mock public Object classify(Object entity) { return null; }
}.getMockInstance();
}
I know that IModel::classify has generic types for both its input and output, but I haven't found a way to use the actual generic method within the mockup.
When calling this method I get an IllegalArgumentException:
java.lang.IllegalArgumentException: Value of type com.classificationmanager.model.$Impl_IModel incompatible with return type com.classificationmanager.model.TextModel of com.classificationmanager.model.TextModelFactory#createModel(com.classificationmanager.model.ModelDescriptor)
at com.classificationmanager.model.ModelFetcherTest$5.(ModelFetcherTest.java:110)
at com.classificationmanager.model.ModelFetcherTest.mockAllFactories(ModelFetcherTest.java:109) ....... (spared you the rest)
I thought that getting and returning an Object instead of T and S was the problem, but I get the same exception when removing the mocked method and just mocking the class:
private <T extends IModel<?, ?>> T mockModel(Class<T> modelClass) {
return new MockUp<T>() {
}.getMockInstance();
}
I could do a switch-case and return a concrete class but that would just be nasty.
Any workaround involving the Expectations API would also work for me.
10x
Maybe the following examples can help (although I still don't understand the question - probable case of the XY problem).
public final class ExampleTest {
public interface IModel<T, S> { S classify(T entity); }
static class TextModel implements IModel<Integer, String> {
#Override public String classify(Integer entity) { return "test"; }
}
static class ImageModel implements IModel<String, Image> {
#Override public Image classify(String entity) { return null; }
}
#Test
public void createNonMockedInstanceForAnyModelClass() {
IModel<Integer, String> m1 = mockModel(TextModel.class);
String s = m1.classify(123);
IModel<String, Image> m2 = mockModel(ImageModel.class);
Image img = m2.classify("test");
assertEquals("test", s);
assertNull(img);
}
<T extends IModel<?, ?>> T mockModel(Class<T> modelClass) {
// Or use newUninitializedInstance in case the model class doesn't
// have a no-args constructor.
return Deencapsulation.newInstance(modelClass);
}
#Test
public void mockAllModelImplementationClassesAndInstances(
#Capturing IModel<?, ?> anyModel
) {
IModel<Integer, String> m = new TextModel();
String s = m.classify(123);
assertNull(s);
}
}

Bound mismatch: The type class name is not a valid substitute for the bounded parameter <T extends interfacename> of the type Service<T>

I have an interface below:
interface Find {
List<String> getParams();
}
I have a simple POJO class as below:
public class FindSystem<T extends Find> {
private List<String> params;
private Class<T> clazz;
public List<String> setParams(List<String> params) {
...
}
public List<String> getParams() {
...
}
public Class<T> getClazz() {
return clazz;
}
public find setClazz(Class<T> clazz) {
this.clazz = clazz;
return this;
}
}
I have a service class as:
public class FindService<T extends Find> {
public fetch(Class<T> class) {
FindSystem<T> f = new FindSystem<T>();
f.setClazz(clazz);
Find en = f.getClazz().newInstance();
f.setParams(en.getParams());
}
}
I have a Controller class, where System is an entity class mapping to a table in database:
#Path("/find")
public class FindController {
#Inject
FindService<System> systemservice; //Getting error here Bound Mismatch, the type System is not a valid substitute for the bounded parameter <T extends Find> of the type FindService<T>
...
}
Getting error at:
FindService<System> systemservice; // Bound Mismatch, the type System is not a valid substitute for the bounded parameter of the type FindService

Type safety with different generic values in collection

Suppose I have an interface like this;
interface Validator<T>{
void validate<T value>
}
And these implementations ;
class StringValidator implements Validator<String>{
void validate<String value>{}
}
class OrderValidator implements Validator<Order>{
void validate<Order value>{}
}
In ValidatorRegisterer class I have a map;
class ValidationRegisterer{
Map<String, Validator> validatorsForPath = new HashMap<String, Validator>();
public Map<String, Validator> registerers(){
return validatorsForPath;
}
public void register(String path, Validator validator){
validatorsForPath.put(path, validator);
}
}
What I want is to iterate over this map in ValidationManager class with type safety;
class ValidationManager<RootObject>{
List<ValidationRegisterer> validationRegisterers;
public ValidationManager(List<ValidationRegisterer> validationRegisterers){
this.validationRegisterers = validationRegisterers;
}
public void validate(RootObject object){
for(ValidationRegisterer validationRegisterer in validationRegisterers){
for(String path : validationRegisterer.keySet()){
Object value = object.getPath(path);
Validator validator = validationRegisterer.get(path);
validator.validate(value);
//this line gets unchecked call to validate(T) warning and I want to get rid of it
//the problem is validationRegisterers map can contain both StringValidator and OrderValidator,
//so the value can be a String or an Order
//do I have to cast the value to the type of validator's T type?
}
}
}
Map<String, Validator> validatorsForPath = new HashMap<String, Validator>();
}
I tried to explain the situation in the last code sample comments.
Declare as follows to remove warnings :
Validator<Object> validator = validationRegisterer.get(path);
In this case you are declaring the validator reference that would work on Object type.
later you can typecast to Order or String after doing an instanceof test.
You need to make ValidationRegisterer class generic like this:
class ValidationRegisterer<T extends Validator> {
Map<String, T> validatorsForPath = new HashMap<String, T>();
public Map<String, T> registerers(){
return validatorsForPath;
}
public void register(String path, T validator){
validatorsForPath.put(path, validator);
}
}
And then maintain separate lists for these two types of ValidationRegisterer
class ValidationManager {
List<ValidationRegisterer<StringValidator>> strValidationRegisterers;
List<ValidationRegisterer<OrderValidator>> ordValidationRegisterers;
....
}
I will assume that with "type safety" you mean that you want to be certain that the object returned for a certain path is really of the type that the associated Validator accepts.
One problem is that the type parameter for the Validator is not available at compile time since, as you say yourself, any kind of Validator can be in the map.
Also, object.getPath(path) will always return an Object which will always need casting at runtime, so the fact that the validate method limits its argument to type T is of little use.
So the best you can do is make validation fail fast in case the object is not of the correct type.
A solution would be to
1. store the Class object for the Validator,
2. let validate accept an Object as parameter and dynamically cast the object to the validator type at the beginning of the validate method. This can be done in an abstract base class.
Example:
interface Validator<T> {
void validate(Object value);
Class<T> getType();
}
abstract class BaseValidator<T> implements Validator<T> {
private final Class<T> type;
public BaseValidator(Class<T> type) {
this.type = type;
}
public final void validate(Object o) {
doValidate(type.cast(o)); // wrong type will fail fast here
}
public final Class<T> getType() {
return type;
}
protected abstract void doValidate(T value);
}
class StringValidator extends BaseValidator<String> {
public StringValidator() {
super(String.class);
}
protected void doValidate(String value) {
// do actual string validation here
}
}
An alternative solution if you want to keep Object out of the Validator interface would be to let the path be resolved by a type parameterized object that has a reference the validator and performs the dynamic cast, and keep that in your registry map as value:
interface Validator<T> {
void validate(final T value);
}
class PathValidator<T> {
private final Class<T> type;
private final Validator<T> validator;
public PathValidator(final Class<T> type, final Validator<T> validator) {
this.type = type;
this.validator = validator;
}
public void validate(final RootObject object, final String path) {
T value = type.cast(object.getPath(path)); // throws ClassCastException here if not the correct type
validator.validate(value);
}
}
You would then have a Map<String, PathValidator<?> in your ValidationRegisterer class.
I'd personally prefer this alternative solution.

Java Generics: Build Parameterized Classes for every inner class

Say I have an interface:
public interface Authentication<T> {
public void authenticate(T token);
}
I have a class called AuthenticationMethods that has several inner classes.
what I want to do is write a utility where I can get all the inner classes, and generate a class that implements the Authentication<T> interface with the Type T of the inner class, like so:
for (Class clazz : AuthenticationMethods.class.getDeclaredClasses()){
createAuthenticationImplClass(clazz);
}
private <T> Authentication<T> createAuthenticationImplClass(Class clazz){
return new Authentication<clazz>() {
#Override
public void authenticate(clazz token) throws Exception {
//do something with the token
}
};
}
Obviously just using clazz in place of T does not work.
How can i get the type from the clazz in to the parameterized implementation of the Authentication interface?
You can do something like this.
private <T extends Class<?>> Authentication<T> createAuthenticationImplClass(T clazz){
return new Authentication<T>() {
#Override
public void authenticate(T token) throws Exception {
//do something with the token
}
};
}
Example
Authentication<Class<String>> = createAuthenticationImplClass(String.class);
or this
private <T> Authentication<T> createAuthenticationImplClass(Class<T> clazz){
return new Authentication<T>() {
#Override
public void authenticate(T token) throws Exception {
//do something with the token
}
};
}
Example:
Authentication<String> = createAuthenticationImplClass(String.class);
The difference is that in first example your authenticate method will have in parameter the Class type. In the second the parameter will be type that class represent.
If I understand you correctly, you want to authenticate tokens of the clazz class. Then you need to parametrize your factory method parameter with generic Class type:
private <T> Authentication<T> createAuthenticationImplClass(Class<T> clazz){
return new Authentication<T>() {
#Override
public void authenticate(T token) throws Exception {
//do something with the token
}
};
}
Of course at the moment you do for loop for declared classes you lose generic types, so the only way to pass type-safe Class instance is explicit class name:
Authentication<TokenType> authForTokenType = createAuthenticationImplClass(TokenType.class);

Categories