Refactor InputConverter and OutputConverter

pull/137/head
johnniang 2019-03-11 16:51:21 +08:00
parent 12a6b548a4
commit d49a21501d
20 changed files with 304 additions and 30 deletions

View File

@ -2,7 +2,7 @@ package cc.ryanc.halo.model.dto;
import cc.ryanc.halo.model.domain.Comment;
import cc.ryanc.halo.model.domain.Post;
import cc.ryanc.halo.model.dto.base.AbstractOutputConverter;
import cc.ryanc.halo.model.dto.base.OutputConverter;
import lombok.Data;
import java.util.Date;
@ -12,7 +12,7 @@ import java.util.Date;
* @date : 2019-03-09
*/
@Data
public class CommentViewOutputDTO extends AbstractOutputConverter<CommentViewOutputDTO, Comment> {
public class CommentViewOutputDTO implements OutputConverter<CommentViewOutputDTO, Comment> {
private Long commentId;

View File

@ -2,7 +2,7 @@ package cc.ryanc.halo.model.dto;
import cc.ryanc.halo.model.domain.Comment;
import cc.ryanc.halo.model.domain.Post;
import cc.ryanc.halo.model.dto.base.AbstractOutputConverter;
import cc.ryanc.halo.model.dto.base.OutputConverter;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
@ -14,7 +14,7 @@ import java.util.List;
* @date : 2019-03-09
*/
@Data
public class PageAdminOutputDTO extends AbstractOutputConverter<PageAdminOutputDTO, Post> {
public class PageAdminOutputDTO implements OutputConverter<PageAdminOutputDTO, Post> {
private Long postId;

View File

@ -1,7 +1,7 @@
package cc.ryanc.halo.model.dto;
import cc.ryanc.halo.model.domain.*;
import cc.ryanc.halo.model.dto.base.AbstractOutputConverter;
import cc.ryanc.halo.model.dto.base.OutputConverter;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
@ -14,7 +14,7 @@ import java.util.List;
* @author johnniang
*/
@Data
public class PostDetailOutputDTO extends AbstractOutputConverter<PostDetailOutputDTO, Post> {
public class PostDetailOutputDTO implements OutputConverter<PostDetailOutputDTO, Post> {
private Long postId;

View File

@ -1,7 +1,9 @@
package cc.ryanc.halo.model.dto;
import cc.ryanc.halo.model.domain.*;
import cc.ryanc.halo.model.dto.base.AbstractOutputConverter;
import cc.ryanc.halo.model.domain.Category;
import cc.ryanc.halo.model.domain.Post;
import cc.ryanc.halo.model.domain.Tag;
import cc.ryanc.halo.model.dto.base.OutputConverter;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
@ -14,7 +16,7 @@ import java.util.List;
* @author johnniang
*/
@Data
public class PostSimpleOutputDTO extends AbstractOutputConverter<PostSimpleOutputDTO, Post> {
public class PostSimpleOutputDTO implements OutputConverter<PostSimpleOutputDTO, Post> {
private Long postId;

View File

@ -1,7 +1,7 @@
package cc.ryanc.halo.model.dto;
import cc.ryanc.halo.model.domain.Post;
import cc.ryanc.halo.model.dto.base.AbstractOutputConverter;
import cc.ryanc.halo.model.dto.base.OutputConverter;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
@ -12,7 +12,7 @@ import java.util.Date;
* @date : 2019-03-09
*/
@Data
public class PostViewOutputDTO extends AbstractOutputConverter<PostViewOutputDTO, Post> {
public class PostViewOutputDTO implements OutputConverter<PostViewOutputDTO, Post> {
private Long postId;

View File

@ -11,6 +11,7 @@ import static cc.ryanc.halo.utils.BeanUtils.updateProperties;
*
* @author johnniang
*/
@Deprecated
public abstract class AbstractInputConverter<DOMAIN> implements InputConverter<DOMAIN> {
@SuppressWarnings("unchecked")

View File

@ -12,7 +12,8 @@ import static cc.ryanc.halo.utils.BeanUtils.updateProperties;
*
* @author johnniang
*/
public abstract class AbstractOutputConverter<DTO, DOMAIN> implements OutputConverter<DTO, DOMAIN> {
@Deprecated
public abstract class AbstractOutputConverter<DTO extends OutputConverter<DTO, DOMAIN>, DOMAIN> implements OutputConverter<DTO, DOMAIN> {
@SuppressWarnings("unchecked")
private final Class<DTO> dtoType = (Class<DTO>) fetchType(0);
@ -23,9 +24,9 @@ public abstract class AbstractOutputConverter<DTO, DOMAIN> implements OutputConv
@Override
@SuppressWarnings("unchecked")
public DTO convertFrom(DOMAIN domain) {
public <T extends DTO> T convertFrom(DOMAIN domain) {
updateProperties(domain, this);
return (DTO) this;
return (T) this;
}
/**

View File

@ -1,5 +1,13 @@
package cc.ryanc.halo.model.dto.base;
import cc.ryanc.halo.utils.ReflectionUtils;
import java.lang.reflect.ParameterizedType;
import java.util.Objects;
import static cc.ryanc.halo.utils.BeanUtils.transformFrom;
import static cc.ryanc.halo.utils.BeanUtils.updateProperties;
/**
* Converter interface for input DTO.
*
@ -12,13 +20,26 @@ public interface InputConverter<DOMAIN> {
*
* @return new domain with same value(not null)
*/
DOMAIN convertTo();
@SuppressWarnings("unchecked")
default DOMAIN convertTo() {
// Get parameterized type
ParameterizedType currentType = ReflectionUtils.getParameterizedType(InputConverter.class, this.getClass());
// Assert not equal
Objects.requireNonNull(currentType, "Cannot fetch actual type because parameterized type is null");
Class<DOMAIN> domainClass = (Class<DOMAIN>) currentType.getActualTypeArguments()[0];
return transformFrom(this, domainClass);
}
/**
* Update a domain by dto.(shallow)
*
* @param domain updated domain
*/
void update(DOMAIN domain);
default void update(DOMAIN domain) {
updateProperties(this, domain);
}
}

View File

@ -1,11 +1,17 @@
package cc.ryanc.halo.model.dto.base;
import static cc.ryanc.halo.utils.BeanUtils.updateProperties;
/**
* Converter interface for output DTO.
*
* <b>The implementation type must be equal to DTO type</b>
*
* @param <DTO> the implementation class type
* @param <DOMAIN> doamin type
* @author johnniang
*/
public interface OutputConverter<DTO, DOMAIN> {
public interface OutputConverter<DTO extends OutputConverter<DTO, DOMAIN>, DOMAIN> {
/**
* Convert from domain.(shallow)
@ -13,5 +19,11 @@ public interface OutputConverter<DTO, DOMAIN> {
* @param domain domain data
* @return converted dto data
*/
DTO convertFrom(DOMAIN domain);
@SuppressWarnings("unchecked")
default <T extends DTO> T convertFrom(DOMAIN domain) {
updateProperties(domain, this);
return (T) this;
}
}

View File

@ -1,7 +1,7 @@
package cc.ryanc.halo.model.params;
import cc.ryanc.halo.model.domain.Post;
import cc.ryanc.halo.model.dto.base.AbstractInputConverter;
import cc.ryanc.halo.model.dto.base.InputConverter;
import cc.ryanc.halo.model.enums.PostTypeEnum;
import cc.ryanc.halo.utils.MarkdownUtils;
import lombok.Data;
@ -13,7 +13,7 @@ import lombok.Data;
* @date : 2019/03/04
*/
@Data
public class JournalParam extends AbstractInputConverter<Post> {
public class JournalParam implements InputConverter<Post> {
/**
*

View File

@ -52,7 +52,7 @@ public class BeanUtils {
// Return the target instance
return targetInstance;
} catch (Exception e) {
throw new BeanUtilsException("Failed to new " + targetClass.getName() + "instance or copy properties", e);
throw new BeanUtilsException("Failed to new " + targetClass.getName() + " instance or copy properties", e);
}
}

View File

@ -0,0 +1,74 @@
package cc.ryanc.halo.utils;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* Reflection utilities.
*
* @author johnniang
*/
public class ReflectionUtils {
private ReflectionUtils() {
}
/**
* Gets parameterized type.
*
* @param interfaceType interface type must not be null
* @param genericTypes generic type array
* @return parameterized type of the interface or null if it is mismatch
*/
@Nullable
public static ParameterizedType getParameterizedType(@NonNull Class<?> interfaceType, Type... genericTypes) {
Assert.notNull(interfaceType, "Interface type must not be null");
ParameterizedType currentType = null;
for (Type genericType : genericTypes) {
if (genericType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericType;
if (parameterizedType.getRawType().getTypeName().equals(interfaceType.getTypeName())) {
currentType = parameterizedType;
break;
}
}
}
return currentType;
}
/**
* Gets parameterized type.
*
* @param interfaceType interface type must not be null
* @param implementationClass implementation class of the interface must not be null
* @return parameterized type of the interface or null if it is mismatch
*/
@Nullable
public static ParameterizedType getParameterizedType(@NonNull Class<?> interfaceType, Class<?> implementationClass) {
Assert.notNull(interfaceType, "Interface type must not be null");
if (implementationClass == null) {
// If the super class is Object parent then return null
return null;
}
// Get parameterized type
ParameterizedType currentType = getParameterizedType(interfaceType, implementationClass.getGenericInterfaces());
if (currentType != null) {
// return the current type
return currentType;
}
Class<?> superclass = implementationClass.getSuperclass();
return getParameterizedType(interfaceType, superclass);
}
}

View File

@ -110,7 +110,7 @@ public class AdminController extends BaseController {
//查询最新的文章
final List<PostViewOutputDTO> postsLatest = postService.findPostLatest()
.stream()
.map(post -> new PostViewOutputDTO().convertFrom(post))
.map(post -> (PostViewOutputDTO) new PostViewOutputDTO().convertFrom(post))
.collect(Collectors.toList());
model.addAttribute("postsLatest", postsLatest);
@ -121,7 +121,7 @@ public class AdminController extends BaseController {
//查询最新的评论
final List<CommentViewOutputDTO> commentsLatest = commentService.findCommentsLatest()
.stream()
.map(comment -> new CommentViewOutputDTO().convertFrom(comment))
.map(comment -> (CommentViewOutputDTO) new CommentViewOutputDTO().convertFrom(comment))
.collect(Collectors.toList());
model.addAttribute("commentsLatest", commentsLatest);

View File

@ -48,9 +48,9 @@ public class MenuController {
public String menus(Model model) {
List<PostViewOutputDTO> posts = postService.findAll(PostTypeEnum.POST_TYPE_PAGE.getDesc())
.stream()
.map(post -> new PostViewOutputDTO().convertFrom(post))
.map(post -> (PostViewOutputDTO) new PostViewOutputDTO().convertFrom(post))
.collect(Collectors.toList());
model.addAttribute("posts",posts);
model.addAttribute("posts", posts);
return "admin/admin_menu";
}

View File

@ -87,8 +87,10 @@ public class PageController {
public String pages(Model model) {
final List<PageAdminOutputDTO> posts = postService.findAll(PostTypeEnum.POST_TYPE_PAGE.getDesc())
.stream()
.map(post -> new PageAdminOutputDTO().convertFrom(post))
.map(post -> (PageAdminOutputDTO) new PageAdminOutputDTO().convertFrom(post))
.collect(Collectors.toList());
model.addAttribute("pages", posts);
return "admin/admin_page";
}

View File

@ -94,6 +94,9 @@ public class PostController extends BaseController {
@RequestParam(value = "status", defaultValue = "0") Integer status,
@PageableDefault(sort = "postDate", direction = DESC) Pageable pageable) {
final Page<Post> posts = postService.findPostByStatus(status, PostTypeEnum.POST_TYPE_POST.getDesc(), pageable);
Page<PostViewOutputDTO> postViewOutputDTOS = posts.map(post -> new PostViewOutputDTO().convertFrom(post));
model.addAttribute("posts", posts);
model.addAttribute("publishCount", postService.getCountByStatus(PostStatusEnum.PUBLISHED.getCode()));
model.addAttribute("draftCount", postService.getCountByStatus(PostStatusEnum.DRAFT.getCode()));
@ -180,7 +183,7 @@ public class PostController extends BaseController {
@RequestParam("tagList") String tagList) {
//old data
final Post oldPost = postService.fetchById(post.getPostId()).orElse(new Post());
BeanUtils.updateProperties(oldPost,post);
BeanUtils.updateProperties(oldPost, post);
post.setPostContent(MarkdownUtils.renderMarkdown(post.getPostContentMd()));
if (null == post.getPostDate()) {
post.setPostDate(new Date());
@ -308,7 +311,7 @@ public class PostController extends BaseController {
final String blogUrl = OPTIONS.get(BlogPropertiesEnum.BLOG_URL.getProp());
final List<PostViewOutputDTO> posts = postService.findAll(PostTypeEnum.POST_TYPE_POST.getDesc())
.stream()
.map(post -> new PostViewOutputDTO().convertFrom(post))
.map(post -> (PostViewOutputDTO) new PostViewOutputDTO().convertFrom(post))
.collect(Collectors.toList());
final StringBuilder urls = new StringBuilder();
for (PostViewOutputDTO post : posts) {

View File

@ -1,12 +1,18 @@
package cc.ryanc.halo.web.controller.api;
import cc.ryanc.halo.model.domain.Post;
import cc.ryanc.halo.model.domain.User;
import cc.ryanc.halo.model.dto.PostDetailOutputDTO;
import cc.ryanc.halo.model.params.JournalParam;
import cc.ryanc.halo.service.PostService;
import cc.ryanc.halo.service.UserService;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;
import static cc.ryanc.halo.model.support.HaloConst.USER_SESSION_KEY;
/**
* <pre>
* API
@ -21,8 +27,12 @@ public class ApiJournalController {
private final PostService postService;
public ApiJournalController(PostService postService) {
private final UserService userService;
public ApiJournalController(PostService postService,
UserService userService) {
this.postService = postService;
this.userService = userService;
}
/**
@ -33,9 +43,12 @@ public class ApiJournalController {
*/
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public PostDetailOutputDTO save(@RequestBody JournalParam journalParam) {
public PostDetailOutputDTO save(@RequestBody JournalParam journalParam, HttpSession session) {
User user = userService.findUser();
Post post = journalParam.convertTo();
post.setUser(user);
return new PostDetailOutputDTO().convertFrom(postService.create(post));
}

View File

@ -0,0 +1,74 @@
package cc.ryanc.halo.model.dto.base;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.Test;
import java.io.Serializable;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
public class InputConverterTest {
@Test
public void convertToTest() {
TestInputDTO inputDTO = new TestInputDTO("test_name");
TestDomain domain = inputDTO.convertTo();
assertThat(domain.getName(), equalTo("test_name"));
assertNull(domain.getAge());
}
@Test
public void updateTest() {
TestInputDTO inputDTO = new TestInputDTO("test_input_dto_name");
TestDomain domain = new TestDomain("test_domain_name", 10);
inputDTO.update(domain);
assertThat(domain.getName(), equalTo("test_input_dto_name"));
assertThat(domain.getAge(), equalTo(10));
}
@Test
public void subConvertToTest() {
SubTestInputDTO subTestInputDTO = new SubTestInputDTO();
subTestInputDTO.setName("test_name");
subTestInputDTO.setAge(10);
TestDomain domain = subTestInputDTO.convertTo();
assertThat(domain.getName(), equalTo("test_name"));
assertThat(domain.getAge(), equalTo(10));
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class TestDomain {
private String name;
private Integer age;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class TestInputDTO implements InputConverter<TestDomain>, Serializable {
private String name;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class SubTestInputDTO extends TestInputDTO {
private Integer age;
}
}

View File

@ -0,0 +1,61 @@
package cc.ryanc.halo.model.dto.base;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* Output converter test.
*
* @author johnniang
*/
public class OutputConverterTest {
@Test
public void convertFromTest() {
TestDomain domain = new TestDomain("test_domain_name", 10);
TestOutputDTO testOutputDTO = new TestOutputDTO().convertFrom(domain);
assertThat(testOutputDTO.getName(), equalTo("test_domain_name"));
}
@Test
public void convertFromSubTest() {
TestDomain domain = new TestDomain("test_domain_name", 10);
SubTestOutputDTO subTestOutputDTO = new SubTestOutputDTO().convertFrom(domain);
assertThat(subTestOutputDTO.getName(), equalTo("test_domain_name"));
assertThat(subTestOutputDTO.getAge(), equalTo(10));
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class TestDomain {
private String name;
private Integer age;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class TestOutputDTO implements OutputConverter<TestOutputDTO, TestDomain> {
private String name;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class SubTestOutputDTO extends TestOutputDTO {
private Integer age;
}
}

View File

@ -0,0 +1,10 @@
package cc.ryanc.halo.utils;
/**
* Reflection utils test.
*
* @author johnniang
*/
public class ReflectionUtilsTest {
}