Spring 3 MVC Default view for spring controller - java

I have a simple question, but I cannot find a solution anywhere.
For a project I have a controller which pulls lists according to some business rules. I have multiple RequestMappings and multiple methods but they should all return the same view. Is there a way to specify a default view for a controller?
Currently my code looks like this:
#Controller
public class OverviewController {
#RequestMapping("/{name}-games")
public String getOverview(#PathVariable("name") String name) {
// Code
return "view";
}
#RequestMapping("/{category}")
public String getCategory(#PathVariable("category") String category) {
// Code
return "view";
}
#RequestMapping("/special-{promo}-games")
public String getSpecialPromo(#PathVariable("promo") String namepromo) {
// Code
return "view";
}
}
I can replace the return "view"; with something like return view(); everywhere but I am hoping to find something more like an annotation:
#DefaultView()
public String view() {
return "view";
}
I am unable to find any such thing in the spring documentation. Is this possible or is the whole setup wrong to start with?

According to the Sping Reference,
The RequestToViewNameTranslator interface determines a logical View name when no such logical view name is explicitly supplied.
(That is when your controller method returns Model, Map or void.)
You could implement this interface, but I think in your example the best thing you can do is defining a constant as CodeChimp has suggested.

Can you not go with the approach of having multiple view resolvers using order??
Have the beanNameViewResolver with order 0,which tries to map the matching bean to the modelAndView(common for controller in your case) that you return.
In case it doesnt match then you can default it onto a internalResourceViewResolver(order=1) to provide default behaviour .

Your default view page required some attributes that should be send via Model Attributes.Assuming,these required model attributes are same in all your methods of different Business logic.You can add them in Flash attributes and redirect to default Method.
Suppose X1,X2 attributes are same in all Handler method independent of Logic
#Controller
#SessionAttribute({"X1","X2"})
public class OverviewController {
#RequestMapping("/{name}-games")
public String getOverview(#PathVariable("name") String name,final RedirectAttributes redirectAttributes) {
// Code
//add attributes requires for view in Flash attribute
redirectAttributes.addFlashAttribute("X1", "X1");
redirectAttributes.addFlashAttribute("X2", "X2");
return "redirect:defaultview";
}
#RequestMapping("/{category}")
public String getCategory(#PathVariable("category") String category,final RedirectAttributes redirectAttributes) {
// Code
//add attributes requires for view in Flash attribute
redirectAttributes.addFlashAttribute("X1", "X1");
redirectAttributes.addFlashAttribute("X2", "X2");
return "redirect:defaultview";
}
#RequestMapping("/special-{promo}-games")
public String getSpecialPromo(#PathVariable("promo") String namepromo,final RedirectAttributes redirectAttributes) {
// Code
//add attributes requires for view in Flash attribute
redirectAttributes.addFlashAttribute("X1", "X1");
redirectAttributes.addFlashAttribute("X2", "X2");
return "redirect:defaultview";
}
#RequestMapping("defaultview")
public String default(Model model) {
//here you can access all attributes in Flash Map via Model Attribute
// Code
model.addAttribute("X1","X1");
model.addAttribute("X1","X1");
return "view";
}
}
Caution:you have to add requires attributes in Session also because if you refresh page ,this avoids well known Exception.
Thank you

Related

DRYing up controller - method that returns entity or redirects (in Java, Spring)

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)

Should I put the ID of my entity in the URL or into the form as a hidden field?

I think in terms of REST, the ID should be placed into the URL, something like:
https://example.com/module/[ID]
and then I call GET, PUT, DELETE on that URL. That's kind of clear I think. In Spring MVC controllers, I'd get the ID with #PathVariable. Works.
Now, my practical problem with Spring MVC is, that if I do this, I have to NOT include the ID as part of the form (as well), Spring emits warnings of type
Skipping URI variable 'id' since the request contains a bind value with the same name.
otherwise. And it also makes kind of sense to only send it once, right? What would you do if they don't match??
That would be fine, but I do have a custom validator for my form backing bean, that needs to know the ID! (It needs to check if a certain unique name is already being used for a different entity instance, but cannot without knowing the ID of the submitted form).
I haven't found a good way to tell the validator that ID from #PathVariable, since the validation happens even before code in my controller method is executed.
How would you solve this dilemma?
This is my Controller (modified):
#Controller
#RequestMapping("/channels")
#RoleRestricted(resource = RoleResource.CHANNEL_ADMIN)
public class ChannelAdminController
{
protected ChannelService channelService;
protected ChannelEditFormValidator formValidator;
#Autowired
public ChannelAdminController(ChannelService channelService, ChannelEditFormValidator formValidator)
{
this.channelService = channelService;
this.formValidator = formValidator;
}
#RequestMapping(value = "/{channelId}/admin", method = RequestMethod.GET)
public String editChannel(#PathVariable Long channelId, #ModelAttribute("channelForm") ChannelEditForm channelEditForm, Model model)
{
if (channelId > 0)
{
// Populate from persistent entity
}
else
{
// Prepare form with default values
}
return "channel/admin/channel-edit";
}
#RequestMapping(value = "/{channelId}/admin", method = RequestMethod.PUT)
public String saveChannel(#PathVariable Long channelId, #ModelAttribute("channelForm") #Valid ChannelEditForm channelEditForm, BindingResult result, Model model, RedirectAttributes redirectAttributes)
{
try
{
// Has to validate in controller if the name is already used by another channel, since in the validator, we don't know the channelId
Long nameChannelId = channelService.getChannelIdByName(channelEditForm.getName());
if (nameChannelId != null && !nameChannelId.equals(channelId))
result.rejectValue("name", "channel:admin.f1.error.name");
}
catch (EmptyResultDataAccessException e)
{
// That's fine, new valid unique name (not so fine using an exception for this, but you know...)
}
if (result.hasErrors())
{
return "channel/admin/channel-edit";
}
// Copy properties from form to ChannelEditRequest DTO
// ...
// Save
// ...
redirectAttributes.addFlashAttribute("successMessage", new SuccessMessage.Builder("channel:admin.f1.success", "Success!").build());
// POST-REDIRECT-GET
return "redirect:/channels/" + channelId + "/admin";
}
#InitBinder("channelForm")
protected void initBinder(WebDataBinder binder)
{
binder.setValidator(formValidator);
}
}
I think I finally found the solution.
As it turns out Spring binds path variables to form beans, too! I haven't found this documented anywhere, and wouldn't have expected it, but when trying to rename the path variable, like #DavidW suggested (which I would have expected to only have a local effect in my controller method), I realized that some things got broken, because of the before-mentioned.
So, basically, the solution is to have the ID property on the form-backing object, too, BUT not including a hidden input field in the HTML form. This way Spring will use the path variable and populate it on the form. The local #PathVariable parameter in the controller method can even be skipped.
The cleanest way to solve this, I think, is to let the database handle the duplicates: Add a unique constraint to the database column. (or JPA by adding a #UniqueConstraint)
But you still have to catch the database exception and transform it to a user friendly message.
This way you can keep the spring MVC validator simple: only validate fields, without needing to query the database.
Could you not simply disambiguate the 2 (URI template variables vs. parameters) by using a different name for your URI template variable?
#RequestMapping(value = "/{chanId}/admin", method = RequestMethod.PUT)
public String saveChannel(#PathVariable Long chanId, #ModelAttribute("channelForm") #Valid ChannelEditForm channelEditForm, BindingResult result, Model model, RedirectAttributes redirectAttributes)
{
[...]
What ever you said is correct the correct way to design rest api is to mention the resource id in path variable if you look at some examples from the swagger now as open api you could find similar examples there
for you the correct solution would be to use a custom validator like this
import javax.validation.Validator;`
import org.apache.commons.lang3.StringUtils;`
import org.springframework.validation.Errors;`
importorg.springframework.validation.beanvalidation.CustomValidatorBean;`
public class MyValidator extends CustomValidatorBean {`
public void myvalidate(Object target,Errors errors,String flag,Profile profile){
super.validate(target,errors);
if(StringUtils.isEmpty(profile.name())){
errors.rejectValue("name", "NotBlank.profilereg.name", new Object[] { "name" }, "Missing Required Fields");
}
}
}
This would make sure all the fields are validated and you dont need to pass the id in the form.

How to return a "lighter" version of the data from a Controller?

I'm using Spring 4.3, and I have a REST Controller that returns a User object to the UI (javascript).
The problem is that I get a User object from the Database (say with Hibernate) that contains a password. I don't want to expose the password by actually returning it. Instead, I want the controller method to put NULL in it before returning it (I could use Optional or other solutions to avoid nulls, but I'm keeping it simple in this question).
public class User {
private String username;
private String password;
//setters and getters
}
#Controller
public class MainController {
#RequestMapping(value = "/user/getOne", method = RequestMethod.GET)
public User getOneUser() {
User user = //getUser
//something to nullify the password?
return user;
}
This question concerns a User and a password for clarity, but I'm looking for a wide solution that would take care of all my data models and the values I don't want them to include in some returns.
Solutions I don't like :)
Disliked solution #1: Remove the password in a private method or a utility class' method or an Adapter class
I don't like this because it makes the code very long. Most controller methods will need their own adaptation of the data.
I prefer something more clean and short.
Disliked solution #2: Use #JsonIgnore annotation
I don't want to bind my data models with Jackson package.
Disliked solution #3: Use a smaller data model class, and blind-copy everything that the smaller can contain
This solution refers to a code such as this:
public class ReturnUser {
private String username;
}
#Controller
public class MainController {
#RequestMapping(value = "/user/getOne", method = RequestMethod.GET)
public User getOneUser() {
User user = //getUser
ReturnUser smaller = copyWhatsInCommon(user, User.class, ReturnUser.class); //sees that there's only username common to both, so copies only it
return smaller;
This also increases the quantity of code, so I don't like it.
Any ideas?
Option 1:
You can add a transformation layer between your controller and the facade (or the service which populates the entity from the database). The transformation layer classes can convert the entities into value objects. The VOs will only contain the minimal information that your view needs. If there are more entities than 1 that you need to transform into value objects, you can also use reflections to read the properties (from a config file or something) that need to be read from the entities and copied to the VOs. However, this is not quite different from the solution 3 in your question that you don't like much. While it serves from performance and security perspective, it does add additional code in form of a transformation layer.
Option 2: An alternate and straightforward option I can propose is read the required attributes from 'User' class and populate them as model attributes.
#RequestMapping(value = "/user/getOne", method = RequestMethod.GET)
public User getOneUser(ModelMap modelMap) {
User user = //getUser
modelMap.addAttribute("userName", user.getName());
modelMap.addAttribute("userEmail", user.getEmail());
...
...
}
}
From experience:
1.) You should not return your business objects from the View layer ie Controller. You see this in many tutorials, but this is poor design.
2.) You should create a response object. This response object will only contain the fields you want to return to the user.
3.) You should instantiate the fields for UserResponse in the constructor with the user object.
Using since you are creating a resposne object, you using the #JsonIgnore annotation doesn't make sense.
While this may be more code, it is a better design with a clear separation of responsibility. The controller only needs to worry about the view object and the business layer never needs to know anything about the view.
Ex
public class UserResponse {
private String firstName;
private String lastName;
public UserResponse(User user){
this.firstName = user.getFirstName();
this.lastName = user.getLastName();
}
...
//The getters
}
In the controller:
return new UserResponse(user);
Why do you want absolutely to return the User as it is represented in your entity?
The service and the controller layers should even not get a User object that contains a password field. So your 1 and 3 solution should be avoided.
In your case returning a view of the User class seems the most relevant way to achieve your need. Just use a DTO
Either you could return the User DTO from a service layer that accesses to the Data Access layer.
Or if you don't have a service layer, you could provide a method in the data access layer that returns a User DTO without the password field.
I am going to offer one more solution. Just for coverage. This is very ugly and not recommended. You can create an object mapper and filter the object:
static ObjectMapper mapper = new ObjectMapper();
public static String filterOutAllExcept(Object obj, String filterName, String... properties) throws YpException {
mapper.registerModule(new Hibernate4Module());
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept(properties);
FilterProvider filterProvider = new SimpleFilterProvider().addFilter(filterName, filter).setFailOnUnknownId(false);
String strValue;
try {
strValue = mapper.writer(filterProvider).writeValueAsString(obj);
} catch (JsonProcessingException e) {
// handle exception
}
return strValue;
}
Then you can call it like:
String filterApplied = ObjectMapperHelper.filterOutAllExcept(user, JsonDTOFilter.SOMEFILTER, "firstName", "lastName");
This will give you a json string with the fields firstName and lastName

How to create custom methods using Struts2 REST plugin

My problem concerns the creation of a custom method within an action. I'm using Struts2 and REST Plugin in order to implement a RESTful WebService. My action class is the following:
public class SampleController implements ModelDriven<Object> {
private Sample sample = new Sample();
private Collection<Sample> list;
private int id;
public HttpHeaders create() {
sdao.save(sample);
return new DefaultHttpHeaders("create");
}
public HttpHeaders destroy() {
return new DefaultHttpHeaders("destroy");
}
public HttpHeaders show() {
return new DefaultHttpHeaders("show").disableCaching();
}
public HttpHeaders update() {
sdao.save(sample);
return new DefaultHttpHeaders("update");
}
public HttpHeaders index() {
list = sdao.findAll();
return new DefaultHttpHeaders("index").disableCaching();
}
public Object getModel() {
return (list != null ? list : sample);
}
public int getId() {
return id;
}
public void setId(Integer id) {
if (id != null) {
this.sample = (Sample) sdao.findById(id);
}
this.id = id;
}
}
I can access to a resource via a GET HTTP method correctly. In order to use a custom method, called by passing a parameter to search resources i.e
public searchBySenderName(String senderName) {
list.addAll(sdao.findBySenderName(senderName))
}
What is the correct procedures? How can I call it via GET following URL?
You can call custom method from any of the predefined methods for GET (index, show) in your case, see RESTful URL mapping logic .
RESTful URL Mapping Logic
This Restful action mapper enforces Ruby-On-Rails REST-style mappings.
If the method is not specified (via '!' or 'method:' prefix), the
method is "guessed" at using REST-style conventions that examine the
URL and the HTTP method. Special care has been given to ensure this
mapper works correctly with the codebehind plugin so that XML
configuration is unnecessary.
Of course you can change the method names used by the action mapper, but it will affect a whole application. If you already occupied a resource URL then you should use another to perform its job. This is in case if you are using a strict rest mapper. In the mixed mode you can map an usual action to some action method.
REST and non-RESTful URL's Together Configuration
If you want to keep using some non-RESTful URL's alongside your REST
stuff, then you'll have to provide for a configuration that utilizes
to mappers.
Plugins contain their own configuration. If you look in the Rest
plugin jar, you'll see the struts-plugin.xml and in that you'll see
some configuration settings made by the plugin. Often, the plugin just
sets things the way it wants them. You may frequently need to override
those settings in your own struts.xml.
And last, you mightn't specify a method via ! or method: prefix because it's restricted by default configuration.

Which view will be resolved, code from spring's docs

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.

Categories