Showing posts with label JSP. Show all posts
Showing posts with label JSP. Show all posts

Tuesday, February 23, 2016

HSQLDB ignores column aliases in select statement by default

Keywords:
HSQL HSQLDB column alias ignored NullPointerException JSTL

Problem:
I can't pin point where this change happened from the HSQLDB changelogs but somewhere between HSQL 1.8.0.7 and 2.2.5, the handling of column aliases has changed. This is particularly problematic if attempting to lookup via alias name in JSTL where failed lookup of results (due to wrong column names) come back as null.

This is a standalone JSP example:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>

<sql:setDataSource var="dataSource" 
    url="jdbc:hsqldb:mem:example/aliasprob"
    driver="org.hsqldb.jdbcDriver"/>
<sql:update dataSource="${dataSource}">
    create table if not exists USERS (
        fullname varchar(255), 
        telephone varchar(255)
    );
</sql:update>
<sql:update dataSource="${dataSource}">
    insert into USERS (fullname, telephone) 
    values ('Joe Bloggs', '123-5555');
</sql:update>
<sql:update dataSource="${dataSource}">
    insert into USERS (fullname, telephone) 
    values ('Fred Twitters', '456-5555');
</sql:update>
<sql:query var="users" dataSource="${dataSource}">
    select fullname as "name"
    , telephone as "phone"
    from USERS
</sql:query>

<!DOCTYPE html>
<html>
    <head>
        <title>Alias Problems</title>
    </head>
    <body>
        <p>These are the users:</p>
        <table border="1">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Phone</th>
                </tr>
            </thead>
            <tbody>
                <c:forEach var="row" items="${users.rows}">
                    <tr>
                        <td>${row['name']}</td>
                        <td>${row['phone']}</td>
                    </tr>            
                </c:forEach>
            </tbody>
        </table>        
 </body>
</html>

<sql:update dataSource="${dataSource}">
    drop table USERS;
</sql:update>

With the above test, you get two rows in the result table with empty cells for the values. If the code above was changed to ignore the aliases used in the 'AS' statements and use the underlying column names the data would be visible. If I have code that uses column aliases in this way does the select statement have to be changed to work with HSQLDB?

Solution:
HSQLDB introduced the get_column_name property which "returns the underlying column name" (despite an alias being used) and is true by default. The documentation also states this is for "compatibility with other JDBC driver implementations". I suspect there's some inconsistency with with way the JSTL SQL tags are using ResultSet.getColumnLabel(int) vs ResultSet.getColumnName(int).

When using JSTL, there's no distinction between column labels or names when using the row['column'] syntax so using get_column_name=false in the HSQL connection URL is a quick fix - making the JSTL behaviour at least, consistent with other JDBC drivers.

In the example code above, this would look like:
<sql:setDataSource var="dataSource" 
    url="jdbc:hsqldb:mem:example/aliasprob;get_column_name=false"
    driver="org.hsqldb.jdbcDriver"/>

Wednesday, September 21, 2011

Get the generated source code for JSPs in WebLogic

Keywords:
generated JSP java class source Oracle WebLogic line numbers

Problem:
Given a stack trace such as the following:
java.lang.NullPointerException
 at jsp_servlet._web_45_inf._jsp._demo.__example._jspService(__example.java:117)
 at weblogic.servlet.jsp.JspBase.service(JspBase.java:34)
 at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:227)
 at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:125)
 at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:300)
 at weblogic.servlet.internal.ServletStubImpl.onAddToMapException(ServletStubImpl.java:416)
 at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:326)
 at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:183)
 at weblogic.servlet.internal.RequestDispatcherImpl.invokeServlet(RequestDispatcherImpl.java:526)
 at weblogic.servlet.internal.RequestDispatcherImpl.forward(RequestDispatcherImpl.java:253)

It won't always be obvious what the issue in example.jsp corresponds to in the generated __example.java. How do you get at the generated source code for the JSPs?

Solution:
The solution involves configuring a [app_name]/WEB-INF/weblogic.xml file in your web-app. The documentation is in the WebLogic 10.3 docs - see weblogic.xml Deployment Descriptor Elements but keep in mind the file will be validated against the schema so the elements must be in the correct spot.

Below is an example - defining the jsp-descriptor with keepgenerated and working-dir elements:
<?xml version="1.0" encoding="UTF-8"?>
<weblogic-web-app
    xmlns="http://www.bea.com/ns/weblogic/90"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.bea.com/ns/weblogic/90 http://www.bea.com/ns/weblogic/90/weblogic-web-app.xsd">
    <jsp-descriptor>
        <keepgenerated>true</keepgenerated>
        <working-dir>c:/my_folder/temp</working-dir>
    </jsp-descriptor>
    <container-descriptor>
        <prefer-web-inf-classes>true</prefer-web-inf-classes>
    </container-descriptor>
</weblogic-web-app>

Monday, June 20, 2011

Convert Oracle CLOBs to String in JSTL and tag file

Keywords:
java.lang.ClassCastException oracle.sql.CLOB cast java.lang.String CLOB jstl tag requestScope requestContext pageContext jspContext

Problem:
It's annoying when SQL that works for other vendors fails for a specific one ... in this case a "text" column in a schema is defined as "clob" in the corresponding oracle schema. Problem is that this is not necessarily equivalent - especially when querying the data. This is even more complex when the SQL is in JSTL. So with the JSTL code (where textValue is a CLOB):

<sql:query var="data" >
select id,
textValue
from example
where id=?
<sql:param value="${param['id']}"/>
</sql:query>
<c:forEach items="${data.rows}" var="row">
<c:out value="${row.textValue}"/><br/>
</c:forEach>


You get the result:

oracle.sql.CLOB@e645e0
oracle.sql.CLOB@1f58913
oracle.sql.CLOB@fa6b82
...


Or if you try to use the textValue in something expecting a string, you'll get:
java.lang.ClassCastException: oracle.sql.CLOB cannot be cast to java.lang.String


How do you turn a Clob to a String without filling the JSP with vendor-specific code (leaving out the argument for not having SQL in the JSP for now)?

Solution:
Great discussion of this very issue is on the OTN Forum: JSP and CLOB. It essentially involves putting the Clob to String code in a scriptlet. To keep this vendor-neutral and take some of the "ugliness" out of the JSP I'd opt for putting this code in a tag file and stick to referencing just the java.sql.* interfaces.

Step 1: create a /WEB-INF/tags/to-string.tag tag file


(Or in a subfolder - the path must start with /WEB-INF/tags/.. if using the tagdir approach).

This takes the CLOB (or other) column value and sets it back in the request context as a String.

<%--
Can turn a CLOB to String for Oracle schema
--%>
<%@ tag body-content="empty" %>
<%@ attribute name="var" required="true" type="java.lang.String" %>
<%@ attribute name="value" required="true" type="java.lang.Object" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ tag import="java.sql.*" %>
<%@ tag import="javax.servlet.jsp.*" %>
<%
String strValue = null;
if (value == null) {
strValue = ""; // NB: oracle empty string is null
} else if (value instanceof Clob) {
Clob clob = (Clob)value;
long size = clob.length();
strValue = clob.getSubString(1, (int)size);
} else {
strValue = value.toString();
}
jspContext.setAttribute(var, strValue, PageContext.REQUEST_SCOPE);
%>


In this tag file, var is the name of the variable to define in the requestScope. Note how this is done by referencing the jspContext variable.

Step 2: Use the to-string tag for your text, clob or Other columns


This involves first defining the new taglib (putting all .tag files in tagdir in the JSP scope using the "eg" prefix in this example) and then simply using the eg:to-string tag to put the string-value of the column in a "local" requestScope variable.

<%@ taglib prefix="eg" tagdir="/WEB-INF/tags" %>
<sql:query var="data" >
select id,
textValue
from example
where id=?
<sql:param value="${param['id']}"/>
</sql:query>
<c:forEach items="${data.rows}" var="row">
<eg:to-string var="textValue" value="${row.textValue}"/>
<c:out value="${textValue}"/><br/>
</c:forEach>


Wednesday, August 18, 2010

Type coercion in JSTL - for sql:param

Keywords:
jstl sql integer string type coercion postgres serial operator does not exist: bigint = character varying

Problem:
After upgrading from PostgreSQL 8.0 to 8.4 the following JSTL that queries a table by a passed in "ID":
<sql:query var="examples" dataSource="${exampleDataSource}">
    select ExampleName as "name"
    from ExampleTable 
    where ExampleId = ?
    order by ExampleName ASC
    <sql:param value="${param['ID']}"/>
</sql:query>

Fails with the exception:
javax.servlet.jsp.JspException:
    select ExampleName as "name"
    from ExampleTable
    where ExampleId = ?
    order by ExampleName ASC

: ERROR: operator does not exist: bigint = character varying
        at org.apache.taglibs.standard.tag.common.sql.QueryTagSupport.doEndTag(QueryTagSupport.java:220)
        ....
Caused by: java.sql.SQLException: ERROR: operator does not exist: bigint = character varying
        at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:1471)
        at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1256)
        at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:175)
        at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:389)
        at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:330)
        at org.postgresql.jdbc2.AbstractJdbc2Statement.executeQuery(AbstractJdbc2Statement.java:240)
        at org.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:93)
        at org.apache.taglibs.standard.tag.common.sql.QueryTagSupport.doEndTag(QueryTagSupport.java:215)


Why are there data type errors all of a sudden? ... and how do you fix it?

Solution:
There is normally Type Coercion for EL expressions but it's a bit vague for sql:param. Ideally it should coerce the param into the type required but this would require knowing the schema & the SQL being executed. So looking at the source for QueryTagSupport it will just call setObject using the default type that was supplied in the param.

Contrary to the sql:param documentation the value does not have to be a String.

To get the right type into sql:param use type coercion in EL before the param gets the value. To coerce a String to Integer, you could multiply by 1. For example:
<sql:query var="examples" dataSource="${exampleDataSource}">
    select ExampleName as "name"
    from ExampleTable 
    where ExampleId = ?
    order by ExampleName ASC
    <sql:param value="${param['ID']*1}"/>
</sql:query>

Or in two steps:
<c:set var="exampleId" value="${param['ID']*1}"/>
<sql:query var="examples" dataSource="${exampleDataSource}">
    select ExampleName as "name"
    from ExampleTable 
    where ExampleId = ?
    order by ExampleName ASC
    <sql:param value="${exampleId}"/>
</sql:query>


Why did this come up after a PostgreSQL upgrade? It seems something to do with different handling of the SERIAL data type which is now compiled to it's actual representation of integer with sequence rather than leaving it as it's "notational convenience" name. Perhaps the PostgreSQL JDBC will coerce a String to a serial but not an integer?

It's hard to say the above work around is best practice but it will be harmless for databases that handle coercion at the JDBC level and necessary for those that don't.

Friday, October 17, 2008

Redirect from a JSP page (to another server)

Keywords:
best practice tips JSP redirect XML

Problem:
<jsp:forward> is not for redirecting to an external server (and the address you forward to will have access the request attributes and parameters, which may be desirable, see discussion here: when to use response redirect and jsp forward.

Is using response.sendRedirect(...) ok/best-practice?
Solution:
The short answer, is it's fine. In summary, there are four approaches I can think of:

Approach #1: Scriptlet to sendRedirect

<%
    response.sendRedirect("http://www.example.com");
%>

This has worked since the introduction of JSPs (see: Implementing a Redirect in a JSP Page) ... sometimes you need the servlet API in your JSP page either because there's other content (eg HTML) that makes it reasonable not to be a pure servlet. Other times it's just convenient to have a text file compiled on the fly.

Though there are many that would disagree (Sciptlet snobs? see How to redirect a page in JSP - I don't see why you'd get wound up about scriptlets. Why would you write a servlet when one line of code in a text file gives you the same result?).

Approach #2: Use Apache JSTL and the c:redirect tag

<c:redirect url="http://www.example.com"/>

or scriptlets again:
<c:redirect url="<%=scriptlet logic%>"/>

Sure, it's 'pure' XML but you'll need the taglib definition in the JSP file and include the JSTL jars in your web application - as discussed in the notes for a previous post.

Approach #3: Refresh meta tag


You could get the JSP to produce a HTML page that contains the Refresh meta tag.
<html>
<head>
  <meta http-equiv="Refresh" content="0; url=http://www.example.com/">
</head>
<body></body>
</html>

Where 0 is the delay in seconds. If more than zero, you'd probably want to put some text on the page to explain what's happening.

Approach #4: Javascript


As above, you could get the JSP to produce a HTML page that contains javascript to perform the redirect.
<html>
<head>
<script type="text/javascript">
window.location = "http://www.example.com/";
</script>
</head>
<body></body>
</html>

You could use setTimeout to get the redirect to happen after a period of milliseconds.


Notes:
Out of curiosity I checked TCP monitor for what the browser is actually receiving when you use Approach #1 or #2 (sendRedirect or c:redirect)
HTTP/1.1 302 Moved Temporarily
Location: http://www.example.com

So it saves you from 2 lines of servlet (or scriptlet) code:
response.setStatus(302);
response.setHeader("Location", "http://www.example.com");

So the difference to Approaches #3 & #4 isn't great or much more inefficient really, in all cases it's up to the browser to go to the specified address.

Wednesday, April 09, 2008

Call a stored procedure from a JSP with JSTL

Keywords:
Call execute SQL stored procedure JSP JSTL tag library

Problem:
The JSTL SQL tag library is a useful way of getting a rapid prototype going - it's all in a plain text file and will get compile on the fly. Examples I've seen show SELECT, UPDATE, INSERT and DELETE(s) ... can a stored procedure be run?

Solution:
The short answer is yes, the key thing is to know if the stored procedure is returning a result set or not as you have two tags available:

  • <sql:query> expects a ResultSet

  • <sql:update> does not expect a ResultSet - will throw an error if gets one. You do have access to an Integer result - eg rows updated.

For example, calling a stored procedure that returns a result set:
<sql:setDataSource var="myDataSource" dataSource="jdbc/myjndiref"/>
<sql:query var="examples" dataSource="${myDataSource}">
    exec ExampleProcLoadExample ?
    <sql:param value="${exampleId}"/>
</sql:query>
<c:choose>
    <c:when test="${fn:length(examples.rows) == 0}">
        <%-- no rows returned ! --%>
    </c:when>
    <c:otherwise>
        <c:set var="example" value="${examples.rows[0]}"/>
        <%-- got your object, can access columns with '.' notation --%>
    </c:otherwise>
</c:choose>


For example, calling a stored procedure that performs an 'update' (or insert/delete) returning number of rows updated:
<sql:setDataSource var="myDataSource" dataSource="jdbc/myjndiref"/>
<sql:update var="updateCount" dataSource="${myDataSource}">
    exec ExampleProcRemoveExample ?
    <sql:param value="${exampleId}"/>
</sql:update>
<c:choose>
    <c:when test="${updateCount le 0}">
        <%-- no rows updated ! --%>
    </c:when>
    <c:otherwise>
        <%-- some row(s) has been updated --%>
        <c:out value="${updateCount} row(s) have been updated"/>
    </c:otherwise>
</c:choose>


Notes:
When calling stored procedures you're getting into RDBMS vendor specific territory. Using the recommended JDBC drivers from the DB vendor for the DB version in use may make some of this work more smoothly.

Wednesday, March 21, 2007

JSP EL expressions are being ignored by tomcat

Keywords:
tomcat JSP JSTL EL

Problem:
Hopefully this is my last post about the possible problems with JSP/web-app specs and EL. In this case, there's a JSP using EL
Eg:

<%
String myUrl = (String)session.getAttribute("SESSION_KEY_MY_URL");
pageContext.setAttribute("myUrl", returnUrl);
%>
<form name="load" action="${myUrl}" method="GET">


It's coming out in HTML with the EL untouched (but the scriptlet is definitely being run).

<form name="load" action="${myUrl}" method="GET">


Why is the EL being ignored?

Solution:
As with the previous 2 posts on this issue, you have to fix the schema reference in the web.xml (there's too many differing copies of the web.xml(s) I'm dealing with here) and make sure the JSP has the right tablib uri reference - see this post.

For the first time I found this didn't immediately solve the problem! The HTML is coming out the same with EL unevaluated. The issue is that the JSP has been compiled to TOMCAT_HOME\work\Catalina\localhost\MyApp\org\apache\jsp\jsp\myfolder\MyJSP.java and tomcat sees no need to recompile even though I've updated the spec of the webapp.

The solution is to stop tomcat, remove the contents of TOMCAT_HOME\work and restart (OR update the timestamp on the JSP source file(s) to force recompilation). The JSP will be recompiled (to the right spec 2.0 now) and the EL will be evaluated as you expect.

Thursday, March 15, 2007

Can't get or find errorData attribute in JSP pageContext

Keywords:
error JSP pageContext errorData scriplet

Problem:
Writing a JSP that handles errors from JSP, servlets (and other!) need to access the ErrorData object in a scriptlet rather that the convenient JSP EL ${pageContext.errorData}.

Tried:
pageContext.getAttribute("errorData"); but this returns null. Maybe it's not in the page scope.
pageContext.findAttribute("errorData"); but still null.

Solution:
Thankfully there's the convenience method:
pageContext.getErrorData();

This works. It seems the JSP EL ${pageContext.errorData} is a way of referencing this accessor rather than an element in the pageContext "map".

Wednesday, March 14, 2007

Structured data to GWT component with JSON

Keywords:
GWT JSON stuctured initialisation data

Problem:
In a previous post I described using the internationalisation Dictionary class to pass in initialisation data. But what if the data is more complex than key-value pairs? You could define a dictionary value as being a delimited (e.g. space or comma separated) list of tokens but is there an easier way of getting in basic structured data without writing your own parser?

Solution:
JavaScript Object Notation (JSON) is a ".. text-based, human-readable format for representing objects and other data structures" (definition from wikipedia - the article includes a good introduction with examples). GWT supports reading and producing data in this format via the com.google.gwt.json.client package. For data interchange I think your still better off using a GWT remote service and sending your beans to it (let GWT handle the encoding/decoding) but for the host page giving data to the component JSON comes into its own.

Step 1


In the host page, define a dictionary as before but the value (or values) is/are now JSON strings rather than plain strings. In this example I'm creating an array of objects, each object having a property "id" and optionally a property "label" (it's got to be on one line as far as I can tell for the dictionary to allow it):
showHide: "[{\"id\": \"A\", \"label\": \"see A detail?\"}, {\"id\": \"B\", \"label\": \"include B detail?\"}, {\"id\": \"C\"}]"

Step 2


In the GWT component, use the dictionary to get at the JSON string. Then use the JSONParser to convert this to a JSON instance - in this example a JSONArray of JSONObjects. For a JSONObject you can get at each of its properties like you would a map via the get(String key) method.
public void onModuleLoad() {
    Dictionary properties = Dictionary.getDictionary("ModuleProperties");
  
    String showHideConfigString = properties.get("showHide");
    JSONArray showHideConfigs = (JSONArray)JSONParser.parse(showHideConfigString);
    
    List panels = new ArrayList();
    for (int i = 0; i < showHideConfigs.size(); i++) {
        JSONObject config = (JSONObject)showHideConfigs.get(i);
        
        JSONString idValue = (JSONString) config.get("id");
        String id = idValue.stringValue();
        
        JSONString labelValue = (JSONString) config.get("label");
        String label = (labelValue == null)? "include?" : labelValue.stringValue();
        
        // ... now we can do stuff with the data
        ShowHidePanel showHidePanel = new ShowHidePanel(label, id);
        RootPanel.get(id).add(showHidePanel);
        panels.add(showHidePanel);
    }
    
    // ... etc


Notes:
Someone asked me via comments how would you handle request parameters ... it's up to you to make something that can process the request parameters (CGI, java servlet, ASP?) and write a host page containing the data from the request params for the GWT component to then have access to them.

For many this will be obvious, but I felt bad for not including their comment ... but I don't want questions on the blog. Hope that helps.

If the data is more complex/rich than what JSON allows, there's XML.

Wednesday, February 28, 2007

org.apache.taglibs.standard.lang.jstl.parser.ParseException: EL functions are not supported.

Keywords:
Compile JSP tomcat JSTL error functions "org.apache.taglibs.standard.lang.jstl.parser.ParseException: EL functions are not supported"

Problem:
Some fairly standard use of JSTL:
<c:out value="${fn:escapeXml(myvalue)}" />

i.e. using a function in the value field of the c:out tag gives the error:
org.apache.jasper.JasperException:
    <h3>Validation error messages from TagLibraryValidator for c</h3>
    <p>41: tag = 'out' / attribute = 'value': 
        An error occurred while parsing custom action attribute "value" with value "${fn:escapeXml(myvalue)}": 
            org.apache.taglibs.standard.lang.jstl.parser.ParseException: EL functions are not supported.</p>


Solution:
It's the old mismatched spec issue again. Same solution as described in According to TLD or attribute directive in tag file, attribute X does not accept any expressions - namely, fix the schema reference in the web.xml and make sure the JSP has the right tablib uri reference.

Monday, January 15, 2007

How to give initialisation parameters to a GWT component

Keywords:
GWT init initialisation parameter dynamic config

Problem:
Where you have a servlet, JSP or other technology dynamically producing the HTML "host page" for a GWT component, it makes sense that you may want to parameterise the way the GWT component renders/behaves based on certain initialisation/input parameters.

How do pass parameters into the Module?

Solution:
There is a useful Dictionary facility in the Internationalisation (I18N) module ... it doesn't really have anything to do with Internationalisation but it's in this module because you could get the host page to provide key-to-locale-specific-message mappings to save the Module using any hard coded message text in a specific language.

It's generic enough that you could use it for any mapping of values.

The GWT documentation for Dictionary shows how to import the I18N module to your own module and then load the key-value mappings defined in the host page from your GWT code.

Tuesday, December 12, 2006

According to TLD or attribute directive in tag file, attribute value does not accept any expressions

Keywords:
Compile JSP tomcat JSTL error "According to TLD or attribute directive in tag file, attribute value does not accept any expressions"

Problem:
Compile error from tomcat when it encounters a JSP: "According to TLD or attribute directive in tag file, attribute value does not accept any expressions"

Solution:
For some reason the JSP is using the 1.2 JSP (and 1.0 JSTL) and EL expressions aren't understood. There's a lot of hits on the web for this but in summary there are 2 important things to do to ensure you're getting the right version of the spec:
  1. Reference the correct servlet specification in your deployment descriptor:
    <?xml version="1.0"?>
    <web-app version="2.4"
     xmlns="http://java.sun.com/xml/ns/j2ee"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  2. Reference the correct JSTL uri in your JSP:
    change
    <%@ taglib uri='http://java.sun.com/jstl/core' prefix='c'%>

    to
    <%@ taglib uri='http://java.sun.com/jsp/jstl/core' prefix='c'%>

Notes:


What Specification goes with what?
Web-app(deployment schema)2.32.42.5
http://java.sun.com/dtd/web-app_2_3.dtdhttp://java.sun.com/xml/ns/j2ee/web-app_2_4.xsdhttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd
Servlet2.32.42.5
JSP1.22.02.1
JSTL(core uri reference)1.01.11.2
http://java.sun.com/jstl/corehttp://java.sun.com/jsp/jstl/corehttp://java.sun.com/jsp/jstl/core
Tomcat4.x5.x6.x
WebSphere5.x6.x7.x (?)