Spring with Quartz worker initialize a member only once? - spring

I have a QuartzJob with multiple methods that call the same rest API
The class holds a member (configuration ) with the rest api configuration.
The rest api configuration is initialize once using getConfiguration method.
public QuartzJob implements Job{
#Value("${API_URL}")private String apiUrl;
ApiCallConfiguration configuration = getConfiguration();
method1(){call api using configuration }
method2(){call api using configuration }
method3(){call api using configuration }
}
When I try to create a constructor, it fails probably because the API values are not constructed yet... #Value("${API_URL}")private String apiUrl;
Exception below.
what is the correct way to initialize the configuration property only once in the QuartzJob after everything is set up?
Caused by: java.lang.NullPointerException: Configuration key Caused by: java.lang.NullPointerException: Configuration key API_URL has no value
at org.apache.commons.lang3.Validate.notBlank(Validate.java:451)
at com.xerox.printerTech.quartz.jobs.PrinterVerificationJob.getPrinterRestApiConfig(PrinterVerificationJob.java:722)
at com.xerox.printerTech.quartz.jobs.PrinterVerificationJob.<init>(PrinterVerificationJob.java:164)
... 10 more has no value
at org.apache.commons.lang3.Validate.notBlank(Validate.java:451)
at com.xerox.printerTech.quartz.jobs.PrinterVerificationJob.getPrinterRestApiConfig(PrinterVerificationJob.java:722)
at com.xerox.printerTech.quartz.jobs.PrinterVerificationJob.<init>(PrinterVerificationJob.java:164)
... 10 more

I was able to resolve this issue using #Component and #Autowired
here is a sample code:
public QuartzJob implements Job{
#Autowired
ApiCallConfiguration configuration;
method1(){configuration.getApiUrl(); }
method2(){configuration.apiUseSSL(); }
method3(){configuration.getApiUserName(); }
}
#Component
public class ApiCallConfiguration {
public ApiCallConfiguration(
#Value("${API_URL}") String apiUrl,
#Value("${API_URL_SSL}") String apiUseSSL
#Value("${API_USER_NAME}") String apiUserName
){
this.apiUrl = apiUrl;
this.apiUseSSL = apiUseSSL;
this.apiUserName = apiUserName;
}
}

Related

Validating Spring Kafka payloads

I am trying to set up a service that has both a REST (POST) endpoint and a Kafka endpoint, both of which should take a JSON representation of the request object (let's call it Foo). I would want to make sure that the Foo object is valid (via JSR-303 or whatever). So Foo might look like:
public class Foo {
#Max(10)
private int bar;
// Getter and setter boilerplate
}
Setting up the REST endpoint is easy:
#PostMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> restEndpoint(#Valid #RequestBody Foo foo) {
// Do stuff here
}
and if I POST, { "bar": 9 } it processes the request, but if I post: { "bar": 99 } I get a BAD REQUEST. All good so far!
The Kafka endpoint is easy to create (along with adding a StringJsonMessageConverter() to my KafkaListenerContainerFactory so that I get JSON->Object conversion:
#KafkaListener(topics = "fooTopic")
public void kafkaEndpoint(#Valid #Payload Foo foo) {
// I shouldn't get here with an invalid object!!!
logger.debug("Successfully processed the object" + foo);
// But just to make sure, let's see if hand-validating it works
Validator validator = localValidatorFactoryBean.getValidator();
Set<ConstraintViolation<SlackMessage>> errors = validator.validate(foo);
if (errors.size() > 0) {
logger.debug("But there were validation errors!" + errors);
}
}
But no matter what I try, I can still pass invalid requests in and they process without error.
I've tried both #Valid and #Validated. I've tried adding a MethodValidationPostProcessor bean. I've tried adding a Validator to the KafkaListenerEndpointRegistrar (a la the EnableKafka javadoc):
#Configuration
public class MiscellaneousConfiguration implements KafkaListenerConfigurer {
private Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
LocalValidatorFactoryBean validatorFactory;
#Override
public void configureKafkaListeners(KafkaListenerEndpointRegistrar registrar) {
logger.debug("Configuring " + registrar);
registrar.setMessageHandlerMethodFactory(kafkaHandlerMethodFactory());
}
#Bean
public MessageHandlerMethodFactory kafkaHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setValidator(validatorFactory);
return factory;
}
}
I've now spent a few days on this, and I'm running out of other ideas. Is this even possible (without writing validation into every one of my kakfa endpoints)?
Sorry for the delay; we are at SpringOne Platform this week.
The infrastructure currently does not pass a Validator into the payload argument resolver. Please open an issue on GitHub.
Spring kafka listener by default do not scan for #Valid for non Rest controller classes. For more details please refer this answer
https://stackoverflow.com/a/71859991/13898185

Spring boot 2 Undertow, not work set UndertowOptions.ALLOW_UNESCAPED_CHARACTERS_IN_URL

Springboot 2.0
When navigating through type references: localhost:9093/?foo=bar|bar
Return error: Caused by: java.net.URISyntaxException: Illegal
character in query
I'm adding an extra option:
#Component
public class MyWebServerCustomizer implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
#Override
public void customize(UndertowServletWebServerFactory factory) {
factory.addBuilderCustomizers(builder-> builder.setServerOption(UndertowOptions.ALLOW_UNESCAPED_CHARACTERS_IN_URL, Boolean.TRUE));
}
}
Does not work, the exception is still.
What am I doing wrong?
Thanks.
#Configuration
public class HttpConfig {
#Bean
public UndertowServletWebServerFactory servletWebServerFactory(ApplicationProperties properties) {
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
factory.addBuilderCustomizers((UndertowBuilderCustomizer) builder ->
builder.setServerOption(UndertowOptions.ALLOW_UNESCAPED_CHARACTERS_IN_URL, Boolean.TRUE));
return factory;
}
}
You should encode the parameters
myUrl = "localhost:9093/?" + URLEncoder.encode("foo=bar|bar", "UTF-8");
please refer here for url encoding. Say suppose if you want to send bar|bar. You can send as bar%7Cbar.
Here %7C is PIPE Character.

how to change the #FeignClient name in runtime

I use Spring Cloud Netflix to build my micro service .
#FeignClient(name = "ms-cloud",configuration = MsCloudClientConfig.class)
public interface TestClient {
/**
* #return
*/
#RequestMapping(value = "/test", method = RequestMethod.GET)
String test();
}
I want to change the name to ms-cloud-pre when some special user.
Anyone can give some advice?
According to the documentation feign supports placeholders in the name and url fields.
#FeignClient(name = "${store.name}")
public interface StoreClient {
//..
}
So you could set store.name=storeProd at runtime using normal spring boot configuration mechanisms.
To create a spring-cloud Feign client at runtime in situations where you don't know the service-id until the point of call:
import org.springframework.cloud.openfeign.FeignClientBuilder;
#Component
public class InfoFeignClient {
interface InfoCallSpec {
#RequestMapping(value = "/actuator/info", method = GET)
String info();
}
FeignClientBuilder feignClientBuilder;
public InfoFeignClient(#Autowired ApplicationContext appContext) {
this.feignClientBuilder = new FeignClientBuilder(appContext);
}
public String getInfo(String serviceId) {
InfoCallSpec spec =
this.feignClientBuilder.forType(InfoCallSpec.class, serviceId).build();
return spec.info();
}
}
That actually is possible. In Spring Cloud Zookeeper we're doing a similar thing since the name of the service in the Feign client is not the one that is there in the in Zookeeper. It can be an alias presented in the yaml file. Here you have the code example https://github.com/spring-cloud/spring-cloud-zookeeper/blob/master/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/DependencyRibbonAutoConfiguration.java#L54 and here you have the description of the dependencies feature - https://github.com/spring-cloud/spring-cloud-zookeeper/blob/master/docs/src/main/asciidoc/spring-cloud-zookeeper.adoc#using-the-zookeeper-dependencies

Spring 3.1 basic usage of Envirionment and PropertySource

I want to inject property values into the Spring context when it is started.
I am trying to do this using the new Environment and PropertySource features of Spring 3.1.
In the class in which loads the Spring context, I define my own PropertySource class as follows:
private static class CustomPropertySource extends PropertySource<String> {
public CustomPropertySource() {super("custom");}
#Override
public String getProperty(String name) {
if (name.equals("region")) {
return "LONDON";
}
return null;
}
Then, I add this property source to the application context:
ClassPathXmlApplicationContext springIntegrationContext =
new ClassPathXmlApplicationContext("classpath:META-INF/spring/ds-spring-intg-context.xml");
context.getEnvironment().getPropertySources().addLast( new CustomPropertySource());
context.refresh();
context.start();
In one of my beans, I try to access the property value:
#Value("${region}")
public void setRegion(String v){
...
}
bur recieve the folowing error:
java.lang.IllegalArgumentException: Caused by:
java.lang.IllegalArgumentException: Could not resolve placeholder
'region' in string value [${region}]
Any help is greatly appreciated
when you pass an XML file location as a constructor argument to ClassPathXmlApplicationContext(..) it straight away does the context.refresh()/context.start() methods. so by passing in your XML locations, you're essentially doing it all in one pass and the context is already started/loaded by the time you get to call context.getEnvironment().getPropertySources....
try this;
ClassPathXmlApplicationContext springIntegrationContext =
new ClassPathXmlApplicationContext();
context.getEnvironment().getPropertySources().addLast( new CustomPropertySource());
context.setLocations("classpath:META-INF/spring/ds-spring-intg-context.xml");
context.refresh();
it will set your sources, then your xml, then start the app context.

How do I override a scoped bean for tests?

I have this bean in my Spring Java config:
#Bean
#Scope( proxyMode=ScopedProxyMode.TARGET_CLASS, value=SpringScopes.DESKTOP )
public BirtSession birtSession() {
return new BirtSession();
}
For tests, I need a mock without a scope (there is no "Desktop" scope in the test). But when I create a configuration for my test which imports the above configuration and contains:
#Bean
public BirtSession birtSession() {
return new MockSession();
}
I get a "Desktop" scoped mocked bean :-(
How do I make Spring "forget" the #Scope annotation?
PS: It works when I don't use #Import and use copy&paste but I don't want to do that.
The problem seems to be in ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod() that uses ScopedProxyCreator.createScopedProxy() static method to create the scoped bean definition:
// replace the original bean definition with the target one, if necessary
BeanDefinition beanDefToRegister = beanDef;
if (proxyMode != ScopedProxyMode.NO) {
BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
new BeanDefinitionHolder(beanDef, beanName), this.registry, proxyMode == ScopedProxyMode.TARGET_CLASS);
beanDefToRegister = proxyDef.getBeanDefinition();
}
As the BeanDefinitionHolder returns a RootBeanDefinition instead of ConfiguratioClassBeanDenition the scoped proxy bean definition (ie, the ScopedProxyFactoryBean) cannot be overriden by another Java Configuration class.
A workaround could be declaring the scoped beans to override in a xml configuration file and importing it with #ImportResource.
The problem isn't Spring keeping the annotation, the problem is that Spring first tries to parse the "productive" config and in order to do that, it checks whether the scope is available. Spring checks scopes eagerly. So it never gets to the second/overriding bean definition.
Create a dummy scope:
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.ObjectFactory;
public class MockSpringScope implements org.springframework.beans.factory.config.Scope {
private Map<String, Object> objects = new HashMap<String, Object>();
#Override
public Object get( String name, ObjectFactory<?> objectFactory ) {
Object result = objects.get( name );
if( null == result ) {
result = objectFactory.getObject();
objects.put( name, result );
}
return result;
}
#Override
public Object remove( String name ) {
return objects.remove( name );
}
#Override
public void registerDestructionCallback( String name, Runnable callback ) {
// NOP
}
#Override
public Object resolveContextualObject( String key ) {
// NOP
return null;
}
#Override
public String getConversationId() {
// NOP
return null;
}
}
and register that under as "Desktop" scope. That will Spring allow to successfully parse the production config.

Resources