2009/05/20 - Apache Shale has been retired.

For more information, please explore the Attic.

View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to you under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  /*
19   * $Id: ClayAmalgam.java 464373 2006-10-16 04:21:54Z rahul $
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("'", "&#39;");
118         encodeMap.put("&", "&amp;");
119         encodeMap.put("<", "&lt;");
120         encodeMap.put(">", "&gt;");
121         encodeMap.put("}", "&#125;");
122         encodeMap.put("{", "&#123;");
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("&quot;", "\"");
149         decodeMap.put("&#39;", "'");
150         decodeMap.put("&amp;", "&");
151         decodeMap.put("&lt;", "<");
152         decodeMap.put("&gt;", ">");
153         decodeMap.put("&#125;", "}");
154         decodeMap.put("&#123;", "{");
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         // add a value attribute
225         AttributeBean attr = new AttributeBean();
226         attr.setName("value");
227         attr.setValue(value);
228         text.addAttribute(attr);
229 
230         // add a escape attribute
231         attr = new AttributeBean();
232         attr.setName("escape");
233         attr.setValue(Boolean.FALSE.toString());
234         text.addAttribute(attr);
235 
236         // add a isTransient attribute
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         // look for a classpath prefix.
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             // if classpath prefix found, use the classloader
300             if (i > -1) {
301                 // load form the classpath
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                 // load from the context root
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         // add a value attribute
354         AttributeBean attr = new AttributeBean();
355         attr.setName("value");
356         attr.setValue(value.toString());
357         text.addAttribute(attr);
358 
359         // add a escape attribute
360         attr = new AttributeBean();
361         attr.setName("escape");
362         attr.setValue(Boolean.FALSE.toString());
363         text.addAttribute(attr);
364 
365         // add a isTransient attribute
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         // lookup the ConfigBean that handles the bodyJsfid
429         ConfigBean config = ConfigBeanFactory.findConfig(bodyJsfid);
430         if (config == null) {
431             throw new NullPointerException(messages
432                     .getMessage("clay.config.notloaded"));
433         }
434 
435         // make sure it's parsed and cached
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                 // create a key for the beans map
483                 StringBuffer id = new StringBuffer("bean" + ++i);
484                 // add the subscripted bean to the beans map with the generated
485                 // key
486                 beans.put(id.toString(), vi.next());
487 
488                 // create a naming container to hold the row
489                 ElementBean namingContainer = new ElementBean();
490                 namingContainer.setRenderId(i);
491                 namingContainer.setJsfid("namingContainer");
492                 namingContainer.setComponentType("javax.faces.NamingContainer");
493 
494                 // create a new nested bean
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                 // prepend the var to the generated key
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 }