I'm having some problem with validation with Spring Rest, the #HandleBeforeCreate event handler is running before the validation. I was expecting it to run after the validation.
In my test application I have a transaction, which has two fields to store the transaction value, one for the real transaction currency and another for the final value converted to the user currency. In my handle before create I'm dealing with that conversion, but I want the request to stop in the validator if the amount is null.
I could validate the resource in the event handler (I'm ready to handle a RepositoryConstraintViolationException), but it make me think on the point of using validator. It also seams a little inefficient that on every data rest request, spring loop through all validator checking if they support the object class.
Is validation on EventHandlers preferable than Validators (for performance reasons)? How can I force validator to run before EventHandlers?
*I'm using spring-boot 1.4.2.RELEASE
Validator
public class TransactionValidator extends SpringValidator<Transaction> {
#Override
public boolean supports(Class<?> clazz) {
return Transaction.class.equals(clazz) ;
}
#Override
public void validateObject(Transaction transaction, Errors errors) {
... validations ...
}
}
public abstract class SpringValidator<T> implements Validator {
#Override
public void validate(Object target, Errors errors) {
validateObject((T) target, errors);
}
protected abstract void validateObject(T target, Errors errors);
}
Event Handler
#Component
#RequiredArgsConstructor
#RepositoryEventHandler(Transaction.class)
public class TransacationEventHandler {
private final CurrencyUnitService currencyUnitService;
#HandleBeforeCreate
public void beforeCreate(Transaction transaction) {
adjustTransactionAmount(transaction);
}
#HandleBeforeSave
public void beforeSave(Transaction transaction) {
adjustTransactionAmount(transaction);
}
}
Edit
I checked the source code and the listeners are invoked in the following order:
Which make sense actually, using a BeforeCreateHandler is the only way to fix/change something in the Entity before running the Validator. I'm 100% open to inputs.
1. Use #Validated (did not test it)
You could do this:
#HandleBeforeCreate
public void beforeCreate(#Validated Transaction transaction) {
adjustTransactionAmount(transaction);
}
2. Otherwise - you may define the Validator through Java Configuration
You may follow this answer from #MathiasDpunkt (Spring data rest validation + exception mapper: confusing):
#Configuration
public class MyValidationConfiguration extends RepositoryRestConfigurerAdapter {
#Bean
#Primary
/**
* Create a validator to use in bean validation - primary to be able to autowire without qualifier
*/
Validator validator() {
return new LocalValidatorFactoryBean();
}
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener
validatingListener) {
Validator validator = validator();
//bean validation always before save and create
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
}
}
Related
I have StreamListener which I would like to replace using the new functional model and Consumer <>. Unfortunately, I don't know how to transfer #Transactional to new model:
#Transactional
#StreamListener(PaymentChannels.PENDING_PAYMENTS_INPUT)
public void executePayments(PendingPaymentEvent event) throws Exception {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
}
I have tired certain things. Sample code below. I added logging messages to a different queue for tests. Then I throw an exception to trigger a rollback. Unfortunately, messages are queued even though they are not there until the method is completed (I tested this using brakepoints). It seems that the transaction was automatically committed despite the error.
#Transactional
#RequiredArgsConstructor
#Component
public class functionalPayment implements Consumer<PendingPaymentEvent> {
private final PaymentsService paymentsService;
private final StreamBridge streamBridge;
public void accept(PendingPaymentEvent event) {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
streamBridge.send("log-out-0",event);
throw new RuntimeException("Test exception to rollback message from log-out-0");
}
}
Configuration:
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.queue-name-group-only=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.declare-exchange=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.bind-queue=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.transacted=true
spring.cloud.stream.source=log
spring.cloud.stream.bindings.log-out-0.content-type=application/json
spring.cloud.stream.bindings.log-out-0.destination=log_a
spring.cloud.stream.bindings.log-out-0.group=log_a
spring.cloud.stream.rabbit.bindings.log-out-0.producer.declare-exchange=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.bind-queue=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.queue-name-group-only=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.binding-routing-key=log
spring.cloud.stream.rabbit.bindings.log-out-0.producer.transacted=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.exchange-type=direct
spring.cloud.stream.rabbit.bindings.log-out-0.producer.routing-key-expression='log'
Have you tried something along the lines of
#Transactional
public class ExecutePaymentConsumer implements Consumer<PendingPaymentEvent> {
public void accept(PendingPaymentEvent event) {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
}
}
. . .
#Bean
public ExecutePaymentConsumer executePayments() {
return new ExecutePaymentConsumer();
}
There are two ways to register a Validator instance in Spring Data REST: wire it by bean name or register the validator manually. For the
majority of cases, the simple bean name prefix style is sufficient.
In order to tell Spring Data REST you want a particular Validator
assigned to a particular event, prefix the bean name with the event in
question. For example, to validate instances of the Person class
before new ones are saved into the repository, you would declare an
instance of a Validator in your ApplicationContext with a bean
name of beforeCreatePersonValidator. Since the beforeCreate prefix
matches a known Spring Data REST event, that validator is wired to the
correct event.
I tried creating this bean but it doesn't register
#Component("beforeCreateOrderValidator")
public class BeforeCreateOrderValidator extends BaseValidator {
#Override
public boolean supports(Class<?> clazz) {
return Order.class.equals(clazz);
}
#Override
public void validate(Object obj, Errors errors) {
}
}
But if I do manual registration it works
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener v) {
v.addValidator("beforeCreate", new BeforeCreateOrderValidator());
}
I'm using Spring 4.3.8.RELEASE with Hibernate 5.1.5.Final. I want to have a method executed after another another transaction completes. That transaction is defined below
#Service("organizationService")
#Transactional
public class OrganizationServiceImpl implements OrganizationService, ApplicationEventPublisherAware
{
private ApplicationEventPublisher publisher;
#Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher)
{
this.publisher = publisher;
}
#Override
public void save(Organization organization)
{
...
// sync data with ThirdParty but only if something has definitelychanged on the SB
// side, in which case we want to send ThirdParty an update.
if (!hasSameAttributes)
{
publisher.publishEvent(new ThirdPartyOrganizationEvent(organization.getId()));
} // if
} // save
So here is the method that I want executed after the above transaction completes ...
#Service
public class ThirdPartyAPIServiceImpl implements ThirdPartyAPIService
{
#Override
#TransactionalEventListener
public boolean updateOrg(final ThirdPartyOrganizationEvent thirdPartyOrgEvent)
{
...
}
But when I load my application context I get this error
Caused by: java.lang.IllegalStateException: No TransactionalEventListener annotation found on method: public abstract boolean org.mainco.subco.myproject.service.ThirdPartyAPIService.updateOrg(org.mainco.subco.myproject.domain.ThirdPartyOrganizationEvent)
at org.springframework.transaction.event.ApplicationListenerMethodTransactionalAdapter.<init>(ApplicationListenerMethodTransactionalAdapter.java:55)
at org.springframework.transaction.event.TransactionalEventListenerFactory.createApplicationListener(TransactionalEventListenerFactory.java:55)
at org.springframework.context.event.EventListenerMethodProcessor.processBean(EventListenerMethodProcessor.java:159)
at org.springframework.context.event.EventListenerMethodProcessor.afterSingletonsInstantiated(EventListenerMethodProcessor.java:104)
... 34 more
Wbat do I need to do to get this configured properly?
Defining #TransactionalEventListener on interface method rather then on method implementing interface worked for me.
I want to produce HTTP Response Body with an error message referencing something like _"missing ... 'CUSTOM_AUTHORITY'"_ in addition to a 403 Forbidden HTTP Status code.
My application is Spring Boot with a Spring-Security-Secured #PreAuthorize method within a Spring-MVC-REST #Controller:
MyController
#Controller
#RequestMapping("/foo")
public FooController{
#PreAuthorize("hasAuthority('CUSTOM_AUTHORITY')")
public Object getSomething(){ ... }
}
GlobalExceptionHandlerResolver
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(AccessDeniedException.class)
#ResponseStatus(HttpStatus.FORBIDDEN)
public Object forbidden(AccessDeniedException exception){ ... }
}
What I want is to expose/inject Collection<ConfigAttribute>. The Spring Security docs reference it.
There doesn't seem to be a straightforward way of accomplishing this. The AccessDecisionManager (which is AffirmativeBased) throws the AccessDeniedException with none of the information you want. So if you want to "expose/inject" the Collection<ConfigAttribute>, you'll want to provide your own AccessDecisionManager that throws a custom exception that holds the ConfigAttributes.
The easiest way to do this could be to wrap the default AccessDecisionManager with your own and delegate method calls to it:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled=true)
CustomMethodSecurityConfig extends GlobalMethodSecurityConfiguration
#Override
protected AccessDecisionManager accessDecisionManager() {
AccessDecisionManager default = super.accessDecisionManager();
MyCustomDecisionManager custom = new CustomDecisionManager(default);
}
}
You could define your custom AccessDecisionManager as follows:
public class MyCustomDecisionManager implements AccessDecisionManager {
private AccessDecisionManager default;
public MyCustomDecisionManager(AccessDecisionManager acm) {
this.default = acm;
}
#Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException{
try {
default.decide(authentication, object, configAttributes)
} catch(AccessDeniedException ex) {
throw new CustomAccessDeniedException(ex.getMessage(), configAttributes);
}
}
// other methods delegate to default
}
Now whenever access is denied, you will get an exception that holds the Collection<ConfigAttribute>.
Your custom exception could look like this:
public class CustomAccessDeniedException extends AccessDeniedException {
private Collection<ConfigAttribute> attributes;
public CustomAccessDeniedException(String message, Collection<ConfigAttribute> attr) {
super(message);
this.attributes = attr;
}
public Collection<ConfigAttribute> getAttributes() {
return this.attributes;
}
}
Now your #ExceptionHandler could handle your CustomAccessDeniedException and have access to the ConfigAttributes.
HOWEVER...
I am not sure that will provide you with the error message you wanted. The ConfigAttribute interface only has one method:
String getAttribute();
And the javadoc states:
If the ConfigAttribute cannot be expressed with sufficient precision as a String, null should be returned.
Since we can't rely on the interface method, how you deal with each ConfigAttribute will be heavily dependent on the type of the particular object you're dealing with.
For example, the ConfigAttribute that corresponds to #PreAuthorize("hasAuthority('CUSTOM_AUTHORITY')") is PreInvocationExpressionAttribute, and to print something that resembles what you want, you could do:
PreInvocationExpressionAttribute attr = (PreInvocationExpressionAttribute)configAttribute;
String expressionString = attr.getAuthorizeExpression().getExpressionString();
System.out.println(expressionString); // "hasAuthority('CUSTOM_AUTHORITY')"
That's the major drawback. Also, you would get ALL the ConfigAttributes, not necessarily the ones that failed.
I tried to add model attribute in controller which is causing Validator to throw exception.
java.lang.IllegalStateException: Invalid target for Validator
#ModelAttribute("summary")
public Summary createStudent(Student student) {
return saveService.saveStudent(student);
}
If #ModelAttribute("summary") is removed it works perfectly but not able to access the data in view.
If #ModelAttribute("summary") is enabled it throws above exception.
Validator
public class StudentValidator implements Validator {
public StudentValidator() {
}
public boolean supports(Class<?> paramClass) {
return Student.class.equals(paramClass);
}
public void validate(Object object, Errors errors) {
Student sutdent = (Student) object;
// Validation here for student
}
}
Already Refered Invalid target for Validator in spring error?
What is wrong?
I don't see any problem with my validator.
Thanks.