Can I exclude a specific property from auto configuration - spring

I'm using spring-boot and trying to use "PropertySourcesPlaceholderConfigurer" to load external properties files from filesystem,
but I got an error like below:
Binding to target org.springframework.boot.autoconfigure.web.ServerProperties#3ec11999 failed:
Property: server.environment
Value: BETA
Reason: Failed to convert property value of type [java.lang.String] to required type [org.springframework.core.env.Environment] for property 'environment'; ...
This is because spring-boot tries to auto-config "ServerProperties" and "ServerProperties" looks like:
#ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties
implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
...
private Environment environment;
...
}
So it tries to "parse" any property with a "server" prefix.
Unfortunately our legacy properties file happens to contain an irrelevant property called
server.environment=BETA
So spring-boot tries to convert string "BETA" to an "Environment" object.
Is there a way that I can exclude "server.environment" from spring-boot's autoconfigure ?

I don't think you can exclude single property, but you can make trick Spring Boot to preserve original Environment object while converting String to Environment.
#Component
#ConfigurationPropertiesBinding
class OriginalEnvironmentPreservingStringToEnvironmentConverter implements GenericConverter {
private static final Set<ConvertiblePair> CONVERTIBLE_TYPES;
static {
Set<ConvertiblePair> types = new HashSet<>();
types.add(new ConvertiblePair(String.class, Environment.class));
CONVERTIBLE_TYPES = Collections.unmodifiableSet(types);
}
private final Environment environment;
public OriginalEnvironmentPreservingStringToEnvironmentConverter(Environment environment) {
this.environment = environment;
}
#Override
public Set<ConvertiblePair> getConvertibleTypes() {
return CONVERTIBLE_TYPES;
}
#Override
public Object convert(final Object o, final TypeDescriptor typeDescriptor, final TypeDescriptor typeDescriptor1) {
return environment;
}
}
I am not sure if there wouldn't be any side effects though. In simple scenario it does work fine.

Related

Using #ConfigurationProperties to override an application.yml property isn't working, isn't it straightforward?

I thought / hoped that overriding property values using ConfigurationProperties would be as easy as reading them, but it isn't, so I feel like I'm missing something trivial as right now I have a JavaFX application with different screens in which I can read and use properties that are in the application.yml, but one of the screens is a "support" screen where some admin should come when logging on and they should be able to change the values in the application.yml
I cannot find what I'm doing wrong as I do see the setter being called when I change something in the support screen but the application.yml stays unchanged :-/
#Configuration
#ConfigurationProperties
public class ConfigProperties {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigProperties.class);
String deviceId;
String resolution;
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getResolution() {
return resolution;
}
public void setResolution(String resolution) {
this.resolution = resolution;
LOGGER.debug("resolution set to {}", resolution);
}
screen controller
public void btnSaveClicked(ActionEvent actionEvent) {
btnSave.setDisable(true);
configProperties.setDeviceId(txtDevice.getText());
configProperties.setResolution(txtResolution.getText());
}
in the pom the only spring references are spring-boot-starter and spring-boot-autoconfigure, both 2.7.4

Spring #Value not working in Spring Boot 2.5.5, getting null values

I am trying to inject some property values into variables by means of Spring #Value annotation but I get null values. I tried different configurations and triks but it doesn't work. Think is that before today everythink was working properly. I do not know what I changed in order to get things broken.
Here is my java class:
#Component
#ConditionalOnProperty(prefix = "studioghibli", name = "get")
public class StudioGhibliRestService {
#Value("${studioghibli.basepath}")
private static String BASE_PATH;
#Value("${studioghibli.path}")
private static String PATH;
#Value("${studioghibli.protocol:http}")
private static String PROTOCOL;
#Value("${studioghibli.host}")
private static String HOST;
private static String BASE_URI = PROTOCOL.concat("://").concat(HOST).concat(BASE_PATH).concat(PATH);
#Autowired
StudioGhibliRestConnector connector;
public List<StudioGhibliFilmDTO> findAllFilms() throws SipadContenziosoInternalException {
var response = connector.doGet(BASE_URI, null, null);
if (!response.getStatusCode().is2xxSuccessful() || !response.hasBody()) {
throw new SipadContenziosoInternalException(Errore.INTERNAL_REST_ERROR, "FindAll(), microservizio ".concat(BASE_URI), null);
}
return (List<StudioGhibliFilmDTO>) response.getBody();
}
}
As you can see, the class is annotated with #Component, that because I will need to use it as #Service layer in order to make a rest call in my business logic.
The class is also annotaded with conditional on property...
Here is a screenshot of the debug window at startup:
Since the PROTOCOL value is null, i get a null pointer exception immediately at start up.
Here is part of the application-dev.properties file:
studioghibli.get
studioghibli.protocol=https
studioghibli.host=ghibliapi.herokuapp.com
studioghibli.basepath=/
studioghibli.path=/films
First of all, #Value annotation does not work with static fields.
Secondly, fields with #Value annotation is processed when the instance of the class (a bean) is created by Spring, but static fields exist for a class (for any instance), so when the compiler is trying to define your static BASE_URI field other fields are not defined yet, so you get the NPE on startup.
So you might need a refactoring, try to inject values with the constructor like this:
#Component
#ConditionalOnProperty(prefix = "studioghibli", name = "get")
public class StudioGhibliRestService {
private final StudioGhibliRestConnector connector;
private final String baseUri;
public StudioGhibliRestService(StudioGhibliRestConnector connector,
#Value("${studioghibli.basepath}") String basePath,
#Value("${studioghibli.path}") String path,
#Value("${studioghibli.protocol:http}") String protocol,
#Value("${studioghibli.host}") String host) {
this.connector = connector;
this.baseUri = protocol.concat("://").concat(host).concat(basePath).concat(path);
}
// other code
}
Thanks, It works for me, I have to add some codes to my project. Then I check the spring core document in "#Value" section. Besides
When configuring a PropertySourcesPlaceholderConfigurer using
JavaConfig, the #Bean method must be static.
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}

Jackson #JsonFilter is not getting applied when used at field or method level

I am using Spring version 4.3.3 and Jackson version 2.8.3. I am trying to filter out specific fields from an entity bean based on some custom logic that is determined at runtime. The #JsonFilter seems ideal for this type of functionality. The problem is that when I put it at the field or method level, my custom filter never gets invoked. If I put it at the class level, it gets invoked just fine. I don't want to use it at the class level though since then I would need to separately maintain the list of hardcoded field names that I want to apply the logic to. As of Jackson 2.3, the ability to put this annotation at the field level is supposed to exist.
Here is the most basic custom filter without any custom logic yet:
public class MyFilter extends SimpleBeanPropertyFilter {
#Override
protected boolean include(BeanPropertyWriter beanPropertyWriter) {
return true;
}
#Override
protected boolean include(PropertyWriter propertyWriter) {
return true;
}
}
Then I have the Jackson ObjectMapper configuration:
public class MyObjectMapper extends ObjectMapper {
public MyObjectMapper () {
SimpleFilterProvider filterProvider = new SimpleFilterProvider();
filterProvider.addFilter("myFilter", new MyFilter());
setFilterProvider(filterProvider);
}
}
Then finally I have my entity bean:
#Entity
public class Project implements Serializable {
private Long id;
private Long version;
#JsonFilter("myFilter") private String name;
#JsonFilter("myFilter") private String description;
// getters and setters
}
If I move the #JsonFilter annotation to the class level where #Entity is, the filter at least gets invoked, but when it is at the field level like in the example here, it never gets invoked.
I have the same need but after examining the unit tests I discovered that this is not the use-case covered by annotating a field.
Annotating a field invokes a filter on the value of the field not the instance containing the field. For example, imagine you have to classes, A and B, where A contains a field of type B.
class A {
#JsonFilter("myFilter") B foo;
}
Jackson applies "myFilter" to the fields in B not in A. Since your example contains fields of type String, which has no fields, Jackson never invokes your filter.
I have a need to exclude certain fields based on the caller's permissions. For example, an employee's profile may contain his taxpayer id, which is considered sensitive information and should only be serialized if the caller is a member of the Payrole department. Since I'm using Spring Security, I wish to integrate Jackson with the current security context.
public class EmployeeProfile {
private String givenName;
private String surname;
private String emailAddress;
#VisibleWhen("hasRole('PayroleSpecialist')")
private String taxpayerId;
}
The most obvious way to do this is to Jackson's filter mechanism but it has a few limitations:
Jackson does not support nested filters so adding an access filter prohibits using filters for any other purpose.
One cannot add Jackson annotations to existing, third-party classes.
Jackson filters are not designed to be generic. The intent is to write a custom filter for each class you wish to apply filtering. For example, I you need to filter classes A and B, then you have to write an AFilter and a BFilter.
For my use-case, the solution is to use a custom annotation introspector in conjunction with a chaining filter.
public class VisibilityAnnotationIntrospector extends JacksonAnnotationIntrospector {
private static final long serialVersionUID = 1L;
#Override
public Object findFilterId(Annotated a) {
Object result = super.findFilterId(a);
if (null != result) return result;
// By always returning a value, we cause Jackson to query the filter provider.
// A more sophisticated solution will introspect the annotated class and only
// return a value if the class contains annotated properties.
return a instanceof AnnotatedClass ? VisibilityFilterProvider.FILTER_ID : null;
}
}
This is basically a copy SimpleBeanProvider that replaces calls to include with calls to isVisible. I'll probably update this to use a Java 8 BiPredicate to make the solution more general but works for now.
This class also takes another filter as an argument and will delegate to it the final decision on whether to serialize the field if the field is visible.
public class AuthorizationFilter extends SimpleBeanPropertyFilter {
private final PropertyFilter antecedent;
public AuthorizationFilter() {
this(null);
}
public AuthorizationFilter(final PropertyFilter filter) {
this.antecedent = null != filter ? filter : serializeAll();
}
#Deprecated
#Override
public void serializeAsField(Object bean, JsonGenerator jgen, SerializerProvider provider, BeanPropertyWriter writer) throws Exception {
if (isVisible(bean, writer)) {
this.antecedent.serializeAsField(bean, jgen, provider, writer);
} else if (!jgen.canOmitFields()) { // since 2.3
writer.serializeAsOmittedField(bean, jgen, provider);
}
}
#Override
public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer) throws Exception {
if (isVisible(pojo, writer)) {
this.antecedent.serializeAsField(pojo, jgen, provider, writer);
} else if (!jgen.canOmitFields()) { // since 2.3
writer.serializeAsOmittedField(pojo, jgen, provider);
}
}
#Override
public void serializeAsElement(Object elementValue, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer) throws Exception {
if (isVisible(elementValue, writer)) {
this.antecedent.serializeAsElement(elementValue, jgen, provider, writer);
}
}
private static boolean isVisible(Object pojo, PropertyWriter writer) {
// Code to determine if the field should be serialized.
}
}
I then add a custom filter provider to each instance of ObjectMapper.
#SuppressWarnings("deprecation")
public class VisibilityFilterProvider extends SimpleFilterProvider {
private static final long serialVersionUID = 1L;
static final String FILTER_ID = "dummy-filter-id";
#Override
public BeanPropertyFilter findFilter(Object filterId) {
return super.findFilter(filterId);
}
#Override
public PropertyFilter findPropertyFilter(Object filterId, Object valueToFilter) {
if (FILTER_ID.equals(filterId)) {
// This implies that the class did not have an explict filter annotation.
return new AuthorizationFilter(null);
}
// The class has an explicit filter annotation so delegate to it.
final PropertyFilter antecedent = super.findPropertyFilter(filterId, valueToFilter);
return new VisibilityPropertyFilter(antecedent);
}
}
Finally, I have a Jackson module that automatically registers the custom annotaion introspector so I don't have to add it to each ObjectMapper instance manually.
public class FieldVisibilityModule extends SimpleModule {
private static final long serialVersionUID = 1L;
public FieldVisibilityModule() {
super(PackageVersion.VERSION);
}
#Override
public void setupModule(Module.SetupContext context) {
super.setupModule(context);
// Append after other introspectors (instead of before) since
// explicit annotations should have precedence
context.appendAnnotationIntrospector(new VisibilityAnnotationIntrospector());
}
}
There are more improvements that can be made and I still have more unit tests to write (e.g., handling arrays and collections) but this is the basic strategy I used.
You can try this approach for the same purpose:
#Entity
#Inheritance(
strategy = InheritanceType.SINGLE_TABLE
)
#DiscriminatorColumn(
discriminatorType = DiscriminatorType.STRING,
length = 2
)
#Table(
name = "project"
)
#JsonTypeInfo(
use = Id.CLASS,
include = As.PROPERTY,
property = "#class"
)
#JsonSubTypes({
#Type(
value = BasicProject.class,
name = "basicProject"
),
#Type(
value = AdvanceProject.class,
name = "advanceProject"
)})
public abstract class Project {
private Long id;
private Long version;
}
#Entity
#DiscriminatorValue("AD")
public class AdvanceProject extends Project {
private String name;
private String description;
}
#Entity
#DiscriminatorValue("BS")
public class BasicProject extends Project {
private String name;
}
I don't think you will make it work. I was trying and these are results of my investigation, maybe it will be helpful.
First of all, as #Faron noticed, the #JsonFilterannotation is applied for the class being annotated not a field.
Secondly, I see things this way. Let's imagine, somewhere in Jackson internals you are able to get the actual field. You can figure out if there is the annotation using Java Reflection API. You can even get the filter name. Then you get to the filter and pass the field value there. But it happens at runtime, how will you get the corresponding JsonSerializer of the field type if you decide to serialize the field? It is impossible because of type erasure.
The only alternative I see is to forget about dynamic logic. Then you can do the following things:
1) extend JacksonAnnotationIntrospector (almost the same as implement AnnotationIntrospector but no useless default code) overriding hasIgnoreMarker method. Take a look at this answer
2) criminal starts here. Kinda weird way taking into account your initial goal but still: extend BeanSerializerModifier and filter out fields there. An example can be found here. This way you can define serializer that actually doesn't serialize anything (again, I understand how strange it is but maybe one will find it helpful)
3) similar to the approach above: define useless serializer based on BeanDescription implementing ContextualSerializer's createContextual method. The example of this magic is here
Thanks to this really good blog, I was able to use #JsonView to filter out specific fields from an entity bean based on some custom logic that is determined at runtime.
Since the #JsonFilter does not apply for the fields within a class, I found this to be a cleaner workaround.
Here is the sample code:
#Data
#AllArgsConstructor
public class TestEntity {
private String a;
#JsonView(CustomViews.SecureAccess.class)
private Date b;
#JsonView(CustomViews.SecureAccess.class)
private Integer c;
private List<String> d;
}
public class CustomViews {
public static interface GeneralAccess {}
public static interface SecureAccess {}
public static class GeneralAccessClass implements GeneralAccess {}
public static class SecureAccessClass implements SecureAccess, GeneralAccess {}
public static Class getWriterView(final boolean hasSecureAccess) {
return hasSecureAccess
? SecureAccessClass.class
: GeneralAccessClass.class;
}
}
#Test
public void test() throws JsonProcessingException {
final boolean hasSecureAccess = false; // Custom logic resolved to a boolean value at runtime.
final TestEntity testEntity = new TestEntity("1", new Date(), 2, ImmutableList.of("3", "4", "5"));
final ObjectMapper objectMapper = new ObjectMapper().enable(MapperFeature.DEFAULT_VIEW_INCLUSION);
final String serializedValue = objectMapper
.writerWithView(CustomViews.getWriterView(hasSecureAccess))
.writeValueAsString(testEntity);
Assert.assertTrue(serializedValue.contains("a"));
Assert.assertFalse(serializedValue.contains("b"));
Assert.assertFalse(serializedValue.contains("c"));
Assert.assertTrue(serializedValue.contains("d"));
}

How to use systemproperties to get property by using Spring EL and #Value()

#Configuration
#PropertySource("classpath:test.properties")
public class Config {
#Bean
public CompactDisc cd(#Value("#{ systemProperties['artist']}") String artist) {
HotelCalifornia hotelCalifornia = new HotelCalifornia();
hotelCalifornia.setArtist( artist);
return hotelCalifornia;
}
#Bean
public CdPlayer player(CompactDisc cd) {
CdPlayer player = new CdPlayer();
player.setCd(cd);
return player;
}
}
The property is in the test.properties file. I cant get the property "artist" from the systemProperties.But I can get it if i use #autowired to instantiate a environment bean.How can i deal with it?
you dont have to do anything. the spring recognizes property is first it will check in the system properties, then the class level properties and then properties initialized using property placeholder.
so try using #Value("${artist}") provided you have artist set in the systems property somehow.

Spring-boot ConditionalOnProperty with map-based properties

My spring-boot yaml properties look like this:
service:
mycomponent:
foo:
url: http://foo
bar:
url: http://bar
This results in the following properties being set in the Spring environment:
service.mycomponent.foo.url: http://foo
service.mycomponent.bar.url: http://bar
I'd like to define a 'mycomponent' bean if there are any properties that match service.mycomponent.[a-z]*.url. Is this possible using #ConditionalOnExpression or some other type of #Conditional?
I realize I can work around this by either adding a property such as service.mycomponent.enabled: true that could be used with #ConditionalOnProperty but I'd rather avoid that if possible.
Here's the solution I ended up taking:
Create a custom Condition which searches for any properties with a certain prefix. The RelaxedPropertyResolver has the convenient getSubProperties() method. Alternative options I found were cumbersome to iterate through the PropertySource instances.
public class MyComponentCondition extends SpringBootCondition {
#Override
public ConditionOutcome getMatchOutcome(final ConditionContext context,
final AnnotatedTypeMetadata metadata) {
final RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(context.getEnvironment());
final Map<String, Object> properties = resolver.getSubProperties("service.mycomponent.");
return new ConditionOutcome(!properties.isEmpty(), "My Component");
}
}
Use that condition when setting up the bean:
#Conditional(MyComponentCondition.class)
#Bean
public MyComponent myComponent() {
return new MyComponent();
}
I'm still curious if the same thing could be done with #ConditionalOnExpression directly.

Resources