Access Job Parameter in Custom ItemProcessor - spring-boot

I am implementing a custom ItemProcessor<I, O> in spring batch for processing data from a Rest api .
I want access some values from jobParameter inside my ItemProcessor class .
Any suggestion on how to do that ?
In Tasklet we can access JobParameter but not sure how to do in ItemProcessor .
MyItemProcessor.java
#Component
public class MyItemProcessor implements ItemProcessor<User, UserDetails> {
#Override
public UserDetails process(User user) throws Exception {
// access values from job parameter here
return null;
}
}

You can make your item processor step-scoped and inject job parameters in it. The following is one way of doing that:
#Component
#StepScope
public class MyItemProcessor implements ItemProcessor<User, UserDetails> {
#Value("#{jobParameters}")
private JobParameters jobParameters;
#Override
public UserDetails process(User user) throws Exception {
// access values from job parameter here
return null;
}
}
You could also inject a specific parameter if you want with something like the following:
#Component
#StepScope
public class MyItemProcessor implements ItemProcessor<User, UserDetails> {
#Value("#{jobParameters['myParameter']}")
private String myParameter;
#Override
public UserDetails process(User user) throws Exception {
// use myParameter as needed here
return null;
}
}
Since field injection is not recommended, you can inject job parameters in your item processor when you define it as a bean, something like:
// Note how nothing related to Spring is used here, and the processor can be unit tested as a regular Java class
public class MyItemProcessor implements ItemProcessor<User, UserDetails> {
private String myParameter;
public MyItemProcessor(String myParameter) {
this.myParameter = myParameter;
}
#Override
public UserDetails process(User user) throws Exception {
// use this.myParameter as needed here
return null;
}
}
Once that in place, you can declare your item processor bean as follows:
#Bean
#StepScope
public MyItemProcessor itemProcessor(#Value("#{jobParameters['myParameter']}") String myParameter) {
return new MyItemProcessor(myParameter);
}
Fore more details about scoped beans, please check the documentation here: Late Binding of Job and Step attributes.

Related

Spring Batch -#BeforeStep not getting invoked in ClassifierCompositeItemWriter

The method is not called with the #BeforeStep decorator that calls the ClassifierCompositeItemWriter, is there a workaround?
step
#Bean
public Step step2(ItemStreamReader <ValidateCandidateRentDto> itemValidateCandidateRentDtoReader,
ValidateCandidateRentProcess
itemValidateCandidateRentDtoProcess,ClassifierCompositeItemWriter<ValidateCandidateRentDto> classifierCompositeItemWriter) throws Exception {
return stepBuilderFactory.get("step2")
.<ValidateCandidateRentDto, ValidateCandidateRentDto>chunk(100)
.reader(itemValidateCandidateRentDtoReader)
.processor(itemValidateCandidateRentDtoProcess)
.writer(classifierCompositeItemWriter)
.build();
}
ClassifierCompositeItemWriter
#Bean
public ClassifierCompositeItemWriter<ValidateCandidateRentDto> classifierCompositeItemWriter(UpdateCandidateRentWriter itemUpdateCandidateRentDtoWriter, ValidateCandidateRentWriter itemValidateCandidateRentDtoWriter) throws Exception {
ClassifierCompositeItemWriter<ValidateCandidateRentDto> classifierCompositeItemWriter = new ClassifierCompositeItemWriter<>();
classifierCompositeItemWriter.setClassifier(new CandidateClassifier(itemUpdateCandidateRentDtoWriter,itemValidateCandidateRentDtoWriter));
return classifierCompositeItemWriter;
}
writer
public class ValidateCandidateRentWriter implements ItemWriter<ValidateCandidateRentDto> {
private static final Logger LOG = LoggerFactory.getLogger(ValidateCandidateRentWriter.class);
#Autowired
private CirCanRepRepository cirCanRepRepository;
private StepExecution stepExecution;
#BeforeStep
public void before(StepExecution stepExecution) {
this.stepExecution=stepExecution;
}
#Override
public void write(List<? extends ValidateCandidateRentDto> list) throws Exception {
this.stepExecution.getJobExecution().getExecutionContext().put("ValidateCandidatesRentDto",list);
}
}
log
java.lang.NullPointerException: null
at cl.ccla.reprogramacion.job.ValidateCandidateRentWriter.write(ValidateCandidateRentWriter.java:31) ~[classes/:?]
at org.springframework.batch.item.support.ClassifierCompositeItemWriter.write(ClassifierCompositeItemWriter.java:69) ~[spring-batch-infrastructure-4.2.4.RELEASE.jar:4.2.4.RELEASE]
Your ValidateCandidateRentWriter is not getting proxied as a StepExecutionListener. You need to declare it as a bean in your application context so that Spring (Batch) introspects the #BeforeStep method and creates a proxy for it.
Another option is to make this component implement StepExecutionListener and register it explicitly in the step definition.
Please check the documentation for more details about this feature.

Is it possible to define a custom rest template?

I'm trying to define a common bean to be used for all my application so to add inside a logger and other logic. My idea would be:
public class MyRestTemplate extends RestTemplate{
Then:
#Configuration
public class RestTemplateConfig {
#Bean
public MyRestTemplate myRestTemplate(RestTemplateBuilder builder){
return (MyRestTemplate) builder.build(); //throws classcast exception!
}
}
What am I doing wrong? Is there another way? I want to be sure that people will have to use my customized class.
If you want some customizations in your restTemplate you could define a class that implements RestTemplateCustomizer and add a custom interceptor to it.
public class CustomRestTemplateCustomizer implements RestTemplateCustomizer {
#Override
public void customize(RestTemplate restTemplate) {
restTemplate.getInterceptors().add(new CustomClientHttpRequestInterceptor());
}
}
Then you have to define that custom interceptor for all the requests going out of this restTemplate with
public class CustomClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
// This is where you can do a lot of thing with this request like logging
return execution.execute(request, body);
}
}
And finally, just define a bean for the custom restTemplate you have written
#Bean
public CustomRestTemplateCustomizer customRestTemplateCustomizer() {
return new CustomRestTemplateCustomizer();
}
builder.build() returns a RestTemplate, not a MyRestTemplate.
If you change your code as shown below you would create a bean named myRestTemplate. Spring use the name of the method as bean name if you don't override it in the #Bean annotation.
#Configuration
public class RestTemplateConfig {
#Bean
public RestTemplate myRestTemplate(RestTemplateBuilder builder){
return builder.build(); //throws classcast exception!
}
}
Please also see https://docs.spring.io/spring-boot/docs/1.5.x/reference/html/boot-features-restclient.html

Spring Boot - Store current user in global variable and initialise from API call when #service bean is created

I am creating a microservice architectured project with Zuul as gateway. I have all authentication handled in a service called common-service. I have exposed a API from common-service to return current logged in user. This is working fine.
Now, I have another microservice called inventory. In service class of inventory, I want to use current loggedin username in multiple methods. So, I am making a webclient call to common-service and getting current username. This is working fine but I am making a webclient API call to common service everytime I require username. Example - if I add a new entry, doing API call, then on update again API call etc. this seems not to be an optimised way
so problem is - I want to make this API call at global level. i.e. whenever my service bean is autowired, this API call should be made and username should be store somewhere which I can use across methods in my service call.
I tried #PostConstruct and #SessionAttributes but not able to get exact problem solved.
Can somebody help me with best suited solution or concept for handling this issue.
Below are code snippets
public class LeadService
{
#Autowired
WebClient.Builder webClientBuilder;
#Autowired
UserDetailsService userDetailsService;
//more autowiring
private void setLeadFields(Lead lead, #Valid LeadCreateData payload,String type)
{
//some logic
if(type.equalsIgnoreCase("create"))
{
lead.setAsigneeId(userDetailsService.getCurrentUser().getId());
lead.setCreatorId(userDetailsService.getCurrentUser().getId());
}
else if(type.equalsIgnoreCase("update"))
{
//some logic
}
}
private StatusEnum setLeadStatus(Lead lead, StatusEnum status,String string)
{
LeadStatus lstatus=null;
switch(string)
{
case "create":
lstatus = new LeadStatus(lead.getLeadId(),status,userDetailsService.getCurrentUser().getId(),userDetailsService.getCurrentUser().getId());
lsRepo.save(lstatus);
break;
case "udpate":
lstatus= lsRepo.FindLeadStatusByLeadID(lead.getLeadId()).get(0);
if(!lstatus.getStatus().equals(lstatus))
{
lstatus = new LeadStatus(lead.getLeadId(),status,userDetailsService.getCurrentUser().getId(),userDetailsService.getCurrentUser().getId());
lsRepo.save(lstatus);
}
break;
}
return lstatus.getStatus();
}
private Address setAddress(#Valid LeadCreateData payload,Address address)
{
//some setters
address.setCreator(userDetailsService.getCurrentUser().getId());
return aRepo.save(address);
}
As you can see, I am using userDetailsService.getCurrentUser().getId() in many places. I am getting this id from below autowired method. But my one API call is required everytime I need this id.
#Service
public class UserDetailsService
{
#Autowired
WebClient.Builder webClientBuilder;
#Autowired
HttpServletRequest request;
#Value("${common.serverurl}")
private String reqUrl;
public UserReturnData getCurrentUser()
{
UserReturnData userDetails = webClientBuilder.build()
.get()
.uri(reqUrl+"user/me")
.header("Authorization", request.getHeader("Authorization"))
.retrieve()
.bodyToMono(UserReturnData.class)
.block();
return userDetails;
}
}
I want a optimal way where I can call this API method to get current user only once. and I can use it throughout my #service class.
Create OncePerPrequestFilter or GenericFilterBean which has your UserDetailsService autowired.
And also you want to create something similar to RequestContextHolder or SecurityContextHolder which can hold your UserReturnData in a ThreadLocal variable. Look at those two spring classes to get idea but yours can be much simpler. Lets call it UserReturnDataContextHolder.
In the filter, you created in step1, when the request comes in populate it and when the response is leaving, clear it.
Now you can access it anywhere in the service via UserReturnDataContextHolder.getUserReturnData() and you are not making multiple calls either
Edit: The section below is contributed by Sridhar Patnaik as reference -
Below code to get it working
Added a class to store currentuserid
public class CurrentUser
{
private Long currentUserId;
//getter setter
}
Added a current user filter to intercept request and fetch current user.
public class CurrentUserFilter implements Filter
{
#Autowired
private CurrentUser currentUser;
#Autowired
UserDetailsService UserDetailsService;
#Override
public void init(FilterConfig arg0) throws ServletException {
// NOOP
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
try
{
this.currentUser.setCurrentUserId(UserDetailsService.getCurrentUser().getId());
chain.doFilter(servletRequest, servletResponse);
}
finally
{
this.currentUser.clear();
}
}
#Override
public void destroy() {
// NOOP
}
}
Added required AppConfig
#Configuration
public class AppConfig
{
#Bean
public Filter currentUserFilter() {
return new CurrentUserFilter();
}
#Bean
public FilterRegistrationBean tenantFilterRegistration() {
FilterRegistrationBean result = new FilterRegistrationBean();
result.setFilter(this.currentUserFilter());
result.setUrlPatterns(Lists.newArrayList("/*"));
result.setName("Tenant Store Filter");
result.setOrder(1);
return result;
}
#Bean(destroyMethod = "destroy")
public ThreadLocalTargetSource threadLocalTenantStore() {
ThreadLocalTargetSource result = new ThreadLocalTargetSource();
result.setTargetBeanName("tenantStore");
return result;
}
#Primary
#Bean(name = "proxiedThreadLocalTargetSource")
public ProxyFactoryBean proxiedThreadLocalTargetSource(ThreadLocalTargetSource threadLocalTargetSource) {
ProxyFactoryBean result = new ProxyFactoryBean();
result.setTargetSource(threadLocalTargetSource);
return result;
}
#Bean(name = "tenantStore")
#Scope(scopeName = "prototype")
public CurrentUser tenantStore() {
return new CurrentUser();
}
}
And then autowired CurrentUser to my existing service class.
{..
#Autowired
CurrentUser currentUser;
...
private void setLeadFields(Lead lead, #Valid LeadCreateData payload,String type)
{
//some logic
if(type.equalsIgnoreCase("create"))
{
lead.setAsigneeId(currentUser.getCurrentUserId());
lead.setCreatorId(currentUser.getCurrentUserId());
lead.setAddress(setAddress(payload, new Address()));
}
else if(type.equalsIgnoreCase("update"))
{
lead.setAsigneeId(userDetailsService.getUserFromId(payload.getAssigneeId()).getId());
lead.setAddress(setAddress(payload,lead.getAddress()));
}
}

Spring Data Rest custom argument Resolver

So i am trying to add a custom argument resolver to my Spring-Data-Rest project.
I am devolping a multi-tenant application, and need to filter data based on a users tenant-id.
So i wrote a simple annotation and ArgumentResolver to query my tenant repository and inject a tenant Object as Parameter on some needed Methods:
Handler:
#AllArgsConstructor
public class TenantInjector implements HandlerMethodArgumentResolver {
private final TenantStore tenantStore;
private final TenantRepository tenantRepository;
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
if(! methodParameter.hasParameterAnnotation(InjectTenant.class)) {
return false;
}
return true;
}
#Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
return tenantRepository.findById(tenantStore.getId()).get();
}
}
This handler queries the tenantRepository to find the current tenant by its Id, which is set when the incoming requests security token is parsed.
To register the handler, i do the following:
#Configuration
public class DispatcherContext implements WebMvcConfigurer {
private final TenantStore tenantStore;
private final TenantRepository tenantRepository;
#Autowired
public DispatcherContext(TenantStore tenantStore, TenantRepository tenantRepository) {
this.tenantStore = tenantStore;
this.tenantRepository= tenantRepository;
}
#Override
public void addArgumentResolvers(
List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new TenantInjector(tenantStore, tenantRepository));
}
}
This works nice as long as the corrensponding Controller is annotated with either #Controller or #RestController
As the #RepositoryRestController has an other context, this configuration is ignored. How can I add the same ArgumentResolver to the Spring-Data-Rest configuration?
It might be an option to just switch the annotations, but i would like to rather stick with this approche, as links get generated by spring-data-rest.
Has anyone stumble over this to?
Your issue could be that you registered your custom argument resolver in your WebMvcConfigurer. Spring Data Rest seems to work in a different context, so you have to register your custom argument resolver in your RepositoryRestMvcConfiguration.
#Configuration
public class RepositoryConfiguration extends RepositoryRestMvcConfiguration {
public RepositoryConfiguration(ApplicationContext context, ObjectFactory<ConversionService> conversionService)
{
super(context, conversionService);
}
#Override
protected List<HandlerMethodArgumentResolver> defaultMethodArgumentResolvers()
{
List<HandlerMethodArgumentResolver> resolvers =
new ArrayList<>(super.defaultMethodArgumentResolvers());
resolvers.add(new TenantInjector(tenantStore, tenantRepository));
return resolvers;
}
}
Answer inspired by: https://github.com/tkaczmarzyk/specification-arg-resolver/issues/6#issuecomment-111952898

Spring MVC instantiate object on request attribute

Assume I am creating a PrinterService class that has a AbstractPrinter object. AbstractPrinter is subclassed by classes such as HPPrinter, FilePrinter etc.
The exact kind of printer object to be used is mentioned in the RequestParam object passed to my Controller (it is a request attribute).
Is there any way I can inject the right kind of concrete printer class using Spring?
All the other dependencies are injected using #Autowired annotation. How to inject this one?
You can create and load a factory of AbstractPrinter objects during container startup as shown below and dynamically call the respective the AbstractPrinter's print() (or your own method) based on the input parameter (comes from controller) to the service.
In the below code for PrinterServiceImpl class, the main point is that all of the List<AbstractPrinter> will be injected by Spring container (depends upon how many implementation classes you provide like HPPrinter, etc..). Then you will load those beans into a Map during container startup with printerType as key.
#Controller
public class YourController {
#Autowired
private PrinterService printerService;
public X myMethod(#RequestParam("input") String input) {
printerService.myServiceMethod(input);
//return X
}
}
PrinterServiceImpl class:
public class PrinterServiceImpl implements PrinterService {
#Autowired
private List<AbstractPrinter> abstractPrinters;
private static final Map<String,AbstractPrinter> myPrinters = new HashMap<>();
#PostConstruct
public void loadPrinters() {
for(AbstractPrinter printer : abstractPrinters) {
myPrinters.put(printer.getPrinterType(), printer);
}
}
//Add your other Autowired dependencies here
#Override
public void myServiceMethod(String input){//get input from controller
AbstractPrinter abstractPrinter= myPrinters.get(input);
abstractPrinter.print();//dynamically calls print() depending on input
}
}
HPPrinter class:
#Component
public class HPPrinter implements AbstractPrinter {
#Override
public String getPrinterType() {
return "HP";
}
#Override
public void print() {
// Your print code
}
}
FilePrinter class:
#Component
public class FilePrinter implements AbstractPrinter {
#Override
public String getPrinterType() {
return "FILE";
}
#Override
public void print() {
// Your print code
}
}
You could create a dedicated PrinterService instance per AbstractPrinter concrete class. For example you could achieve this using Spring configuration which follow the factory pattern:
#Configuration
public class PrinterServiceConfiguration {
#Autowired
private HPPrinter hpPrinter;
#Autowired
private FilePrinter filePrinter;
#Bean
public PrinterService hpPrinterService() {
return new PrinterService(hpPrinter);
}
#Bean
public PrinterService filePrinterService() {
return new PrinterService(filePrinter);
}
public PrinterService findPrinterService(PrinterType type){
if (type == HP)
return hpPrinterService();
....
}
}
Then in your controller, inject PrinterServiceConfiguration then call findPrinterService with the right printer type.
Don't forget to add PrinterServiceConfiguration at your configuration #Import.
If the list of printer is dynamic you could switch to prototype bean :
#Configuration
public class PrinterServiceConfiguration {
#Autowired
private List<AbstractPrinter> printers;
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public PrinterService createPrinterService(PrinterType type){
return new PrinterService(findPrinterByType(type));
}
private Printer findPrinterByType(PrinterType type) {
// iterate over printers then return the printer that match type
// throw exception if no printer found
}
}

Resources