org.apache.taglibs.standard.tlv.JstlXmlTLV Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jakarta.servlet.jsp.jstl Show documentation
Show all versions of jakarta.servlet.jsp.jstl Show documentation
Jakarta Standard Tag Library Implementation
/*
* Copyright (c) 1997-2020 Oracle and/or its affiliates. All rights reserved.
* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.taglibs.standard.tlv;
import java.util.Set;
import java.util.Stack;
import jakarta.servlet.jsp.tagext.PageData;
import jakarta.servlet.jsp.tagext.ValidationMessage;
import org.apache.taglibs.standard.resources.Resources;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
/**
* A SAX-based TagLibraryValidator for the JSTL XML library.
* Currently implements the following checks:
*
*
* - Expression syntax validation.
*
- Choose / when / otherwise constraints
* - Tag bodies that must either be empty or non-empty given
* particular attributes.
* - Other minor constraints.
*
*
* @author Shawn Bayern
*/
public class JstlXmlTLV extends JstlBaseTLV {
//*********************************************************************
// Implementation Overview
/*
* We essentially just run the page through a SAX parser, handling
* the callbacks that interest us. We collapse elements
* into the text they contain, since this simplifies processing
* somewhat. Even a quick glance at the implementation shows its
* necessary, tree-oriented nature: multiple Stacks, an understanding
* of 'depth', and so on all are important as we recover necessary
* state upon each callback. This TLV demonstrates various techniques,
* from the general "how do I use a SAX parser for a TLV?" to
* "how do I read my init parameters and then validate?" But also,
* the specific SAX methodology was kept as general as possible to
* allow for experimentation and flexibility.
*
* Much of the code and structure is duplicated from JstlCoreTLV.
* An effort has been made to re-use code where unambiguously useful.
* However, splitting logic among parent/child classes isn't
* necessarily the cleanest approach when writing a parser like the
* one we need. I'd like to reorganize this somewhat, but it's not
* a priority.
*/
//*********************************************************************
// Constants
// tag names
private final String CHOOSE = "choose";
private final String WHEN = "when";
private final String OTHERWISE = "otherwise";
private final String PARSE = "parse";
private final String PARAM = "param";
private final String TRANSFORM = "transform";
private final String JSP_TEXT = "jsp:text";
// attribute names
private final String VALUE = "value";
private final String SOURCE = "xml";
//*********************************************************************
// set its type and delegate validation to super-class
public ValidationMessage[] validate(
String prefix, String uri, PageData page) {
return super.validate( TYPE_XML, prefix, uri, page );
}
//*********************************************************************
// Contract fulfillment
protected DefaultHandler getHandler() {
return new Handler();
}
//*********************************************************************
// SAX event handler
/** The handler that provides the base of our implementation. */
private class Handler extends DefaultHandler {
// parser state
private int depth = 0;
private Stack chooseDepths = new Stack();
private Stack chooseHasOtherwise = new Stack();
private Stack chooseHasWhen = new Stack();
private String lastElementName = null;
private boolean bodyNecessary = false;
private boolean bodyIllegal = false;
private Stack transformWithSource = new Stack();
// process under the existing context (state), then modify it
public void startElement(
String ns, String ln, String qn, Attributes a) {
// substitute our own parsed 'ln' if it's not provided
if (ln == null)
ln = getLocalPart(qn);
// for simplicity, we can ignore for our purposes
// (don't bother distinguishing between it and its characters)
if (qn.equals(JSP_TEXT))
return;
// check body-related constraint
if (bodyIllegal)
fail(Resources.getMessage("TLV_ILLEGAL_BODY", lastElementName));
// validate expression syntax if we need to
Set expAtts;
if (qn.startsWith(prefix + ":")
&& (expAtts = (Set) config.get(ln)) != null) {
for (int i = 0; i < a.getLength(); i++) {
String attName = a.getLocalName(i);
if (expAtts.contains(attName)) {
String vMsg =
validateExpression(
ln,
attName,
a.getValue(i));
if (vMsg != null)
fail(vMsg);
}
}
}
// validate attributes
if (qn.startsWith(prefix + ":") && !hasNoInvalidScope(a))
fail(Resources.getMessage("TLV_INVALID_ATTRIBUTE",
SCOPE, qn, a.getValue(SCOPE)));
if (qn.startsWith(prefix + ":") && hasEmptyVar(a))
fail(Resources.getMessage("TLV_EMPTY_VAR", qn));
if (qn.startsWith(prefix + ":") && hasDanglingScope(a))
fail(Resources.getMessage("TLV_DANGLING_SCOPE", qn));
// check invariants for
if (chooseChild()) {
// mark for the first the first
if (isXmlTag(ns, ln, WHEN)) {
chooseHasWhen.pop();
chooseHasWhen.push(Boolean.TRUE);
}
// ensure has the right children
if(!isXmlTag(ns, ln, WHEN) && !isXmlTag(ns, ln, OTHERWISE)) {
fail(Resources.getMessage("TLV_ILLEGAL_CHILD_TAG",
prefix, CHOOSE, qn));
}
// make sure is the last tag
if (((Boolean) chooseHasOtherwise.peek()).booleanValue()) {
fail(Resources.getMessage("TLV_ILLEGAL_ORDER",
qn, prefix, OTHERWISE, CHOOSE));
}
if (isXmlTag(ns, ln, OTHERWISE)) {
chooseHasOtherwise.pop();
chooseHasOtherwise.push(Boolean.TRUE);
}
}
// Specific check, directly inside
if (!transformWithSource.empty() &&
topDepth(transformWithSource) == (depth - 1)) {
// only allow
if (!isXmlTag(ns, ln, PARAM))
fail(Resources.getMessage("TLV_ILLEGAL_BODY",
prefix + ":" + TRANSFORM));
// thus, if we get the opportunity to hit depth++,
// we know we've got a subtag
}
// now, modify state
// we're a choose, so record new choose-specific state
if (isXmlTag(ns, ln, CHOOSE)) {
chooseDepths.push(Integer.valueOf(depth));
chooseHasWhen.push(Boolean.FALSE);
chooseHasOtherwise.push(Boolean.FALSE);
}
// set up a check against illegal attribute/body combinations
bodyIllegal = false;
bodyNecessary = false;
if (isXmlTag(ns, ln, PARSE)) {
if (hasAttribute(a, SOURCE))
bodyIllegal = true;
} else if (isXmlTag(ns, ln, PARAM)) {
if (hasAttribute(a, VALUE))
bodyIllegal = true;
else
bodyNecessary = true;
} else if (isXmlTag(ns, ln, TRANSFORM)) {
if (hasAttribute(a, SOURCE))
transformWithSource.push(Integer.valueOf(depth));
}
// record the most recent tag (for error reporting)
lastElementName = qn;
lastElementId = a.getValue("http://java.sun.com/JSP/Page", "id");
// we're a new element, so increase depth
depth++;
}
public void characters(char[] ch, int start, int length) {
bodyNecessary = false; // body is no longer necessary!
// ignore strings that are just whitespace
String s = new String(ch, start, length).trim();
if (s.equals(""))
return;
// check and update body-related constraints
if (bodyIllegal)
fail(Resources.getMessage("TLV_ILLEGAL_BODY", lastElementName));
// make sure has no non-whitespace text
if (chooseChild()) {
String msg =
Resources.getMessage("TLV_ILLEGAL_TEXT_BODY",
prefix, CHOOSE,
(s.length() < 7 ? s : s.substring(0,7)));
fail(msg);
}
// Specific check, directly inside
if (!transformWithSource.empty()
&& topDepth(transformWithSource) == (depth - 1)) {
fail(Resources.getMessage("TLV_ILLEGAL_BODY",
prefix + ":" + TRANSFORM));
}
}
public void endElement(String ns, String ln, String qn) {
// consistently, we ignore JSP_TEXT
if (qn.equals(JSP_TEXT))
return;
// handle body-related invariant
if (bodyNecessary)
fail(Resources.getMessage("TLV_MISSING_BODY",
lastElementName));
bodyIllegal = false; // reset: we've left the tag
// update -related state
if (isXmlTag(ns, ln, CHOOSE)) {
Boolean b = (Boolean) chooseHasWhen.pop();
if (!b.booleanValue())
fail(Resources.getMessage("TLV_PARENT_WITHOUT_SUBTAG",
CHOOSE, WHEN));
chooseDepths.pop();
chooseHasOtherwise.pop();
}
// update -related state
if (!transformWithSource.empty()
&& topDepth(transformWithSource) == (depth - 1))
transformWithSource.pop();
// update our depth
depth--;
}
// are we directly under a ?
private boolean chooseChild() {
return (!chooseDepths.empty()
&& (depth - 1) == ((Integer) chooseDepths.peek()).intValue());
}
// returns the top int depth (peeked at) from a Stack of Integer
private int topDepth(Stack s) {
return ((Integer) s.peek()).intValue();
}
}
}