20

I have been trying to add spring validators to a spring-data-rest project.

I followed along and setup the "getting started" application via this link: http://spring.io/guides/gs/accessing-data-rest/

...and now I am trying to add a custom PeopleValidator by following the documents here: http://docs.spring.io/spring-data/rest/docs/2.1.0.RELEASE/reference/html/validation-chapter.html

My custom PeopleValidator looks like

package hello;

import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

public class PeopleValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }

    @Override
    public void validate(Object target, Errors errors) {
        errors.reject("DIE");
    }
}

...and my Application.java class now looks like this

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;

@Configuration
@EnableJpaRepositories
@Import(RepositoryRestMvcConfiguration.class)
@EnableAutoConfiguration
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public PeopleValidator beforeCreatePeopleValidator() {
        return new PeopleValidator();
    }
}

I would expect that POSTing to the http://localhost:8080/people URL would result in an error of some kind since the PeopleValidator is rejecting everything. However, no error is thrown, and the validator is never called.

I have also tried manually setting up the validator as shown in section 5.1 of the spring-data-rest documentation.

What am I missing?

Robert Greathouse
  • 1,024
  • 1
  • 10
  • 18

4 Answers4

17

So it appears that the before/after "save" events only fire on PUT and PATCH. When POSTing, the before/after "create" events fire.

I tried it the manual way again using the configureValidatingRepositoryEventListener override and it worked. I'm not sure what I'm doing differently at work than here at home. I'll have to look tomorrow.

I sure would love to hear if others have suggestions on why it wouldn't work.

For the record, here is what the new Application.java class looks like.

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;

@Configuration
@EnableJpaRepositories
@Import(RepositoryRestMvcConfiguration.class)
@EnableAutoConfiguration
public class Application extends RepositoryRestMvcConfiguration {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    protected void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
        validatingListener.addValidator("beforeCreate", new PeopleValidator());
    }
}
Robert Greathouse
  • 1,024
  • 1
  • 10
  • 18
  • Apparently the "beforeSave" event does not fire, but the "beforeCreate" event does. I still had to wire it the "manual" way instead of using a bean named "beforeCreatePeopleValidator". – Robert Greathouse Jun 21 '14 at 23:58
  • I am finding the same situation, auto discovery is not working. – JBCP Dec 05 '14 at 23:12
  • I'm also having a similair issue - manually wiring events is working just fine, automatic discovery fails. – Mikk Dec 19 '14 at 00:48
  • I ran into the same issue with it not being auto discovered. I created a JIRA on it: https://jira.spring.io/browse/DATAREST-524 – Daniel Moses Apr 22 '15 at 14:26
  • 2
    Now, you have to extend `RepositoryRestConfigurerAdapter` as `RepositoryRestMvcConfiguration` version is deprecated. – Arun Karunagath Jan 14 '17 at 20:03
3

Looks like the feature is currently not implemented (2.3.0), unluckily there are no constants for the event names otherwise the solution below would not be that fragile.

The Configuration adds all properly named Validator beans to ValidatingRepositoryEventListener using the right event.

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()));
        }
    }
}
Andreas
  • 5,251
  • 30
  • 43
1

A bit of a stab in the dark - I've not used spring-data-rest. However, after having a read of the tutorial you're following, I think the problem is that you need a PersonValidator not a PeopleValidator. Rename everything accordingly:

PersonValidator

package hello;

import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

public class PersonValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }

    @Override
    public void validate(Object target, Errors errors) {
        errors.reject("DIE");
    }
}

Application

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;

@Configuration
@EnableJpaRepositories
@Import(RepositoryRestMvcConfiguration.class)
@EnableAutoConfiguration
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public PersonValidator beforeCreatePersonValidator() {
        return new PersonValidator();
    }
}
Muel
  • 4,309
  • 1
  • 23
  • 32
0

Another way of doing it is to use annotated handlers as specified here http://docs.spring.io/spring-data/rest/docs/2.1.0.RELEASE/reference/html/events-chapter.html#d5e443

Here is an example of how to use annotated handlers:

import gr.bytecode.restapp.model.Agent;
import org.springframework.data.rest.core.annotation.HandleBeforeCreate;
import org.springframework.data.rest.core.annotation.HandleBeforeSave;
import org.springframework.data.rest.core.annotation.RepositoryEventHandler;
import org.springframework.stereotype.Component;

@Component
@RepositoryEventHandler(Agent.class)
public class AgentEventHandler {

    public static final String NEW_NAME = "**modified**";

    @HandleBeforeCreate
    public void handleBeforeCreates(Agent agent) {
            agent.setName(NEW_NAME);
    }

    @HandleBeforeSave
    public void handleBeforeSave(Agent agent) {
        agent.setName(NEW_NAME + "..update");
    }
}

Example is from github edited for brevity.

Alpar
  • 2,807
  • 23
  • 16
Amit
  • 519
  • 1
  • 5
  • 20