Spring Data Rest #RepositoryRestController and #RequestMapping - java

I want to override #RepositoryRestResource autogenerated controller methods using #RepositoryRestController having set the SDR's Base Path
to "/api".
Spring Data Rest 3.0 (and earlier) says:
"This controller [as shown in the snippet] will be served from the same API base path defined in RepositoryRestConfiguration.setBasePath that is used by all other RESTful endpoints (e.g. /api)".
https://docs.spring.io/spring-data/rest/docs/3.0.1.RELEASE/reference/html/#customizing-sdr.overriding-sdr-response-handlers (chapter 15.4)
This code snippet DOES NOT have a #RequestMapping on the class level, though.
My SDR app is configured with RepositoryRestConfiguration object
config.setBasePath("/api");
and yet #RepositoryRestController doesn't override SDR's autogenerated controller methods.
Please consider the accepted answear to this post:
Spring Data Rest controllers: behaviour and usage of #BasePathAwareController, #RepositoryRestController, #Controller and #RestController
Please help me understand this! :)
AppConf.java:
#Configuration
#Import(value = {DataConf.class})
#EnableWebMvc
#ComponentScan(value = "pl.mydomain.controller")
public class AppConf
{
#Bean
public RepositoryRestConfigurer repositoryRestConfigurer() {
return new RepositoryRestConfigurerAdapter() {
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setBasePath("/api");
}
};
}
}
TokenController.java:
#RepositoryRestController
public class TokenController
{
private TokenRepository repository;
#Autowired
public TokenController(TokenRepository tokenRepository) {
this.repository = tokenRepository;
}
#RequestMapping(method = GET, path = "/tokens")
public #ResponseBody ResponseEntity<?> tokens()
{
return ResponseEntity.ok("Hello");
}
}
TokenRepository.java:
#RepositoryRestResource(path = "tokens")
public interface TokenRepository extends CrudRepository<Token, Long>{
}

The key to resolve the above dilemma was configuring the project in a correct fashion. That is, to put #ComponentScan in the class passed to AbstractAnnotationConfigDispatcherServletInitializer::getServletConfigClasses() method (not in AppConf.java passed to getRootConfigClasses()).
DispatcherConf.java:
public class DispatcherConf extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {AppConf.class};
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {WebConf.class}; // !!!
}
#Override
protected String[] getServletMappings() {
return new String[] {"/*"};
}
}
AppConf.java:
#Configuration
#Import({DataConf.class})
public class ApplicationConf
{
#Bean
public RepositoryRestConfigurer repositoryRestConfigurer() {
return new RepositoryRestConfigurerAdapter() {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setBasePath("/api"); // !!!
}
};
}
}
DataConf.java:
#Configuration
#EnableJpaRepositories(basePackages = {
"pl.example.data.repository"
})
#EnableTransactionManagement
public class DataConf
{ ... }
WebConf.java:
#Import(RepositoryRestMvcConfiguration.class)
#ComponentScan({"pl.example.api.controller"}) // !!!
public class WebConf {
}
Even if I solved the riddle I don't understand why it was an issue. The rather that https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/ComponentScan.html states:
Annotation Type ComponentScan onfigures component scanning directives
for use with #Configuration classes.

Related

Spring security pass SecurityExpressionRoot to custom method

I want to pass SecurityExpressionRoot class, which i can access inside #PreAuthorise() annotation, to my custom checkAccess() method which checks access to specific method, using some logic based on authorities, roles and a additional variables that i pass to this method.
Inside #PreAuthorise() I can access methods from SecurityExpressionRoot, for example. hasAuthority()
Is there any way to do that?
Controller:
public class TestController {
private final PreAuthorizeChecker preAuthorizeChecker;
#Autowired
public TestController(PreAuthorizeChecker preAuthorizeChecker) {
this.preAuthorizeChecker = preAuthorizeChecker;
}
#GetMapping(path = "/test")
#PreAuthorize("#preAuthorizeChecker.checkAccess(/*SecurityExpressionRoot.getSomehow()*/)") //How to obtain SecurityExpressionRoot instance?
public ResponseEntity<Void> get() {
return;
}
PreAuthorizeChecker:
#Component
public class PreAuthorizeChecker {
#Autowired
public PreAuthorizeChecker() {
}
public boolean checkAccess(SecurityExpressionRoot securityExpressionRoot) {
//do sth with securityExpressionRoot
return true;
}
}
You might find that part 5 of this blog, A Custom Security Expression with Spring Security, on Baelding.com helpful. The author suggests extending SecurityExpressionRoot and adding your custom method to the new class like this:
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
public CustomMethodSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
public boolean checkAccess() {
//do sth with securityExpressionRoot
return true;
}
...
}
Then you will need to inject that new class into the expression handler like this:
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
#Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication);
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(this.trustResolver);
root.setRoleHierarchy(getRoleHierarchy());
return root;
}
}
Finally, you just need to create the expression handler in your security config like this:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
CustomMethodSecurityExpressionHandler expressionHandler = new CustomMethodSecurityExpressionHandler();
return expressionHandler;
}
}
Then your new expression should be available:
#PreAuthorize("checkAccess()")
public ResponseEntity<Void> get() {
return;
}

How can I override configuration in spring using conditional annotations (for example)

I have the following class available in a dependency jar:
#Configuration
#EnableSpringDataWebSupport
public class RepositoryRestConfig extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setBasePath(CoreHttpPathStore.REST_PATH);
config.setReturnBodyOnCreate(true);
config.setReturnBodyOnUpdate(true);
config.hasResourceMappingForDomainType(GrantedAuthority.class);
}
#Override
public void configureJacksonObjectMapper(ObjectMapper mapper) {
super.configureJacksonObjectMapper(mapper);
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
}
}
I want this to be the default configuration, but in some case, I must add some configuration, for this, I create in my application this new class:
#Configuration
#EnableSpringDataWebSupport
public class MyAppRepositoryRestConfig extends RepositoryRestConfig {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
super.configureRepositoryRestConfiguration(config);
config.exposeIdsFor(
User.class,
Client.class,
Role.class,
Organization.class,
);
}
// #Override
// public void configureJacksonObjectMapper(ObjectMapper mapper) {
// super.configureJacksonObjectMapper(mapper);
// }
}
The problem is that the method configureRepositoryRestConfiguration in the jar is called twice, which makes me believe this is not what I should do.
How can I boot my configuration conditionally ?
You can try to do this, create your config as a bean:
#Bean
public RepositoryRestConfigurer repositoryRestConfigurer() {
return new RepositoryRestConfigurer() {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Class1.class, Class2.class);
}
};
}

how to resolve Spring boot #EnableAsync and #Async problem?

my service:
#Service
public class ForgetService{
#Async
public CompletableFuture<Object> getTokenService() {
Map<String, Object> map = new HashMap<>(8);
map.put("status", ErrorEnum.TOKEN_SUSSCESS.getStatus());
map.put("message", ErrorEnum.TOKEN_SUSSCESS.getMessage());
return CompletableFuture.completedFuture(map);
}
}
my controller:
#RestController
public class ForgetController {
private final ForgetService forgetService;
#Autowired
private ForgetController(ForgetService forgetService) {
this.forgetService = forgetService;
}
#PostMapping(value = "/forget/token")
#Async
public CompletableFuture<Object> getTokenController() {
return CompletableFuture.completedFuture(forgetService.getTokenService());
}
}
spring boot application class:
#SpringBootApplication
#EnableAsync
public class ApitestApplication {
public static void main(String[] args) {
SpringApplication.run(ApitestApplication.class, args);
}
}
when i run my application, console log :
The bean 'forgetService' could not be injected as a 'com.apitest.service.ForgetService' because it is a JDK dynamic proxy that implements:
com.apitest.inf.ForgetServiceInf
Action:
Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on #EnableAsync and/or #EnableCaching.
i tried setting 'spring.aop.proxy-target-class=true' in application.properties and setting '#EnableAsync(proxyTargetClass=true), but it's useless,so what's wrong? how to resolve it?
please use below approach, it might help you to fix this issue.
#Service
public class ForgetService{
#Bean("GetTokenService")
public CompletableFuture<Object> getTokenService() {
Map<String, Object> map = new HashMap<>(8);
map.put("status", ErrorEnum.TOKEN_SUSSCESS.getStatus());
map.put("message", ErrorEnum.TOKEN_SUSSCESS.getMessage());
return CompletableFuture.completedFuture(map);
}
#RestController
public class ForgetController {
private final ForgetService forgetService;
#Autowired
private ForgetController(ForgetService forgetService) {
this.forgetService = forgetService;
}
#PostMapping(value = "/forget/token")
#Async("GetTokenService")
public CompletableFuture<Object> getTokenController() {
return CompletableFuture.completedFuture(forgetService.getTokenService());
}
}

#RepositoryRestController custom controller with RepositoryRestMvcConfiguration and AbstractAnnotationConfigDispatcherServletInitializer

I am trying to implement custom controller to handle method defined in custom repository to be able to expose resources using this method via REST (according to Implementing custom methods of Spring Data repository and exposing them through REST).
Here is the configuration and other relevant code:
public class ApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] {ApplicationConfig.class};
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] {RepositoryRestMvcConfiguration.class};
}
#Override
protected String[] getServletMappings() {
return new String[]{"/api/*"};
}
}
ApplicationConfig:
#Configuration
#EnableJpaRepositories
#EnableTransactionManagement
public class ApplicationConfig {
#Bean
public DataSource dataSource() {
// data source settings
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
// em factory settings
}
#Bean
public PlatformTransactionManager transactionManager() {
// tx mng settings
}
}
ExperimentRepository:
#RepositoryRestResource
public interface ExperimentRepository extends PagingAndSortingRepository<Experiment, Long>,
ExperimentRepositoryCustom {
#RestResource(rel = "byName", path = "byName")
Page<Experiment> findByNameContaining(#Param("name") String name, Pageable pageable);
}
ExperimentRepositoryCustom:
public interface ExperimentRepositoryCustom {
Page<Experiment> findUsingCustomFilter(...);
}
ExperimentRepositoryImpl:
public class ExperimentRepositoryImpl implements ExperimentRepositoryCustom {
#Override
public Page<Experiment> findUsingCustomFilter(...) {
// search for experiment based on given filter
}
}
ExperimentController:
#RepositoryRestController
public class ExperimentController {
#Autowired
private ExperimentRepository experimentRepository;
#RequestMapping(value = "/experiments/search/findByFilter", method= RequestMethod.GET)
#ResponseBody
public ResponseEntity<Experiment> searchUsingFilter(#RequestParam Long id) {
// for test purpose call method from CrudRepository (will be replaced by findUsingCustomFilter(...))
return new ResponseEntity(experimentRepository.findOne(id), HttpStatus.OK);
}
}
Project structure:
basepackage.model.Experiment
basepackage.repository.ExperimentController
basepackage.repository.ExperimentRepository
basepackage.repository.ExperimentRepositoryImpl
basepackage.repository.ExperimentRepositoryCustom
basepackage.ApplicationInitializer
basepackage.ApplicationConfig
All resources exposed by links automatically generated based on used Repositories are accessible with no problem but calling GET method on http://localhost:8080/app/api/experiments/search/findByFilter?id=1 ends with 404 (resources exposed by links automatically generated based on used Repositories work fine). I assume that ExperimentController is not registered in Spring container or I am missing some additional settings regarding controller method. Any suggestions?
Thanks in advance!
You need configuration to load your Controllers. One way to do this is to add a configuration class to load you Controllers and add it to your ApplicationInitializer.
//PLACE THIS IN A PACKAGE WHERE YOUR CONTROLLERS ARE
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
#ComponentScan
#Import(RepositoryRestMvcConfiguration.class)
public class WebConfig {
}
Change you ApplicationInitializer to
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import com.spring.data.rest.test.web.WebConfig;
public class ApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { ApplicationConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/api/*" };
}
}

How to use #EnableTransactionManagement in combination with a StaticMethodMatcherPointcutAdvisor

Given the following service:
public interface MyService {
void method();
}
And it's implementation:
#Service
public class MyServiceImpl implements MyService {
#Transactional
#CustomAnnotation
#Override
public void method() {
...
}
}
I would like to use a StaticMethodMatcherPointcutAdvisor in the following manner:
public class MyPointcutAdvisor extends StaticMethodMatcherPointcutAdvisor {
...
#Override
public boolean matches(Method method, Class targetClass) {
Method m = method;
if(annotationPresent(method)) {
return true;
}
Class<?> userClass = ClassUtils.getUserClass(targetClass);
Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if(annotationPresent(specificMethod )) {
return true;
}
return false;
}
...
}
The problem is that Spring uses an InfrastructureAdvisorAutoProxyCreator to create a Proxy of that class, whereas the DefaultAdvisorAutoProxyCreator would create the proxy for the MyPointcutAdvisor, but the MyPointcutAdvisor is only given the proxy as targetClass parameter. Thus, the PointcutAdvisor cannot find the annotation and therefore does not match.
For completion this is my Configuration-class:
#Configuration
#EnableTransactionManagement
public class MyConfiguration {
#Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
#Bean
public MyPointcutAdvisor myPointcutAdvisor() {
return new MyPointcutAdvisor();
}
...
}
My question is: Is there a way to use #EnableTransactionManagement in combination with a StaticMethodMatcherPointcutAdvisor ?
Workarounds:
Put #CustomAnnotation into the service interface: I want to have clean interfaces.
Add #Role(BeanDefinition.ROLE_INFRASTRUCTURE) to MyPointCutAdvisor bean configuration, thus, the InfrastructureAdvisorAutoProxyCreator will create the proxy. This seems like the wrong way, since this bean is not infrastructure
Copy the beans from ProxyTransactionManagementConfiguration, remove #EnableTransactionManagement and remove #Role(BeanDefinition.ROLE_INFRASTRUCTURE), thus the DefaultAdvisorAutoProxyCreator will create the proxy, which is my current workaround and results in the following configuration:
#Configuration
public class MyWorkaroundConfiguration {
#Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
#Bean
public MyPointcutAdvisor myPointcutAdvisor() {
return new MyPointcutAdvisor();
}
#Bean
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
#Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
TransactionInterceptor transactionInterceptor) {
BeanFactoryTransactionAttributeSourceAdvisor advisor =
new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource());
advisor.setAdvice(transactionInterceptor);
return advisor;
}
#Bean
public TransactionInterceptor transactionInterceptor(
PlatformTransactionManager transactionManager) {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource());
interceptor.setTransactionManager(transactionManager);
return interceptor;
}
...
}
Using #EnableAspectJAutoProxy instead of the DefaultAutoProxyCreator works for me.
#Configuration
#EnableAspectJAutoProxy
#EnableTransactionManagement
public class MyConfiguration {
}
This also allows using #Aspect like M. Deinum suggested.

Categories