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.Comment;
import cc.ryanc.halo.model.domain.Post; 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 lombok.Data;
import java.util.Date; import java.util.Date;
@ -12,7 +12,7 @@ import java.util.Date;
* @date : 2019-03-09 * @date : 2019-03-09
*/ */
@Data @Data
public class CommentViewOutputDTO extends AbstractOutputConverter<CommentViewOutputDTO, Comment> { public class CommentViewOutputDTO implements OutputConverter<CommentViewOutputDTO, Comment> {
private Long commentId; 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.Comment;
import cc.ryanc.halo.model.domain.Post; 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 com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data; import lombok.Data;
@ -14,7 +14,7 @@ import java.util.List;
* @date : 2019-03-09 * @date : 2019-03-09
*/ */
@Data @Data
public class PageAdminOutputDTO extends AbstractOutputConverter<PageAdminOutputDTO, Post> { public class PageAdminOutputDTO implements OutputConverter<PageAdminOutputDTO, Post> {
private Long postId; private Long postId;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,13 @@
package cc.ryanc.halo.model.dto.base; 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. * Converter interface for input DTO.
* *
@ -12,13 +20,26 @@ public interface InputConverter<DOMAIN> {
* *
* @return new domain with same value(not null) * @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) * Update a domain by dto.(shallow)
* *
* @param domain updated domain * @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; package cc.ryanc.halo.model.dto.base;
import static cc.ryanc.halo.utils.BeanUtils.updateProperties;
/** /**
* Converter interface for output DTO. * 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 * @author johnniang
*/ */
public interface OutputConverter<DTO, DOMAIN> { public interface OutputConverter<DTO extends OutputConverter<DTO, DOMAIN>, DOMAIN> {
/** /**
* Convert from domain.(shallow) * Convert from domain.(shallow)
@ -13,5 +19,11 @@ public interface OutputConverter<DTO, DOMAIN> {
* @param domain domain data * @param domain domain data
* @return converted dto 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; package cc.ryanc.halo.model.params;
import cc.ryanc.halo.model.domain.Post; 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.model.enums.PostTypeEnum;
import cc.ryanc.halo.utils.MarkdownUtils; import cc.ryanc.halo.utils.MarkdownUtils;
import lombok.Data; import lombok.Data;
@ -13,7 +13,7 @@ import lombok.Data;
* @date : 2019/03/04 * @date : 2019/03/04
*/ */
@Data @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 the target instance
return targetInstance; return targetInstance;
} catch (Exception e) { } 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() final List<PostViewOutputDTO> postsLatest = postService.findPostLatest()
.stream() .stream()
.map(post -> new PostViewOutputDTO().convertFrom(post)) .map(post -> (PostViewOutputDTO) new PostViewOutputDTO().convertFrom(post))
.collect(Collectors.toList()); .collect(Collectors.toList());
model.addAttribute("postsLatest", postsLatest); model.addAttribute("postsLatest", postsLatest);
@ -121,7 +121,7 @@ public class AdminController extends BaseController {
//查询最新的评论 //查询最新的评论
final List<CommentViewOutputDTO> commentsLatest = commentService.findCommentsLatest() final List<CommentViewOutputDTO> commentsLatest = commentService.findCommentsLatest()
.stream() .stream()
.map(comment -> new CommentViewOutputDTO().convertFrom(comment)) .map(comment -> (CommentViewOutputDTO) new CommentViewOutputDTO().convertFrom(comment))
.collect(Collectors.toList()); .collect(Collectors.toList());
model.addAttribute("commentsLatest", commentsLatest); model.addAttribute("commentsLatest", commentsLatest);

View File

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

View File

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

View File

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

View File

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