Source code: org/apache/taglibs/standard/tlv/JstlCoreTLV.java
1 /*
2 * Copyright 1999-2004 The Apache Software Foundation.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.apache.taglibs.standard.tlv;
18
19 import java.util.Set;
20 import java.util.Stack;
21
22 import javax.servlet.jsp.tagext.PageData;
23 import javax.servlet.jsp.tagext.ValidationMessage;
24
25 import org.apache.taglibs.standard.resources.Resources;
26 import org.xml.sax.Attributes;
27 import org.xml.sax.helpers.DefaultHandler;
28
29 /**
30 * <p>A SAX-based TagLibraryValidator for the core JSTL tag library.
31 * Currently implements the following checks:</p>
32 *
33 * <ul>
34 * <li>Expression syntax validation.
35 * <li>Choose / when / otherwise constraints</li>
36 * <li>Tag bodies that must either be empty or non-empty given
37 * particular attributes. (E.g., <set> cannot have a body when
38 * 'value' is specified; it *must* have a body otherwise.) For
39 * these purposes, "having a body" refers to non-whitespace
40 * content inside the tag.</li>
41 * <li>Other minor constraints.</li>
42 * </ul>
43 *
44 * @author Shawn Bayern
45 */
46 public class JstlCoreTLV extends JstlBaseTLV {
47
48 //*********************************************************************
49 // Implementation Overview
50
51 /*
52 * We essentially just run the page through a SAX parser, handling
53 * the callbacks that interest us. We collapse <jsp:text> elements
54 * into the text they contain, since this simplifies processing
55 * somewhat. Even a quick glance at the implementation shows its
56 * necessary, tree-oriented nature: multiple Stacks, an understanding
57 * of 'depth', and so on all are important as we recover necessary
58 * state upon each callback. This TLV demonstrates various techniques,
59 * from the general "how do I use a SAX parser for a TLV?" to
60 * "how do I read my init parameters and then validate?" But also,
61 * the specific SAX methodology was kept as general as possible to
62 * allow for experimentation and flexibility.
63 */
64
65
66 //*********************************************************************
67 // Constants
68
69 // tag names
70 private final String CHOOSE = "choose";
71 private final String WHEN = "when";
72 private final String OTHERWISE = "otherwise";
73 private final String EXPR = "out";
74 private final String SET = "set";
75 private final String IMPORT = "import";
76 private final String URL = "url";
77 private final String REDIRECT = "redirect";
78 private final String PARAM = "param";
79 // private final String EXPLANG = "expressionLanguage";
80 private final String TEXT = "text";
81
82 // attribute names
83 private final String VALUE = "value";
84 private final String DEFAULT = "default";
85 private final String VAR_READER = "varReader";
86
87 // alternative identifiers for tags
88 private final String IMPORT_WITH_READER = "import varReader=''";
89 private final String IMPORT_WITHOUT_READER = "import var=''";
90
91
92 //*********************************************************************
93 // set its type and delegate validation to super-class
94 public ValidationMessage[] validate(
95 String prefix, String uri, PageData page) {
96 return super.validate( TYPE_CORE, prefix, uri, page );
97 }
98
99
100 //*********************************************************************
101 // Contract fulfillment
102
103 protected DefaultHandler getHandler() {
104 return new Handler();
105 }
106
107
108 //*********************************************************************
109 // SAX event handler
110
111 /** The handler that provides the base of our implementation. */
112 private class Handler extends DefaultHandler {
113
114 // parser state
115 private int depth = 0;
116 private Stack chooseDepths = new Stack();
117 private Stack chooseHasOtherwise = new Stack();
118 private Stack chooseHasWhen = new Stack();
119 private Stack urlTags = new Stack();
120 private String lastElementName = null;
121 private boolean bodyNecessary = false;
122 private boolean bodyIllegal = false;
123
124 // process under the existing context (state), then modify it
125 public void startElement(
126 String ns, String ln, String qn, Attributes a) {
127
128 // substitute our own parsed 'ln' if it's not provided
129 if (ln == null)
130 ln = getLocalPart(qn);
131
132 // for simplicity, we can ignore <jsp:text> for our purposes
133 // (don't bother distinguishing between it and its characters)
134 if (isJspTag(ns, ln, TEXT))
135 return;
136
137 // check body-related constraint
138 if (bodyIllegal)
139 fail(Resources.getMessage("TLV_ILLEGAL_BODY", lastElementName));
140
141 // validate expression syntax if we need to
142 Set expAtts;
143 if (qn.startsWith(prefix + ":")
144 && (expAtts = (Set) config.get(ln)) != null) {
145 for (int i = 0; i < a.getLength(); i++) {
146 String attName = a.getLocalName(i);
147 if (expAtts.contains(attName)) {
148 String vMsg =
149 validateExpression(
150 ln,
151 attName,
152 a.getValue(i));
153 if (vMsg != null)
154 fail(vMsg);
155 }
156 }
157 }
158
159 // validate attributes
160 if (qn.startsWith(prefix + ":") && !hasNoInvalidScope(a))
161 fail(Resources.getMessage("TLV_INVALID_ATTRIBUTE",
162 SCOPE, qn, a.getValue(SCOPE)));
163 if (qn.startsWith(prefix + ":") && hasEmptyVar(a))
164 fail(Resources.getMessage("TLV_EMPTY_VAR", qn));
165 if (qn.startsWith(prefix + ":") && hasDanglingScope(a))
166 fail(Resources.getMessage("TLV_DANGLING_SCOPE", qn));
167
168 // check invariants for <choose>
169 if (chooseChild()) {
170 // mark <choose> for the first the first <when>
171 if (isCoreTag(ns, ln, WHEN)) {
172 chooseHasWhen.pop();
173 chooseHasWhen.push(Boolean.TRUE);
174 }
175
176 // ensure <choose> has the right children
177 if(!isCoreTag(ns, ln, WHEN) && !isCoreTag(ns, ln, OTHERWISE)) {
178 fail(Resources.getMessage("TLV_ILLEGAL_CHILD_TAG",
179 prefix, CHOOSE, qn));
180 }
181
182 // make sure <otherwise> is the last tag
183 if (((Boolean) chooseHasOtherwise.peek()).booleanValue()) {
184 fail(Resources.getMessage("TLV_ILLEGAL_ORDER",
185 qn, prefix, OTHERWISE, CHOOSE));
186 }
187 if (isCoreTag(ns, ln, OTHERWISE)) {
188 chooseHasOtherwise.pop();
189 chooseHasOtherwise.push(Boolean.TRUE);
190 }
191
192 }
193
194 // check constraints for <param> vis-a-vis URL-related tags
195 if (isCoreTag(ns, ln, PARAM)) {
196 // no <param> outside URL tags.
197 if (urlTags.empty() || urlTags.peek().equals(PARAM))
198 fail(Resources.getMessage("TLV_ILLEGAL_ORPHAN", PARAM));
199
200 // no <param> where the most recent <import> has a reader
201 if (!urlTags.empty() &&
202 urlTags.peek().equals(IMPORT_WITH_READER))
203 fail(Resources.getMessage("TLV_ILLEGAL_PARAM",
204 prefix, PARAM, IMPORT, VAR_READER));
205 } else {
206 // tag ISN'T <param>, so it's illegal under non-reader <import>
207 if (!urlTags.empty()
208 && urlTags.peek().equals(IMPORT_WITHOUT_READER))
209 fail(Resources.getMessage("TLV_ILLEGAL_CHILD_TAG",
210 prefix, IMPORT, qn));
211 }
212
213 // now, modify state
214
215 // we're a choose, so record new choose-specific state
216 if (isCoreTag(ns, ln, CHOOSE)) {
217 chooseDepths.push(new Integer(depth));
218 chooseHasWhen.push(Boolean.FALSE);
219 chooseHasOtherwise.push(Boolean.FALSE);
220 }
221
222 // if we're introducing a URL-related tag, record it
223 if (isCoreTag(ns, ln, IMPORT)) {
224 if (hasAttribute(a, VAR_READER))
225 urlTags.push(IMPORT_WITH_READER);
226 else
227 urlTags.push(IMPORT_WITHOUT_READER);
228 } else if (isCoreTag(ns, ln, PARAM))
229 urlTags.push(PARAM);
230 else if (isCoreTag(ns, ln, REDIRECT))
231 urlTags.push(REDIRECT);
232 else if (isCoreTag(ns, ln, URL))
233 urlTags.push(URL);
234
235 // set up a check against illegal attribute/body combinations
236 bodyIllegal = false;
237 bodyNecessary = false;
238 if (isCoreTag(ns, ln, EXPR)) {
239 if (hasAttribute(a, DEFAULT))
240 bodyIllegal = true;
241 } else if (isCoreTag(ns, ln, SET)) {
242 if (hasAttribute(a, VALUE))
243 bodyIllegal = true;
244 // else
245 // bodyNecessary = true;
246 }
247
248 // record the most recent tag (for error reporting)
249 lastElementName = qn;
250 lastElementId = a.getValue(JSP, "id");
251
252 // we're a new element, so increase depth
253 depth++;
254 }
255
256 public void characters(char[] ch, int start, int length) {
257
258 bodyNecessary = false; // body is no longer necessary!
259
260 // ignore strings that are just whitespace
261 String s = new String(ch, start, length).trim();
262 if (s.equals(""))
263 return;
264
265 // check and update body-related constraints
266 if (bodyIllegal)
267 fail(Resources.getMessage("TLV_ILLEGAL_BODY", lastElementName));
268 if (!urlTags.empty()
269 && urlTags.peek().equals(IMPORT_WITHOUT_READER)) {
270 // we're in an <import> without a Reader; nothing but
271 // <param> is allowed
272 fail(Resources.getMessage("TLV_ILLEGAL_BODY",
273 prefix + ":" + IMPORT));
274 }
275
276 // make sure <choose> has no non-whitespace text
277 if (chooseChild()) {
278 String msg =
279 Resources.getMessage("TLV_ILLEGAL_TEXT_BODY",
280 prefix, CHOOSE,
281 (s.length() < 7 ? s : s.substring(0,7)));
282 fail(msg);
283 }
284 }
285
286 public void endElement(String ns, String ln, String qn) {
287
288 // consistently, we ignore JSP_TEXT
289 if (isJspTag(ns, ln, TEXT))
290 return;
291
292 // handle body-related invariant
293 if (bodyNecessary)
294 fail(Resources.getMessage("TLV_MISSING_BODY",
295 lastElementName));
296 bodyIllegal = false; // reset: we've left the tag
297
298 // update <choose>-related state
299 if (isCoreTag(ns, ln, CHOOSE)) {
300 Boolean b = (Boolean) chooseHasWhen.pop();
301 if (!b.booleanValue())
302 fail(Resources.getMessage("TLV_PARENT_WITHOUT_SUBTAG",
303 CHOOSE, WHEN));
304 chooseDepths.pop();
305 chooseHasOtherwise.pop();
306 }
307
308 // update state related to URL tags
309 if (isCoreTag(ns, ln, IMPORT)
310 || isCoreTag(ns, ln, PARAM)
311 || isCoreTag(ns, ln, REDIRECT)
312 || isCoreTag(ns, ln, URL))
313 urlTags.pop();
314
315 // update our depth
316 depth--;
317 }
318
319 // are we directly under a <choose>?
320 private boolean chooseChild() {
321 return (!chooseDepths.empty()
322 && (depth - 1) == ((Integer) chooseDepths.peek()).intValue());
323 }
324
325 }
326 }