I'm using the #PropertyInject annotation to get properties from the application.properties file to use in my beans.
This normally works fine, but now I need to be able to change the injected property based on a header value.
In my head it looks something like this:
#PropertyInject(PROPERTY_NAME)
private String property;
public void pickProperty(String msgVersion) {
if (msgVersion.equals("A")) {
PROPERTY_NAME = "property.firstType.name";
} else {
PROPERTY_NAME = "property.secondType.name";
}
}
I've considered just injecting both properties and deciding in the main method which one to use, but that seems like a roundabout way of doing things and will get a bit bloated if more versions are added.
Is there an easy way this can be done?
now I need to be able to change the injected property based on a header value
Properties and Beans are created on application startup and typically do not change while the application is running. They both have application scope.
Header values on the other hand can change for every message that is processed by your application.
As you suggested yourself: You can inject both properties into the Bean and provide a method that is called once per message to get the correct value
#PropertyInject(PROPERTY_A)
private String propertyA;
#PropertyInject(PROPERTY_B)
private String propertyB;
// called for every message processing
public String pickProperty(#Header("msgVersion") String msgVersion) {
if (msgVersion.equals("A")) {
return propertyA;
} else {
return propertyB;
}
}
This is not at all a workaround, but simply a method that returns a different result based on the input.
Related
So I want to concatenate two messages from profile in spring boot.
This is my current code (Which is not working because fields are null):
#Profile("fetchGame")
#Service
public class FetchGameApiService {
#Value("${game.api.url}")
private static String LINK;
#Value("${game.api.key}")
private static String KEY;
private static Integer gameId = 1;
private static final String URL = LINK + "/" + gameId + "?key=" + KEY;
//somelogic
}
Here is my profile page:
game.api.url = https://api.rawg.io/api/games
game.api.key = 250b2de5e7734f638760ae2bad8bd29f
this_IS_The_Correct_Url = https://api.rawg.io/api/games/1?key=250b2de5e7734f638760ae2bad8bd29f
Note That I have set active profile in app.properties as: spring.profiles.active = fetchGame
My Question is: How is the proper way to concatenate two strings into one from spring profiles? Without having a huge amount of code and make it simple and understandable.
First, spring profile has nothing to do w/ injecting values to fields or method/constructor arguments.
Spring profiles allow configuring application context conditionally.
Here is the list to check out:
Make sure to keep fields non-static.
Check if PropertySourcesPlaceholderConfigurer bean is registered in context
Or use #PropertySource("classpath:application.properties") to set a custom property file to resolve values from.
Here is the reference documentation on the feature.
I suggest learning and adopting type safe way to deal w/ configuration properties w/ #ConfigurationProperties annotation.
I'm writing an API Gateway that must route requests based on a MAC address. Example of endpoints:
/api/v2/device/AABBCCDDEEFF
/api/v2/device/AABBCCDDEEFF/metadata
/api/v2/device/search?deviceId=AABBCCDDEEFF
I've written a Custom Predicate Factory that extracts the MAC address, performs the necessary logic to determine what URL the MAC address should be routed to, then stores that information on the ServerWebExchange attributes.
public class CustomRoutePredicateFactory extends AbstractRoutePredicateFactory<CustomRoutePredicateFactory.Config> {
// Fields/Constructors Omitted
private static final String IP_ATTRIBUTE = "assignedIp";
private static final String MAC_ATTRIBUTE = "mac";
#Override
public Predicate<ServerWebExchange> apply(Config config) {
return (ServerWebExchange exchange) -> {
String mac = exchange.getAttributes().get(MAC_ATTRIBUTE);
if(mac == null){
mac = extractMacAddress(exchange);
}
if(!exchange.getAttributes().contains(IP_ATTRIBUTE)){
exchange.getAttributes().put(IP_ATTRIBUTE, findAssignedIp(mac);
}
return config.getRouteIp().equals(exchange.getAttribute(IP_ATTRIBUTE));
});
}
// Config Class & utility methods omitted
}
NOTE: This implementation is greatly simplified for brevity
With this implementation I'm able to guarantee that the MAC is extracted only once and the logic determining what URL the request belongs to is performed only once. The first call to the predicate factory will extract and set that information on ServerWebExchange Attributes and any further calls to the predicate factory will detect those attributes and use them to determine if they match.
This works, but it isn't particularly neat. It would be much easier and simpler if I could somehow set the Exchange Attributes on every single request entering the gateway BEFORE the application attempts to match routes. Then the filter could be a simple predicate that checks for equality on the Exchange Attributes.
I've read through the documentation several times, but nothing seems to be possible. Filters are always scoped to a particular route and run only after a route matches. It might be possible to make the first route be another Predicate that executes the necessary code, sets the expected attributes and always returns false, but can I guarantee that this predicate is always run first? It seems like there should be support for this kind of use case, but I cannot for the life of me find a way that doesn't seem like a hack. Any ideas?
Use a WebFilter instead of a GatewayFilter or a GlobalFilter. They are applied only after the predicates chain. Whereas WebFilter works as an interceptor.
#Component
public class CustomRoutePredicateFactory implements WebFilter, Ordered {
private static final String IP_ATTRIBUTE = "assignedIp";
private static final String MAC_ATTRIBUTE = "mac";
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String mac = (String) exchange.getAttributes().computeIfAbsent(MAC_ATTRIBUTE, key -> extractMacAddress(exchange));
exchange.getAttributes().computeIfAbsent(IP_ATTRIBUTE, key -> findAssignedIp(mac));
return chain.filter(exchange);
}
#Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
I think your approach makes sense since you want it to run before filters.
Have you considered using a GlobalFilter with an order set on it? You can ensure it's always the first filter to run. You can also modify the URL in the ServerWebExchange by mutating the request and setting the GATEWAY_REQUEST_URL_ATTR attribute on the exchange.
Take a look at the PrefixPathGatewayFilterFactory for an example of how to change the URI being routed to.
You can set an order on the Global filter by implementing the org.springframework.core.Ordered interface.
That being said, it still feels a little like a hack but it's an alternative approach.
i think it may help you that overriding the class RoutePredicateHandlerMapping.
see: org.springframework.web.reactive.handler.AbstractHandlerMapping#getHandler
I have Spring Boot project with a MainService that uses several helper methods and other "microservices" to create a FinalObject, that's eventually persisted using Hibernate/JPA. The methods and services may log several messages, and I want these to be associated with the object that was being created when the logged event occurred.
The problem is that the helper methods and microservices don't have access to the finalObject instance, so even though everything is logged, only caught exceptions get saved as a finalObject attribute - not warning messages or other logs:
class FinalObject {
private int value;
private int price;
private List<String> logs;
...
}
class MainService {
#Autowired ValueService valueService; // + other services
void createFinalObject() { // Main method
FinalObject o1 = new FinalObject();
try {
o1.setValue(valueService.getValue("some argument"));
}
catch (Exception e) {
log.error(e.toString()); // Logging using Log4j2
o1.addLog(e.toString()); // If there's an exception, I can easily log it to a o1 attribute.
}
o1.setPrice(calculatePrice(o1.getValue()));
...
}
int calculatePrice(int value) { // Helper method
if (value > getMarketPrice())
log.info("This is very valuable!"); // I need a way to associate this with o1!
...
return price;
}
}
// ValueService.java
int getValue(String arg) {
if (arg.matches("\\d$"))
log.warn("arg ends with a number"); // Must also be saved to o1!
...
return value;
}
Solution 1: Passing o1 around everywhere:
int calculatePrice(int value, FinalObject o1) {
if (value > getMarketPrice()) {
o1.addLog("This is very valuable!"); // Now I have access to o1 here
log.info("This is very valuable!");
}
...
Solution 2: Pass o1's logs attribute around as a modifiable list:
o1.setPrice(calculatePrice(o1.getValue(), o1.getLogs()));
...
int calculatePrice(int value, List<String> finalObjectLogs) {
if (value > getMarketPrice()) {
finalObjectLogs.add("This is very valuable!"); // Directly modify o1's logs attribute
log.info("This is very valuable!");
}
...
Solution 3: Add a log4j2 database appender
A more elegant solution may be do add a database appender to log4j2. A challenge with this is how I can relate these logs to o1. The FinalObject id only gets generated at the very end of createFinalObject(), when it is saved to the database, so I don't have an id when the log statements are executed.
Question:
How can I do this more elegantly than the ways I mentioned above?
Or:
If solution 3 seems like a good approach, how do I implement it?
I am curious. In your example the method calculate price doesn't seem to relate to any object so why would you want to include information about a particular object in it?
On the other hand, if you want to correlate it with other operations being performed in a single Request I would suggest you look at the RequestContext that Log4j-Audit describes. You don't have to use Log4j Audit to implement something like this. It simply leverages the Log4j ThreadContext and defines specific keys that are used. You then initialized the values in the ThreadContext and the beginning of the request and clear it at the end of the Request. You can add items to the ThreadContext as needed. Log4j can then be configured to include specific keys in every log event.
I should note that Spring Cloud Sleuth is doing something that I have been doing for years. To propagate the Request information from one service to the next they simply need to be converted to and from HTTP headers when calling a service and when the service starts. That is why the Log4j-Audit example RequestContext shows annotations to classify the attributes as ClientServer, Local, or Chained. It then provides the components needed to accomplish this.
You can use spring sleuth and logging filters to log the endpoints.
Check the Spring Sleuth Docs;
https://docs.spring.io/spring-cloud-sleuth/docs/2.2.4.RELEASE/reference/html/
You can create a filter do log every endpoint, or log one of them.
And with this loggers you can log the types that you want.
I am trying to build up a multiple-module project, every module would be loaded by executing the PropertyPlaceholderConfigure properties, then I found the properties were not injected when I started to run. Instead, it's still using the default property, for example, I configure the input expression in the main class as Value("${esb.mas.path:/home/mas}")
, and define esb.mas.path=/home/vfs/mas in the corresponding module property file, but the modified values were never used.
By debugging and tracing the code, I found the problem, but I am still not sure if this is a bug from spring framework, in the following lines,
public String org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(String value) {
String result = value;
for (StringValueResolver resolver : this.embeddedValueResolvers) {
if (result == null) {
return null;
}
result = resolver.resolveStringValue(result);
}
return result;
}
I noticed that if the value of first place of embeddedValueResolvers list cannot be caught, they would use the default /home/mas, when continually resolving the embeddedValueResolvers, the parameter for method of resolveStringValue is the returned default value which is /home/mas, the real value of esb.mas.path was not used.
org.springframework.util.PropertyPlaceholderHelper.parseStringValue(String, PlaceholderResolver, Set<String>)
I have to access some application through an mbean so that I can change its application properties. Now i think this can be done in two ways:
First, either I ask the developer of that application to register all the application properties in an arraylist which my mbean will access.
Secondly, if there is any other way, such that the developer will only need to register editable properties and still my mbean can access both readable/editable(r/w) application properties.
Now since I don't know where these application properties are stored in the JVM, is there a way to implement my second point so that the mbean will just need to access that object and it will get all application properties?
Seems like you have some contradicting requirements here.
You want to change minimal code in the application.
You want to be cause to expose all properties for read and/or write.
You may or may not be talking about System.getProperty(...). If not then I guess you are talking about just fields in various objects.
There are (at least) two ways of doing this. Without knowing how you are exporting the mbeans from the source code right now, I can't tailor my answer to your specific config. My answer will instead show how you might use my SimpleJMX package to expose your properties.
If you are talking about System.getProperty(...) then you could write a JMX mbean that could query any property and return the value:
#JmxOperation(description = "Query for property")
public String queryForProperty(String name) {
return System.getProperty(name);
}
If, instead, you need to export of fields from some list of objects then you are going to either have to add annotations to each fields you are exporting or you are going to have to write beans that export the fields through delegation. I see no easy way and I know of no package that will easily export a series of objects for you without some sort of information about what is to be exported and how.
Using SimpleJMX, you can export a field by annotating either the field or the get method:
#JmxAttributeField(description = "Number of hits in the cache")
private int hitCount;
...
// this can also be specified as #JmxAttributeMethod on the getter/setter methods
#JmxAttributeMethod(description = "Number of misses in the cache")
private int getMissCount() {
return missCount;
}
The #JmxAttributeField supports a isWritable = true to allow the value to be set by JMX. You can also annotation the setter with #JmxAttributeMethod to make it writable.
If you don't want to add annotations to each of your classes, then you are going to have to write some sort of JMX container class that exports the values through delegation:
public class JmxPublisher {
private Class1 object1;
private Class2 object2;
...
public JmxPublisher(Class1 object1, Class2 object2) {
this.object1 = object1;
this.object2 = object2;
...
}
#JmxAttributeMethod(description = "Number of hits in the cache")
public int getClass1HitCount() {
return object1.getHitCount();
}
#JmxAttributeMethod(description = "Shutdown the background thread")
public void setClass2Shutdown(boolean shutdown) {
return object2.setShutdown(shutdown);
}
...
}
I also think you should express yourself more clearly.
From what I understood - why not providing a way to query the remote application, and get information on all properties and if they are Read-only, Write-only or RW?
This way the list of properties will not be mentioned at the source code of the client application - maybe you should let the user of the client application see the list of properties, and let him edit the properties he can edit, and prevent him from editing the properties he can't.