Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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 }