1 /**
2 * Copyright 2007 Art Technology Group, Inc (ATG)
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 *
7 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and limitations under the License.
13 */
14
15 package atg.taglib.json;
16
17 import atg.taglib.json.util.JSONException;
18
19 import java.util.Stack;
20
21 import javax.servlet.ServletContext;
22 import javax.servlet.jsp.JspException;
23 import javax.servlet.jsp.JspWriter;
24 import javax.servlet.jsp.PageContext;
25 import javax.servlet.jsp.tagext.SimpleTagSupport;
26
27 /**
28 * Abstract base class for all JSON tags
29 *
30 * @author James Wiltshire
31 * @version $Id$
32 */
33
34 public abstract class JsonBaseTag extends SimpleTagSupport
35 {
36 private static final String JSON_OBJECT_STACK_KEY = "atg.taglib.json.objectStack";
37 private static final String PRETTY_PRINT_KEY = "atg.taglib.json.prettyPrint";
38 private static final String ESCAPE_XML_KEY="atg.taglib.json.escapeXml";
39 private static final String CURRENT_ESCAPE_XML_VALUE_KEY="atg.taglib.json.escapeXml.currentValue";
40
41 private static final int JSON_OBJECT_STACK_SCOPE = PageContext.REQUEST_SCOPE;
42 protected static final int PRETTY_PRINT_INDENT=2;
43
44 private static final boolean ESCAPE_XML_DEFAULT=true;
45 private static final boolean TRIM_DEFAULT = true;
46 private static final boolean PRETTY_PRINT_DEFAULT=false;
47
48 protected String mName;
49
50 /**
51 * Gets the Name
52 * @return the Name
53 */
54 public String getName()
55 {
56 return mName;
57 }
58
59 /**
60 * Sets the Name
61 *
62 * @param pName The Name to set
63 */
64 public void setName(String pName)
65 {
66 mName = pName;
67 }
68
69 protected boolean mTrim = TRIM_DEFAULT;
70
71 /**
72 * Gets the Trim
73 * @return the Trim
74 */
75 public boolean getTrim()
76 {
77 return mTrim;
78 }
79
80 /**
81 * Sets the Trim
82 *
83 * @param pTrim The Trim to set
84 */
85 public void setTrim(boolean pTrim)
86 {
87 mTrim = pTrim;
88 }
89
90 protected boolean mNewStackCreated=false;
91
92 /**
93 * Sets the PrettyPrint flag. If set, then any JSON output should be nicely formatted
94 *
95 * @param pPrettyPrint The PrettyPrint to set
96 */
97 public void setPrettyPrint(boolean pPrettyPrint)
98 {
99 getJspContext().setAttribute(PRETTY_PRINT_KEY, Boolean.valueOf(pPrettyPrint));
100 }
101
102 /**
103 * Get the number of spaces that should be used to indent the pretty-printed output,
104 * or 0 if no pretty-printing should be used
105 *
106 * @return The number of spaces to indent when pretty printing
107 */
108 public int getPrettyPrintIndentFactor()
109 {
110 boolean prettyPrint=getBooleanDefaultValue(PRETTY_PRINT_KEY, Boolean.valueOf(PRETTY_PRINT_DEFAULT));
111 return (prettyPrint?PRETTY_PRINT_INDENT:0);
112 }
113
114 protected Boolean mEscapeXmlOriginalValue=null;
115 protected boolean mEscapeXmlValueSet=false;
116
117 /**
118 * Gets the value of the EscapeXml flag. This can be set on any tag by setting the
119 * <code>escapeXml</code> attribute to <code>true</code>, or it can be set by setting
120 * a page attribute <code>atg.taglib.json.escapeXml</code>. When set on a tag it will
121 * recursively apply to all sub-tags, but may be overriden by any sub-tag.
122 * <br/>
123 * Defaults to <code>true</code>
124 *
125 * @return the EscapeXml flag value
126 */
127 public boolean getEscapeXml()
128 {
129 Boolean escapeXml=(Boolean)getJspContext().getAttribute
130 (CURRENT_ESCAPE_XML_VALUE_KEY,PageContext.REQUEST_SCOPE);
131 if (escapeXml==null){
132 escapeXml = getEscapeXmlDefault();
133 }
134 return escapeXml.booleanValue();
135 }
136
137 /**
138 * Sets the EscapeXml flag. If set, then any output will be xml-escaped
139 *
140 * @param pEscapeXml The EscapeXml to set
141 */
142 public void setEscapeXml(boolean pEscapeXml)
143 {
144 mEscapeXmlValueSet=true;
145 mEscapeXmlOriginalValue=(Boolean)getJspContext().getAttribute
146 (CURRENT_ESCAPE_XML_VALUE_KEY,PageContext.REQUEST_SCOPE);
147 getJspContext().setAttribute
148 (CURRENT_ESCAPE_XML_VALUE_KEY, Boolean.valueOf(pEscapeXml),PageContext.REQUEST_SCOPE);
149 }
150
151 /**
152 * Get the default value of the escapeXml property
153 *
154 * @return the default escapeXml value
155 */
156 public Boolean getEscapeXmlDefault()
157 {
158 return Boolean.valueOf(getBooleanDefaultValue(ESCAPE_XML_KEY, Boolean.valueOf(ESCAPE_XML_DEFAULT)));
159 }
160
161 /**
162 * Replace the escapeXml value with the original value if this tag modified it
163 *
164 */
165 protected void resetEscapeXmlValue()
166 {
167 if (mEscapeXmlValueSet){
168 getJspContext().setAttribute
169 (CURRENT_ESCAPE_XML_VALUE_KEY, mEscapeXmlOriginalValue, PageContext.REQUEST_SCOPE);
170 }
171 }
172
173
174 /**
175 * Get the JSON object stack. This is stored as an attribute in the
176 * page context. Request scope is used so that jsp:include can be used
177 * to nest json: tags in included pages.
178 * <p>
179 * If an entity stack does not exists, then this method will create a new one
180 *
181 *
182 * @return The JSON Entity Stack
183 * @exception JspException If there is a problem creating a new entity stack
184 */
185 protected Stack getEntityStack() throws JspException{
186 Stack stack=(Stack)getJspContext().getAttribute(JSON_OBJECT_STACK_KEY, JSON_OBJECT_STACK_SCOPE);
187 if (stack==null){
188 stack = createEntityStack();
189 mNewStackCreated=true;
190 }
191 return stack;
192 }
193
194 /**
195 * Create a new entity stack and set it in pageContext
196 *
197 * @return The newly created JSON entity statck
198 */
199 protected Stack createEntityStack() throws JspException{
200 Stack stack = new Stack();
201 getJspContext().setAttribute(JSON_OBJECT_STACK_KEY, stack, JSON_OBJECT_STACK_SCOPE);
202 return stack;
203 }
204
205 /**
206 * Remove the entity stack object from page context
207 *
208 */
209 protected void removeEntityStack(){
210 getJspContext().removeAttribute(JSON_OBJECT_STACK_KEY, JSON_OBJECT_STACK_SCOPE);
211 }
212
213 /**
214 * Get the topmost JSONObject or JSONArray from the stack
215 *
216 * @return the topmost <code>JsonEntity</code> on the stack
217 * @throws JspException if unable to get the object stack
218 */
219 protected JsonEntity getCurrentEntity() throws JspException{
220 return (JsonEntity)getEntityStack().peek();
221 }
222
223 /**
224 * Does an entity stack exist?
225 *
226 * @return <code>true</code> if an entity stack is found in pageContext, <code>false</code> otherwise
227 */
228 protected boolean entityStackExists()
229 {
230 return (getJspContext().getAttribute(JSON_OBJECT_STACK_KEY, JSON_OBJECT_STACK_SCOPE)!=null);
231 }
232
233 /**
234 * Is this tag the root <code>json:</code> tag?
235 *
236 * @return <code>true</code> if this tag instance is the root tag, <code>false</code> otherwise
237 */
238 protected boolean isRootTag()
239 {
240
241 return mNewStackCreated;
242 }
243
244 /**
245 * Get the default value for a parameter. This will search each of the following locations in order,
246 * returning the first value that it finds.
247 * <ol>
248 * <li>page/request/session/application attributes. Uses findAttribute to try and find the attribute</li>
249 * <li>Servlet Context initParams - as set in a web.xml context-param</li>
250 * <li>The Default Constant value passed in to this method</li>
251 * </ol>
252 *
253 * @param pParamKey The key of the param.
254 * @return
255 */
256 protected Object getDefaultValue(String pParamKey, Object pDefaultConstant)
257 {
258 Object value;
259
260
261 value=getJspContext().findAttribute(pParamKey);
262 if (value!=null){
263 return value;
264 }
265
266
267 ServletContext servletContext=((PageContext)getJspContext()).getServletContext();
268 value=servletContext.getInitParameter(pParamKey);
269 if (value!=null){
270 return value;
271 }
272
273
274 return pDefaultConstant;
275 }
276
277 /**
278 * Get the default value of a parameters as a boolean
279 *
280 * @param pParamKey
281 * @param pDefaultConstant
282 * @return
283 */
284 private boolean getBooleanDefaultValue(String pParamKey, Object pDefaultConstant)
285 {
286 Object defaultValue=getDefaultValue(pParamKey, pDefaultConstant);
287 if (defaultValue instanceof String){
288 return Boolean.valueOf((String)defaultValue).booleanValue();
289 }
290 else if (defaultValue instanceof Boolean){
291 return ((Boolean)defaultValue).booleanValue();
292 }
293 else{
294 return false;
295 }
296 }
297
298 /**
299 * Trim and EscapeXml chars in the value depending on whether the <code>trim</code> and
300 * <code>escapeXml</code> attributes have been set
301 *
302 * @param pValue The value to trim and xml-escape
303 * @return The processed value
304 */
305 protected Object trimAndEscapeValue(Object pValue)
306 {
307
308 if (pValue instanceof String == false){
309 return pValue;
310 }
311
312 String result=(String)pValue;
313
314
315
316 if (getTrim()){
317 result=result.trim();
318 }
319
320
321 if (getEscapeXml()){
322 result=result
323 .replaceAll("&", "&")
324 .replaceAll("<", "<")
325 .replaceAll(">", ">")
326 .replaceAll("'", "'")
327 .replaceAll("\"", """);
328 }
329
330 return result;
331 }
332
333 /**
334 * Process the end of a tag. Check to see if this is the top level tag that created the
335 * entity stack, and if so, render the JSON data to the output stream.
336 *
337 * @param pNewEntity The entity that has just been created by this tag
338 * @throws JspException
339 * @throws JSONException
340 *
341 */
342 protected void processTagEnd(JsonEntity pNewEntity) throws JspException, JSONException
343 {
344 if (isRootTag()){
345 try{
346 JspWriter out = getJspContext().getOut();
347
348
349 String jsonText=(getPrettyPrintIndentFactor()>0)?
350 pNewEntity.toString(getPrettyPrintIndentFactor()) : pNewEntity.toString();
351 out.write(jsonText);
352 }
353 catch (Exception e){
354 throw new JspException(Messages.getString("atg.taglib.json.error.base.0"),e);
355 }
356 finally{
357
358 removeEntityStack();
359 }
360 }
361 else{
362
363
364 getCurrentEntity().add(pNewEntity.getWrappedObject(), getName());
365 }
366
367 resetEscapeXmlValue();
368 }
369 }