I have the following endpoint
#GetMapping(value = "/mypath/{mychoice}")
public ResponseClass generateEndpoint(
#PathVariable("mychoice") FormatEnum format,
) {
...
and the following enum annotated with Jackson
#JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
public enum Format {
AVRO,
ORC,
PARQUET,
PROTOBUF
}
I hoped, #JsonNaming annotation will tell swagger to display cases in lowercase, but it doesn't
Adding #JsonProperty to each case also doesn't help.
It also doesn't accept lowercase URL with error
org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'FormatEnum'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [#org.springframework.web.bind.annotation.RequestParam FormatEnum] for value 'avro'; nested exception is java.lang.IllegalArgumentException: No enum constant FormatEnum.avro
Setting
spring.jackson.mapper.ACCEPT_CASE_INSENSITIVE_ENUMS = true
has no effect (and is true in code).
Looks like it just doesn't use Jackson to deserialize enum!
Answering the part for serializing Enum case insensitive
The reason why setting spring.jackson.mapper.ACCEPT_CASE_INSENSITIVE_ENUMS=true is not working can be found in Konstantin Zyubin's answer.
And inspired from above answer, we can have a more generic approach to handle case insensitive enums in request parameter.
Converter class
import org.springframework.core.convert.converter.Converter;
public class CaseInsensitiveEnumConverter<T extends Enum<T>> implements Converter<String, T> {
private Class<T> enumClass;
public CaseInsensitiveEnumConverter(Class<T> enumClass) {
this.enumClass = enumClass;
}
#Override
public T convert(String from) {
return T.valueOf(enumClass, from.toUpperCase());
}
}
Add in configuration
import com.example.enums.EnumA;
import com.example.enums.EnumB;
import com.example.enums.FormatEnum;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
List<Class<? extends Enum>> enums = List.of(EnumA.class, EnumB.class, FormatEnum.class);
enums.forEach(enumClass -> registry.addConverter(String.class, enumClass,
new CaseInsensitiveEnumConverter<>(enumClass)));
}
}
Related: Jackson databind enum case insensitive
Related
I am trying to have specific beans enabled based on the "functionality" for the deployment, such as a rest interface, a message consumer, an indexer, an archiver, and an admin portal. In some instances the app should have all, some or one of the "functionalities" like local, dev, and qa should have all of the functionalities, but in staging, and production the functionalities should be segregated so that they can have performance improvements, like memory, threads, etc...
To do this I've setup a custom configuration based on the functionality passed in through the command line. I'm using a ConfigurationProperties to determine whether each of the "funcionalities" should be available. I have a custom configuration:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
#Configuration
#ConfigurationProperties(prefix = "com.example.config.functionality")
public class FunctionalityConfig {
public static final Logger LOGGER = LoggerFactory.getLogger(FunctionalityConfig.class);
private boolean restInterface;
private boolean messageConsumer;
private boolean adminInterface;
private boolean indexing;
private boolean archive;
public void setRestInterface(final boolean restInterface) {
this.restInterface = restInterface;
}
public boolean isRestInterface() {
return restInterface;
}
public void setMessageConsumer(final boolean messageConsumer) {
this.messageConsumer = messageConsumer;
}
public boolean isMessageConsumer() {
return messageConsumer;
}
...
}
Then I have a custom annotation:
...
/**
*
*/
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
#Documented
#ConditionalOnExpression("#{ functionalityConfig.isRestInterface }")
public #interface ConditionalOnRestInterface {
}
But when I add it to a bean definition like this:
#Component
#ConditionalOnRestInterface
public class RestInterface implements InitializingBean {
private static final Logger LOGGER = LoggerFactory.getLogger(RestInterface.class);
public void afterPropertiesSet() throws Exception {
LOGGER.info("Rest Interface is available.");
}
}
I get the following error: Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'functionalityConfig' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext' - maybe not public?
If I get rid of the #ConditionalOnExpression annotation, everything works, additionally:
in the Application class I have the following lines:
#Value("#{functionalityConfig.restInterface}")
public boolean restInterface;
And they work perfectly. I'm trying to figure out why the #ConditionalOnExpression isn't picking it up. I've even added the #EnableConfigurationProperties(FunctionalityConfig.class) annotation to the application, but no change to the exception.
Spring boot version:2.4.0
Converter class
import java.util.UUID;
import org.springframework.core.convert.converter.Converter;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
#Component
public class StringToUuidConverter implements Converter<String, UUID> {
#Override
public UUID convert(#NonNull String uuid) {
return UUID.fromString(uuid);
}
}
controller
....
#Autowired
TestRepos testRepos;
#RequestMapping("test")
public Task t( UUID uuid) {
return this.testRepos.findById(uuid).orElse(null);
}
....
call the controller with
http://localhost:9090/test?uuid=0459828a-a630-491d-80b9-ab9a412f066e
error log
"error": "Bad Request",
"trace": "org.springframework.web.method.annotation.ModelAttributeMethodProcessor$1: org.springframework.validation.BeanPropertyBindingResult: 2 errors\nField error in object 'UUID' on field 'mostSigBits': rejected value [null]; codes [typeMismatch.UUID.mostSigBits,typeMismatch.mostSigBits,typeMismatch.long,typeMismatch];
Any config missed?
Try it:
public Task t(#RequestParam UUID uuid) {
// ...
}
1.3.3. Handler Methods
Any other argument:
If a method argument is not matched to any of the earlier values in this table and it is a simple type (as determined by BeanUtils#isSimpleProperty, it is a resolved as a #RequestParam. Otherwise, it is resolved as a #ModelAttribute.
In addition, you don't need create StringToUuidConverter class yourself.
It has been already included.
I have using Jersey so far and I am doing my first implementation with JSON-B.
I am using Payara, so I working with Jersey and Yasson. I had an issue, because the serialized dates would always contain the "[UTC]" suffix.
I have managed to use an annotation on my date property, in my DTO. But I would like to configure that globally (in the JAX-RS application config?), instead of repeating myself on every date property. Is that possible? I haven't found anything so far...
Side question: I assume that it is possible to get rid of this "[UTC]" suffix, since it breaks all clients trying to parse the date. Any idea?
Thanks to this Github issue, I was able to solve my problem. Here is what I ended up writing in my code:
JSONConfigurator.java:
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbConfig;
import javax.json.bind.config.PropertyNamingStrategy;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
#Provider
public class JSONConfigurator implements ContextResolver<Jsonb> {
#Override
public Jsonb getContext(Class<?> type) {
JsonbConfig config = getJsonbConfig();
return JsonbBuilder
.newBuilder()
.withConfig(config)
.build();
}
private JsonbConfig getJsonbConfig() {
return new JsonbConfig()
.withDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", null);
}
}
And:
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;
#ApplicationPath("/api")
public class ApplicationConfig extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new HashSet<Class<?>>();
addRestResourceClasses(resources);
resources.add(JSONConfigurator.class);
return resources;
}
private void addRestResourceClasses(Set<Class<?>> resources) {
...
}
}
Given some application configuration with an unresolvable placeholder, like the following application.yml
my:
thing: ${missing-placeholder}/whatever
When I use #Value annotations, the placeholders in the configuration file are validated, so in this case:
package com.test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
#Component
public class PropValues {
#Value("${my.thing}") String thing;
public String getThing() { return thing; }
}
I get an IllegalArgumentException: Could not resolve placeholder 'missing-placeholder' in value "${missing-placeholder}/whatever". This is because the value is being set directly by AbstractBeanFactory.resolveEmbeddedValue and there is nothing to catch the exception thrown by PropertyPlaceholderHelper.parseStringValue
However, looking to move to #ConfigurationProperties style I noticed that this validation is missing, for example in this case:
package com.test;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
#ConfigurationProperties(prefix = "my")
public class Props {
private String thing;
public String getThing() { return thing; }
public void setThing(String thing) { this.thing = thing; }
}
there is no exception. I can see PropertySourcesPropertyValues.getEnumerableProperty catches the exception with the comment // Probably could not resolve placeholders, ignore it here and gathers the invalid value into its internal map. Subsequent data binding does not check for unresolved placeholders.
I checked that simply applying the #Validated and #Valid annotations to the class and field do not help.
Is there any way to preserve the behaviour of throwing an exception on unresolved placeholders with ConfigurationProperties binding?
Apparently there are no better solutions. At least this is kind of nicer than afterPropertiesSet().
#Data
#Validated // enables javax.validation JSR-303
#ConfigurationProperties("my.config")
public static class ConfigProperties {
// with #ConfigurationProperties (differently than #Value) there is no exception if a placeholder is NOT RESOLVED. So manual validation is required!
#Pattern(regexp = ".*\$\{.*", message = "unresolved placeholder")
private String uri;
// ...
}
UPDATE: I got the regex wrong the first time. It as to match the entire input (not just java.util.regex.Matcher#find()).
The correct regex to pass in #Pattern annotation is ^(?!\\$\\{).+
#Validated
#ConfigurationProperties("my.config")
public class ConfigProperties {
#Pattern(regexp = "^(?!\\$\\{).+", message = "unresolved placeholder")
private String uri;
// ...
}
I had the same issue exactly 10 minutes ago!
Try to add this bean in your configuration:
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(true);
return propertySourcesPlaceholderConfigurer;
}
I'm working with Spring 3.2. In order to validate double values globally, I use CustomNumberEditor. The validation is indeed performed.
But when I input a number like 1234aaa, 123aa45 and so forth, I expect the NumberFormatException to be thrown but it doesn't. The docs says,
ParseException is caused, if the beginning of the specified string cannot be
parsed
Therefore, such values as mentioned above are parsed up to they are represented as numbers and the rest of the string is then omitted.
To avoid this, and to make it throw an exception, when such values are fed, I need to implement my own Property Editor by extending the PropertyEditorSupport class as mentioned in this question.
package numeric.format;
import java.beans.PropertyEditorSupport;
public final class StrictNumericFormat extends PropertyEditorSupport
{
#Override
public String getAsText()
{
System.out.println("value = "+this.getValue());
return ((Number)this.getValue()).toString();
}
#Override
public void setAsText(String text) throws IllegalArgumentException
{
System.out.println("value = "+text);
super.setValue(Double.parseDouble(text));
}
}
The editors I have specified inside a method annotated with the #InitBinder annotation are as follows.
package spring.databinder;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.Format;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.context.request.WebRequest;
#ControllerAdvice
public final class GlobalDataBinder
{
#InitBinder
public void initBinder(WebDataBinder binder, WebRequest request)
{
DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
dateFormat.setLenient(false);
binder.setIgnoreInvalidFields(true);
binder.setIgnoreUnknownFields(true);
//binder.setAllowedFields("startDate");
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
//The following is the CustomNumberEditor
NumberFormat numberFormat = NumberFormat.getInstance();
numberFormat.setGroupingUsed(false);
binder.registerCustomEditor(Double.class, new CustomNumberEditor(Double.class, numberFormat, false));
}
}
Since I'm using Spring 3.2, I can take advantage of #ControllerAdvice
Out of curiosity, the overridden methods from the PropertyEditorSupport class in the StrictNumericFormat class are never invoked and the statements that redirect the output to the console as specified inside of those methods (getAsText() and setAsText()) don't print anything on the server console.
I have tried all the approaches described in all the answers of that question but none worked for me. What am I missing here? Is this required to configure in some xml file(s)?
Clearly you have nowhere passed the StrictNumericFormat reference. You should register your editor like:
binder.registerCustomEditor(Double.class, new StrictNumericFormat());
BTW Spring 3.X introduced a new way achieving conversion:Converters