Open spring-projects-issues opened 9 years ago
Fabian Trampusch commented
Are there any news on this one?
Andreas Kluth commented
You could add this configuration to add the expected behavior.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener;
import org.springframework.validation.Validator;
@Configuration
public class ValidatorRegistrar implements InitializingBean {
private static final List<String> EVENTS;
static {
List<String> events = new ArrayList<String>();
events.add("beforeCreate");
events.add("afterCreate");
events.add("beforeSave");
events.add("afterSave");
events.add("beforeLinkSave");
events.add("afterLinkSave");
events.add("beforeDelete");
events.add("afterDelete");
EVENTS = Collections.unmodifiableList(events);
}
@Autowired
ListableBeanFactory beanFactory;
@Autowired
ValidatingRepositoryEventListener validatingRepositoryEventListener;
@Override
public void afterPropertiesSet() throws Exception {
Map<String, Validator> validators = beanFactory.getBeansOfType(Validator.class);
for (Map.Entry<String, Validator> entry : validators.entrySet()) {
EVENTS.stream().filter(p -> entry.getKey().startsWith(p)).findFirst()
.ifPresent(p -> validatingRepositoryEventListener.addValidator(p, entry.getValue()));
}
}
}
Fabian Trampusch commented
Thanks a lot, Andreas! I will try that approach. Anyway, we should fix the docs or the behaviour. I am not yet that familiar with the codebase. Any idea, if and where something is implemented regarding this?
Sebastian Bathke commented
Thanks Andreas for providing the configuration! Had the same problem that validation beans with this prefix didn't got catched up as the documentation suggests.
However I encountered a problem that in some occasions (multiple repositories for the same entity) some tests failed to startup the context correctly with
No qualifying bean of type [org.springframework.data.rest.core.event.ValidatingRepositoryEventListener]
I could fix that by migrating your solution to RepositoryRestConfigurerAdapter:
@Configuration
public class ValidatorRegistrar extends RepositoryRestConfigurerAdapter {
private static final List<String> EVENTS;
static {
List<String> events = new ArrayList<String>();
events.add("beforeCreate");
events.add("afterCreate");
events.add("beforeSave");
events.add("afterSave");
events.add("beforeLinkSave");
events.add("afterLinkSave");
events.add("beforeDelete");
events.add("afterDelete");
EVENTS = Collections.unmodifiableList(events);
}
@Autowired
ListableBeanFactory beanFactory;
@Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
super.configureValidatingRepositoryEventListener(validatingListener);
Map<String, Validator> validators = beanFactory.getBeansOfType(Validator.class);
for (Map.Entry<String, Validator> entry : validators.entrySet()) {
EVENTS.stream().filter(p -> entry.getKey().startsWith(p)).findFirst()
.ifPresent(p -> validatingListener.addValidator(p, entry.getValue()));
}
}
}
jamlee commented
it work for me . spring-data-rest 2.4
bitsofinfo commented
Any progress on fixing doc or in the code?
Casey Link commented
After debugging and fighting with this for way to long, I land here on this bug report :(
If the bug itself can't be fixed for whatever reasons, it would be nice at least to update the documentation
The docs say:
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 will be sufficient.
That's exactly wrong!
Farrukh Najmi commented
I am using springboot 2.0.1.RELEASE with spring-data-rest and followed the workaround mentioned here and my Validator is still not being invoked. Here are the details:
@Component("beforeSaveBidValidator")
public class BeforeSaveBidValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Bid.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
Bid bid = (Bid)target;
if (!bid.getAddendaAcknowledged()) {
errors.rejectValue("addendaAcknowledged",
"addendaAcknowledged is not true");
}
}
}
Custom RestController for Bids
@RestController
@RequestMapping(path = "/bids")
@Api(value = "/bids", description = "CRUD operations with Bids")
public class BidController {
private BidRepository bidRepository;
@Autowired
public BidController(
BidRepository bidRepository) {
this.bidRepository = bidRepository;
}
@PutMapping("{id}")
public Bid update(@RequestBody @Valid Bid bid) {
return bidRepository.save(bid);
}
}
Rest Client code
Bid bid = new Bid()
...
bid.setAddendaAcknowledged(false)
Map<String, String> uriVariables = new HashMap<String, String>()
uriVariables.put("id", bid.id)
HttpHeaders headers = new HttpHeaders()
headers.setContentType(MediaType.APPLICATION_JSON)
HttpEntity<Bid> entity = new HttpEntity<>(bid, headers)
ResponseEntity<String> response = restTemplate.exchange(
"/bids/{id}", HttpMethod.PUT, entity, Bid.class, bid.id)
// Expected: response.statusCode == HttpStatus.BAD_REQUEST
// Found: response.statusCode == HttpStatus.OK
// Debugger showed that Validator was never invoked.
Any idea what I am missing?
Eddie Bush commented
What's the current work-around for this?
Rafael Renan Pacheco commented
The workaround is blogged here: https://www.baeldung.com/spring-data-rest-validators
You set the events you want to register in the events array, like "beforeCreate", and the code will look for all validators the starts with this string and register it. This way you can create your validators as components, like this:
@Component
public class BeforeCreateItemValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return MyEntity.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required");
}
}
And the workaround to load all validators that begins with "beforeCreate" is this:
@Configuration
public class ValidatorEventRegister implements InitializingBean {
@Autowired
ValidatingRepositoryEventListener validatingRepositoryEventListener;
@Autowired
private Map<String, Validator> validators;
@Override
public void afterPropertiesSet() throws Exception {
List<String> events = Arrays.asList("beforeCreate");
for (Map.Entry<String, Validator> entry : validators.entrySet()) {
events.stream()
.filter(p -> entry.getKey().startsWith(p))
.findFirst()
.ifPresent(
p -> validatingRepositoryEventListener
.addValidator(p, entry.getValue()));
}
}
}
If you add all possible events in the "events" array, you will get what Spring Data Rest should have been doing in the first place
kwix commented
Has anyone tried the workaround yet? I tried using the workaround but the events are not getting picked up. Would appreciate anyone's input on what might be causing this issue
Servan Fichet commented
Yes it is working for me!
I added the configuration class and the Validator has been picked up.
Do not forget to annotate the validator class with @Component
("beforeCreateItemValidator") and it should work.
Do you know if the bug has been fixed?
The above-mentioned code solved the issue on my side. I slightly modified it to also register JSR 380 bean validators:
/**
* Configuration to merge multiple validator concepts.
*
* @author Sebastian Ullrich
* @since 1.0.0
*/
@Log4j2
@Configuration
@RequiredArgsConstructor
public class ValidatorConfig implements InitializingBean
{
public static final String BEFORE_CREATE = "beforeCreate";
public static final String BEFORE_SAVE = "beforeSave";
private final Map<String, Validator> validators;
private final LocalValidatorFactoryBean beanValidator;
private final ValidatingRepositoryEventListener validatingListener;
/**
* Assigns all present {@link org.springframework.validation.Validator Validators}
* to the {@link ValidatingRepositoryEventListener}.
*
* @see <a href="https://jira.spring.io/browse/DATAREST-524">DATAREST-524</a>
*/
@Override
public void afterPropertiesSet ()
{
// Assign custom validators
validators.entrySet().stream()
.filter(entry -> entry.getKey().startsWith(BEFORE_CREATE))
.map(Map.Entry::getValue)
.forEach(validator -> validatingListener.addValidator(BEFORE_CREATE, validator));
// Assign BeanValidator (JSR 380)
validatingListener.addValidator(BEFORE_CREATE, beanValidator);
validatingListener.addValidator(BEFORE_SAVE, beanValidator);
}
}
This is still an issue as of 2021, according to baeldung, you can do:
@Configuration
public class ValidatorEventRegister implements InitializingBean {
@Autowired
ValidatingRepositoryEventListener validatingRepositoryEventListener;
@Autowired
private Map<String, Validator> validators;
@Override
public void afterPropertiesSet() throws Exception {
List<String> events = Arrays.asList("beforeCreate");
for (Map.Entry<String, Validator> entry : validators.entrySet()) {
events.stream()
.filter(p -> entry.getKey().startsWith(p))
.findFirst()
.ifPresent(
p -> validatingRepositoryEventListener
.addValidator(p, entry.getValue()));
}
}
}
In 2024, https://docs.spring.io/spring-data/rest/reference/validation.html mentioned auto wired still not work.
Daniel Moses opened DATAREST-524 and commented
See documentation http://docs.spring.io/spring-data/rest/docs/2.2.2.RELEASE/reference/html/#validation-chapter
Discovery should happen with Validator Prefix. Add a Validator bean to the context and notice that it does not get auto-detected. Manual wiring still works. Here is an example validator that will not work if included in the example Spring boot project:
See problem as reported: http://stackoverflow.com/questions/24318405/spring-data-rest-validator
34 votes, 37 watchers