i have servlet
#Component(service={Servlet.class},
property={"sling.servlet.methods=get",
"sling.servlet.resourceTypes=/content/wknd-events"})
public class MainServlet extends SlingAllMethodsServlet
{
#Reference
DemoInterfaceImpl demoInterface;
protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
demoInterface.redirectUser(request,response);
}
}
demoInterface
#Component(
service= IDemoInterface.class,
immediate = true)
public class DemoInterfaceImpl implements IDemoInterface {
#Override
public void redirectUser(final SlingHttpServletRequest req,
final SlingHttpServletResponse resp) throws IOException {
resp.sendRedirect("/content/we-retail/us/en.html");
}
}
and i have my component
<a href="/content/wknd-events.html">
<button> go</button>
</a>
i want to redirect to /content/we-retail/us/en.html on button click but it doesn't work. I still go to /content/wknd-events.html. I don't understand where is my mistake
Your basic mistake is here "sling.servlet.resourceTypes=/content/wknd-events"})
The resourceType in Sling is the resourceType property set on the resources/nodes. So, if you register a servlet based on resourceType, it will get invoked only if you have the component or resource of that type. In your case , the servlet is registered with a content path as resourceType . Instead of sling.servlet.resourceTypes ; you can try sling.servlet.paths (e.g "sling.servlet.paths=/bin/test") and register your servlet to a path. Instead of giving a href attribute in the <a> tag, write an AJAX call on the click event of the button and give the servlet path as the URL.
$.ajax({
type: 'GET',
url:'/bin/test',
success: function(msg){
<!-- some code -->
}
});
Related
I am pretty new to thymeleaf and not too experienced with Spring. In my controller
#Controller
#RequestMapping("form")
#SessionAttributes(types = {Root.class /** another type here**/})
public class FormController extends BaseController {
#ModelAttribute("errorMap")
public Map<String,Integer> getErrorMap(Root root, BindingResult bindingResult) {
Map<String,Integer> result = new HashMap<>();
result.put("somevalue",5); // pretend there are 5 errors for somevalue.
// some logic here processing bindingResult
}
#GetMapping
public void show(#Valid Root root)
// do nothing here, just show the page
}
...
}
public class BaseController {
#ExceptionHandler
void handleBindException( BindException bindException,
Model model, HttpServletRequest request,
HttpSession session) throws Exception {
model.addAllAttributes(bindException.getModel());
addModelAttributes(model, request, true, session);
}
#ModelAttribute
public final void addModelAttributes( Model model, HttpServletRequest request,
boolean isException, HttpSession session) throws Exception {
if(GET.matches(request.getMethod()) || isException)
addModelAttributes(model);
}
}
}
In my form.html there is this part
<div th:if="${errorMap['somevalue']}>0" class="error"> <!-- marked line -->
<sup th:text="${errorMap['somevalue']}" class="error"></sup>
</div>
Now if there are no validation errors everything works fine, the show(...) method is called and a 5 is displayed. But if there is a validation error however there is an error org.attoparser.ParseException: Exception evaluating SpringEL expression: "errorMap['somevalue']" (template: "form" - line 38, col 8) caused by org.springframework.expression.spel.SpelEvaluationException: EL1012E: Cannot index into a null value in the marked line.
I expect this to work especially in case of validation errors. What am I doing wrong here or misunderstanding?
A simple, dirty quickfix is to add a method which adds the model attribute manually:
protected void addErrorAttribute(Model model) {
Root root = (Root)model.getAttribute("root");
BindingResult bindingResult = (BindingResult)model.getAttribute("org.springframework.validation.BindingResult.root");
model.addAttribute("errorMap", getErrorMap(root, bindingResult));
}
and call this method from the handleBindException(...) method.
I've been struggling with a rather absurd problem for a few days now:
The project I'm on is using Spring MVC with FreeMarker for it's templating.
This is running atop a Tomcat container (testing locally using Cargo).
The issue I'm working has the brief of implementing uniform behaviour in a standardised error page but covering covering the various types of errors that may be encountered. (Exceptions bubbling up from back-end services, inadequate permissions, http errors, etc)
So far, the results are as follows (Graphic included):
Fig A: Normal navigation to page - renders as expected.
Fig B & Fig C: Service and Permission Exceptions caught by ControllerAdvice.java - likewise, no issues.
Fig D: Any HTTP Error (yes, even 418 if you trigger that response) - Inner freemarker template is correctly retrieved and populated with bindings but decorations applied by filters fail to trigger.
Currently we're using Spring to configure the servlet handling so the web.xml is beautifully sparse:
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!--
This application uses the config of the mapping by Spring MVC
This is why you will not see servlet declarations here
The web app is defined in
- butler.SpringWebInit
- butler.SpringWebConfig
-->
<context-param>
<description>Escape HTML form data by default when using Spring tags</description>
<param-name>defaultHtmlEscape</param-name>
<param-value>true</param-value>
</context-param>
<!-- Disabling welcome list file for Tomcat, handling it in Spring MVC -->
<welcome-file-list>
<welcome-file/>
</welcome-file-list>
<!-- Generic Error redirection, allows for handling in Spring MVC -->
<error-page>
<location>/http-error</location>
<!-- Was originally just "/error" it was changed for internal forwarding/proxying/redirection attempts -->
</error-page>
</web-app>
The Configuration is handled by SpringWebInit.java to which I have not made any modifications:
SpringWebInit.java
/**
* Automatically loaded by class org.springframework.web.SpringServletContainerInitializer
*
* #see http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-container-config
*
* According to {#link AbstractSecurityWebApplicationInitializer}, this class should be
* annotated with a Order so that it is loaded before {#link SpringSecurityInit}
*/
#Order(0)
public class SpringWebInit extends AbstractAnnotationConfigDispatcherServletInitializer implements InitializingBean {
private final Logger LOG = LoggerFactory.getLogger(getClass());
#Override
public void afterPropertiesSet() throws Exception {
LOG.info("DispatcherServlet loaded");
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null; // returning null, getRootConfigClasses() will handle this as well
}
#Override
protected String[] getServletMappings() {
return new String[] {"/**"}; // Spring MVC should handle everything
}
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {SpringWebConfig.class, SpringSecurityConfig.class};
}
#Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter =
new CharacterEncodingFilter(StandardCharsets.UTF_8.name(), true);
return new Filter[] {characterEncodingFilter, new SiteMeshFilter()};
}
}
Which in turn loads The various config for Freemarker and Sitemesh:
SpringWebConfig.java
#EnableWebMvc
#Configuration
#PropertySource("classpath:/butler-init.properties")
#ComponentScan({"butler"})
class SpringWebConfig extends WebMvcConfigurerAdapter implements InitializingBean {
private final Logger LOG = LoggerFactory.getLogger(getClass());
#Autowired
LoggedInUserService loggedInUserService;
#Override
public void afterPropertiesSet() throws Exception {
LOG.info("Web Mvc Configurer loaded");
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userHeaderInterceptor());
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("/static/").setCacheControl(
CacheControl.maxAge(30, TimeUnit.MINUTES).noTransform().cachePublic().mustRevalidate());
}
#Bean
FreeMarkerViewResolver viewResolver() throws TemplateException {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setCache(/*true*/false); // Set to false for debugging
resolver.setPrefix("");
resolver.setSuffix(".ftlh");
resolver.setRequestContextAttribute("rContext");
resolver.setContentType("text/html;charset=UTF-8");
DefaultObjectWrapper wrapper =
new DefaultObjectWrapperBuilder(freemarker.template.Configuration.getVersion()).build();
Map<String, Object> attrs = new HashMap<>();
attrs.put("loggedInUserService", wrapper.wrap(loggedInUserService));
resolver.setAttributesMap(attrs);
return resolver;
}
#Bean
FreeMarkerConfigurer freeMarkerConfig() {
Properties freeMarkerVariables = new Properties();
// http://freemarker.org/docs/pgui_config_incompatible_improvements.html
// http://freemarker.org/docs/pgui_config_outputformatsautoesc.html
freeMarkerVariables.put(freemarker.template.Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY,
freemarker.template.Configuration.getVersion().toString());
FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer();
freeMarkerConfigurer.setDefaultEncoding("UTF-8");
freeMarkerConfigurer.setTemplateLoaderPath("/WEB-INF/mvc/view/ftl/");
freeMarkerConfigurer.setFreemarkerSettings(freeMarkerVariables);
return freeMarkerConfigurer;
}
#Bean
UserHeaderInterceptor userHeaderInterceptor() {
return new UserHeaderInterceptor();
}
#Bean
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
SiteMeshFilter.java
public class SiteMeshFilter extends ConfigurableSiteMeshFilter {
#Override
protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {
// Don't use decorator REST api pages
builder.addExcludedPath("/api/*");
builder.addDecoratorPath("/*", Views.DECORATOR_HEADER_FOOTER);
builder.setIncludeErrorPages(true);
}
}
Finally, onto the meat of the problem, the error handling is being handled via a combination of DefaultControllerAdvice.java, which provides the rules for intercepting exceptions and ErrorController.java itself, which handles the mappings and eventually, the message handling (displaying information about the error, adapting according to the type of error, etc)
DefaultControllerAdvice.java
#ControllerAdvice(annotations = Controller.class)
class DefaultControllerAdvice {
private static String EXCEPTION = "butlerexception";
#ExceptionHandler(ServiceException.class)
public String exceptionHandler(ServiceException se, Model model) {
model.addAttribute(EXCEPTION, se.getMessage());
return Views.ERROR;
}
#ExceptionHandler(PermissionException.class)
public String exceptionHandler(PermissionException pe, Model model) {
model.addAttribute(EXCEPTION, "Incorrect Permissions");
return Views.ERROR;
}
/*#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(IOException.class)
public String exceptionHandler(Model model) { // Trying another way of intercepting 404 errors
model.addAttribute(EXCEPTION, "HTTP Error: 404");
return Views.ERROR;
}*/
}
ErrorController.java
#Controller
class ErrorController extends AbstractController {
#Autowired
private LoggedInUserService loggedInUserService;
#RequestMapping(path="error",method = {GET,POST}) // Normal Error Controller, Returns fully decorated page without issue for Exceptions and normal requests.
public String error(RedirectAttributes redirectAttributes, HttpServletResponse response,Model model) {
//if (redirectAttributes.containsAttribute("errorCode")) { // Trying to invisibly use redirection
// Map<String, ?> redirAttribs = redirectAttributes.getFlashAttributes();
// model.addAttribute("butlerexception", "HTTP Error: "+redirAttribs.get("errorCode"));
//} else {
model.addAttribute("butlerexception", "Error");
//}
return ERROR;
}
#RequestMapping("/http-error") // Created to test HTTP requests being proxied via ServiceExceptions, Redirections, etc...
public String httpError(/*RedirectAttributes redirectAttributes,*/ HttpServletResponse response, HttpServletRequest request, Model model){
model.addAttribute("butlerexception", "HTTP Error: " + response.getStatus());
//throw new ServiceException("HTTP Error: " + response.getStatus()); // Trying to piggyback off Exception handling
//redirectAttributes.addFlashAttribute("errorCode", response.getStatus()); // Trying to invisibly use redirection
//redirectAttributes.addFlashAttribute("originalURL",request.getRequestURL());
return /*"redirect:"+*/ERROR;
}
}
So Far, I have tried:
Throwing exceptions to piggy-back off the working ControllerAdvice rules. - Result was undecorated.
Adding in Rules for response codes, IONotFound nad NoHandlerFound exceptions - Result was undecorated.
Redirecting to the error page - Result was decorated correctly, but URL and response codes were incorrect, attempting to mask the URL with the original request URL resulted in the correct URL and code, but the same lack of decoration as before.
Additionally, from the debugging logs, I can see that the filters from Spring Security are triggered normally but the ones involved with decorating the site (for both logged in and anonymous requests) fail to trigger for HTTP errors only.
One of the limiting factors currently is that I cannot gut the system and define it all in the web.xml (as many of the solutions here and in the Spring documentation seem to call for) without causing excessive disruption to development at this stage. (nor do I have the authority to effect such a change (Junior rank))
For Convenience sake, a few of the solutions I've tried so far:
Spring MVC 404 Error Page
404 error redirect in Spring with java config
Generic Error Page not decorated
Custom Error Page Not Decorated by Sitemesh in Spring Security Application
Custom 404 using Spring DispatcherServlet
<error-page> setup doesn't work in Spring MVC
At this point I'm really not sure what else to try, what on earth am I missing here?
Edit: it turned out to be a bug in SiteMesh to do with the triggering of .setContentType(...) that was solved via setting the contentType again after sitemesh in order to trigger decoration: Bug report with description and solution
This turned out to a two-part issue, firstly SiteMesh3's handling of error pages means that it believes it has processed all the filters even when an error causes decorators to be skipped. (expanded upon in this issue on github)
The second part was that SiteMesh3 appears to only buffer pages for decoration when SpringMVC calls .setContentType(...).
This was tripping up since Spring will only trigger this on elements with undefined content type whereas errors have already had their content type defined before they even reach Spring. (expanded upon by my lead in this issue)
My lead managed to solve this by adding a filter after SiteMesh that triggered .setContentType(...) and forced SiteMesh to buffer the page for decoration.
It's a little heavy, since it means that the content type is set twice per request, but it works.
Edit: Originally had a note here asking not to upvote to avoid receiving rep for a solution my lead found, but found a blog post explaining that self-answers don't earn rep - huzzah!
Solution 1:
Check if you have disabled property spring.resources.add-mappings=false. Enabling it could solve the problem. But in my case enabling it removed custom error pages at all.
Solution 2:
Based on comments on github issue https://github.com/sitemesh/sitemesh3/issues/25 declare custom selector inside your SiteMeshFilter:
public class SiteMeshFilter extends ConfigurableSiteMeshFilter {
#Override
protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {
builder.setCustomSelector(new CustomBasicSelector());
}
private static class CustomBasicSelector extends BasicSelector {
private static final String ALREADY_APPLIED_KEY = BasicSelector.class.getName() + ".APPLIED_ONCE";
public CustomBasicSelector() {
super(true, "text/html");
}
protected boolean filterAlreadyAppliedForRequest(HttpServletRequest request) {
if (request.getDispatcherType().equals(DispatcherType.ERROR)) {
if (Boolean.TRUE.equals(request.getAttribute(ALREADY_APPLIED_KEY + ".ERROR"))) {
return true;
} else {
request.setAttribute(ALREADY_APPLIED_KEY + ".ERROR", true);
return false;
}
}
return super.filterAlreadyAppliedForRequest(request);
}
}
}
I have a form for submitting credit card information data:
<g:FormPanel ui:field="creditCardForm" action="/app/create-credit-card" method="post">
<g:HTML>
<input data-braintree-name="number" value="4111111111111111"/>
<input data-braintree-name="cvv" value="100"/>
<input data-braintree-name="expiration_date" value="10/20"/>
<input data-braintree-name="postal_code" value="94107"/>
<input data-braintree-name="cardholder_name" value="John Smith"/>
</g:HTML>
</g:FormPanel>
But for some reason the SubmitCompleteHandler does not come into action when the POST is done:
this.creditCardForm.addSubmitHandler(new SubmitHandler() {
#Override
public void onSubmit(SubmitEvent event) {
LOGGER.fine("onSubmit()");
}
});
this.creditCardForm.addSubmitCompleteHandler(new SubmitCompleteHandler() {
#Override
public void onSubmitComplete(SubmitCompleteEvent event) {
LOGGER.fine("complete ..");
LOGGER.fine("Submit result: " + event.getResults());
}
});
The Servlet resturns just a String and writes some debug messages:
public class CreateCreditCardServlet extends HttpServlet implements Servlet {
private final static Logger LOGGER = Logger.getLogger(CreateCreditCardServlet.class.getName());
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOException { LOGGER.debug("#########################################################");
LOGGER.debug("doPost");
LOGGER.debug("#########################################################");
resp.getOutputStream().println("This is servlet response");
}
}
I see the response coming in the developer tool but why is the handler not getting called?
From the FormPanel documentation:
The back-end server is expected to respond with a content-type of
'text/html', meaning that the text returned will be treated as HTML.
If any other content-type is specified by the server, then the result
HTML sent in the onFormSubmit event will be unpredictable across
browsers, and the onSubmitComplete event may not fire at all.
So, in the servlet you need to do something like that:
resp.setContentType("text/html")
I am trying to show background image in my Spring mvc app. Now, the image is getting displayed in my home page but when I click a link which points to same page with some parameter appended to the url, the image is not appearing.
I am using this tag for displaying image:
<img src="./resources/css/welcome.png" id="bg" alt="">
In my application context,
<mvc:resources mapping="/resources/**" location="/resources/" />
Now, in my home page, if I click a link
MyLink
Image is not coming. The page name is welcome.jsp. Its controller is :
#Controller
public class WelcomeController {
#RequestMapping(value = "/welcome/{category}", method = RequestMethod.GET)
public String showPage(Model modelMap, HttpServletRequest request,
HttpServletResponse response) throws Exception {
try {
return "welcome";
} catch (Exception exception) {
}
}
#RequestMapping(value = "/welcome", method = RequestMethod.GET)
public String showPage1(Model modelMap, HttpServletRequest request,
HttpServletResponse response) throws Exception {
try {
return "welcome";
} catch (Exception exception) {
}
}
}
I am not able to understand if the image is coming when there is not path variable in welcome.html url, why it is not being loaded when I click MyLink which appends path variable in the url? Please help me out here..
Use jstl tags core lib and c:URL to create the image src definition.
I've been thinking if it is possible to handle Multipart request that is not an Action request. There is a reason why it seems impossible to me :
Only ActionRequest implements
getFile() kind of methods. I can't
find any easy way how to get the file
out of request other than Action
request
What if I don't use a html form to upload a file and I don't want a view to be rendered after action request - render phase happens always after the action phase.
What if I want to create a post request (with file(s)) by ajax and use #ResourceMapping handler. How do I get it out of ResourceRequest ?
Thank you very much for your thoughts.
This is the "pattern" that is afaik the best way of handling Multipart requests
Action request from view layer goes to this method:
#ActionMapping(params = "javax.portlet.action=sample")
public void response(MultipartActionRequest request, ActionResponse response) {
response.setRenderParameter("javax.portlet.action", "success");
List<MultipartFile> fileList = request.getFiles("file");
}
render phase follows :
#RequestMapping(params = "javax.portlet.action=success")
public ModelAndView process(RenderRequest request, Model model) throws IOException {
Map map = new HashMap();
map.put("test", new Integer(1));
return new ModelAndView("someView", map);
}
You create a "bean" view :
#Component("someView")
public class SomeView extends AbstractView {
private Logger logger = Logger.getLogger(SomeView.class);
#Override
protected void renderMergedOutputModel(Map map, HttpServletRequest request, HttpServletResponse response)
throws Exception {
logger.info("Resolving ajax request view - " + map);
JSONObject jsonObj = new JSONObject(map);
logger.info("content Type = " + getContentType());
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(jsonObj.toString());
response.getWriter().flush();
}
}
You add BeanNameViewResolver into your servlet/portlet context:
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver" p:order="1" />