About Rafal Borowiec

Rafal is an IT specialist with about 8 years of commercial experience, specializing in software testing and quality assurance, software development, project management and team leadership.

HOW-TO: Get started quickly with Spring 4.0 to build a simple REST-Like API (walkthrough)

HOW-TO: Get started quickly with Spring 4.0 to build a simple REST-Like API (walkthrough)

Yet another tutorial about creating Web API with Spring MVC. Not really sophisticated. Just a walkthrough. The resulting app will serve simple API, will use Mongo as its persistence and it will be secured with Spring Security.

Getting started – POM

Of course, I am still a huge fan of Maven so the project is Maven based. Since there is Spring 4.0 RC2 available, I decided to utilize its new dependency managament which results in the following pom.xml: It is quite simple as it goes to Spring MVC application. The new thing is the dependencyManagement element. More explanation on that can be found here: http://spring.io/blog/2013/12/03/spring-framework-4-0-rc2-available

Configuration

The application is configured using JavaConfig. I divided it into several parts:

ServicesConfig

@Configuration
public class ServicesConfig {

    @Autowired
    private AccountRepository accountRepository;

    @Bean
    public UserService userService() {
        return new UserService(accountRepository);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

No component scan. Really simple.

PersistenceConfig

A MongoDB configuration with all available repositories. In this simple application we have only one repository, so the configuration is really simple.

@Configuration
class PersistenceConfig {

    @Bean
    public AccountRepository accountRepository() throws UnknownHostException {
        return new MongoAccountRepository(mongoTemplate());
    }

    @Bean
    public MongoDbFactory mongoDbFactory() throws UnknownHostException {
        return new SimpleMongoDbFactory(new Mongo(), "r");
    }

    @Bean
    public MongoTemplate mongoTemplate() throws UnknownHostException {
        MongoTemplate template = new MongoTemplate(mongoDbFactory(), mongoConverter());
        return template;
    }

    @Bean
    public MongoTypeMapper mongoTypeMapper() {
        return new DefaultMongoTypeMapper(null);
    }

    @Bean
    public MongoMappingContext mongoMappingContext() {
        return new MongoMappingContext();
    }

    @Bean
    public MappingMongoConverter mongoConverter() throws UnknownHostException {
        MappingMongoConverter converter = new MappingMongoConverter(mongoDbFactory(), mongoMappingContext());
        converter.setTypeMapper(mongoTypeMapper());
        return converter;
    }
}

SecurityConfig

In theory, Spring Security 3.2 can be fully configured with JavaConfig. For me it is still a theory, so I use XML here:

@Configuration
@ImportResource("classpath:spring-security-context.xml")
public class SecurityConfig {}

And the XML: As you can see basic authentication will be used for the API.

WebAppInitializer

We don’t want the web.xml so we use the following code to configure the web application:

@Order(2)
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    @Override
    protected Class[] getRootConfigClasses() {
        return new Class[] {ServicesConfig.class, PersistenceConfig.class, SecurityConfig.class};
    }

    @Override
    protected Class[] getServletConfigClasses() {
        return new Class[] {WebMvcConfig.class};
    }

    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceEncoding(true);
        return new Filter[] {characterEncodingFilter};
    }

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {        
        registration.setInitParameter("spring.profiles.active", "default");
    }
}

WebAppSecurityInitializer

New to Spring Security 3.

@Order(1)
public class WebAppSecurityInitializer extends AbstractSecurityWebApplicationInitializer {

}

WebMvcConfig

The dispatcher servlet configuration. Really basic. Only crucial components to build a simple API.

@Configuration
@ComponentScan(basePackages = { "pl.codeleak.r" }, includeFilters = {@Filter(value = Controller.class)})
public class WebMvcConfig extends WebMvcConfigurationSupport {

    private static final String MESSAGE_SOURCE = "/WEB-INF/i18n/messages";

    @Override
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        RequestMappingHandlerMapping requestMappingHandlerMapping = super.requestMappingHandlerMapping();
        requestMappingHandlerMapping.setUseSuffixPatternMatch(false);
        requestMappingHandlerMapping.setUseTrailingSlashMatch(false);
        return requestMappingHandlerMapping;
    }

    @Bean(name = "messageSource")
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename(MESSAGE_SOURCE);
        messageSource.setCacheSeconds(5);
        return messageSource;
    }

    @Override
    public Validator getValidator() {
        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
        validator.setValidationMessageSource(messageSource());
        return validator;
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

And that’s the config. Simple.

IndexController

To verify the config is fine, I created an IndexController, that serves simple “Hello, World” like text:

@Controller
@RequestMapping("/")
public class IndexController {

    @RequestMapping
    @ResponseBody
    public String index() {
        return "This is an API endpoint.";
    }
}

As you run the application, you should see this text in the browser.

Building the API

UserService

To finish up with the Spring Security configuration, one part is actually still needed: UserService which instance was created earlier:

public class UserService implements UserDetailsService {

    private AccountRepository accountRepository;

    public UserService(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

 @Override
 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  Account account = accountRepository.findByEmail(username);
  if(account == null) {
   throw new UsernameNotFoundException("user not found");
  }
  return createUser(account);
 }

 public void signin(Account account) {
  SecurityContextHolder.getContext().setAuthentication(authenticate(account));
 }

 private Authentication authenticate(Account account) {
  return new UsernamePasswordAuthenticationToken(createUser(account), null, Collections.singleton(createAuthority(account)));  
 }

 private User createUser(Account account) {
  return new User(account.getEmail(), account.getPassword(), Collections.singleton(createAuthority(account)));
 }

 private GrantedAuthority createAuthority(Account account) {
  return new SimpleGrantedAuthority(account.getRole());
 }

}

The requirement was to build an API endpoint that handles 3 methods: gets currently logged in user, gets all users (not really safe), creates a new account. So let’s do it.

Account

Account will be our first Mongo document. It is really easy one:

@SuppressWarnings("serial")
@Document
public class Account implements java.io.Serializable {

    @Id
    private String objectId;

    @Email
    @Indexed(unique = true)
    private String email;

    @JsonIgnore
    @NotBlank
    private String password;

    private String role = "ROLE_USER";

    private Account() {

    }

    public Account(String email, String password, String role) {
        this.email = email;
        this.password = password;
        this.role = role;
    }

   // getters and setters
}

Repository

I started with the interface:

public interface AccountRepository {

    Account save(Account account);

    List findAll();

    Account findByEmail(String email);
}

And later with its Mongo implementation:

public class MongoAccountRepository implements AccountRepository {

    private MongoTemplate mongoTemplate;

    public MongoAccountRepository(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }

    @Override
    public Account save(Account account) {
        mongoTemplate.save(account);
        return account;
    }

    @Override
    public List findAll() {
        return mongoTemplate.findAll(Account.class);
    }

    @Override
    public Account findByEmail(String email) {
        return mongoTemplate.findOne(Query.query(Criteria.where("email").is(email)), Account.class);
    }
}

API Controller

So we are almost there. We need to serve the content to the user. So let’s create our endpoint:

@Controller
@RequestMapping("api/account")
class AccountController {

    private AccountRepository accountRepository;

    @Autowired
    public AccountController(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

    @RequestMapping(value = "current", method = RequestMethod.GET)
    @ResponseStatus(value = HttpStatus.OK)
    @ResponseBody
    @PreAuthorize(value = "isAuthenticated()")
    public Account current(Principal principal) {
        Assert.notNull(principal);
        return accountRepository.findByEmail(principal.getName());
    }

    @RequestMapping(method = RequestMethod.GET)
    @ResponseStatus(value = HttpStatus.OK)
    @ResponseBody
    @PreAuthorize(value = "isAuthenticated()")
    public Accounts list() {
        List accounts = accountRepository.findAll();
        return new Accounts(accounts);
    }

    @RequestMapping(method = RequestMethod.POST)
    @ResponseStatus(value = HttpStatus.CREATED)
    @ResponseBody
    @PreAuthorize(value = "permitAll()")
    public Account create(@Valid Account account) {
        accountRepository.save(account);
        return account;
    }

    private class Accounts extends ArrayList {
        public Accounts(List accounts) {
            super(accounts);
        }
    }
}

I hope you noticed that we talk directly to the repository, so the passwords will not be encoded. Small detail to be fixed later on, if you wish. For now it is OK.

Finishing up

The last think I needed was some error handler so the consumer can see error messages in JSON instead of HTML. This is simple with Spring MVC and @Controller advice.

@ControllerAdvice
public class ErrorHandler {

    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorResponse errorResponse(Exception exception) {
        return new ErrorResponse(exception.getMessage());
    }

}

public class ErrorResponse {
    private String message;
    public ErrorResponse(String message) {
        this.message = message;
    }
    public String getMessage() {
        return message;
    }
}

If you want to see more advance usage of @ControllerAdvice in Spring 4 read
this post.

Testing the app

As a unit test geek I should have created Unit Tests first. But… I just wanted to test a new tool: Postman (Chrome extension). So I did:

Get accounts (not authorized):

gna1

Post account (does not require authentication:

gna2

Get accounts (authorized):

gna3

Get current account (authorized):

gna4

We are done

That’s it for now. Hope you enjoyed as I enjoyed creating the project. The project and this post took me about ~3h in overall. Most of the time I spent on figuring out the security configuration (I wanted it to be fully in Java) and writing this walkthrough.
 

Related Whitepaper:

Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions

Get ready to program in a whole new way!

Functional Programming in Java will help you quickly get on top of the new, essential Java 8 language features and the functional style that will change and improve your code. This short, targeted book will help you make the paradigm shift from the old imperative way to a less error-prone, more elegant, and concise coding style that’s also a breeze to parallelize. You’ll explore the syntax and semantics of lambda expressions, method and constructor references, and functional interfaces. You’ll design and write applications better using the new standards in Java 8 and the JDK.

Get it Now!  

3 Responses to "HOW-TO: Get started quickly with Spring 4.0 to build a simple REST-Like API (walkthrough)"

  1. Matthias says:

    Hi Rafal!
    Is it possible to upload the source code to GitHub?
    Thank you!

  2. Rob says:

    In your repository you have

    @Override
    public Account save(Account account) {
    mongoTemplate.save(account);
    return account;
    }

    When it should probably be:
    @Override
    public Account save(Account account) {
    return mongoTemplate.save(account);
    }

  3. Wesslan says:

    Is there something in this tutorial that is Spring 4.0-specific?

Leave a Reply


6 − four =



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.

Sign up for our Newsletter

15,153 insiders are already enjoying weekly updates and complimentary whitepapers! Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

As an extra bonus, by joining you will get our brand new e-books, published by Java Code Geeks and their JCG partners for your reading pleasure! Enter your info and stay on top of things,

  • Fresh trends
  • Cases and examples
  • Research and insights
  • Two complimentary e-books
Get tutored by the Geeks! JCG Academy is a fact... Join Now
Hello. Add your message here.