Asked  7 Months ago    Answers:  5   Viewed   25 times

I am interested in your tricks etc used when writing JSP/Servlet. I will start:

I somewhat recently found out how you can include the output of one JSP tag in an attribute of another tag:

<c:forEach items="${items}">
  <jsp:attribute name="var">
    <mytag:doesSomething/>
  </jsp:attribute>
  <jsp:body>
    <%-- when using jsp:attribute the body must be in this tag --%>
  </jsp:body>
</c:forEach>

 Answers

23

Note: I find it hard to think of any "hidden features" for JSP/Servlet. In my opinion "best practices" is a better wording and I can think of any of them. It also really depends on your experience with JSP/Servlet. After years of developing you don't see those "hidden features" anymore. At any way, I'll list some of those little "best practices" of which I in years discovered that many starters aren't fully aware of it. Those would be categorized as "hidden features" in the eye of many starters. Anyway, here's the list :)


Hide JSP pages from direct access

By placing JSP files in /WEB-INF folder you effectively hide them from direct access by for example http://example.com/contextname/WEB-INF/page.jsp. This will result in a 404. You can then only access them by a RequestDispatcher in Servlet or using jsp:include.


Preprocess request for JSP

Most are aware about Servlet's doPost() to post-process a request (a form submit), but most don't know that you can use Servlet's doGet() method to pre-process a request for a JSP. For example:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    List<Item> items = itemDAO.list();
    request.setAttribute("items", items);
    request.getRequestDispatcher("/WEB-INF/page.jsp").forward(request, response);
}

which is used to preload some tabular data which is to be displayed with help of JSTL's c:forEach:

<table>
    <c:forEach items="${items}" var="item">
        <tr><td>${item.id}</td><td>${item.name}</td></tr>
    </c:forEach>
</table>

Map such a servlet on an url-pattern of /page (or /page/*) and just invoke http://example.com/contextname/page by browser address bar or a plain vanilla link to run it. See also e.g. doGet and doPost in Servlets.


Dynamic includes

You can use EL in jsp:include:

<jsp:include page="/WEB-INF/${bean.page}.jsp" />

The bean.getPage() can just return a valid pagename.


EL can access any getter

EL does not per-se require the object-to-be-accessed to be a fullworthy Javabean. The presence of a no-arg method which is prefixed with get or is is more than sufficient to access it in EL. E.g.:

${bean['class'].name}

This returns the value of bean.getClass().getName() where the getClass() method is actually inherited from Object#getClass(). Note that class is specified using "brace notation" [] for reasons mentioned here instanceof check in EL expression language.

${pageContext.session.id}

This returns the value of pageContext.getSession().getId() which is useful in a.o. Can an applet communicate with an instance of a servlet.

${pageContext.request.contextPath}

This returns the value of pageContext.getRequest().getContextPath() which is useful in a.o. How to use relative paths without including the context root name?


EL can access Maps as well

The following EL notation

${bean.map.foo}

resolves to bean.getMap().get("foo"). If the Map key contains a dot, you can use the "brace notation" [] with a quoted key:

${bean.map['foo.bar']}

which resolves to bean.getMap().get("foo.bar"). If you want a dynamic key, use brace notation as well, but then unquoted:

${bean.map[otherbean.key]}

which resolves to bean.getMap().get(otherbean.getKey()).


Iterate over Map with JSTL

You can use c:forEach as well to iterate over a Map. Each iteration gives a Map.Entry which in turn has getKey() and getValue() methods (so that you can just access it in EL by ${entry.key} and ${entry.value}). Example:

<c:forEach items="${bean.map}" var="entry">
    Key: ${entry.key}, Value: ${entry.value} <br>
</c:forEach>

See also e.g. Debugging with jstl - how exactly?


Get current date in JSP

You can get the current's date with jsp:useBean and format it with help of JSTL fmt:formatDate

<jsp:useBean id="date" class="java.util.Date" />
...
<p>Copyright &copy; <fmt:formatDate value="${date}" pattern="yyyy" /></p>

This prints (as of now) like follows: "Copyright © 2010".


Easy friendly URL's

An easy way to have friendly URL's is to make use of HttpServletRequest#getPathInfo() and JSP's hidden in /WEB-INF:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.getRequestDispatcher("/WEB-INF" + request.getPathInfo() + ".jsp").forward(request, response);
}

If you map this servlet on for example /pages/*, then a request on http://example.com/contextname/pages/foo/bar will effectively display /WEB-INF/foo/bar.jsp. You can get a step further by splitting the pathinfo on / and only take the first part as JSP page URL and the remnant as "business actions" (let the servlet act as a page controller). See also e.g. Design Patterns web based applications.


Redisplay user input using ${param}

The implicit EL object ${param} which refers to the HttpServletRequest#getParameterMap() can be used to redisplay user input after a form submit in JSP:

<input type="text" name="foo" value="${param.foo}">

This basically does the same as request.getParameterMap().get("foo"). See also e.g. How can I retain HTML form field values in JSP after submitting form to Servlet?
Don't forget to prevent from XSS! See following chapter.


JSTL to prevent XSS

To prevent your site from XSS, all you need to do is to (re)display user-controlled data using JSTL fn:escapeXml or c:out.

<p><input type="text" name="foo" value="${fn:escapeXml(param.foo)}">
<p><c:out value="${bean.userdata}" />

Alternating <table> rows with LoopTagStatus

The varStatus attribute of JSTL c:forEach gives you a LoopTagStatus back which in turn has several getter methods (which can be used in EL!). So, to check for even rows, just check if loop.getIndex() % 2 == 0:

<table>
    <c:forEach items="${items}" var="item" varStatus="loop">
        <tr class="${loop.index % 2 == 0 ? 'even' : 'odd'}">...</tr>
    <c:forEach>
</table>

which will effectively end up in

<table>
    <tr class="even">...</tr>
    <tr class="odd">...</tr>
    <tr class="even">...</tr>
    <tr class="odd">...</tr>
    ...
</table>

Use CSS to give them a different background color.

tr.even { background: #eee; }
tr.odd { background: #ddd; }

Populate commasepared string from List/Array with LoopTagStatus:

Another useful LoopTagStatus method is the isLast():

<c:forEach items="${items}" var="item" varStatus="loop">
    ${item}${!loop.last ? ', ' : ''}
<c:forEach>

Which results in something like item1, item2, item3.


EL functions

You can declare public static utility methods as EL functions (like as JSTL functions) so that you can use them in EL. E.g.

package com.example;

public final class Functions {
     private Functions() {}

     public static boolean matches(String string, String pattern) {
         return string.matches(pattern);
     }
}

with /WEB-INF/functions.tld which look like follows:

<?xml version="1.0" encoding="UTF-8" ?>
<taglib 
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
    version="2.1">
   
    <tlib-version>1.0</tlib-version>
    <short-name>Custom_Functions</short-name>
    <uri>http://example.com/functions</uri>
    
    <function>
        <name>matches</name>
        <function-class>com.example.Functions</function-class>
        <function-signature>boolean matches(java.lang.String, java.lang.String)</function-signature>
    </function>
</taglib>

which can be used as

<%@taglib uri="http://example.com/functions" prefix="f" %>

<c:if test="${f:matches(bean.value, '^foo.*')}">
    ...
</c:if>

Get the original request URL and query string

If the JSP has been forwarded, you can get the original request URL by,

${requestScope['javax.servlet.forward.request_uri']} 

and the original request query string by,

${requestScope['javax.servlet.forward.query_string']}

That was it as far. Maybe I'll add some more sooner or later.

Tuesday, June 1, 2021
 
ShadowZzz
answered 7 Months ago
65

My brain just exploded

If you try to compile this code:

{-# LANGUAGE ExistentialQuantification #-}
data Foo = forall a. Foo a
ignorefoo f = 1 where Foo a = f

You will get this error message:

$ ghc Foo.hs

Foo.hs:3:22:
    My brain just exploded.
    I can't handle pattern bindings for existentially-quantified constructors.
    Instead, use a case-expression, or do-notation, to unpack the constructor.
    In the binding group for
        Foo a
    In a pattern binding: Foo a = f
    In the definition of `ignorefoo':
        ignorefoo f = 1
                    where
                        Foo a = f
Saturday, July 31, 2021
 
skrilled
answered 4 Months ago
20

As the lead developer of PyCharm, I can tell you that we don't usually hide features in random places, and there are a few reliable ways to discover most of them.

  • Try Ctrl-clicking on everything (methods, functions, template tag names and parameters, etc.)
  • If Ctrl-clicking works, usually so does completion (Ctrl-Space), rename (Shift-F6) and Find Usages (Alt-F7)
  • Look through the menus and try out the actions that seem interesting
  • Look at Settings | Inspections to configure the warnings which can be highlighted by PyCharm, and note that many of the inspections have quickfixes to correct the problems automatically
  • Read the blog and try out the features highlighted there.
Saturday, July 31, 2021
 
o_flyer
answered 4 Months ago
37
  • You can load the properties using java.util.Properties (or commons-configuration) in a ServletContextListener's contextInitialized(..) method.

  • register the listener with <listener> in web.xml

  • You then store the Properties into the ServletContext (you can get it from the event) (ctx.setAttribute("properties", properties)

  • then access the properties using ${applicationScope.properties.propName} (as BalusC noted, applicationScope is optional)

Update:

Initially I thought spring had some ready-to-use facility for that, but it turns out it's not exactly the case. You have two options:

  • this article explains something similar to my suggestion above, but using spring's PropertyPlaceholderConfigurer

  • this answer and this answer allow you to expose all your beans, including a PropertyPlaceholderConfigurer to the servlet context.

Wednesday, August 11, 2021
 
Jeffrey Stilwell
answered 4 Months ago
40

If you look at the top of generated jsp class, you will see the following line

 private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();

Now, one possible solution to customize out object is to have a custom JspFactory implmentation.

Steps

Create a custom JspFactory implementation

public class MyJspFactory extends JspFactory {
    private static JspFactory _myFactory = null;
    public MyJspFactory(JspFactory factory) {
        _myFactory = factory;
    } 
   //All abstract methods which looks up _myFactory and does the same thing

   public PageContext getPageContext(Servlet servlet, ServletRequest request, ServletResponse response, String errorPageURL, boolean needsSession, int bufferSize, boolean autoflush) {
        PageContext myCtxt = _myFactory.getPageContext(....)
        //Create a customPageContext and wrap myCtxt in it and return
   }
}

Create a CutsomPageContext class

public class MyPageContext extends PageContext {
    private PageContext _ctxt = null;

    public void setPageContext(PageContext ctxt) {
        _ctxt = ctxt;
    }

    //Implement all abstract methods using _ctxt object

    @override
    public  JspWriter getOut() {
        JspWriter _out = _ctxt.getOut();

        //Wrap _out object using MyJSPWriter as mentioned in question and return back;

    }
}

Now during the init face of the servlets, add the following lines

JspFactory newFactory = new MyJspFactory(JspFactory.getDefaultFactory());
JspFactory.setDefaultFactory(newFactory);   

I have not tried it out. But conceptually it should work. Please let us know if you could achieve what you wanted through this.

Good Luck!

Thursday, September 23, 2021
 
Torxed
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share