2009/05/20 - Apache Shale has been retired.
For more information, please explore the Attic.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.shale.clay.utils;
22
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.util.Collections;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.TreeMap;
30
31 import javax.faces.el.ValueBinding;
32
33 import org.apache.shale.clay.component.Clay;
34 import org.apache.shale.clay.config.ClayConfigureListener;
35 import org.apache.shale.clay.config.Globals;
36 import org.apache.shale.clay.config.beans.AttributeBean;
37 import org.apache.shale.clay.config.beans.ComponentBean;
38 import org.apache.shale.clay.config.beans.ConfigBean;
39 import org.apache.shale.clay.config.beans.ConfigBeanFactory;
40 import org.apache.shale.clay.config.beans.ElementBean;
41 import org.apache.shale.clay.config.beans.SymbolBean;
42 import org.apache.shale.util.Messages;
43 import org.apache.shale.util.Tags;
44
45 /***
46 * <p>
47 * This class is a mix of runtime utilities for the
48 * {@link org.apache.shale.clay.component.Clay} component. It is loaded as a
49 * managed bean in application scope by the clay component's registration.
50 * </p>
51 */
52 public class ClayAmalgam {
53
54 /***
55 * <p>
56 * Message resources for this class.
57 * </p>
58 */
59 private static Messages messages = new Messages(
60 "org.apache.shale.clay.Bundle", ClayConfigureListener.class
61 .getClassLoader());
62
63 /***
64 * <p>
65 * Shale tag helper class that contains utility methods for setting
66 * component binding and method properties.
67 * </p>
68 */
69 private Tags tagUtils = new Tags();
70
71 /***
72 * <p>
73 * Replaces tokens in the <code>document</code> with matching tokens in
74 * the <code>context</code>.
75 * </p>
76 *
77 * @param document
78 * containing tokens to replace
79 * @param context
80 * tokens
81 */
82 protected void replace(StringBuffer document, Map context) {
83
84 Iterator di = context.entrySet().iterator();
85 while (di.hasNext()) {
86 Map.Entry e = (Map.Entry) di.next();
87
88 String name = (String) e.getKey();
89 String value = (String) e.getValue();
90
91 for (int i = 0; i <= (document.length() - name.length()); i++) {
92 String token = document.substring(i, i + name.length());
93 if (token.compareToIgnoreCase(name) == 0) {
94 document.delete(i, i + name.length());
95 document.insert(i, value);
96 }
97
98 token = null;
99 }
100
101 e = null;
102 name = null;
103 value = null;
104 }
105 di = null;
106
107 }
108
109 /***
110 * <p>
111 * Mapping used to encode special characters.
112 * </p>
113 */
114 private static TreeMap encodeMap = null;
115 static {
116 encodeMap = new TreeMap();
117 encodeMap.put("'", "'");
118 encodeMap.put("&", "&");
119 encodeMap.put("<", "<");
120 encodeMap.put(">", ">");
121 encodeMap.put("}", "}");
122 encodeMap.put("{", "{");
123 }
124
125 /***
126 * <p>
127 * Encodes a string value using the <code>encodeMap</code>.
128 * </p>
129 *
130 * @param value
131 * source string
132 * @return target encode string
133 */
134 public String encode(String value) {
135 StringBuffer buff = new StringBuffer(value);
136 replace(buff, encodeMap);
137 return buff.toString();
138 }
139
140 /***
141 * <p>
142 * Mapping used to decode special characters.
143 * </p>
144 */
145 private static TreeMap decodeMap = null;
146 static {
147 decodeMap = new TreeMap();
148 decodeMap.put(""", "\"");
149 decodeMap.put("'", "'");
150 decodeMap.put("&", "&");
151 decodeMap.put("<", "<");
152 decodeMap.put(">", ">");
153 decodeMap.put("}", "}");
154 decodeMap.put("{", "{");
155 }
156
157 /***
158 * <p>
159 * Decodes a string value using the <code>decodeMap</code>.
160 * </p>
161 *
162 * @param value
163 * source string
164 * @return decoded value
165 */
166 public String decode(String value) {
167 StringBuffer buff = new StringBuffer(value);
168 replace(buff, decodeMap);
169 return buff.toString();
170 }
171
172 /***
173 * <p>
174 * This is a method binding "validator" signature that can be bound to the
175 * <code>shapeValidator</code> attribute of the
176 * {@link org.apache.shale.clay.component.Clay} component. It expects that
177 * the <code>value</code> attribute will contain an html string that
178 * represents an HTML node. The value will be encode or decode depending on
179 * the value of the <code>escapeXml</code> optional attribute. The default
180 * is "false".
181 * </p>
182 *
183 * @param context
184 * faces context
185 * @param component
186 * clay
187 * @param displayElementRoot
188 * config bean
189 */
190 public void clayOut(javax.faces.context.FacesContext context,
191 javax.faces.component.UIComponent component,
192 java.lang.Object displayElementRoot) {
193
194 if (!(displayElementRoot instanceof ComponentBean)
195 || !(component instanceof Clay)) {
196 throw new RuntimeException(messages.getMessage("invalid.binding",
197 new Object[] { "clayOut" }));
198 }
199
200 ComponentBean text = (ComponentBean) displayElementRoot;
201 Clay clay = (Clay) component;
202 String value = (String) clay.getAttributes().get("value");
203 value = tagUtils.evalString(value);
204 if (value == null) {
205 throw new IllegalArgumentException(messages.getMessage(
206 "missing.attribute", new Object[] { "value", "clayOut" }));
207 }
208
209 boolean escapeXml = false;
210 String tmp = (String) clay.getAttributes().get("escapeXml");
211 if (tmp != null) {
212 escapeXml = tagUtils.evalBoolean(tmp).booleanValue();
213 }
214
215 if (!escapeXml) {
216 value = decode(value);
217 } else {
218 value = encode(value);
219 }
220
221 text.setJsfid("outputText");
222 text.setComponentType("javax.faces.HtmlOutputText");
223
224
225 AttributeBean attr = new AttributeBean();
226 attr.setName("value");
227 attr.setValue(value);
228 text.addAttribute(attr);
229
230
231 attr = new AttributeBean();
232 attr.setName("escape");
233 attr.setValue(Boolean.FALSE.toString());
234 text.addAttribute(attr);
235
236
237 attr = new AttributeBean();
238 attr.setName("isTransient");
239 attr.setValue(Boolean.TRUE.toString());
240 text.addAttribute(attr);
241
242 }
243
244 /***
245 * <p>
246 * This is a method binding "validator" signature that can be bound to the
247 * <code>shapeValidator</code> attribute of the
248 * {@link org.apache.shale.clay.component.Clay} component. It expects that
249 * the <code>url</code> attribute will contain the file to import relative
250 * to the web context root. The content of the file will be encode or decode
251 * depending on the value of the <code>escapeXml</code> optional
252 * attribute. The default doesn't apply any encoding.
253 * </p>
254 *
255 * @param context
256 * faces context
257 * @param component
258 * clay
259 * @param displayElementRoot
260 * config bean
261 */
262 public void clayImport(javax.faces.context.FacesContext context,
263 javax.faces.component.UIComponent component,
264 java.lang.Object displayElementRoot) {
265
266 if (!(displayElementRoot instanceof ComponentBean)
267 || !(component instanceof Clay)) {
268 throw new RuntimeException(messages.getMessage("invalid.binding",
269 new Object[] { "clayImport" }));
270 }
271
272 ComponentBean text = (ComponentBean) displayElementRoot;
273 Clay clay = (Clay) component;
274 String url = (String) clay.getAttributes().get("url");
275 if (url == null) {
276 throw new IllegalArgumentException(messages.getMessage(
277 "missing.attribute", new Object[] { "url", "clayImport" }));
278 }
279 url = tagUtils.evalString(url);
280
281 boolean escapeXml = true;
282 String escAttribute = (String) clay.getAttributes().get("escapeXml");
283 if (escAttribute != null) {
284 escapeXml = tagUtils.evalBoolean(escAttribute).booleanValue();
285 }
286
287 StringBuffer value = new StringBuffer();
288 StringBuffer buff = new StringBuffer(url);
289
290
291 int i = buff.indexOf(Globals.CLASSPATH_PREFIX);
292 if (i > -1) {
293 buff.delete(0, i + Globals.CLASSPATH_PREFIX.length());
294 }
295
296 InputStream in = null;
297
298 try {
299
300 if (i > -1) {
301
302 ClassLoader classloader = Thread.currentThread()
303 .getContextClassLoader();
304 if (classloader == null) {
305 classloader = this.getClass().getClassLoader();
306 }
307
308 in = classloader.getResourceAsStream(buff.toString());
309
310 } else {
311
312 in = context.getExternalContext().getResourceAsStream(
313 buff.toString());
314 }
315
316 if (in != null) {
317 int c = 0;
318 done: while (true) {
319 c = in.read();
320 if (c > -1) {
321 value.append((char) c);
322 } else {
323 break done;
324 }
325
326 }
327 }
328 } catch (IOException e) {
329 throw new RuntimeException(messages.getMessage("invalid.attribute",
330 new Object[] { "url", "clayImport" }));
331 } finally {
332 if (in != null) {
333 try {
334 in.close();
335 } catch (IOException e) {
336 in = null;
337 }
338 }
339
340 }
341
342 if (escAttribute != null) {
343 if (!escapeXml) {
344 replace(value, decodeMap);
345 } else {
346 replace(value, encodeMap);
347 }
348 }
349
350 text.setJsfid("outputText");
351 text.setComponentType("javax.faces.HtmlOutputText");
352
353
354 AttributeBean attr = new AttributeBean();
355 attr.setName("value");
356 attr.setValue(value.toString());
357 text.addAttribute(attr);
358
359
360 attr = new AttributeBean();
361 attr.setName("escape");
362 attr.setValue(Boolean.FALSE.toString());
363 text.addAttribute(attr);
364
365
366 attr = new AttributeBean();
367 attr.setName("isTransient");
368 attr.setValue(Boolean.TRUE.toString());
369 text.addAttribute(attr);
370 }
371
372 /***
373 * <p>
374 * This is a method binding "validator" signature that can be bound to the
375 * <code>shapeValidator</code> attribute of the
376 * {@link org.apache.shale.clay.component.Clay} component. It expects four
377 * attributes value, bodyJsfid, var and scope. The <code>value</code>
378 * attribute is like a dataTable. It should be a value binding expression
379 * that is a Map, List or Object[]. The <code>bodyJsfid</code> attribute
380 * is root of the subtree that will be repeated in the for loop. The
381 * <code>var</code> attribute is the tag used to cache a Map of the bound
382 * objects. It will always be loaed in "session" scope. Limitation exists
383 * because the "shapeValidator" event is only called when the component is
384 * created. This means that you must take care in removing the
385 * <code>var</code> object from session scope.
386 * </p>
387 *
388 * @param context
389 * faces
390 * @param component
391 * clay
392 * @param displayElementRoot
393 * config bean
394 */
395 public void clayForEach(javax.faces.context.FacesContext context,
396 javax.faces.component.UIComponent component,
397 java.lang.Object displayElementRoot) {
398
399 if (!(displayElementRoot instanceof ComponentBean)
400 || !(component instanceof Clay)) {
401 throw new RuntimeException(messages.getMessage("invalid.binding",
402 new Object[] { "clayForEach" }));
403 }
404
405 Clay clay = (Clay) component;
406 String value = (String) clay.getAttributes().get("value");
407 if (value == null) {
408 throw new IllegalArgumentException(messages.getMessage(
409 "missing.attribute",
410 new Object[] { "value", "clayForEach" }));
411 }
412
413 String bodyJsfid = (String) clay.getAttributes().get("bodyJsfid");
414 bodyJsfid = tagUtils.evalString(bodyJsfid);
415 if (bodyJsfid == null) {
416 throw new IllegalArgumentException(messages.getMessage(
417 "missing.attribute", new Object[] { "bodyJsfid",
418 "clayForEach" }));
419 }
420
421 String var = (String) clay.getAttributes().get("var");
422 var = tagUtils.evalString(var);
423 if (var == null) {
424 throw new IllegalArgumentException(messages.getMessage(
425 "missing.attribute", new Object[] { "var", "clayForEach" }));
426 }
427
428
429 ConfigBean config = ConfigBeanFactory.findConfig(bodyJsfid);
430 if (config == null) {
431 throw new NullPointerException(messages
432 .getMessage("clay.config.notloaded"));
433 }
434
435
436 ComponentBean b = config.getElement(bodyJsfid);
437 if (b == null) {
438 throw new NullPointerException(messages.getMessage(
439 "clay.jsfid.notfound", new Object[] { bodyJsfid }));
440 }
441
442 ValueBinding vb = context.getApplication().createValueBinding(value);
443 final Object valueList = vb.getValue(context);
444
445 Map beans = new TreeMap();
446 int i = 0;
447 Iterator vi = null;
448 if (valueList == null) {
449 vi = Collections.EMPTY_LIST.iterator();
450 } else if (valueList instanceof List) {
451 vi = ((List) valueList).iterator();
452 } else if (valueList instanceof Map) {
453 vi = ((Map) valueList).entrySet().iterator();
454 } else {
455 Object[] anArray = new Object[0];
456 if (anArray.getClass().isAssignableFrom(valueList.getClass())) {
457 vi = new Iterator() {
458 private int index = 0;
459
460 private final Object[] list = (Object[]) valueList;
461
462 public boolean hasNext() {
463 return (index < list.length);
464 }
465
466 public Object next() {
467 return list[index++];
468 }
469
470 public void remove() {
471 };
472
473 };
474 } else {
475 throw new IllegalArgumentException(messages.getMessage(
476 "invalid.collectiontype", new Object[] { value }));
477 }
478 }
479
480 if (vi != null) {
481 while (vi.hasNext()) {
482
483 StringBuffer id = new StringBuffer("bean" + ++i);
484
485
486 beans.put(id.toString(), vi.next());
487
488
489 ElementBean namingContainer = new ElementBean();
490 namingContainer.setRenderId(i);
491 namingContainer.setJsfid("namingContainer");
492 namingContainer.setComponentType("javax.faces.NamingContainer");
493
494
495 ElementBean target = new ElementBean();
496 target.setJsfid(bodyJsfid);
497 target.setExtends(bodyJsfid);
498 target.setRenderId(i);
499 config.assignParent(target);
500 config.realizingInheritance(target);
501
502
503 id.insert(0, var + ".");
504 SymbolBean symbol = new SymbolBean();
505 symbol.setName(Globals.MANAGED_BEAN_MNEMONIC);
506 symbol.setValue(id.toString());
507 target.addSymbol(symbol);
508
509 namingContainer.addChild(target);
510 ((ComponentBean) displayElementRoot).addChild(namingContainer);
511 }
512
513 }
514
515 context.getExternalContext().getSessionMap().put(var, beans);
516
517 }
518 }