Skip to content

Instantly share code, notes, and snippets.

@christherama
Last active May 17, 2021 07:55
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save christherama/f35599c6084cfdc2834311c4f44358a5 to your computer and use it in GitHub Desktop.
Save christherama/f35599c6084cfdc2834311c4f44358a5 to your computer and use it in GitHub Desktop.
These files demonstrate how to implement a custom Spring validator for a Gif entity that includes as a mapped property a Category entity.
<!-- gif/form.html -->
<!-- Notice that the "file" field now uses object binding with th:field="*{file}" -->
<!-- since the "file" is now part of the Gif entity class (see Gif.java). -->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="layout :: head('upload')"></head>
<body>
<div th:replace="layout :: nav"></div>
<div th:replace="layout :: flash"></div>
<div class="container upload">
<div class="row">
<div class="col s12">
<h2 th:text="${heading}">Category</h2>
<div class="subtitle" th:if="${heading == 'Upload'}">Upload and share your GIFs with friends and family on Facebook, Twitter, and everywhere else.</div>
</div>
</div>
<div class="divider"></div>
<form action="#" th:action="@{${action}}" th:object="${gif}" method="post" enctype="multipart/form-data">
<input type="hidden" th:field="*{id}"/>
<input type="hidden" th:field="*{username}"/>
<input type="hidden" th:field="*{favorite}" />
<input type="hidden" th:field="*{hash}"/>
<div class="row">
<div class="col s12 l8">
<div class="file-wrapper" th:classappend="${#fields.hasErrors('file')}? 'error' : ''">
<input type="file" th:field="*{file}"/>
<span class="placeholder" data-placeholder="Choose an image...">Choose an image...</span>
<label th:for="file" class="button">Browse</label>
<div class="error-message" th:if="${#fields.hasErrors('file')}" th:errors="*{file}"></div>
</div>
</div>
</div>
<div class="row">
<div class="col s12 l8" th:classappend="${#fields.hasErrors('description')}? 'error' : ''">
<input type="text" th:field="*{description}" placeholder="Description"/>
<div class="error-message" th:if="${#fields.hasErrors('description')}" th:errors="*{description}"></div>
</div>
</div>
<div class="row">
<div class="col s12 l8" th:classappend="${#fields.hasErrors('category')}? 'error' : ''">
<select th:field="*{category.id}" class="cs-select cs-skin-border">
<option value="" disabled="disabled">Select a category</option>
<option th:each="cat : ${categories}" th:value="${cat.id}" th:text="${cat.name}" th:style="|color:${cat.colorCode}|"></option>
</select>
<div class="error-message" th:if="${#fields.hasErrors('category')}" th:errors="*{category}"></div>
</div>
</div>
<div class="row">
<div class="col s12">
<button type="submit" th:text="${submit}" class="button">Upload</button>
<a href="javascript:window.location = document.referrer;" class="button">Cancel</a>
</div>
</div>
</form>
</div>
<div th:replace="layout :: scripts"></div>
</body>
</html>
/*
Notice the @Transient annotation on the MultipartFile field.
This means that the field value will NOT be persisted to the database.
*/
package com.teamtreehouse.giflib.model;
import org.springframework.web.multipart.MultipartFile;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
@Entity
public class Gif {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Lob
private byte[] bytes;
@Transient
private MultipartFile file;
private String description;
@ManyToOne
private Category category;
private LocalDateTime dateUploaded = LocalDateTime.now();
private String username = "You";
private boolean favorite;
private String hash;
public Gif(){}
/* Getters, setters, and unrelated methods omitted for brevity */
}
/*
Notice the method annotated with @InitBinder("gif").
This ensures that the GifValidator is used for validating @Valid parameters named "gif"
*/
package com.teamtreehouse.giflib.web.controller;
import com.teamtreehouse.giflib.model.Gif;
import com.teamtreehouse.giflib.service.CategoryService;
import com.teamtreehouse.giflib.service.GifService;
import com.teamtreehouse.giflib.validator.GifValidator;
import com.teamtreehouse.giflib.web.FlashMessage;
import com.teamtreehouse.giflib.web.FlashMessage.Status;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
/*
Unrelated code omitted for brevity.
*/
@Controller
public class GifController {
@Autowired
private GifService gifService;
@InitBinder("gif")
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new GifValidator());
}
// Upload a new GIF
@RequestMapping(value = "/gifs", method = RequestMethod.POST)
public String addGif(@Valid Gif gif, BindingResult result, RedirectAttributes redirectAttributes) {
if(result.hasErrors()) {
redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.gif",result);
redirectAttributes.addFlashAttribute("gif",gif);
return "redirect:/upload";
}
gifService.save(gif, gif.getFile());
redirectAttributes.addFlashAttribute("flash",new FlashMessage("GIF added!", Status.SUCCESS));
return String.format("redirect:/gifs/%s",gif.getId());
}
// Update an existing GIF
@RequestMapping(value = "/gifs/{gifId}", method = RequestMethod.POST)
public String updateGif(@Valid Gif gif, BindingResult result, RedirectAttributes redirectAttributes) {
if(result.hasErrors()) {
redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.gif",result);
redirectAttributes.addFlashAttribute("gif",gif);
return String.format("redirect:/gifs/%s/edit",gif.getId());
}
gifService.update(gif,gif.getFile());
redirectAttributes.addFlashAttribute("flash",new FlashMessage("GIF updated!", Status.SUCCESS));
return String.format("redirect:/gifs/%s",gif.getId());
}
}
package com.teamtreehouse.giflib.service;
import com.teamtreehouse.giflib.dao.GifDao;
import com.teamtreehouse.giflib.model.Gif;
import org.hashids.Hashids;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
/*
Unrelated code omitted for brevity
*/
@Service
public class GifServiceImpl implements GifService {
@Autowired
private GifDao gifDao;
@Override
public void save(Gif gif, MultipartFile file) {
try {
gif.setBytes(file.getBytes());
} catch (IOException e) {
}
gifDao.save(gif);
}
@Override
public void update(Gif gif, MultipartFile file) {
if(file != null && !file.isEmpty()) {
try {
gif.setBytes(file.getBytes());
} catch (IOException e) {
}
} else {
Gif oldGif = gifDao.findById(gif.getId());
gif.setBytes(oldGif.getBytes());
}
gifDao.save(gif);
}
}
/*
This is the custom validator class.
*/
package com.teamtreehouse.giflib.validator;
import com.teamtreehouse.giflib.model.Gif;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
@Component
public class GifValidator implements Validator {
@Autowired
private Validator validator;
@Override
public boolean supports(Class<?> clazz) {
return Gif.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
Gif gif = (Gif)target;
// Validate file only if the Gif's id is null (with a null id, it must be a new Gif),
// so that existing Gif can be updated without uploading new file
if(gif.getId() == null && (gif.getFile() == null || gif.getFile().isEmpty())) {
errors.rejectValue("file","file.required","Please choose a file to upload");
}
// Validate description
ValidationUtils.rejectIfEmptyOrWhitespace(errors,"description","description.empty","Please enter a description");
// Validate category
ValidationUtils.rejectIfEmpty(errors,"category","category.empty","Please choose a category");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment