I've registered a Spring Post Processor to register some beans based on application properties.
Also I would like to add support for encrypted properties.
I use spring and jasypt spring boot
Here are my configurations:
application.yml:
rest:
jwt-client:
location:
base-url: loc_URL
invoke-timeout:
connection: 5000
read: 500
jwt-config:
token: ENC(gRbmO8Gv2Yb3vUf27nykDPd5/wjVdj+r5REr5HcIh4k=)
Post Processor class:
#RequiredArgsConstructor
public class DynamicClientBeanConfiguration implements BeanDefinitionRegistryPostProcessor {
private static final String BEAN_POSTFIX = "-api-client-config";
private final Environment environment;
#Override
public void postProcessBeanDefinitionRegistry(#NonNull BeanDefinitionRegistry registry) {
Binder.get(environment)
.bind("rest.jwt-client", Bindable.mapOf(String.class, JwtClientConfig.class))
.orElse(new LinkedHashMap<>())
.forEach((key, value) -> {
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(JwtClientConfig.class);
bd.setInstanceSupplier(() -> value);
bd.addQualifier(new AutowireCandidateQualifier(key + BEAN_POSTFIX));
registry.registerBeanDefinition(key + BEAN_POSTFIX, bd);
log.debug("Registered JwtClientConfig bean with #Qualifier(\"{}\")", key + BEAN_POSTFIX);
});
}
#Override
public void postProcessBeanFactory(#NonNull ConfigurableListableBeanFactory beanFactory) {
// Nothing to override
}
}
Custom Configuration Properties class:
#Getter
#Setter
#ConfigurationProperties
public class JwtClientConfig {
private String baseUrl;
private InvokeTimeout invokeTimeout;
private JwtConfig jwtConfig;
#Getter
#Setter
public static class InvokeTimeout {
private int connection;
private int read;
}
#Getter
#Setter
public static class JwtConfig {
private String token;
}
}
However when the following part of code in the post processor executed it binds raw data from properties like ENC(...) but not decrypted as I expect:
Binder.get(environment)
.bind("rest.jwt-client", Bindable.mapOf(String.class, JwtClientConfig.class))
.orElse(new LinkedHashMap<>())
It it possible to bind already decrypted properties in Post Processor?
Related
Here is my code:
I need to save java object value as jsonb in database (r2dbc).
#Getter
#Setter
#ToString
#NoArgsConstructor
#AllArgsConstructor
#Table("scoring")
public class ScoringModel extends BaseModel {
#Column("client_id")
#SerializedName(value = "clientId", alternate = {"client_id"})
private String clientId;
//othres
#Column("languages")
#SerializedName(value = "languages", alternate = {"languages"})
private String languages;
#SerializedName(value = "response", alternate = {"response"})
//Need to save as JsonB
private Object response;
}
Please help me resolve the issue
You need to implement ReadingConverter and WritingConverter and then register them in R2dbcCustomConversions in configuration.
#Bean
public R2dbcCustomConversions myConverters(ConnectionFactory connectionFactory){
var dialect = DialectResolver.getDialect(connectionFactory);
var converters = List.of(…);
return R2dbcCustomConversions.of(dialect, converters);
}
Converters itself should produce JSONs.
If you using Postgres, there are two approaches you can use to map to Postgres JSON/JSONB fields.
use Postgres R2dbc Json type directly in Java entity class.
use any Java type and convert it between Json via registering custom converters.
The first one is simple and stupid.
Declare a json db type field, eg.
metadata JSON default '{}'
Declare the Json type field in your entity class.
class Post{
#Column("metadata")
private Json metadata;
}
For the second the solution, similarly
Declare json/jsonb db type in the schema.sql.
Declare the field type as your custom type.eg.
class Post{
#Column("statistics")
private Statistics statistics;
record Statistics(
Integer viewed,
Integer bookmarked
) {}
}
Declare a R2dbcCustomConversions bean.
#Configuration
class DataR2dbcConfig {
#Bean
R2dbcCustomConversions r2dbcCustomConversions(ConnectionFactory factory, ObjectMapper objectMapper) {
R2dbcDialect dialect = DialectResolver.getDialect(factory);
return R2dbcCustomConversions
.of(
dialect,
List.of(
new JsonToStatisticsConverter(objectMapper),
new StatisticsToJsonConverter(objectMapper)
)
);
}
}
#ReadingConverter
#RequiredArgsConstructor
class JsonToStatisticsConverter implements Converter<Json, Post.Statistics> {
private final ObjectMapper objectMapper;
#SneakyThrows
#Override
public Post.Statistics convert(Json source) {
return objectMapper.readValue(source.asString(), Post.Statistics.class);
}
}
#WritingConverter
#RequiredArgsConstructor
class StatisticsToJsonConverter implements Converter<Post.Statistics, Json> {
private final ObjectMapper objectMapper;
#SneakyThrows
#Override
public Json convert(Post.Statistics source) {
return Json.of(objectMapper.writeValueAsString(source));
}
}
The example codes is here.
Finally verify it with a #DataR2dbcTest test.
#Test
public void testInsertAndQuery() {
var data = Post.builder()
.title("test title")
.content("content of test")
.metadata(Json.of("{\"tags\":[\"spring\",\"r2dbc\"]}"))
.statistics(new Post.Statistics(1000, 200))
.build();
this.template.insert(data)
.thenMany(
this.posts.findByTitleContains("test%")
)
.log()
.as(StepVerifier::create)
.consumeNextWith(p -> {
log.info("saved post: {}", p);
assertThat(p.getTitle()).isEqualTo("test title");
}
)
.verifyComplete();
}
I try to create endpoints /api/streams/positions and /api/streams/orders but confused with configuration.
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/api/streams");
config.setApplicationDestinationPrefixes("streams");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/api/streams/positions", "/api/streams/orders")
.setAllowedOrigins("*")
.withSockJS();
}
}
#Controller("/api/streams")
public class WebSocketController {
private final SimpMessagingTemplate simpMessagingTemplate;
#MessageMapping("/positions")
public void positionsSendToBrowserAuthenticatedClient(PositionDto position) {
this.simpMessagingTemplate.convertAndSendToUser(user.getUsername(),"/streams/position", position);
}
}
What is the configuration I need for the controller work? And what is enableSimpleBroker() and setApplicationDestinationPrefixes() actually means? Can I have one endpoint to get and send messages at the same time?
UPDATE_1 +DTO
#Data
#NoArgsConstructor
#AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class PositionDto {
#JsonProperty("id")
private Long id;
#JsonProperty("type")
private PositionType type;
#JsonProperty("openAt")
private Timestamp openAt;
#JsonProperty("openPrice")
private BigDecimal openPrice;
#JsonProperty("orders")
private List<OrderDto> orders;
}
public enum PositionType {
LONG("LONG"), SHORT("SHORT");
public final String value;
PositionType(String value) {
this.value = value;
}
public String getType() {
return value;
}
}
Maybe I could help because it seems that you're building a kind of trading application, but need more informations: are you building a websocket yourself or do you want to get data from an existing one to build a DTO?
The SendTo is like an transmitter. You just have to "subscribe" to this ws like this, for an angular application's service (for example):
getTestnetCurrentPrice(symbol: string): Observable<any> {
const subject = webSocket(`wss://YOURWEBSOCKETADRESS?param=${param}`);
return this.http.get(`https://YOURWEBSOCKETADRESS?param=${param}`);
}
You can also test your websocket with a simple const like that:
const yourSocket = new WebSocket('wss://YOURWEBSOCKETADRESS' + this.param());
You can find a lot of good tutorials with cryptocurrencies plateform who will help you.
Here is an example of a front Angular financial service example dedicated to a websocket:
#Injectable({
providedIn: 'root'
})
export class CandlecollectService {
private baseUrl = 'http://YOURBACKENDPOINT/';
obj: any;
object: JSON;
constructor(private http: HttpClient) { }
extractCandles(symbol: string, interval: string, limit: number): Observable<any> {
return this.http.get(`${this.baseUrl}${symbol}/${interval}/param`);
}
getCurrentPrice(symbol: string): Observable<any> {
const subject = webSocket(`wss://YOURWEBSOCKETURL?symbol=${symbol}`);
return this.http.get(`https://YOURWEBSOCKERURL?symbol=${symbol}`);
}
}
I am reading configuration from properties file. Now I am having an error that I think is related to sequence of initialization of spring bean.
If I do private Map name = new HashMap<>(); It can be successfully load from properties file.
But now I am having Could not bind properties to ServiceNameConfig
I don't know why this happen and how to deal with it.
#ConfigurationProperties(prefix = "amazon.service")
#Configuration
#EnableConfigurationProperties(ServiceNameConfig.class)
public class ServiceNameConfig {
//If I do private Map<String, String> name = new HashMap<>(); It can be successfully load from properties file.
private Map<String, String> name;
#Bean(value = "serviceName")
public Map<String, String> getName() {
return name;
}
public void setName(Map<String, String> name) {
this.name = name;
}
}
its usage;
#Autowired
#Qualifier("serviceName")
Map<String, String> serviceNameMap;
You can replace your config class to be like this (simpler);
#Configuration
public class Config {
#Bean
#ConfigurationProperties(prefix = "amazon.service")
public Map<String, String> serviceName() {
return new HashMap<>();
}
}
For #ConfigurationProperties injection, you'd need to supply an empty instance of the bean object. Check more about it on baeldung
Or an alternative way, you can use a pojo class to handle the configuration. For example;
You have properties like;
amazon:
service:
valueA: 1
valueB: 2
details:
valueC: 3
valueD: 10
and you can use a pojo like the following;
class Pojo {
private Integer valueA;
private Integer valueB;
private Pojo2 details;
// getter,setters
public static class Pojo2 {
private Integer valueC;
private Integer valueD;
// getter,setters
}
}
and use it in the config class like;
#Configuration
public class Config {
#Bean
#ConfigurationProperties(prefix = "amazon.service")
public Pojo serviceName() {
return new Pojo();
}
}
This question already has answers here:
How to autowire #ConfigurationProperties into #Configuration?
(2 answers)
Closed 3 years ago.
Java 8 and Spring Boot 1.5.8 here. I have the following application.properties file:
logging:
config: 'logback.groovy'
myapp:
hystrixTimeoutMillis: 500
jwt:
expiry: 86400000
secret: 12345
machineId: 12345
spring:
cache:
type: none
Which maps to the following #ConfigurationProperties POJO:
#ConfigurationProperties(prefix = "myapp")
public class MyAppConfig {
private Jwt jwt;
private Long hystrixTimeoutMillis;
private String machineId;
public Jwt getJwt() {
return jwt;
}
public void setJwt(Jwt jwt) {
this.jwt = jwt;
}
public Long getHystrixTimeoutMillis() {
return hystrixTimeoutMillis;
}
public void setHystrixTimeoutMillis(Long hystrixTimeoutMillis) {
this.hystrixTimeoutMillis = hystrixTimeoutMillis;
}
public String getMachineId() {
return machineId;
}
public void setMachineId(String machineId) {
this.machineId = machineId;
}
public static class Jwt {
private Long expiry;
private String secret;
public Long getExpiry() {
return expiry;
}
public void setExpiry(Long expiry) {
this.expiry = expiry;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
}
}
And I have the following #Configuration (injector) class:
#Configuration
public class MyAppInjector implements ApplicationContextAware {
private Logger log = LoggerFactory.getLogger(this.getClass());
private ApplicationContext applicationContext;
#Autowired
private MyAppConfig myAppConfig;
#Bean
public AuthService authService(MyAppConfig myAppConfig) {
return new JwtAuthService(myAppConfig);
}
}
And the following JwtAuthService class:
public class JwtAuthService implements AuthService {
private static final String BEARER_TOKEN_NAME = "Bearer";
private Logger log = LoggerFactory.getLogger(this.getClass());
private MyAppConfig myAppConfig;
#Autowired
public JwtAuthService(MyAppConfig myAppConfig) {
this.myAppConfig = myAppConfig;
}
#Override
public boolean isValidAuthToken(String authToken) {
return true;
}
}
At startup I get the following error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field myAppConfig in com.example.myapp.spring.MyAppInjector required a bean of type 'com.example.myapp.spring.MyAppConfig' that could not be found.
Action:
Consider defining a bean of type 'com.example.myapp.spring.MyAppConfig' in your configuration.
Why am I getting this error? Where am I injecting/configuring things incorrectly?
You are not declaring MyAppConfig as a bean anywhere in your example, #ConfigurationProperties doesn't make annotated class a bean. You can do it as part of MyAppInjector configuration:
#Configuration
public class MyAppInjector {
#Bean
public AuthService authService() {
return new JwtAuthService(myAppConfig());
}
#Bean
public MyAppConfig myAppConfig() {
return new MyAppConfig();
}
}
Class with #ConfigurationProperties should also be bean. You need to annotate it as #Component or manually register in #Configuration class with #Bean annotation (instead of trying to autowire it there)
I have org.springframework.core.convert.converter.Converter
#Component
public class CatalogConverter implements Converter<ServiceCatalogType, Catalog> {
#Override
public Catalog convert(ServiceCatalogType source) {
Catalog catalog = new Catalog();
//convert
return catalog;
}
}
And I register this converter:
#Configuration
public class ConvertersConfig {
private final CatalogConverter catalogConverter;
#Autowired
public ConvertersConfig(CatalogConverter catalogConverter) {
this.catalogConverter = catalogConverter;
}
#Bean(name="conversionService")
ConversionService conversionService() {
ConversionServiceFactoryBean factoryBean = new ConversionServiceFactoryBean();
HashSet<Converter> converters = new HashSet<>();
converters.add(catalogConverter);
factoryBean.setConverters(converters);
factoryBean.afterPropertiesSet();
return factoryBean.getObject();
}
}
But I need pass some parameter to my custom converter. I have some ways:
Pass it in constructor - but how can register this converter?
Use wrapper
class Wrapper{
private ServiceCatalogType catalog;
private String uuid;
}
and change converter like this:
implements Converter<ServiceCatalogType, Wrapper>
Or maybe Spring has another way?
EDIT
I need next.
in service:
pulic void send() {
ServiceCatalogType cs = getServiceCatalogType();//get from net
User user = getUser();//get from db
//convert cs+user to Catalog(all cs fields+ some user fields to catalog)
Catalog catalog = conversionService.convert(cs, user, Catalog.class);
}
EDIT2
Wrapper implementation:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class CatalogWrapper {
private ServiceCatalogType serviceCatalogType;
private User user;
}
CatalogWrapper wrapper = new CatalogWrapper(getServiceCatalog(), getUser);
catalog = conversionService.convert(wrapper, Catalog.class);