tags:

views:

117

answers:

4

One of the things that has always bothered me about <jsp:include..> is that it's not possible to pass non-String values into the included page as distinct inputs to the page. For example, I'd like to be able to do the following:

<c:forEach var="item" items="${listOfItems}">
    <jsp:include page="mypage.jsp">
        <jsp:attribute name="item" value="${item}/>
    </jsp:include>
</c:forEach>

The idea being that, by declaring the item being passed in as an attribute inside the jsp:include node, the code is making it clear what the intent was... to supply that parameter to the included page.

The only mechanism currently available for doing this is to define the attribute "globally", and then let the included page read it from the global space. Unfortunately, this looses that same "intent" that using <jsp:param> provides. Additionally, it makes the code harder to debug, for the same reason that globals in any programming environment do.

Does anyone know of an implementation of an include mechanism that performs the functions of jsp:include, but allows for passing in non-String values? Or, if not, I'd be open to alternative ideas that preserve the same goal of intent and ease of debugging.

As a side note, I'd love to see a builtin "catch" mechanism for when the included page throws an error:

<abc:include page="mypage">
    <abc:param name="something" value="some string value"/>
    <abc:attribute name="somethingelse" value="${nonStringValue}"/>
    <abc:catch var="ex">
        The page failed, so the content it generated will not be sent to the output
        stream. Instead, we can collapse the part of the page that it's content
        would be... possibly putting a comment in the page with
        <!-- There was an exception generating this module: 
             <c:out value="${ex.getMessage}/>
          -->
    </abc:catch>
</abc:include>
+1  A: 

allows for passing in non-String values?

When you are restricted to passing Strings around, a common solution is to Serialize your objects.

Aillyn
+1  A: 

From the JSTL specification:

Nested scoped variables are only visible within the body of the action and are stored in "page" scope.

You can pass object references via request scope.

standardInclude.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%&gt;
<c:out value="${foo}" />

standardAction.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%&gt;
<%
  //evil scriptlet
  request.setAttribute("mylist", java.util.Arrays.asList("bar", "baz"));
%>
<c:forEach items="${mylist}" var="foo">
    <c:set var="foo" scope="request" value="${foo}" />
    <jsp:include page="standardInclude.jsp" />
    <c:set var="foo" scope="request" value="${null}" />
</c:forEach>

You can use the catch tag to catch exceptions.

thrower.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%&gt;
<c:out value="OK: ${foo}" />
<%
  //evil scriptlet
  if ("bar".equals(request.getAttribute("foo")))
    throw new java.io.IOException("oops");
%>

catcher.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%&gt;
<%
  //evil scriptlet
  request.setAttribute("mylist", java.util.Arrays.asList("bar", "baz"));
%>
<c:forEach items="${mylist}" var="foo">
    <br />
    <c:set var="foo" scope="request" value="${foo}" />
    <c:catch var="ex">
        <jsp:include page="thrower.jsp" />
    </c:catch>
    <c:if test="${ex ne null}">
        <c:out value="Error for ${foo}" />
    </c:if>
    <c:set var="foo" scope="request" value="${null}" />
</c:forEach>

This will emit:

Error for bar 
OK: baz 
McDowell
While I agree that the request scope does move part of the way towards "less global scope", it still makes the variable available at a larger scope than it needs to be (ie, the current page level rather than the included page level). Additionally, it does address the issue of intent... it doesn't show that you're specifically passing the variable into the included page. On the catch front, I agree that's an option, but it's ugly is all. The entire catch mechanism in jsp, while useful, is somewhat less than clean.
RHSeeger
By setting the request scope binding back to null after the include, you're limiting its visibility. What you're asking for is to expand the scope of a variable from one page into another. JSPs define a finite number of scopes: page, param, request, session and application. Unless you want to get into `ThreadLocal`s (another can of worms), this is as good as it gets. A custom forEach implementation would have to pick one of these options. I recommend looking at the Java code generated by JSP translators. It will give you a better idea of the problem you're trying to tackle.
McDowell
What I'm interested in is a scope that makes the variable visible for (where X includes Y) in Y but not in X. However, I want X to say "this will be visible in Y", much the way it does for params when it includes Y. It "seems" like this should be possible, since the include mechanism needs to create a context for Y (the included JSP) to run in. The problem is that the "standard" include->requestdispatch doesn't allow for this. As such, I was hoping there was a custom include tag (using a custom RequestDispatcher) that did do this.
RHSeeger
A: 

This can be achived with jsp tag files instead of includes. See my answer to a similar question at http://stackoverflow.com/questions/3053903/jsp-component-creation/3054579#3054579 for details.

Jörn Horstmann
While it's true that tag files can have objects passed in as attributes, they serve a different purpose than JSP files. Especially as the JSP being included gets larger, moving it into a tag seems to defeat the original goal of conveying the intent of the programmer.
RHSeeger
@RHSeger, your comment is not entirely clear to me, but I think in this your first example a tag file would be a better solution. I use includes for mostly static content that gets used in multiple pages. Tag files on the other hand are useful for variable content depending on their parameters, that are then also used multiple time on the same page.
Jörn Horstmann
Ignore the example I posed specifically, and assume I'm calling out to a JSP and need to pass in actual objects rather than strings. From there, the included JSP may include other JSP, etc. A tag file, from my understanding, is used more for small, modular components that are self contained. Is there any chance you can point me at an article/description of tag files in more detail?
RHSeeger
I think one of the things we have a disconnect on is your comment that you use includes mostly for "static content that gets used in multiple pages". I'm talking about includes that are used specifically for dynamic content. Not @include, but jsp:include.
RHSeeger
@RHSeger, ok thats probably because I use tag files instead of includes most of the time. With tag files you can do everything you could do in a jsp include, from small snippets to whole page fragments. You could also include other files in them or access objects in other scopes, not just parameters. I just think the contract between tag and including file is much clearer than with a plain jsp:include because the expected parameters and their types are declared right at the top, an can get autocompletet by an ide.
Jörn Horstmann
+1  A: 

Having read the answers here, plus doing additional research on the subject, I've come up with the following:

  • You can serialize your objects then deserialize them in the included JSP. Not a fan of this because it makes the code more complex (every object you pass in must be serializable, etc)
  • You can use taglibs. Not a fan of this, since I feel they serve a different role than JSP include files
  • You can define the variables at the request scope, which will make them available to the included JSP. Not a big fan of this since it doesn't show the intent of the programmer (passing in the values to the included page, for it's use only).
  • There really isn't (that I could find) an implementation that achieves what I'm looking for, but it is possibly to build something that comes close via custom tags.

I did some work and put together the code to achieve what I was looking for, and then threw it up on sourceforge. It allows you to specify the inputs in the way I was describing:

<inc:include page="normal.jsp">
    <inc:param name="param1" value="param1value" />
    <inc:param name="param2" value="param2value" />
    <inc:attrib name="attrib1" value="${attrib1value}" />
    <inc:attrib name="attrib2" value="${attrib2value}" />
    <inc:catch var="ex">
       This block was not rolled up because there wasn't an error. 
       Should never see this, but just in case, the exception was: ${ex.message}
    </inc:catch>
</inc:include>

The only problem with it (at this time) is that I add the attributes to the request scope before the page is included, then remove them after (or reset them if they already existed). What I'd like to do, instead, is wrap the request object instead and override the attribute methods to automatically include the values passed in... I'm still working on that.

RHSeeger
So, I feel bad marking my own answer as "the answer", but I really didn't get what I was looking for elsewhere. There was useful information provided, but I wound up having to combine it together and use it to develop my own implementation.
RHSeeger