I have a controller that has a few methods that get an optional of entity from service, checks if is present and proceeds with some other actions or redirects with message "Entity not found".
It looks like that:
#GetMapping("action")
public String method(#PathVariable Long id,
final RedirectAttributes redirectAttributes){
Optional<Entity> eOpt = entityService.findById(id);
if(eOpt.isEmpty()){
alertHandler.set(redirectAttributes, Status.ENTITY_NOT_FOUND);
return "redirect:/entity/list"
}
Entity e = eOpt.get();
// other actions that are using e
return "view-name";
}
The six lines repeat in a few methods and for different entities too. Is there a way to assign it to some private method? The only thing I came up with is using a private method like:
private Optional<Entity> getEntityOpt(Long id){
Optional<Entity> eOpt = entityService.findById(id);
if(eOpt.isEmpty()){
alertHandler.set(redirectAttributes, Status.ENTITY_NOT_FOUND);
}
return Optional.empty();
}
This only saves me one line in mapped methods, so I don't have to set up alert message. Otherwise I still have to check again if the Optional is empty to redirect it.
So I guess the question really is - can I set up the private method to either return entity or redirect like:
Entity e = getEntityOrRedirect(Long id);
or maybe you have different ways to handle that problem. Or maybe it is what it is and you have to repeat yourself...
You may treat empty Optional as an exceptional situation.
In that case you may provide your own RuntimeException containing path to redirect.
public class EntityNotFoundException extends RuntimeException {
private final String fallbackView;
public EntityNotFoundException(final String fallbackView) {
this.fallbackView = fallbackView;
}
public String getFallbackView() {
return fallbackView;
}
Then provide a method annotated with #ExceptionHandler to your controller class (or if the situation is common for multiple controllers then provide such method to class annotated with #ControllerAdvice). Your exception handler should catch just defined exception and do a redirect.
#ExceptionHandler(EntityNotFoundException.class)
public String redirectOnEntityNotFoundException(final EntityNotFoundException exception,
final RedirectAttributes redirectAttributes) {
alertHandler.set(redirectAttributes, Status.ENTITY_NOT_FOUND);
return exception.getFallbackView();
}
Finally you achieved some kind of getEntityOrRedirect. Now you may use the above setup as following:
#GetMapping("action")
public String method(#PathVariable Long id){
Entity e = entityService.findById(id)
.orElseThrow(() -> new EntityNotFoundException("redirect:/entity/list"));
// other actions that are using e
return "view-name";
}
Code not tested so apologize for typos in advance.
Note I believe it would work for Spring >= 4.3.5 as otherwise RedirectAttributes wouldn't be resolved for #ExceptionHandler (as stated here)
alright so this is a bit tricky to explain well but I will do my best and hopefully someone has an idea.
So this project in built using the Spring Framework. I did not write this code but a previous dev who is no longer here.
here is a snippet from the controller.
#RequestMapping(value = "/affidavit/{id}/{userid}", method = RequestMethod.GET)
public String getProgramAffidavit(#PathVariable("id")
Program program, #PathVariable("userid")Attendee attendee, Model model) {
model.addAttribute("affidavitDetailDtoTest",affidavitDetailDto);
return "affidavit/program_affidavit";
}
I won't bother with the code that constructs the affidavitDetailDto object for now as I don't think it is relevant.
so you will see that this adds an attribute called affidavitDetailDTO to the model.
then over on my view page which is a jsp page I have a form with a model attribute of "affidavitDetailDTO" and my related information.
so now is the part I don't understand. I click the 'submit' button and my page and the form posts and calls the following controller method.
#RequestMapping(value = "/affidavit", method = RequestMethod.POST)
public String post(AffidavitDetailDto affidavitDetail, HttpServletRequest request, HttpServletResponse response, Model model) {
String out;
if(request.getParameter("programID")!= null)
out = request.getParameter("programID");
else
return "redirect: " + request.getContextPath()+ "/home";
//Session Information Section
//gets a list of program sessions and assigns it to affidavitProgramSessions dto list
List<AffidavitProgramSessionDto> affidavitProgramSessions = affidavitDetail.getAffProgramSessionList();
//Iterates through list of session in affidavitProgramSessions
for (Iterator<AffidavitProgramSessionDto> iter = affidavitProgramSessions.listIterator(); iter.hasNext(); ){
//create AffidavitProgramSession DTO object and fill it with value from list
AffidavitProgramSessionDto session = iter.next();
//get the programs session ID and assign it to a string
String programSessionDetailId = session.getProgramSessionDetailId();
logger.debug("programSessionDetailId:: " + programSessionDetailId);
//if there was no program session id then remove the item from list
if(StringUtil.isBlank(programSessionDetailId)) {
iter.remove();
}else{
// set the program session detail from value in the program session detail repo found by program session ID
session.setProgramSessionDetail(psdRepo.findOne(UUID.fromString(programSessionDetailId)));
}
}
//End oF Session Information Section
out = "affidavit/summary";
return out;
}
now on the summary jsp page there is the following loop to spit out the results.
c:forEach items="${affidavitDetailDto.affProgramSessionList}" var="session">
so here is my issues.. how the hell is it working. where is that affidavitDetailDto object coming from?
it isn't the GET method in the controller adding it because I can rename the attribute and the summary page still works.
any ideas?
EDIT
Showing how affidavitDetailDto is created in controller.
in the class this is done
#Controller
public class AffidavitController extends BaseController {
private AffidavitDetailDto affidavitDetailDto;
then in the GET Request.
#RequestMapping(value = "/affidavit/{id}/{userid}", method = RequestMethod.GET)
public String getProgramAffidavit(#PathVariable("id") Program program, #PathVariable("userid")Attendee attendee, Model model) {
affidavitDetailDto = new AffidavitDetailDto();
List<AffidavitProgramSessionDto> affProgramSessionList = affidavitDetailDto.getAffProgramSessionList();
Set<ProgramSessionDetail> programSessionDetails = ps.getProgramSessionDetail();
if(programSessionDetails != null) {
for (ProgramSessionDetail programSessionDetail : programSessionDetails) {
AffidavitProgramSessionDto affidavitProgramSessionDto = new AffidavitProgramSessionDto();
AffidavitAttendeeTypeDetailDto affidavitAttendeeTypeDetailDto = new AffidavitAttendeeTypeDetailDto();
affidavitProgramSessionDto.setAffidavitAttendeeTypeDetailDto(affidavitAttendeeTypeDetailDto);
affProgramSessionList.add(affidavitProgramSessionDto);
}
}
model.addAttribute("affidavitDetailDto",affidavitDetailDto);
Also on the model attribute level.
#ModelAttribute
private void addAttributes(Model model) {
logger.debug("call addAttributes.....");
if(affidavitDetailDto != null && affidavitDetailDto.getAffProgramSessionList().size() != 0) {
logger.debug("add affidavitDetailDto to model......");
model.addAttribute("affidavitDetailDto", affidavitDetailDto);
}
If I change the line
model.addAttribute("affidavitDetailDto", affidavitDetailDto);
to be something like
model.addAttribute("testing1234", affidavitDetailDto);
then the affidavitDetailDto objects STILL work on the summary post page.
Edit 2
This is the form tag that the for each loop runs in.
<form:form acceptCharset="utf-8" method="POST" enctype="multipart/form-data" action="${CTX_PATH}/approvedProgram" modelAttribute="affidavitDetailDto" id="jpFormInput">
however.. I can change the modelAtttrbute to equal "Madeupnothing123" and the jsp page still functions as normal.. this is why I am so confused.
I have an object that I fill in a form in the view "submit".
After that, it post the object "WelcomeMessageFinder" in the view "return".
I call a service with that use this object. If the service fails, I want to redirect to the view "submit" and keep the form filled with the previous values.
My issue is that I don't find how to keep the "WelcomeMessageFinder" object after the redirect. It always create a new empty object.
Here is my code :
#Controller
#SessionAttributes("welcomeMessageFinder")
public class SandBoxController extends PortalWebuiController {
#ModelAttribute("welcomeMessageFinder")
public WelcomeMessageFinder welcomeMessageFinder() {
return new WelcomeMessageFinder();
}
#RequestMapping(value = "/submit", method = RequestMethod.GET)
public String submit(WelcomeMessageFinder welcomeMessageFinder, Model model, SessionStatus sessionStatus, HttpSession httpSession) {
// On Init : a new WelcomeMessageFinder is created
// After redirect : I want to keep the filled WelcomeMessageFinder but a new one is created ...
model.addAttribute("zenithUserSession", zenithUserSession);
return "submitwelcomemessage";
}
#RequestMapping(value = "/return", method = RequestMethod.POST)
public String retun(
WelcomeMessageFinder welcomeMessageFinder,
Model model,
RedirectAttributes redirectAttributes,
SessionStatus sessionStatus, HttpSession httpSession) {
// welcomeMessageFinder contains the parameters I enter in the form.
redirectAttributes.addFlashAttribute("welcomeMessageFinder", welcomeMessageFinder);
return "redirect:/submit";
}
}
What can I do to keep the same WelcomeMessageFinder object before and after the redirect ?
I find this question that says that I can't use SessionAttributes with redirect because it doesn't keep the session. And it says to use RedirectAttributes but the attributes seems to be reinitialized.
EDIT :
I finally found my error. This code works, the problem is with my class WelcomeMessageFinder. To add an object in the flash session, this object need to be Serializable. I forget to implements Serializable in my class.
After adding this, it works fine.
I finally found my error. This code works, the problem is with my class WelcomeMessageFinder. To add an object in the flash session, this object need to be Serializable. I forget to implements Serializable in my class.
After adding this, it works fine.
it is because of this piece of code
"#ModelAttribute("welcomeMessageFinder")
public WelcomeMessageFinder welcomeMessageFinder() {
return new WelcomeMessageFinder();
}"
. it is ALWAYS executed before any requestmapping method is called
We are trying to implement a special partial update function in Spring 3.2. We are using Spring for the backend and have a simple Javascript frontend. I've not been able to find a straight-forward solution to our requirements, which is The update() function should take in any number of field:values and update the persistence model accordingly.
We have in-line editing for all of our fields, so that when the user edits a field and confirms, an id and the modified field get passed to the controller as json. The controller should be able to take in any number of fields from the client (1 to n) and update only those fields.
e.g., when a user with id==1 edits his displayName, the data posted to the server looks like this:
{"id":"1", "displayName":"jim"}
Currently, we have an incomplete solution in the UserController as outlined below:
#RequestMapping(value = "/{id}", method = RequestMethod.POST )
public #ResponseBody ResponseEntity<User> update(#RequestBody User updateUser) {
dbUser = userRepository.findOne(updateUser.getId());
customObjectMerger(updateUser, dbUser);
userRepository.saveAndFlush(updateUuser);
...
}
The code here works, but has some issues: The #RequestBody creates a new updateUser, fills in the id and the displayName. CustomObjectMerger merges this updateUser with the corresponding dbUser from the database, updating the only fields included in updateUser.
The problem is that Spring populates some fields in updateUser with default values and other auto-generated field values, which, upon merging, overwrites valid data that we have in dbUser. Explicitly declaring that it should ignore these fields is not an option, as we want our update to be able to set these fields as well.
I am looking into some way to have Spring automatically merge ONLY the information explicitly sent into the update() function into the dbUser (without resetting default/auto field values). Is there any simple way to do this?
Update: I've already considered the following option which does almost what I'm asking for, but not quite. The problem is that it takes update data in as #RequestParam and (AFAIK) doesn't do JSON strings:
//load the existing user into the model for injecting into the update function
#ModelAttribute("user")
public User addUser(#RequestParam(required=false) Long id){
if (id != null) return userRepository.findOne(id);
return null;
}
....
//method declaration for using #MethodAttribute to pre-populate the template object
#RequestMapping(value = "/{id}", method = RequestMethod.POST )
public #ResponseBody ResponseEntity<User> update(#ModelAttribute("user") User updateUser){
....
}
I've considered re-writing my customObjectMerger() to work more appropriately with JSON, counting and having it take into consideration only the fields coming in from HttpServletRequest. but even having to use a customObjectMerger() in the first place feels hacky when spring provides almost exactly what I am looking, minus the lacking JSON functionality. If anyone knows of how to get Spring to do this, I'd greatly appreciate it!
I've just run into this same problem. My current solution looks like this. I haven't done much testing yet, but upon initial inspection it looks to be working fairly well.
#Autowired ObjectMapper objectMapper;
#Autowired UserRepository userRepository;
#RequestMapping(value = "/{id}", method = RequestMethod.POST )
public #ResponseBody ResponseEntity<User> update(#PathVariable Long id, HttpServletRequest request) throws IOException
{
User user = userRepository.findOne(id);
User updatedUser = objectMapper.readerForUpdating(user).readValue(request.getReader());
userRepository.saveAndFlush(updatedUser);
return new ResponseEntity<>(updatedUser, HttpStatus.ACCEPTED);
}
The ObjectMapper is a bean of type org.codehaus.jackson.map.ObjectMapper.
Hope this helps someone,
Edit:
Have run into issues with child objects. If a child object receives a property to partially update it will create a fresh object, update that property, and set it. This erases all the other properties on that object. I'll update if I come across a clean solution.
We are using #ModelAttribute to achive what you want to do.
Create a method annotated with#modelattribute which loads a user based on a pathvariable throguh a repository.
create a method #Requestmapping with a param #modelattribute
The point here is that the #modelattribute method is the initializer for the model. Then spring merges the request with this model since we declare it in the #requestmapping method.
This gives you partial update functionality.
Some , or even alot? ;) would argue that this is bad practice anyway since we use our DAOs directly in the controller and do not do this merge in a dedicated service layer. But currently we did not ran into issues because of this aproach.
I build an API that merge view objects with entities before call persiste or merge or update.
It's a first version but I think It's a start.
Just use the annotation UIAttribute in your POJO`S fields then use:
MergerProcessor.merge(pojoUi, pojoDb);
It works with native Attributes and Collection.
git: https://github.com/nfrpaiva/ui-merge
Following approach could be used.
For this scenario, PATCH method would be more appropriate since the entity will be partially updated.
In controller method, take the request body as string.
Convert that String to JSONObject. Then iterate over the keys and update matching variable with the incoming data.
import org.json.JSONObject;
#RequestMapping(value = "/{id}", method = RequestMethod.PATCH )
public ResponseEntity<?> updateUserPartially(#RequestBody String rawJson, #PathVariable long id){
dbUser = userRepository.findOne(id);
JSONObject json = new JSONObject(rawJson);
Iterator<String> it = json.keySet().iterator();
while(it.hasNext()){
String key = it.next();
switch(key){
case "displayName":
dbUser.setDisplayName(json.get(key));
break;
case "....":
....
}
}
userRepository.save(dbUser);
...
}
Downside of this approach is, you have to manually validate the incoming values.
I've a customized and dirty solution employs java.lang.reflect package. My solution worked well for 3 years with no problem.
My method takes 2 arguments, objectFromRequest and objectFromDatabase both have the type Object.
The code simply does:
if(objectFromRequest.getMyValue() == null){
objectFromDatabase.setMyValue(objectFromDatabase.getMyValue); //change nothing
} else {
objectFromDatabase.setMyValue(objectFromRequest.getMyValue); //set the new value
}
A "null" value in a field from request means "don't change it!".
-1 value for a reference column which have name ending with "Id" means "Set it to null".
You can also add many custom modifications for your different scenarios.
public static void partialUpdateFields(Object objectFromRequest, Object objectFromDatabase) {
try {
Method[] methods = objectFromRequest.getClass().getDeclaredMethods();
for (Method method : methods) {
Object newValue = null;
Object oldValue = null;
Method setter = null;
Class valueClass = null;
String methodName = method.getName();
if (methodName.startsWith("get") || methodName.startsWith("is")) {
newValue = method.invoke(objectFromRequest, null);
oldValue = method.invoke(objectFromDatabase, null);
if (newValue != null) {
valueClass = newValue.getClass();
} else if (oldValue != null) {
valueClass = oldValue.getClass();
} else {
continue;
}
if (valueClass == Timestamp.class) {
valueClass = Date.class;
}
if (methodName.startsWith("get")) {
setter = objectFromRequest.getClass().getDeclaredMethod(methodName.replace("get", "set"),
valueClass);
} else {
setter = objectFromRequest.getClass().getDeclaredMethod(methodName.replace("is", "set"),
valueClass);
}
if (newValue == null) {
newValue = oldValue;
}
if (methodName.endsWith("Id")
&& (valueClass == Number.class || valueClass == Integer.class || valueClass == Long.class)
&& newValue.equals(-1)) {
setter.invoke(objectFromDatabase, new Object[] { null });
} else if (methodName.endsWith("Date") && valueClass == Date.class
&& ((Date) newValue).getTime() == 0l) {
setter.invoke(objectFromDatabase, new Object[] { null });
}
else {
setter.invoke(objectFromDatabase, newValue);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
In my DAO class, simcardToUpdate comes from http request:
simcardUpdated = (Simcard) session.get(Simcard.class, simcardToUpdate.getId());
MyUtil.partialUpdateFields(simcardToUpdate, simcardUpdated);
updatedEntities = Integer.parseInt(session.save(simcardUpdated).toString());
The main problem lies in your following code:
#RequestMapping(value = "/{id}", method = RequestMethod.POST )
public #ResponseBody ResponseEntity<User> update(#RequestBody User updateUser) {
dbUser = userRepository.findOne(updateUser.getId());
customObjectMerger(updateUser, dbUser);
userRepository.saveAndFlush(updateUuser);
...
}
In the above functions, you call some of your private functions & classes (userRepository, customObjectMerger, ...), but give no explanation how it works or how those functions look like. So I can only guess:
CustomObjectMerger merges this updateUser with the corresponding
dbUser from the database, updating the only fields included in
updateUser.
Here we don't know what happened in CustomObjectMerger (that's your function, and you don't show it). But from what you describe, I can make a guess: you copy all the properties from updateUser to your object at database. This is absolutely a wrong way, since when Spring map the object, it will fill all the data. And you only want to update some specific properties.
There are 2 options in your case:
1) Sending all the properties (including the unchanged properties) to the server. This may cost a little more bandwidth, but you still keep your way
2) You should set some special values as the default value for the User object (for example, id = -1, age = -1...). Then in customObjectMerger you just set the value that is not -1.
If you feel the 2 above solutions aren't satisfied, consider parsing the json request yourself, and don't bother with Spring object mapping mechanism. Sometimes it just confuse a lot.
Partial updates can be solved by using #SessionAttributes functionality, which are made to do what you did yourself with the customObjectMerger.
Look at my answer here, especially the edits, to get you started:
https://stackoverflow.com/a/14702971/272180
I've done this with a java Map and some reflection magic:
public static Entidade setFieldsByMap(Map<String, Object> dados, Entidade entidade) {
dados.entrySet().stream().
filter(e -> e.getValue() != null).
forEach(e -> {
try {
Method setter = entidade.getClass().
getMethod("set"+ Strings.capitalize(e.getKey()),
Class.forName(e.getValue().getClass().getTypeName()));
setter.invoke(entidade, e.getValue());
} catch (Exception ex) { // a lot of exceptions
throw new WebServiceRuntimeException("ws.reflection.error", ex);
}
});
return entidade;
}
And the entry point:
#Transactional
#PatchMapping("/{id}")
public ResponseEntity<EntityOutput> partialUpdate(#PathVariable String entity,
#PathVariable Long id, #RequestBody Map<String, Object> data) {
// ...
return new ResponseEntity<>(obj, HttpStatus.OK);
}
So when you go to /appointments the get() action is called, so then would the view be get.jsp (assuming you are using .jsp, and assuming you are mapping action names to views)?
And what about the getnewform? It seems to be returning an object? Is that basically passed into the view?
#Controller #RequestMapping("/appointments") public class AppointmentsController {
private final AppointmentBook appointmentBook;
#Autowired
public AppointmentsController(AppointmentBook appointmentBook) {
this.appointmentBook = appointmentBook;
}
#RequestMapping(method = RequestMethod.GET)
public Map<String, Appointment> get() {
return appointmentBook.getAppointmentsForToday();
}
#RequestMapping(value="/{day}", method = RequestMethod.GET)
public Map<String, Appointment> getForDay(#PathVariable #DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
return appointmentBook.getAppointmentsForDay(day);
}
#RequestMapping(value="/new", method = RequestMethod.GET)
public AppointmentForm getNewForm() {
return new AppointmentForm();
}
#RequestMapping(method = RequestMethod.POST)
public String add(#Valid AppointmentForm appointment, BindingResult result) {
if (result.hasErrors()) {
return "appointments/new";
}
appointmentBook.addAppointment(appointment);
return "redirect:/appointments";
} }
In the example, the #RequestMapping is used in a number of places. The first usage is on the type (class) level, which indicates that all handling methods on this controller are relative to the /appointments path. The get() method has a further #RequestMapping refinement: it only accepts GET requests, meaning that an HTTP GET for /appointments invokes this method. The post() has a similar refinement, and the getNewForm() combines the definition of HTTP method and path into one, so that GET requests for appointments/new are handled by that method.
#RequestMapping-annotated methods can return a wide variety of objects, including a View, a Model, a Map, a String, and so on. These return values are interpreted by ServletHandlerMethodInvoker.getModelAndView(), which constructs a ModelAndView objects based on that return value.
In cases where the return value does not specify a view name (in your example, every method other than add() returns no view name), then Spring will attempt to automatically resolve the view name. By default, this is done by DefaultRequestToViewNameTranslator, which uses the information about the request to choose the view name. The examples in the javadoc are:
http://localhost:8080/gamecast/display.html -> display
http://localhost:8080/gamecast/displayShoppingCart.html -> displayShoppingCart
http://localhost:8080/gamecast/admin/index.html -> admin/index
Note that the selected view name is not based upon the information in the #RequestMapping methods, but on the properties of the request itself.