Skip to content

Commit

Permalink
Implement rest API for Member, add swagger, flyway db migration.
Browse files Browse the repository at this point in the history
  • Loading branch information
hoangtle committed Nov 3, 2016
1 parent c766e02 commit 848fea0
Show file tree
Hide file tree
Showing 13 changed files with 408 additions and 22 deletions.
8 changes: 8 additions & 0 deletions etc/members.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
1|Messi||Lionel|[email protected]|0939481929|1987-01-01
2|Ronaldo||Cristiano|[email protected]|0939481929|1985-01-01
3|Xavier||Creus|[email protected]|0939481929|1980-01-01
4|Iniesta||Andres|[email protected]|0939481929|1984-01-01
5|Ibrahimovic||Zlatan|[email protected]|0939481929|1981-01-01
6|Falcao||Radamel|[email protected]|0939481929|1986-01-01
7|Persie||Robin|[email protected]|0939481929|1983-01-01
8|Pirlo||Andrea|[email protected]|0939481929|1987-01-01
27 changes: 21 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- <dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency> -->
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
Expand All @@ -38,8 +38,8 @@
<!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope> </dependency> -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
Expand All @@ -54,20 +54,35 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<app.version>${project.version}</app.version>
<swagger.version>2.0.3</swagger.version>
</properties>
</project>
27 changes: 13 additions & 14 deletions src/main/java/com/trhoanglee/expense/Application.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
package com.trhoanglee.expense;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;

/**
* @author hoangtle
*/
@ComponentScan
@EnableAutoConfiguration
@EntityScan(basePackageClasses = {Application.class, Jsr310JpaConverters.class})
@SpringBootApplication
public class Application {
private static final Logger LOG = LoggerFactory.getLogger(Application.class.getCanonicalName());
public static void main(String... args) {
ApplicationContext appContext = SpringApplication.run(Application.class, args);

MemberService memberService = appContext.getBean(MemberService.class);
LOG.info(memberService.ping());
SpringApplication.exit(appContext);
}
public static void main(String... args) throws IOException {
ApplicationContext appContext = SpringApplication.run(Application.class, args);
MemberService memberService = appContext.getBean(MemberService.class);
String membersFilePath = (args.length > 0)? args[0] : "etc/members.txt";
memberService.loadMembersFromFile(membersFilePath);
}
}
80 changes: 78 additions & 2 deletions src/main/java/com/trhoanglee/expense/MemberService.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,86 @@
package com.trhoanglee.expense;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Date;
import java.util.List;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.trhoanglee.expense.domain.Member;
import com.trhoanglee.expense.domain.Name;
import com.trhoanglee.expense.repository.MemberRepository;

/**
* @author trhoanglee
*/
@Service
@Transactional(readOnly = true)
public class MemberService {
public String ping() {
return "pong";
@Autowired
private MemberRepository memberRepo;

@Transactional
public Member saveMember(@Valid Member member) {
return memberRepo.save(member);
}

@Transactional
public void deleteAllMembers() {
memberRepo.deleteAllInBatch();
}

public Member getMember(Long id) {
return memberRepo.getOne(id);
}

public List<Member> search(String keyword, int page, int pageSize) {
keyword = (keyword == null) ? "" : keyword;
return memberRepo.searchMembers(keyword, new PageRequest(page, pageSize));
}

@Transactional
public void deleteMembers(String[] ids) {
memberRepo.deleteMembers(ids);
}

@Transactional
public long loadMembersFromFile(String filePath) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
return reader.lines()
.map(this::parseMember)
.map(this::saveMember)
.count();
}
}

/**
* memberLine format: id|firstName|middleName|lastName|email|mobile|dob dob
* format: yyyy-MM-dd
*/
private Member parseMember(String memberLine) {
String[] items = memberLine.split("\\|");
if (items.length < 7) {
throw new IllegalArgumentException(String.format("Invalid member-line format: %s", memberLine));
}

Member member = new Member();
try {
member.setId(Long.parseLong(items[0]));
} catch (NumberFormatException ex) {
throw new IllegalArgumentException(String.format("Invalid member-line format: %s", memberLine, ex));
}
member.setName(new Name(items[1], items[2], items[3]));
member.setEmail(items[4]);
member.setMobile(items[5]);
member.setDob(new Date(java.sql.Date.valueOf(items[6]).getTime()));
return member;
}

}
28 changes: 28 additions & 0 deletions src/main/java/com/trhoanglee/expense/config/CommonConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.trhoanglee.expense.config;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;


@Configuration
public class CommonConfig {
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:i18n/messages", "classpath:i18n/validation");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}

@Bean
public Validator validator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
factoryBean.setValidationMessageSource(messageSource());
return factoryBean;
}
}
17 changes: 17 additions & 0 deletions src/main/java/com/trhoanglee/expense/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.trhoanglee.expense.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/**").permitAll()
.anyRequest().permitAll();
}
}
36 changes: 36 additions & 0 deletions src/main/java/com/trhoanglee/expense/config/SwaggerConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.trhoanglee.expense.config;

import static springfox.documentation.builders.PathSelectors.regex;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StopWatch;

import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
private final Logger logger = LoggerFactory.getLogger(SwaggerConfig.class);

@Bean
public Docket swaggerSpringfoxDocket() {
logger.debug("Starting Swagger");
StopWatch watch = new StopWatch();
watch.start();

Docket docket = new Docket(DocumentationType.SWAGGER_2)
.select()
.paths(regex("/api/.*"))
.build();

watch.stop();
logger.debug("Started Swagger in {} ms", watch.getTotalTimeMillis());

return docket;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.trhoanglee.expense.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.trhoanglee.expense.domain.Expense;

public interface ExpenseRepository extends JpaRepository<Expense, Long>{

}
13 changes: 13 additions & 0 deletions src/main/java/com/trhoanglee/expense/util/CommonUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.trhoanglee.expense.util;

import javax.ws.rs.BadRequestException;

public class CommonUtils {
public static Long parsePathVariableId(String id) {
try {
return Long.parseLong(id);
} catch (NumberFormatException ex) {
throw new BadRequestException(String.format("'%s' is not a valid id", id));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.trhoanglee.expense.web.controller;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@ControllerAdvice
@RestController
public class ExceptionController {
private static final Logger LOG = LoggerFactory.getLogger(ExceptionController.class.getCanonicalName());


@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> handleValidationError(MethodArgumentNotValidException ex) {
Map<String, String> fieldErrorMap = new HashMap<>();

BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
for (FieldError fieldError: fieldErrors) {
fieldErrorMap.put(fieldError.getField(), fieldError.getDefaultMessage());
}

return fieldErrorMap;
}

@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleUnexpectedException(Exception ex) {
LOG.error("Error during process request", ex);
return ex.getMessage();
}
}
Loading

0 comments on commit 848fea0

Please sign in to comment.