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
Related
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.
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()));
}
}
I would like to create own class that will transform HTTP request and initializes object from this HTTP request in my Spring MVC application. I can create object by defining parameters in method but I need to do mapping in my own way and do it manually.
How can I do it with my own implementation that will pass to Spring and it will use it seamlessly?
Update1
Solution that kindly provided Bohuslav Burghardt doesn't work:
HTTP Status 500 - Request processing failed; nested exception is
java.lang.IllegalStateException: An Errors/BindingResult argument is
expected to be declared immediately after the model attribute, the
#RequestBody or the #RequestPart arguments to which they apply: public
java.lang.String
cz.deriva.derivis.api.oauth2.provider.controllers.OAuthController.authorize(api.oauth2.provider.domain.AuthorizationRequest,org.springframework.ui.Model,org.springframework.validation.BindingResult,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
Maybe I should mention that I use own validator:
public class RequestValidator {
public boolean supports(Class clazz) {
return AuthorizationRequest.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
AuthorizationRequest request = (AuthorizationRequest) obj;
if ("foobar".equals(request.getClientId())) {
e.reject("clientId", "nomatch");
}
}
}
and declaration of my method in controller (please not there is needed a validation - #Valid):
#RequestMapping(value = "/authorize", method = {RequestMethod.GET, RequestMethod.POST})
public String authorize(
#Valid AuthorizationRequest authorizationRequest,
BindingResult result
) {
}
I have two configurations classes in my application.
#Configuration
#EnableAutoConfiguration
#EnableWebMvc
#PropertySource("classpath:/jdbc.properties")
public class ApplicationConfig {
}
and
#Configuration
#EnableWebMvc
public class WebappConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthorizationRequestArgumentResolver());
}
}
What is wrong?
Update 2
The problem is with param BindingResult result, when I remove it it works. But I need the result to process it when some errors occur.
If I understand your requirements correctly, you could implement custom HandlerMethodArgumentResolver for that purpose. See example below for implementation details:
Model object
public class AuthorizationRequestHolder {
#Valid
private AuthorizationRequest authorizationRequest;
private BindingResult bindingResult;
// Constructors, accessors omitted
}
Resolver
public class AuthorizationRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return AuthorizationRequestHolder.class.isAssignableFrom(parameter.getParameterType());
}
#Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
// Map the authorization request
AuthorizationRequest authRequest = mapFromServletRequest(request);
AuthorizationRequestHolder authRequestHolder = new AuthorizationRequestHolder(authRequest);
// Validate the request
if (parameter.hasParameterAnnotation(Valid.class)) {
WebDataBinder binder = binderFactory.createBinder(webRequest, authRequestHolder, parameter.getParameterName());
binder.validate();
authRequestHolder.setBindingResult(binder.getBindingResult());
}
return authRequestHolder;
}
}
Configuration
#Configuration
#EnableWebMvc
public class WebappConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthorizationRequestMethodArgumentResolver());
}
}
Usage
#RequestMapping("/auth")
public void doSomething(#Valid AuthRequestHolder authRequestHolder) {
if (authRequestHolder.getBindingResult().hasErrors()) {
// Process errors
}
AuthorizationRequest authRequest = authRequestHolder.getAuthRequest();
// Do something with the authorization request
}
Edit: Updated answer with workaround to non-supported usage of #Valid with HandlerMethodArgumentResolver parameters.
I have a spring (3.1) application with a service and dao layer.
I try to use requestfactory (gwt 2.4) withi this spring layer.
Here some of my class
My domain class
public class Account {
Long id;
String username;
// get, set
}
The bridge between spring and gwt
public class SpringServiceLocator implements ServiceLocator {
#Override
public Object getInstance(Class<?> clazz) {
HttpServletRequest request = RequestFactoryServlet.getThreadLocalRequest();
ServletContext servletContext = request.getSession().getServletContext();
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
return context.getBean(clazz);
}
}
My account proxy
#ProxyFor(value=Account.class, locator = AccountLocator.class)
public interface AccountProxy extends EntityProxy{
public Long getId();
public String getUsername();
public void setUsername(String userName);
public void setId(Long id);
}
RequestContext class
#Service(locator = SpringServiceLocator.class, value =AccountService.class)
public interface AccountRequest extends RequestContext {
Request<List<AccountProxy>> loadAllAccounts();
}
My requestFactory class
public interface AccountRequestFactory extends RequestFactory {
AccountRequest accountRequest();
}
My spring service
public interface AccountService {
public List<Account> loadAllAccounts();
}
#Service
public class AccountServiceImpl implements AccountService{
#Autowired
private AccountDAO accountDAO;
}
Account locator to avoid to put method in the entity
public class AccountLocator extends Locator<Account, Long> {
#Autowired
private AccountDAO accountDAO;
#Override
public Account create(Class<? extends Account> clazz) {
return new Account();
}
}
applicationContext.xml
<context:annotation-config />
<context:component-scan base-package="com.calibra" />
<bean id="accountService" class="org.calibra.server.service.AccountServiceImpl"/>
<bean id="accountDAO" class="org.calibra.server.dao.AccountDAOImpl"/>
The demo work but i get this error:
com.google.web.bindery.requestfactory.server.UnexpectedException: Could not find static method with a single parameter of a key type
Also on my AccountProxy i get this complain (a warning)
The domain type org.calibra.domain.Account has no Account findAccount(java.lang.Long) method. Attempting to send a AccountProxy to the server will result in a server error.
I don't want to add a find methond in my domain class.
I tried to put this method in my spring service, but i get the same warning.
Edit with the Locator that work fine
Just strange i need to put bean in the applicationContext, context:annotation and context:component-scan seem useless
Any idea?
The domain type org.calibra.domain.Account has no Account findAccount(java.lang.Long) method.
If you don't provide a find method of some kind, RequestFactory has no way of reconstituting objects when they get to the server - it can only create brand new ones, which prevents it from merging with existing data. Take this away, and you might as well have RPC again.
If you don't want static methods, provide a Locator instance which is able to find objects. From https://developers.google.com/web-toolkit/doc/latest/DevGuideRequestFactory#locators:
What if you don't want to implement persistence code in an entity itself? To implement the required entity locator methods, create an entity locator class that extends Locator:
public class EmployeeLocator extends Locator<Employee, Long> {
#Override
public Employee create(Class<? extends Employee> clazz)
{
return new Employee();
}
...
}
Then associate it with the entity in the #ProxyFor annotation:
#ProxyFor(value = Employee.class, locator = EmployeeLocator.class)
public interface EmployeeProxy extends EntityProxy {
...
}
You'll need to implement all of the methods, not just create - and the main one you are interested in is find(Class, Long). It may be possible to use one single Locator type for all proxies - as of 2.4.0 and 2.5.0-rc1 it is safe to fail to implement getDomainType(), and all of the other methods that need to know the exact type are provided with it as an argument.
Here is an example of what this can look like with JPA and Guice, but I think the idea is clear enough that it can be implemented with Spring and whatever persistence mechanism you are using. Here, all entities are expected to implement HasVersionAndId, allowing the locator to generalize on how to invoke getVersion and getId - you might have your own base class for all persisted entities.
(from https://github.com/niloc132/tvguide-sample-parent/blob/master/tvguide-client/src/main/java/com/acme/gwt/server/InjectingLocator.java)
public class InjectingLocator<T extends HasVersionAndId> extends Locator<T, Long> {
#Inject
Provider<EntityManager> data;
#Inject
Injector injector;
#Override
public T create(Class<? extends T> clazz) {
return injector.getInstance(clazz);
}
#Override
public T find(Class<? extends T> clazz, Long id) {
return data.get().find(clazz, id);
}
#Override
public Class<T> getDomainType() {
throw new UnsupportedOperationException();//unused
}
#Override
public Long getId(T domainObject) {
return domainObject.getId();
}
#Override
public Class<Long> getIdType() {
return Long.class;
}
#Override
public Object getVersion(T domainObject) {
return domainObject.getVersion();
}
}
I would like to create a Spring's bean producer method which is aware who invoked it, so I've started with the following code:
#Configuration
public class LoggerProvider {
#Bean
#Scope("prototype")
public Logger produceLogger() {
// get known WHAT bean/component invoked this producer
Class<?> clazz = ...
return LoggerFactory.getLogger(clazz);
}
}
How can I get the information who wants to get the bean injected?
I'm looking for some equivalent of CDI's InjectionPoint in Spring world.
Spring 4.3.0 enables InjectionPoint and DependencyDescriptor parameters for bean producing methods:
#Configuration
public class LoggerProvider {
#Bean
#Scope("prototype")
public Logger produceLogger(InjectionPoint injectionPoint) {
Class<?> clazz = injectionPoint.getMember().getDeclaringClass();
return LoggerFactory.getLogger(clazz);
}
}
By the way, the issue for this feature SPR-14033 links to a comment on a blog post which links to this question.
As far as I know, Spring does not have such a concept.
Then only thing that is aware of the point that is processed is a BeanPostProcessor.
Example:
#Target(PARAMETER)
#Retention(RUNTIME)
#Documented
public #interface Logger {}
public class LoggerInjectBeanPostProcessor implements BeanPostProcessor {
public Logger produceLogger() {
// get known WHAT bean/component invoked this producer
Class<?> clazz = ...
return LoggerFactory.getLogger(clazz);
}
#Override
public Object postProcessBeforeInitialization(final Object bean,
final String beanName) throws BeansException {
return bean;
}
#Override
public Object postProcessAfterInitialization(final Object bean,
final String beanName) throws BeansException {
ReflectionUtils.doWithFields(bean.getClass(),
new FieldCallback() {
#Override
public void doWith(final Field field) throws IllegalArgumentException, IllegalAccessException {
field.set(bean, produceLogger());
}
},
new ReflectionUtils.FieldFilter() {
#Override
public boolean matches(final Field field) {
return field.getAnnotation(Logger.class) != null;
}
});
return bean;
}
}