Spring Boot object between controller in different class - java

I am trying to get a object sent to a Spring-boot controller in one Class to be available in the model of another controller.
It seems like #SessionAttributes is for objects that are global (like the logged on user).
I have tried this on controller 1
#Component
#Controller
#SessionAttributes(value = { "user"})
public class FormController {
#GetMapping("/occurence/{occno}")
public String findOcc(#PathVariable String occno, OccViewModel occViewModel, Model model) {
Occ occ = occRepository.findByOccno(occno);
occViewModel.setOcc(occ);
occViewModel.setPersons(occPersonRepository.findOccPersonByOcc(occ.getId()));
List<OccPerson.OccRole> occRoles = Arrays.asList(OccPerson.OccRole.values());
model.addAttribute("occRoles", occRoles);
model.addAttribute("occViewModel", occViewModel);
model.addAttribute("countries", countries);
return "occ";
}
I have a button on this form which sends the user to this endpoint - I would like the same occViewModel to available to this endpoint on controller 2
#Component
#Controller
#SessionAttributes(value = { "user" })
public class PlanController {
#GetMapping("/newplan")
public String newPlan(Model model, OccViewModel occViewModel, HttpSession session) {
// create PlanViewModel DTO
Occ occ = new occVieModel.getOcc();
PlanViewModel planViewModel = new PlanViewModel;
planViewModel.setOcc(occ);
model.addAttribute(planViewModel);
//etc
}
I see there is #SessionAttributes but i do not understand in my first controller how I would even load it into the session if I dont know what Occ to get from the repo as it appears you need to #ModelAttribute prior to the handler - but the URI gives the occno?
I looked here also but it appeared do deal with the same class only and it wasn't clear how you would apply this to a id passed in on the URI.

I think I have this working correctly to retrieve a object from the database, put it in session and send via GET to the view. The view then later POST the data back to the handler and the object coming back from view is hydrated against the object in session (before being passed to Hibernate).
There is a POST and GET controller for OccViewModel. The #SessionAttributes means that between a Get and a Post the object is held in session and I do not need to pass the object id in hidden tags.
#Component
#Controller
#SessionAttributes(value = { "user", "occViewModel" })
public class FormController {
#PostMapping("newOcc")
public String occSubmit(#Valid #ModelAttribute("occViewModel") OccViewModel occViewModel, BindingResult result, Model model
HttpServletRequest request, SessionStatus status) {
// NEW OCC
if( occViewModel.getOcc().getId() == null ) {
occService.saveNewOcc(occViewModel.getOcc(), occViewModel.getPersons());
}
//UPDATE OCC
if( occViewModel.getOcc().getId() != null ) {
occService.updateOcc(occViewModel.getOcc(), occViewModel.getPersons());
}
}
status.setComplete();
return "redirect:/dashboard";
}
#GetMapping("/occ/{occno}")
public String findOcc(#PathVariable String occno, OccViewModel occViewModel, Model model) {
// POPULATE OCC VIEW MODEL
Occ occ = occRepository.findByOccno(occno);
occViewModel.setOcc(occ);
model.addAttribute("occViewModel", occViewModel);
return "occ";
}
I believe that OccViewModel must be passed as a parameter into POST and GET handlers.
In terms of then passing this object to another handler in another controller which was done for the sake of keeping the project tidy:
#Component
#Controller
#SessionAttributes(value = { "user", "occPlanWork" })
public class PlanController {
#GetMapping("/occ/{occno}/plan")
public String findPlan(#PathVariable String occno, OccPlanWork occPlanWork, Model model) {
// GET OCC FROM OCCNO
Occ occ = new Occ();
occ = occRepository.findByOccno(occno);
// SET EPT DATA
occPlanWork.setOccno(occ.getOccno());
occPlanWork.setStart_date(occ.getStart_date());
occPlanWork.setOccId(occ.getId());
// CREATE NEW PLAN IF BLANK
if (occ.getPlan()== null) {
Plan plan = new Plan();
occPlanWork.setPlan(plan);
}
if (occ.getPlan()!=null) {
Plan plan = new Plan();
plan = occ.getPlan();
occPlanWork.setPlan(plan);
}
// SET THE EXISTING WORKS (AND CONVERT SET<WORK> TO LIST<LIST> FOR TH COMPATABILITY)
if (occPlanWork.getPlan().getId()!=null) {
List<Work> works = new ArrayList<>(occPlanWork.getPlan().getWorks());
Collections.sort(works);
occPlanWork.setWorks(works);
} // CREATE NEW WORK LIST IF PLAN IS NEW
if (occPlanWork.getPlan().getId()==null) {
List works = new ArrayList();
occPlanWork.setWorks(works);
}
// RETURN EPT
model.addAttribute("occPlanWork", occPlanWork);
return "newplan";
}
That was achieved by passing the occurrence number in the URI to the other controller. Again the object backing the view (occPlanWork) is in Session Attributes on the controller level.

Related

Spring MVC Accessing Session Attribute in Function in Controller Class

I was having some problem when trying to access session attribute in Controller. I declared my session attributes as such:
#Controller
#SessionAttributes({ WebKeys.OBJECT_SIX, WebKeys.DSP_LOGIC, WebKeys.NEW_CARD_FORM })
In each of my API, I am calling the function:
#RequestMapping(value = "/apiA.do", method = RequestMethod.POST)
public String doAPIa(Model model) {
setInfo(model);
}
#RequestMapping(value = "/apiB.do", method = RequestMethod.POST)
public String doAPIb(Model model) {
setInfo(model);
}
In my setInfo(), I am trying to access the session attribute and add value back to the model:
private void setInfo(Model model) throws Exception{
String populationId = // need to get from WebKeys.OBJECT_SIX session attribute
if(populationId!=null && (populationId.equals(Constants.POP_TYPE_ID))){
DisplayHelperTO helper = (DisplayHelperTO) // need to get from WebKeys.DSP_LOGIC;
NewCardNewBasicForm newCardForm = (NewCardNewBasicForm ) // need to get from WebKeys.NEW_CARD_FORM);
model.addAttribute("newCardForm", newCardForm);
model.addAttribute("dspLogic", helper);
}
}
I tried to declare in such way:
private void setInfo(Model model,
#SessionAttribute(WebKeys.OBJECT_SIX) String populationId) throws Exception{
}
However, if I am declaring the function in this way, the part to call the setInfo() in both doAPIa and doAPIb will be highlighted in syntax error. Any ideas on how to access the session attributes in function? Thanks!

Using Spring #ModelAttribute with a #RestController

I'm building a REST API that uses a #PathParameter for a parent PK and a #RequestBody for the child form parameters. Next I need to validate the #RequestBody values against regex values stored in a database using the param key from the #RequestBody and the parent pk from the #PathParameter, however I've been unable to figure out a good way to add the #PathParameter pk id to the #RequestBody child object before #Valid is called without using #ModelAttribute.
Using #ModelAttribute, I've been able to add the #PathParameter to the #RequestBody object and then validate the #RequestBody object using #Valid. However I found while using #ModelAttribute, Spring no longer throws the MethodArgumentNotValidException therefore eliminating the ability to use a global exception handler.
I found adding BindingResult to the controller handler followed by throwing a new MethodArgumentNotValidException when errors exist the global exception handler could be triggered.
It's my understanding the ModelAttribute is for Spring MVC and not so much RestController since I'm not using the view, I'm wondering if this is a correct approach or whether there's a better solution. Here's a sample of my code.
HTTP Post
localhost:8072/api/clover/graph_run/2
[
{
"graphKey" : "DATA_DIRECTORY",
"graphValue" : "/data/clover-prod"
},
{
"graphKey" : "DATAOUT_DIR",
"graphValue" : "/data/clover-prod/94l"
},
{
"graphKey" : "DELAY_MS",
"graphValue" : "0"
}
]
RestController
#Slf4j
#RestController
#RequiredArgsConstructor
#RequestMapping("/api/clover")
public class GraphRunController {
final CloverServerService cloverServerService;
#PostMapping("/graph_run/{graphId}")
public String graphRun(#ModelAttribute("graphId") #Valid GraphJobDTO graphJobDTO,
BindingResult bindingResult ) throws MethodArgumentNotValidException {
log.debug("GraphRun {}", graphJobDTO);
if (bindingResult.hasErrors()) {
throw new MethodArgumentNotValidException(null, bindingResult);
}
return cloverServerService.runGraph(graphJobDTO);
}
}
ModelAttribute in ControllerAdvise
#Slf4j
#RequiredArgsConstructor
#ControllerAdvice( assignableTypes = {GraphRunController.class})
public class GraphRunControllerAdvise {
final GraphJobRepository graphJobRepository;
#ModelAttribute("graphId")
public GraphJobDTO addGraphId(#PathVariable(value = "graphId") Long graphId,
#RequestBody List<GraphJobPropertyDTO> graphProperties) {
log.debug("ModelAttribute graphId {}", graphId);
//Query database for the graph job and all it's parameters
GraphJob graphJob = graphJobRepository.findById(graphId)
.orElseThrow(() -> new HRINotFoundException("No such job execution."
+ graphId));
GraphJobDTO graphJobDTO = new GraphJobDTO();
graphJobDTO.setGraph(graphJob.getGraph());
graphJobDTO.setGraphProperties(graphProperties);
//Create a map from the database with the key being the GraphKey contained in both the request and the database
Map<String, GraphJobProperty> jobMap = graphJob.getJobProperties().stream().collect(
Collectors.toMap(s -> s.getGraphKey().toUpperCase(), Function.identity()));
graphProperties.forEach(v -> {
String graphKey = v.getGraphKey();
//If the graphKey in the request cannot be found in the database, throw an exception. We will handle the exception
//in th exception handler
if(!jobMap.containsKey(graphKey)) {
throw new HRINotFoundException(String.format("%s is an invalid job parameter.", v.getGraphKey()));
}
//Within the database record is the validation message as well as the regex used to validate the incoming value,
//we set the message and regex in the GraphPropertyDTO object for cross validation later on in the validator.
GraphJobProperty jobProperty = jobMap.get(graphKey);
v.setValidationMessage(jobProperty.getValidationMessage());
v.setValidationRegex(jobProperty.getValidationRegex());
});
//Return the graphJobDTO object for validation
return graphJobDTO;
}
}
Constraint Validator
#Slf4j
public class GraphRegexValidator implements ConstraintValidator<GraphRegex, GraphJobPropertyDTO> {
#Override
public void initialize(final GraphRegex graphRegex) {
}
#Override
public boolean isValid(final GraphJobPropertyDTO dto, final ConstraintValidatorContext context) {
String regex = dto.getValidationRegex();
String value = dto.getGraphValue();
if(regex != null && value != null && !Pattern.matches(regex, value)) {
log.debug("isValid {} - {}", false, dto);
addConstraintViolation(context, getMessage(dto, context));
return false;
}
log.debug("isValid {} - {}", true, dto);
return true;
}
private void addConstraintViolation(ConstraintValidatorContext context, String message) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message).addPropertyNode("graphKey").addConstraintViolation();
}
private String getMessage(GraphJobPropertyDTO dto, ConstraintValidatorContext context) {
return dto.getValidationMessage() != null ? String.format(dto.getValidationMessage(), dto.getGraphValue()) :
context.getDefaultConstraintMessageTemplate();
}
}
DTOs
#Data
public class GraphJobDTO {
#NotNull
Long graphId;
#NotNull
String graph;
#Valid
List<GraphJobPropertyDTO> graphProperties;
}
#Data
#GraphRegex
public class GraphJobPropertyDTO {
String graphKey;
String graphValue;
String validationMessage;
String validationRegex;
}

Spring command object discrimination

How to i can discriminate command objects in spring-mvc Controller? For example, i have following bean-classes used as form object:
public class CreateServiceFormBean {
#NotBlank
#Length(min = 3, max = 120)
private String name;
}
public class CreateDependedServiceFormBean extends CreateServiceFormBean {
#NotNull
private Short parentServiceId;
}
Getter's and Setter's is cutted out.
#RequestMapping(method = RequestMethod.POST)
public String createService(CreateServiceFormBean form) {
if (form instanceof CreateServiceFormBean) {
System.out.println("create Service");
//new ServiceEntity(form.getName())
} else if (form instanceof CreateDependedServiceFormBean) {
System.out.println("create depended Service");
parentService = ... get parent service entity...
//new DependedServiceEntity(form.getName(), parentService)
}
return null;
}
How do this? I think about create abstract controller for this two dto's, but is not elegant..maybe can handled in one method..
And how to correct get parent service entity? Some like method ModelAttribute annotation whose return entity by id?
Thanks for Replies!

SessionAttributes cause problems with multiple tabs

I have a Spring web app (Spring 3.2) and I have used following scenario to handle edit pages:
#Controller
#SessionAttributes(value = { "packet" })
public class PacketController {
#RequestMapping(value = "/edit-packet/{packet_id}", method = RequestMethod.GET)
public String editPacketForm(#PathVariable(value = "packet_id") Long packet_id, Model model)
{
model.addAttribute("packet", packetService.findById(packet_id));
return "packets/packetEdit";
}
POST method:
#RequestMapping(value = "/edit-packet/{packet_id}", method = RequestMethod.POST)
public String packetEditAction(Model model, #Valid #ModelAttribute(value = "packet")
Packet packet, BindingResult result, SessionStatus status)
{
if (result.hasErrors())
{
return "packets/packetEdit";
}
packetService.update(packet);
status.setComplete();
return "redirect:/";
}
Now the problem is what if someone tries to open multiple tabs for /edit-packet/{id} with different ids. With every new open tab the session 'packet' object will be overwritten. Then after trying to submit forms on multiple tabs, first tab will be submitted but it actually change the second packet because in session is second object and second tab will cause error because setComplete has been invoked so there is no 'packet' object in session.
(This is known issue https://jira.spring.io/browse/SPR-4160).
I am trying to implement this solution http://duckranger.com/2012/11/add-conversation-support-to-spring-mvc/ to solve this problem. I copied ConversationalSessionAttributeStore.java
ConversationIdRequestProcessor.java classes and in my servlet-config.xml I made this:
<mvc:annotation-driven />
<bean id="conversationalSessionAttributeStore"
class="com.xx.session.ConversationalSessionAttributeStore">
</bean>
<bean name="requestDataValueProcessor" class="com.xx.session.ConversationIdRequestProcessor" />
But it doesn't work, in my POST methods I don't see any new parameters, did I miss something?
UPDATE: Actually, it started working, but maybe someone has a better idea to solve this issue?
My other idea is to force a new session on every new tab, but it's not a nice solution.
Don't use session attributes, make your controller stateless and simply use the path variable to retrieve the correct model attribute.
#Controller
public class PacketController {
#ModelAttribute
public Packet packet(#PathVariable(value = "packet_id") Long packet_id) {
return packetService.findById(packet_id);
}
#RequestMapping(value = "/edit-packet/{packet_id}", method = RequestMethod.GET)
public String editPacketForm() {
return "packets/packetEdit";
}
#RequestMapping(value = "/edit-packet/{packet_id}", method = RequestMethod.POST)
public String packetEditAction(Model model, #Valid #ModelAttribute(value = "packet")
Packet packet, BindingResult result) {
if (result.hasErrors()) {
return "packets/packetEdit";
}
packetService.update(packet);
return "redirect:/";
}
}
Something like that should do the trick.

Spring #SessionAttribute how to retrieve the session object in same controller

I am using Spring 3.2.0 MVC. In that I have to store one object to session.
Currently I am using HttpSession set and get attribute to store and retrieve the value.
It returns only the String not Object. I want to use #SessionAttribute when I tried it sets the object in session but I could not retrieve the session object
#RequestMapping(value = "/sample-login", method = RequestMethod.POST)
public String getLoginClient(HttpServletRequest request,ModelMap modelMap) {
String userName = request.getParameter("userName");
String password = request.getParameter("password");
User user = sample.createClient(userName, password);
modelMap.addAttribute("userObject", user);
return "user";
}
#RequestMapping(value = "/user-byName", method = RequestMethod.GET)
public
#ResponseBody
String getUserByName(HttpServletRequest request,#ModelAttribute User user) {
String fas= user.toString();
return fas;
}
Both methods are in same controller. How would I use this to retrieve the object?
#SessionAttributes annotation are used on the class level to :
Mark a model attribute should be persisted to HttpSession after handler methods are executed
Populate your model with previously saved object from HttpSession before handler methods are executed -- if one do exists
So you can use it alongside your #ModelAttribute annotation like in this example:
#Controller
#RequestMapping("/counter")
#SessionAttributes("mycounter")
public class CounterController {
// Checks if there's a model attribute 'mycounter', if not create a new one.
// Since 'mycounter' is labelled as session attribute it will be persisted to
// HttpSession
#RequestMapping(method = GET)
public String get(Model model) {
if(!model.containsAttribute("mycounter")) {
model.addAttribute("mycounter", new MyCounter(0));
}
return "counter";
}
// Obtain 'mycounter' object for this user's session and increment it
#RequestMapping(method = POST)
public String post(#ModelAttribute("mycounter") MyCounter myCounter) {
myCounter.increment();
return "redirect:/counter";
}
}
Also don't forget common noobie pitfall: make sure you make your session objects Serializable.

Categories

Resources