thenReturn() wants Optional, but test fails - java

I'm trying to do an Unit test, but I receive the error 'cannot resolve the method thenReturn(suspectTest) ; wrap using java.Optional', but when I do (.thenReturn(Optional.of(suspectTest)), the test fails - 'Optional cannot be cast to class Suspect'. Here it is the test, the SuspectService and the SuspectController (edited post*). Thank you in advance!
#DataJpaTest
public class ControllerTest {
#Mock
Model model;
#Mock
SuspectService suspectService;
SuspectController suspectController;
#Rule
public MockitoRule rule = MockitoJUnit.rule();
#Before
public void setUp() throws Exception {
suspectController = new SuspectController(suspectService);
}
#Captor
ArgumentCaptor<Suspect> argumentCaptor;
#Test
public void showById() {
Integer id = 1;
Suspect suspectTest = new Suspect();
suspectTest.setId(Long.valueOf(id));
when(suspectService.getById(Long.valueOf(id))).thenReturn(Optional.of(suspectTest));
String viewName = suspectController.showById(id.toString(), model);
Assert.assertEquals("info", viewName);
verify(suspectService, times(1)).getById(Long.valueOf(id));
verify(model, times(1))
.addAttribute(eq("suspect"), argumentCaptor.capture() );
Suspect suspectArg = argumentCaptor.getValue();
Assert.assertEquals(suspectArg.getId(), suspectTest.getId() );
}
}
#Service
public class SuspectService implements BaseService<Suspect> {
private final com.unibuc.AWBD_Project_v1.repositories.SuspectRepository suspectRepository;
#Autowired
public SuspectService(SuspectRepository SuspectRepository) {
this.suspectRepository = SuspectRepository;
}
#Override
public Suspect insert(Suspect object) {
return suspectRepository.save(object);
}
#Override
public Suspect update(Long id, Suspect updatedObject) {
var foundId = suspectRepository.findById(id);
return foundId.map(suspectRepository::save).orElse(null);
}
#Override
public List<Suspect> getAll() {
return suspectRepository.findAll();
}
#Override
public Optional<Suspect> getById(Long id) {
return suspectRepository.findById(id);
}
#Override
public void deleteById(Long id)
{
try {
suspectRepository.deleteById(id);
} catch (SuspectException e) {
throw new SuspectException("Suspect not found");
}
}
#Override
public Page<Suspect> findAll(int page, int size, String sortBy, String sortType){
Sort sort = sortType.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortBy).ascending() :
Sort.by(sortBy).descending();
Pageable pageable = PageRequest.of(page - 1, size, sort);
return suspectRepository.findAll(pageable);
}
}
#RestController
#RequestMapping("/api/suspect")
public class SuspectController implements BaseController<Suspect> {
private final com.unibuc.AWBD_Project_v1.services.SuspectService SuspectService;
#Autowired
public SuspectController(SuspectService SuspectService) {
this.SuspectService = SuspectService;
}
#PostMapping("/suspects/add")
#Override
public String NewObject(Model model) {
model.addAttribute("suspect", new Suspect());
return "addsuspect";
}
#PostMapping("/suspects/update/{id}")
#Override
public String update(#PathVariable Long id, #Valid #ModelAttribute("suspect") Suspect suspect, BindingResult bindingResult, Model model) {
if(bindingResult.hasErrors())
{
boolean update = true;
model.addAttribute("update", update);
return "addsuspect";
}
SuspectService.insert(suspect);
return "redirect:/suspects";
}
#DeleteMapping("/suspects/delete/{id}")
#Override
public String deleteById(#PathVariable Long id) {
try {
SuspectService.deleteById(id);
} catch (SuspectException e)
{
throw new SuspectException("Suspect with " + id + " does not exist!");
}
return "redirect:/suspects";
}
#GetMapping("/suspect/{id}")
public String showById(#PathVariable String id, Model model){
model.addAttribute("suspect",
SuspectService.getById(Long.valueOf(id)));
return "info";
}
#RequestMapping("/suspects")
public String objectsList(#RequestParam(value = "page", required = false) Integer page,
#RequestParam(value = "sortBy", required = false) String sortBy,
#RequestParam(value = "sortType", required = false) String sortType, Model model) {
int size = 5;
if (page == null)
page = 1;
if(sortBy==null)
sortBy ="name";
if(sortType==null)
sortType ="asc";
Page<Suspect> pageSuspect = SuspectService.findAll(page, size, sortBy, sortType);
List<Suspect> suspects = pageSuspect.getContent();
model.addAttribute("currentPage", page);
model.addAttribute("totalPages", pageSuspect.getTotalPages());
model.addAttribute("totalItems", pageSuspect.getTotalElements());
model.addAttribute("sortBy", sortBy);
model.addAttribute("sortType", sortType);
model.addAttribute("reverseSortDir", sortType.equals("asc") ? "desc" : "asc");
model.addAttribute("suspects", suspects);
var suspect = new Suspect();
model.addAttribute("suspect", suspect);
return "suspects";
}
}

Related

Spring rest controller not working for file upload

I have tried a rest api without any View with mongodb. My application is working fine with Spring boot embedded server with no error. With standalone tomcat application is running without error but cannot access #RestController mapped url. It shows 404 page not found. It is not creating that url but other url works fine.
Here is config
FileStorageProperties.java
#Component
public class FileStorageProperties {
private static String baseDir = "E://temp//FileManager//";
private String uploadDir = "";
public String getUploadDir() {
return baseDir + uploadDir;
}
public void setUploadDir(String uploadDir) {
this.uploadDir = baseDir + uploadDir;
}
}
MediaTypeUtils.java
public class MediaTypeUtils {
// abc.zip
// abc.pdf,..
public static MediaType getMediaTypeForFileName(ServletContext servletContext, String fileName) {
// application/pdf
// application/xml
// image/gif, ...
String mineType = servletContext.getMimeType(fileName);
try {
MediaType mediaType = MediaType.parseMediaType(mineType);
return mediaType;
} catch (Exception e) {
return MediaType.APPLICATION_OCTET_STREAM;
}
}
}
Here is model
Avatar.java:
#Document
#Component
public class Avatar {
#Id
// #Indexed(unique = true)
private String id;
private String originalName;
#Indexed(unique = true)
private String generatedName;
private String size;
#DBRef
private User user;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getOriginalName() {
return originalName;
}
public void setOriginalName(String originalName) {
this.originalName = originalName;
}
public String getGeneratedName() {
return generatedName;
}
public void setGeneratedName(String generatedName) {
this.generatedName = generatedName;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
#Override
public String toString() {
return "Avatar [id=" + id + ", originalName=" + originalName + ", generatedName=" + generatedName + ", size="
+ size + ", user=" + user + "]";
}
}
UploadFileResponse.java
public class UploadFileResponse {
private String fileName;
private String fileType;
private long size;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFileType() {
return fileType;
}
public void setFileType(String fileType) {
this.fileType = fileType;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
public UploadFileResponse(String fileName, String fileType, long size) {
this.fileName = fileName;
this.fileType = fileType;
this.size = size;
}
}
FileStorageException.java
public class FileStorageException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = -2482390550398268830L;
public FileStorageException(String message) {
super(message);
}
public FileStorageException(String message, Throwable cause) {
super(message, cause);
}
}
MyFileNotFoundException.java
#ResponseStatus(HttpStatus.NOT_FOUND)
public class MyFileNotFoundException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = -7680896574598153059L;
public MyFileNotFoundException(String message) {
super(message);
}
public MyFileNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
Now repository:
AvatarRepository.java
public interface AvatarRepository extends MongoRepository<Avatar, String>{
public Avatar findAvatarByGeneratedName(String generatedName);
public Avatar findAvatarByUser(User user);
}
AvatarRepositopryImpl.java - There are some overrided MongoRepository methods which are ignored on the following code as those are not used at all.
#Repository
public class AvatarRepositopryImpl implements AvatarRepository {
#Autowired
MongoTemplate mongoTemplate;
private static final String COLLECTION_NAME = "avatar";
#Override
public <S extends Avatar> List<S> saveAll(Iterable<S> entities) {
// TODO Auto-generated method stub
return null;
}
#Override
public List<Avatar> findAll() {
return mongoTemplate.findAll(Avatar.class, COLLECTION_NAME);
}
#Override
public <S extends Avatar> S save(S entity) {
return mongoTemplate.save(entity);
}
#Override
public void deleteById(String id) {
mongoTemplate.remove(id);
}
#Override
public void delete(Avatar entity) {
mongoTemplate.remove(entity);
}
#Override
public Avatar findAvatarByGeneratedName(String generatedName) {
Query query = new Query();
query.addCriteria(Criteria.where("generatedName").is(generatedName));
return (Avatar) mongoTemplate.findOne(query, Avatar.class);
}
#Override
public Avatar findAvatarByUser(User user) {
// System.out.println(user);
Query query = new Query();
query.addCriteria(Criteria.where("user").is(user));
return (Avatar) mongoTemplate.find(query, Avatar.class);
}
}
Here is my service:
AvatarService.java
#Service
public class AvatarService {
#Autowired
private Avatar avatar;
//
// #Autowired
// private FileStorageService fileStorageService;
#Autowired
AvatarRepository avatarRepository;
/**
* #param fileStorageService
* #param avatarRepository
*/
public Avatar findAvatarByGeneratedName(String generatedName) {
return avatarRepository.findAvatarByGeneratedName(generatedName);
}
public Avatar findAvatarByUser(User user) {
System.out.println(user);
return avatarRepository.findAvatarByUser(user);
}
public Avatar saveAvatar(User user,String fileOriginalName, String fileNameToSave, String fileSize) {
avatar.setId(UUID.randomUUID().toString());
avatar.setOriginalName(fileOriginalName);
avatar.setGeneratedName(fileNameToSave);
avatar.setSize(fileSize);
avatar.setUser(user);
return avatarRepository.save(avatar);
}
public void deleteAvatarByGeneratedName(String generatedName, Avatar avatar) {
avatar = findAvatarByGeneratedName(generatedName);
avatarRepository.deleteById(avatar.getId());
}
AvatarManager.java
public class AvatarManager {
AvatarService avatarService;
#Autowired
Avatar avatar;
public AvatarManager(AvatarService avatarService) {
this.avatarService = avatarService;
}
public String saveAvatar(User user, MultipartFile file) {
deleteAvatarByUser(user);
String[] ext = file.getOriginalFilename().split(Pattern.quote("."));
String generatedName = new UUIDGenerator().generateId(avatar).toString() + "." + ext[ext.length - 1];
avatarService.saveAvatar(user, file.getOriginalFilename(), generatedName, file.getSize() + "");
new FileStorageService(new FileStorageProperties()).uploadFile(file, generatedName);
new UploadFileResponse(generatedName, file.getContentType(), file.getSize());
return generatedName;
}
public void deleteAvatar(String generatedName) {
Avatar avatar = avatarService.findAvatarByGeneratedName(generatedName);
if (avatar != null) {
new FileStorageService(new FileStorageProperties()).deleteFile(generatedName);
avatarService.deleteAvatarByGeneratedName(generatedName, avatar);
}
}
public void deleteAvatarByUser(User user) {
// System.out.println(user);
avatar = avatarService.findAvatarByUser(user);
// System.out.println(avatar.getGeneratedName());
if (avatar != null) {
deleteAvatar(avatar.getGeneratedName());
}
}
public ResponseEntity<Resource> downloadAvatar(String avatarGeneratedName, HttpServletRequest request) {
// System.out.println(request);
return new FileStorageService(new FileStorageProperties()).downloadFile(avatarGeneratedName, request);
}
}
Now Controller. I am giving two mapping. The first one is not involved with file manager. It works on both server. The another one is working on spring boot embedded server but not on standalone tomcat. It also is not showing any error or create any url.
Here is the Controller.java;
#CrossOrigin(origins = { "*" })
#RestController
public class UserController {
#Autowired
EmployeeService employeeService;
#Autowired
UserService userService;
#Autowired
AvatarService avatarService;
#Autowired
BCryptPasswordEncoder passwordEncoder;
//It works on both server
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String loginEmployee(#RequestParam Map<String, String> data) {
return new EmployeeManager(employeeService, userService)
.validateUserLogin(data.get("0").toString(), data.get("1"), passwordEncoder).toString();
}
//It doesn't work with standalone server
#RequestMapping(value = "/edit-employee-profile", method = RequestMethod.POST)
public void editProfileEmployee(#RequestParam(value = "avatar", required = false) MultipartFile file,
#RequestParam(value = "user") String data) {
JSONParser jp = new JSONParser(data);
System.out.println(jp);
LinkedHashMap<String, Object> userInfo;
try {
userInfo = jp.parseObject();
User user = userService.findUserByEmail(userInfo.get("email").toString());
System.out.println(user);
user.setAvatarExistance(true);
userService.updateUser(user);
// Employee employee = employeeService.findEmployeeByUser(user);
System.out.println(data);
new EmployeeManager(employeeService, userService).employeeProfileEdit(data);
if (file != null) {
System.out.println(file);
new AvatarManager(avatarService).saveAvatar(user, file);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// return "";
}
Post the file as request body (rather than as URL parameter and #RequestParam) to not excced the safe maximum URL length:
#RequestMapping(value = "/uploadFile", method = RequestMethod.POST)
#ResponseStatus(value = HttpStatus.OK)
public void uploadFile(ModelMap m, MultipartHttpServletRequest request) {
MultiValueMap<String, MultipartFile> multiFileMap = request.getMultiFileMap();
List<MultipartFile> files = multiFileMap.isEmpty() ? null : multiFileMap.values().iterator().next();
MultipartFile file = files != null && !files.isEmpty() ? files.get(0) : null;
// ...
}

POST, DELTE,GET operation RestController spring boot with composite key

I'm looking for the correct way to implements this operations (delete, post, get) in my controller class using JPA.
Here's my code i'm not sure about post if it is correct but really delete is causing problem because HttpDelete from Apache Component doesn't have setEntity method for request body.
The DAO :
#Repository
public interface PiecesReparationDAO extends JpaRepository<PiecesReparation, Integer>{
PiecesReparation findById(PieceRId id);
PiecesReparation findById_Id_produit(int id);
PiecesReparation findById_Id_Reparation(int id);
void deleteById(PieceRId id);
}
the model :
#Entity
public class PiecesReparation {
#EmbeddedId
PieceRId id;
#MapsId("id_produit")
#ManyToOne(optional = false)
#JoinColumn(name = "id_produit", referencedColumnName = "id")
private Produit product;
#MapsId("id_reparation")
#ManyToOne(optional = false)
#JoinColumn(name = "", referencedColumnName = "id")
private Reparation reparation;
private Timestamp date_utilisation;
public PieceRId getPieceRId(){
return this.id;
}
public void setPieceRId(PieceRId id){
this.id=id;
}
public Timestamp getDateUtilisation(){
return this.date_utilisation;
}
public void setDateUtilisation(Timestamp date){
this.date_utilisation=date;
}
}
classe PieceRId :
#Embeddable
public class PieceRId implements Serializable {
int id_produit;
int id_reparation;
//implements equals and hashCode
public int getIdProduit(){
return this.id_produit;
}
public int getIdReparation(){
return this.id_reparation;
}
public PieceRId(){
}
public PieceRId(int id_produit, int id_reparation){
this.id_produit=id_produit;
this.id_reparation=id_reparation;
}
#Override
public boolean equals(Object o){
if(o ==null) return false;
if(((PieceRId)o).getIdProduit() == this.getIdProduit() &&
((PieceRId)o).getIdReparation() == this.getIdReparation())
return true;
else return false;
}
#Override
public int hashCode() {
return Integer.valueOf(String.valueOf(id_produit)+String.valueOf(id_reparation));
}
}
The controller :
#RestController
public class PiecesReparationController {
#Autowired
private PiecesReparationDAO piecesReparationDao;
#GetMapping(value = "/Pieces")
public ArrayList<PiecesReparation> listePiecesReparations() {
ArrayList<PiecesReparation> listePiecesReparations= new ArrayList() ;
Iterable<PiecesReparation> piecesReparations = piecesReparationDao.findAll();
MappingJacksonValue produitsFiltres = new MappingJacksonValue(piecesReparations);
for (Iterator<PiecesReparation> i = piecesReparations.iterator(); i.hasNext();) {
PiecesReparation item = i.next();
listePiecesReparations.add(item);
}
return listePiecesReparations;
}
//Récupérer un produit par son Id
#GetMapping(value = "/Pieces/{id}")
public PiecesReparation selectionPiecesReparation(#PathVariable PieceRId id) {
return piecesReparationDao.findById(id);
}
//ajouter un produit
#PostMapping(value = "/Pieces")
public ResponseEntity<?> ajouterPiecesReparation(#RequestBody PieceReparationDTO pieceReparation) {
PiecesReparation pieceR = new PiecesReparation();
pieceR.setPieceRId(new PieceRId(pieceReparation.getIdProduit(), pieceReparation.getIdReparation()));
pieceR.setDateUtilisation(pieceReparation.getDateUtilisation());
PiecesReparation piecesReparationAdded = piecesReparationDao.save(pieceR);
if (piecesReparationAdded == null)
return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY);
else return new ResponseEntity<>(HttpStatus.CREATED);
}
#DeleteMapping(value = "/Pieces/{id}")
public void supprimerPiecesReparation(#PathVariable PieceRId id) {
piecesReparationDao.delete(id);
}
#DeleteMapping(value = "/Pieces")
public void supprimerPiecesReparation2(#RequestBody PieceRId id) {
PiecesReparation pieceR= new PiecesReparation();
pieceR.setPieceRId(new PieceRId(id.getIdProduit(),id.getIdReparation()));
piecesReparationDao.delete(pieceR);
}
#PutMapping (value = "/Pieces")
public void updateProduit(#RequestBody PieceReparationDTO pieceReparation) {
PiecesReparation pieceR = new PiecesReparation();
pieceR.setPieceRId(new PieceRId(pieceReparation.getIdProduit(), pieceReparation.getIdReparation()));
pieceR.setDateUtilisation(pieceReparation.getDateUtilisation());
PiecesReparation piecesReparationAdded = piecesReparationDao.save(pieceR);
}
}
method to consume web service :
public static void deleteRest(int id, String type){
try{
CloseableHttpClient client = HttpClientBuilder.create().build();
HttpDelete deleteRequest = new HttpDelete(getUrl(type)+"/"+id);
HttpResponse response=client.execute(deleteRequest);
System.out.print("response code :
"+response.getStatusLine().getStatusCode());
client.close();
}catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
I know how to delete with url and path variable but don't how to do with request body because no setEntity method. I'm not sure it it's ok for the others operations if it's implemented correctly. Thanks.

How to do paging on Object that isn't Entity?

I'm trying to do paging, very similar to the option of #RepositoryRestResource
but only on object that isn't Entity.
the code:
public class FloorplanDevice {
private String FloorplanName;
private Long FloorplanId;
private Long DeviceId;
private String DeviceName;
private String macAddress;
private Long groupId;
private LocatableType deviceType;
public String getFloorplanName() {
return FloorplanName;
}
public void setFloorplanName(String floorplanName) {
FloorplanName = floorplanName;
}
public Long getFloorplanId() {
return FloorplanId;
}
public void setFloorplanId(Long floorplanId) {
FloorplanId = floorplanId;
}
public Long getDeviceId() {
return DeviceId;
}
public void setDeviceId(Long deviceId) {
DeviceId = deviceId;
}
public String getDeviceName() {
return DeviceName;
}
public void setDeviceName(String deviceName) {
DeviceName = deviceName;
}
public String getMacAddress() {
return macAddress;
}
public void setMacAddress(String macAddress) {
this.macAddress = macAddress;
}
public Long getGroupId() {
return groupId;
}
public void setGroupId(Long groupId) {
this.groupId = groupId;
}
public LocatableType getDeviceType() {
return deviceType;
}
public void setDeviceType(LocatableType deviceType) {
this.deviceType = deviceType;
}
public FloorplanDevice() {
}
public FloorplanDevice(String floorplanName, Long floorplanId, Long deviceId, String deviceName, String macAddress, Long groupId, LocatableType deviceType) {
FloorplanName = floorplanName;
FloorplanId = floorplanId;
DeviceId = deviceId;
DeviceName = deviceName;
this.macAddress = macAddress;
this.groupId = groupId;
this.deviceType = deviceType;
}
}
This object doesn't have Repository but it has controller:
#RequestMapping(
path = arrayOf("/floorplanDevice/{groupId}", "/floorplanDevice/{groupId}/"),
method = arrayOf(RequestMethod.GET))
open fun getFloorplanDevice(#PathVariable("groupId") groupId: Long): ResponseEntity<*>{
var floorplanDevice= floorplanService.getFloorplanDevice(groupId)
return ResponseEntity(floorplanDevice, HttpStatus.OK)
}
So how can I do Paging to this object with page number and size (if it possible sorting also)?
I'm using java Spring
Thank you
Try something like this:
public Page<FloorplanDevice> getFloorplanDevice(#PathVariable("groupId") Long groupId,
#PageableDefault Pageable pageable)
List<FloorplanDevice> list = floorplanService.getFloorplanDevice(groupId);
MutableSortDefinition sort = pageable.getSort() != null ?
StreamSupport.stream(pageable.getSort().spliterator(), false)
.findFirst()
.map(it -> new MutableSortDefinition(it.getProperty(), it.isIgnoreCase(), it.isAscending()))
.orElse(null)
: null;
PagedListHolder<FloorplanDevice> pageHolder = new PagedListHolder<>(list, sort);
pageHolder.setPage(pageable.getPageNumber());
pageHolder.setPageSize(pageable.getPageSize());
pageHolder.resort();
List<FloorplanDevice> content = pageHolder.getPageList();
Page<FloorplanDevice> page = new PageImpl<>(content, pageable, list.size());
return page;
}
Solution:
This is my solution by using PageListHolder.
#RequestMapping(
path = arrayOf("/floorplanDevice/{groupId}/page/{pageNum}/size/{sizeNum}", "/floorplanDevice/{groupId}/page/{pageNum}/size/{sizeNum}/"),
method = arrayOf(RequestMethod.GET))
open fun getFloorplanDevicePage(#PathVariable("groupId") groupId: Long, #PathVariable("pageNum") pageNum: Int,#PathVariable("sizeNum") sizeNum: Int): ResponseEntity<*>{
var floorplanDevice= floorplanService.getFloorplanDevice(groupId)
var paging= PagedListHolder<FloorplanDevice>(floorplanDevice)
var setsize= paging.setPageSize(sizeNum)
if(paging.pageCount>pageNum){
var setpage=paging.setPage(pageNum)}
else{
return throw RuntimeException("page: $pageNum is too big, insert smaller page from 0 to ${paging.pageCount-1}")
}
var pageList= paging.pageList
return ResponseEntity(pageList, HttpStatus.OK)
}

Generic autowire fails with no bean defined

This is going to be somewhat long, but I am simply trying to learn fancy things using spring 4.1.7
The problem that I am facing is that spring doesn't like that there is no bean declared for R and W in the Reader and Writer Controllers. Everything compiles but during runtime nothing works and I get error
Error creating bean with name 'userController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: protected carefree.coding.dao.CommerceReaderDAO carefree.coding.controllers.rest.CommerceReaderController.reader; nested exception is java.lang.IllegalArgumentException: Can not set carefree.coding.dao.CommerceReaderDAO field carefree.coding.controllers.rest.CommerceReaderController.reader to com.sun.proxy.$Proxy57
I have a simple DAO class
#MappedSuperclass
public abstract class CommerceObject
{
public enum Type
{
USER
}
#Transient
protected Type type;
#Id
#GeneratedValue
#Column(name = "id")
protected long id;
public CommerceObject(Type type)
{
this.type = type;
setId(-1l);
}
public Type getType()
{
return type;
}
public long getId()
{
return id;
}
public void setId(long id)
{
this.id = id;
}
public boolean equals(CommerceObject object)
{
return getId() == object.getId() && getType().equals(object.getType());
}
#Override
public String toString()
{
return new Gson().toJson(this);
}
public CommerceObject fromString(String json)
{
return new Gson().fromJson(json, getClass());
}
public abstract boolean update(CommerceObject object);
public abstract CommerceObject copy();
#Override
public int hashCode()
{
throw new UnsupportedOperationException("No hashing for game objects");
}
}
Which is extended by
#Entity(name = "user")
public class User extends CommerceObject
{
#Column(name = "email")
private String email;
public User()
{
super(Type.USER);
}
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
#Override
public boolean update(CommerceObject object)
{
if (object instanceof User)
{
User user = (User) object;
if (equals(user))
{
user.setEmail(email);
}
}
return false;
}
#Override
public CommerceObject copy()
{
User user = new User();
user.setEmail(getEmail());
return user;
}
}
For reading and writing things to database I have two interfaces
Reader
public interface CommerceReaderInterface<V extends CommerceObject>
{
#Transactional(readOnly = true)
List<V> get();
#Transactional(readOnly = true)
V get(long id);
}
Writer
public interface CommerceWriterInterface<V extends CommerceObject>
{
#Transactional
V add(V v);
#Transactional
V update(V v);
#Transactional
V delete(V v);
}
Basic database access class
public abstract class CommerceDAO
{
protected SessionFactory sessionFactory;
protected Class aClass;
public CommerceDAO(SessionFactory sessionFactory, Class aClass)
{
this.sessionFactory = sessionFactory;
this.aClass = aClass;
}
}
Which allows reader and writer to exist
Reader
public class CommerceReaderDAO<V extends CommerceObject> extends CommerceDAO implements CommerceReaderInterface<V>
{
public CommerceReaderDAO(SessionFactory sessionFactory, Class aClass)
{
super(sessionFactory, aClass);
}
#Override
public List<V> get()
{
ClassMetadata hibernateMetadata = sessionFactory.getClassMetadata(aClass);
if (hibernateMetadata == null)
{
return null;
}
if (hibernateMetadata instanceof AbstractEntityPersister)
{
AbstractEntityPersister persister = (AbstractEntityPersister) hibernateMetadata;
String tableName = persister.getTableName();
if (tableName != null)
{
return sessionFactory.getCurrentSession().
createQuery(tableName).list();
}
}
return null;
}
#Override
public V get(long id)
{
V object = (V) sessionFactory.getCurrentSession().
get(aClass, id);
Hibernate.initialize(object);
return object;
}
}
Writer
public class CommerceWriterDAO<V extends CommerceObject> extends CommerceDAO implements CommerceWriterInterface<V>
{
public CommerceWriterDAO(SessionFactory sessionFactory, Class aClass)
{
super(sessionFactory, aClass);
}
#Override
public V add(V v)
{
if (v != null)
{
sessionFactory.getCurrentSession().save(v);
return v;
}
return null;
}
#Override
public V update(V v)
{
if (v != null)
{
sessionFactory.getCurrentSession().update(v);
return v;
}
return null;
}
#Override
public V delete(V v)
{
if (v != null)
{
sessionFactory.getCurrentSession().delete(v);
sessionFactory.getCurrentSession().flush();
return v;
}
return null;
}
}
And for controllers I decided to do same thing and have a reader and writer interface
Reader
public interface CommerceReaderInterface
{
#RequestMapping(value = "/get",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<String> get();
#RequestMapping(value = "/get/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<String> get(#PathVariable long id);
}
Writer
public interface CommerceWriterInterface<V extends CommerceObject>
{
#RequestMapping(value = "/add",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<String> add(#RequestBody V v);
#RequestMapping(value = "/update",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<String> update(#RequestBody V v);
#RequestMapping(value = "/delete",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<String> delete(#RequestBody V v);
}
And they are implemented normally
Reader
public abstract class CommerceReaderController<R extends CommerceReaderDAO<V>, V extends CommerceObject> implements CommerceReaderInterface
{
#Autowired
protected R reader;
#Override
public ResponseEntity<String> get()
{
List<V> list = reader.get();
if (list == null || list.isEmpty())
{
return ResponseUtil.notFound();
}
return ResponseUtil.ok(list);
}
#Override
public ResponseEntity<String> get(#PathVariable long id)
{
V object = reader.get(id);
if (object == null)
{
return ResponseUtil.notFound();
}
return ResponseUtil.ok(object);
}
}
Writer
public abstract class CommerceWriterController<W extends CommerceWriterDAO<V>, R extends CommerceReaderDAO<V>, V extends CommerceObject> extends CommerceReaderController<R, V> implements CommerceWriterInterface<V>
{
#Autowired
protected W writer;
#Override
public ResponseEntity<String> add(#RequestBody V v)
{
v = writer.add(v);
if (v == null)
{
return ResponseUtil.notFound();
}
return ResponseUtil.ok(v);
}
#Override
public ResponseEntity<String> update(#RequestBody V v)
{
v = writer.update(v);
if (v == null)
{
return ResponseUtil.notFound();
}
return ResponseUtil.ok(v);
}
#Override
public ResponseEntity<String> delete(#RequestBody V v)
{
v = writer.delete(v);
if (v == null)
{
return ResponseUtil.notFound();
}
return ResponseUtil.ok(v);
}
}
After all that work I thought that having a simple controller would be allowed
#Controller
#RequestMapping("/commerce/api/user")
public class UserController extends CommerceWriterController<UserWriterDAO, UserReaderDAO, User>
{
}
However as I understand in my abstract controllers I am not allowed to have such vague DAO objects. Is there any way I can circumvent this?

Select JsonView in the Spring MVC Controller

I'm currently writing a REST api using Jackson (2.4.0-rc3) and spring mvc (4.0.3), and I'm trying to make it secure.
In this way, I try to use JsonView to select the parts of the objects that can be serialized.
I've found the solution (which is not for me) to annotate my Controller method with the view I want. But I'd like to select on the fly the view inside the controller.
Is it possible to extend the ResponseEntity class in order to specify which JsonView I want ?
A little piece of code :
Here is the account class
public class Account {
#JsonProperty(value = "account_id")
private Long accountId;
#JsonProperty(value = "mail_address")
private String mailAddress;
#JsonProperty(value = "password")
private String password;
#JsonProperty(value = "insert_event")
private Date insertEvent;
#JsonProperty(value = "update_event")
private Date updateEvent;
#JsonProperty(value = "delete_event")
private Date deleteEvent;
#JsonView(value = PublicView.class)
public Long getAccountId() {
return accountId;
}
#JsonView(value = PublicView.class)
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
#JsonView(value = OwnerView.class)
public String getMailAddress() {
return mailAddress;
}
#JsonView(value = OwnerView.class)
public void setMailAddress(String mailAddress) {
this.mailAddress = mailAddress;
}
#JsonIgnore
public String getPassword() {
return password;
}
#JsonView(value = OwnerView.class)
public void setPassword(String password) {
this.password = password;
}
#JsonView(value = AdminView.class)
public Date getInsertEvent() {
return insertEvent;
}
#JsonView(value = AdminView.class)
public void setInsertEvent(Date insertEvent) {
this.insertEvent = insertEvent;
}
#JsonView(value = AdminView.class)
public Date getUpdateEvent() {
return updateEvent;
}
#JsonView(value = AdminView.class)
public void setUpdateEvent(Date updateEvent) {
this.updateEvent = updateEvent;
}
#JsonView(value = AdminView.class)
public Date getDeleteEvent() {
return deleteEvent;
}
#JsonView(value = OwnerView.class)
public void setDeleteEvent(Date deleteEvent) {
this.deleteEvent = deleteEvent;
}
#JsonProperty(value = "name")
public abstract String getName();
}
Here is the account controller
#RestController
#RequestMapping("/account")
public class AccountCtrlImpl implements AccountCtrl {
#Autowired
private AccountSrv accountSrv;
public AccountSrv getAccountSrv() {
return accountSrv;
}
public void setAccountSrv(AccountSrv accountSrv) {
this.accountSrv = accountSrv;
}
#Override
#RequestMapping(value = "/get_by_id/{accountId}", method = RequestMethod.GET, headers = "Accept=application/json")
public ResponseEntity<Account> getById(#PathVariable(value = "accountId") Long accountId) {
try {
return new ResponseEntity<Account>(this.getAccountSrv().getById(accountId), HttpStatus.OK);
} catch (ServiceException e) {
return new ResponseEntity<Account>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
#Override
#RequestMapping(value = "/get_by_mail_address/{mail_address}", method = RequestMethod.GET, headers = "Accept=application/json")
public ResponseEntity<Account> getByMailAddress(#PathVariable(value = "mail_address") String mailAddress) {
try {
return new ResponseEntity<Account>(this.getAccountSrv().getByMailAddress(mailAddress), HttpStatus.OK);
} catch (ServiceException e) {
return new ResponseEntity<Account>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
#Override
#RequestMapping(value = "/authenticate/{mail_address}/{password}", method = RequestMethod.GET, headers = "Accept=application/json")
public ResponseEntity<Account> authenticate(#PathVariable(value = "mail_address") String mailAddress, #PathVariable(value = "password") String password) {
return new ResponseEntity<Account>(HttpStatus.NOT_IMPLEMENTED);
}
}
I really like the solution presented here to dynamically select a json view inside your controller method.
Basically, you return a MappingJacksonValue which you construct with the value you want to return. After that you call setSerializationView(viewClass) with the proper view class. In my use case, I returned a different view depending on the current user, something like this:
#RequestMapping("/foos")
public MappingJacksonValue getFoo(#AuthenticationPrincipal UserDetails userDetails ) {
MappingJacksonValue value = new MappingJacksonValue( fooService.getAll() );
if( userDetails.isAdminUser() ) {
value.setSerializationView( Views.AdminView.class );
} else {
value.setSerializationView( Views.UserView.class );
}
return value;
}
BTW: If you are using Spring Boot, you can control if properties that have no view associated are serialized or not by setting this in your application.properties:
spring.jackson.mapper.default_view_inclusion=true
I've solved my problem extending ResponseEntity like this :
public class ResponseViewEntity<T> extends ResponseEntity<ContainerViewEntity<T>> {
private Class<? extends BaseView> view;
public ResponseViewEntity(HttpStatus statusCode) {
super(statusCode);
}
public ResponseViewEntity(T body, HttpStatus statusCode) {
super(new ContainerViewEntity<T>(body, BaseView.class), statusCode);
}
public ResponseViewEntity(T body, Class<? extends BaseView> view, HttpStatus statusCode) {
super(new ContainerViewEntity<T>(body, view), statusCode);
}
}
and ContainerViewEntity encapsulate the object and the selected view
public class ContainerViewEntity<T> {
private final T object;
private final Class<? extends BaseView> view;
public ContainerViewEntity(T object, Class<? extends BaseView> view) {
this.object = object;
this.view = view;
}
public T getObject() {
return object;
}
public Class<? extends BaseView> getView() {
return view;
}
public boolean hasView() {
return this.getView() != null;
}
}
After that, we have convert only the object with the good view.
public class JsonViewMessageConverter extends MappingJackson2HttpMessageConverter {
#Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
if (object instanceof ContainerViewEntity && ((ContainerViewEntity) object).hasView()) {
writeView((ContainerViewEntity) object, outputMessage);
} else {
super.writeInternal(object, outputMessage);
}
}
protected void writeView(ContainerViewEntity view, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
JsonEncoding encoding = this.getJsonEncoding(outputMessage.getHeaders().getContentType());
ObjectWriter writer = this.getWriterForView(view.getView());
JsonGenerator jsonGenerator = writer.getFactory().createGenerator(outputMessage.getBody(), encoding);
try {
writer.writeValue(jsonGenerator, view.getObject());
} catch (IOException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
}
}
private ObjectWriter getWriterForView(Class<?> view) {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
return mapper.writer().withView(view);
}
}
And to finish, I enable the converter
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="wc.handler.view.JsonViewMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
And that's it, I can select the View in the controller
#Override
#RequestMapping(value = "/get_by_id/{accountId}", method = RequestMethod.GET, headers = "Accept=application/json")
public ResponseViewEntity<Account> getById(#PathVariable(value = "accountId") Long accountId) throws ServiceException {
return new ResponseViewEntity<Account>(this.getAccountSrv().getById(accountId), PublicView.class, HttpStatus.OK);
}
FYI, Spring 4.1 already supported using #JsonView directly on #ResponseBody and ResponseEntity:
Jackson’s #JsonView is supported directly on #ResponseBody and ResponseEntity controller methods for serializing different amounts of detail for the same POJO (e.g. summary vs. detail page). This is also supported with View-based rendering by adding the serialization view type as a model attribute under a special key.
And in http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-jsonview you can find the much simpler solution:
#RestController
public class UserController {
#RequestMapping(value = "/user", method = RequestMethod.GET)
#JsonView(User.WithoutPasswordView.class)
public User getUser() {
return new User("eric", "7!jd#h23");
}
}
public class User {
public interface WithoutPasswordView {};
public interface WithPasswordView extends WithoutPasswordView {};
private String username;
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
#JsonView(WithoutPasswordView.class)
public String getUsername() {
return this.username;
}
#JsonView(WithPasswordView.class)
public String getPassword() {
return this.password;
}
}
This works great :
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public void getZone(#PathVariable long id, #RequestParam(name = "tree", required = false) boolean withChildren, HttpServletResponse response) throws IOException {
LOGGER.debug("Get a specific zone with id {}", id);
Zone zone = zoneService.findById(id);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
if (withChildren) {
response.getWriter().append(mapper.writeValueAsString(zone));
} else {
response.getWriter().append(mapper.writerWithView(View.ZoneWithoutChildren.class).writeValueAsString(zone));
}
}

Categories