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: Clay.java 516834 2007-03-11 01:33:36Z gvanmatre $
20   */
21  package org.apache.shale.clay.component;
22  
23  import java.io.IOException;
24  import java.net.URL;
25  import java.util.Iterator;
26  import java.util.Map;
27  import java.util.TreeMap;
28  import java.util.TreeSet;
29  
30  import javax.faces.component.UIComponent;
31  import javax.faces.component.UIComponentBase;
32  import javax.faces.context.FacesContext;
33  import javax.faces.el.MethodBinding;
34  
35  import org.apache.commons.chain.Catalog;
36  import org.apache.commons.chain.CatalogFactory;
37  import org.apache.commons.chain.Command;
38  import org.apache.commons.chain.config.ConfigParser;
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  import org.apache.shale.clay.component.chain.AbstractCommand;
42  import org.apache.shale.clay.component.chain.ClayContext;
43  import org.apache.shale.clay.config.Globals;
44  import org.apache.shale.clay.config.beans.AttributeBean;
45  import org.apache.shale.clay.config.beans.ComponentBean;
46  import org.apache.shale.clay.config.beans.ConfigBean;
47  import org.apache.shale.clay.config.beans.ConfigBeanFactory;
48  import org.apache.shale.clay.config.beans.SymbolBean;
49  import org.apache.shale.util.Messages;
50  
51  /***
52   * This component grafts a subview onto the current JSF view.
53   *
54   */
55  public class Clay extends UIComponentBase {
56  
57      /***
58       * <p>
59       * Commons logger utility class instance.
60       * </p>
61       */
62      private static Log log;
63      static {
64          log = LogFactory.getLog(Clay.class);
65      }
66  
67      /***
68       * <p>
69       * Message resources for this class.
70       * </p>
71       */
72      private static Messages messages = new Messages(
73              "org.apache.shale.clay.Bundle", Clay.class.getClassLoader());
74  
75  
76      /***
77       * <p>Holds the symbol table for the component.</p>
78       */
79      private Map symbols = new TreeMap();
80  
81      /***
82       * <p>Returns the symbol table for the component.</p>
83       *
84       * @return Map of {@link org.apache.shale.clay.config.beans.SymbolBean}
85       */
86      public Map getSymbols() {
87          return symbols;
88      }
89  
90      /***
91       * <p>
92       * Loads the chains config using the resource defined by config file
93       * <code>Globals.CLAY_RESOURCE_NAME</code>. These chain commands are used
94       * to build the clay component subtree from a root {@link ComponentBean}
95       * obtained for a {@link ConfigBean} instance from the
96       * {@link ConfigBeanFactory}.
97       * </p>
98       *
99       * @return {@link Catalog} instance of a catalog defined by catalog
100      *         <code>Globals.CLAY_CATALOG_NAME</code>
101      * @exception Exception loading catalog
102      */
103     protected Catalog getCatalog() throws Exception {
104 
105         // Look up the "shale" catalog, creating one if necessary
106         Catalog catalog = CatalogFactory.getInstance().getCatalog(
107                 Globals.CLAY_CATALOG_NAME);
108         if (catalog == null) {
109 
110             ConfigParser parser = new ConfigParser();
111             URL url = this.getClass().getClassLoader().getResource(
112                     Globals.CLAY_RESOURCE_NAME);
113             if (url == null) {
114                 throw new IllegalArgumentException(Globals.CLAY_RESOURCE_NAME);
115             }
116             parser.parse(url);
117 
118             catalog = CatalogFactory.getInstance().getCatalog(
119                     Globals.CLAY_CATALOG_NAME);
120 
121         }
122 
123         return catalog;
124 
125     }
126 
127     /***
128      * <p>
129      * Unique identifier that is used to define a simple or complex component.
130      * The suffix of this identifier will be used to determine if the component
131      * metadata will be loaded from a HTML style of document or a XML defining
132      * document
133      * </p>
134      */
135     private String jsfid = null;
136 
137 
138     /***
139      * <p>
140      * A <code>validator</code> style of method event that is fired when
141      * building the sub component tree using the <code>jsfid</code> as the key
142      * defining the component metadata.
143      * </p>
144      */
145     private String shapeValidator = null;
146 
147     /***
148      * <p>
149      * The root of the metadata used to build the component subtree.
150      * </p>
151      */
152     private ComponentBean displayElementRoot = null;
153 
154     /***
155      * <p>
156      * Returns the unique identifier used to build the component subtree.
157      * </p>
158      *
159      * @return jsfid
160      */
161     public String getJsfid() {
162         return jsfid;
163     }
164 
165     /***
166      * <p>
167      * Sets the unique identifier used to build the component subtree.
168      * </p>
169      *
170      * @param jsfid root identifier of the sub tree
171      */
172     public void setJsfid(String jsfid) {
173         this.jsfid = jsfid;
174     }
175 
176 
177     /***
178      * <p>
179      * Sets the unique identifier used to build the component subtree.
180      * This property is not accessible from the JSP tag.
181      * </p>
182      *
183      * @param jsfid alias to the jsfid property
184      */
185     public void setClayJsfid(String jsfid) {
186         this.jsfid = jsfid;
187     }
188 
189     /***
190      * <p>
191      * Returns the unique identifier used to build the component subtree.
192      * This property is not accessible from the JSP tag.
193      * </p>
194      *
195      * @return jsfid alias
196      */
197     public String getClayJsfid() {
198        return jsfid;
199     }
200 
201 
202     /***
203      * <p>
204      * Retuns the <code>validator</code> signature event that is invoked when
205      * the component metadata is retrieved.
206      * </p>
207      *
208      * @return method binding expression
209      */
210     public String getShapeValidator() {
211         return shapeValidator;
212     }
213 
214     /***
215      * <p>
216      * Sets the <code>validator</code> signature event that is invoked when
217      * the component metadata is retrieved.
218      * </p>
219      *
220      * @param loadShapeAction method binding expression
221      */
222     public void setShapeValidator(String loadShapeAction) {
223         this.shapeValidator = loadShapeAction;
224     }
225 
226     /***
227      * <p>
228      * Returns the logical bean name that replaces any occurance of
229      * "@managed-bean-name" within a binding expression.
230      * </p>
231      *
232      * @return managed bean name symbol
233      */
234     public String getManagedBeanName() {
235         SymbolBean symbol = (SymbolBean) symbols.get(Globals.MANAGED_BEAN_MNEMONIC);
236         return ((symbol != null) ? symbol.getValue() : null);
237     }
238 
239     /***
240      * <p>
241      * Sets the logical bean name that replaces any occurrences of
242      * "@managed-bean-name" within a binding expression.
243      * </p>
244      *
245      * @param mbeanMnemonic managed bean symbol
246      */
247     public void setManagedBeanName(String mbeanMnemonic) {
248         SymbolBean symbol = new SymbolBean();
249         symbol.setName(Globals.MANAGED_BEAN_MNEMONIC);
250         symbol.setValue(mbeanMnemonic);
251         symbols.put(symbol.getName(), symbol);
252     }
253 
254     /***
255      * <p>
256      * Returns the root metadata component that is used to add to the component
257      * tree. It locates the {@link ComponentBean} using the <code>jsfid</code>
258      * attribute as the key. A call to the {@link ConfigBeanFactory} locates the
259      * correct {@link ConfigBean} used to find the {@link ComponentBean}. </p>
260      *
261      * @return root config bean used to define the subtree
262      */
263     protected ComponentBean getRootElement() {
264 
265         ConfigBean config = ConfigBeanFactory.findConfig(getJsfid());
266 
267         if (config == null) {
268             throw new NullPointerException(messages
269                     .getMessage("clay.config.notloaded"));
270         }
271 
272         // find the top-level display element associated with the subtree
273         ComponentBean b = config.getElement(getJsfid());
274         if (b == null) {
275             throw new NullPointerException(messages.getMessage(
276                     "clay.jsfid.notfound", new Object[] { getJsfid() }));
277         }
278 
279         return b;
280     }
281 
282     /***
283      * <p>
284      * This is where the clay component loads the root {@link ComponentBean} by
285      * calling the
286      * <code>getRootElement()</code> method or while invoking the <code>shapeValidator</code> callback event.
287      * The subtree is created using the <code>Globals.ADD_COMPONENT_COMMAND_NAME</code> from the
288      * <code>Globals.CLAY_CATALOG_NAME</code> in the <code>Globals.CLAY_RESOURCE_NAME</code>.
289      *
290      * @param context faces context
291      * @exception IOException render error
292      */
293     public void encodeBegin(FacesContext context) throws IOException {
294 
295         if (log.isTraceEnabled()) {
296             log.trace("encodeBegin(FacesContext)");
297         }
298 
299         //I added a more descriptive message to the null pointer exception.
300         //The challenge here is that the symbol replacement happens after the
301         //HTML node has been thrown out.  Look to see if the clayJsfid can be
302         //found in the forming ComponentBean.
303         if (getJsfid() == null) {
304             String attrClayJsfid = "null";
305             ComponentBean displayElement = (ComponentBean) getAttributes().get(Globals.CLAY_RESERVED_ATTRIBUTE);
306             AttributeBean attr = displayElement.getAttribute("clayJsfid");
307             if (displayElement != null && (attr != null)) {
308                 attrClayJsfid = attr.getValue();
309             }
310             throw new NullPointerException(messages.getMessage("clay.jsfid.null",
311                   new Object[] { attrClayJsfid}));
312         }
313 
314         if (getDisplayElementRoot() == null) {
315             if (!getJsfid().equals(Globals.RUNTIME_ELEMENT_ID)) {
316                 displayElementRoot = getRootElement();
317             } else {
318                 displayElementRoot = new ComponentBean();
319                 displayElementRoot.setComponentType("javax.faces.HtmlOutputText");
320                 displayElementRoot.setJsfid(getJsfid());
321 
322             }
323 
324             // this callback will give a hook to override
325             // or alter the root display element by
326             // using the getter and setter for the displayElementRoot
327             if (this.getShapeValidator() != null) {
328 
329                 ClayContext clayContext = new ClayContext();
330 
331                 Map symbolTable = new TreeMap();
332                 symbolTable.putAll(getSymbols());
333                 clayContext.setSymbols(symbolTable);
334                 //evaluate nested symbols
335                 AbstractCommand.realizeSymbols(clayContext);
336 
337                 //resolve the literal "@managed-bean-name"
338                 String expr = AbstractCommand.replaceMnemonic(clayContext, getShapeValidator());
339                 Class[] methodSignature = {
340                         javax.faces.context.FacesContext.class,
341                         javax.faces.component.UIComponent.class,
342                         java.lang.Object.class };
343 
344                 MethodBinding binding = getFacesContext().getApplication()
345                     .createMethodBinding(expr, methodSignature);
346 
347                 if (log.isDebugEnabled()) {
348                     log
349                     .debug(messages
350                             .getMessage("clay.invoke.shapeValidator"));
351                 }
352 
353                 Object[] args = { context, this, displayElementRoot };
354                 binding.invoke(context, args);
355 
356             }
357         }
358 
359         // Recursively build the subtree and fixup references. The root
360         // of the subview will be assigned to the child private property.
361 
362         ClayContext clayContext = new ClayContext();
363         clayContext.setChild(null);
364         clayContext.setDisplayElement(getDisplayElementRoot());
365         clayContext.setJsfid(getJsfid());
366         clayContext.setFacesContext(getFacesContext());
367         clayContext.setJspIds(new TreeSet());
368 
369         Map symbolTable = new TreeMap();
370         symbolTable.putAll(getDisplayElementRoot().getSymbols());
371         symbolTable.putAll(getSymbols());
372         clayContext.setSymbols(symbolTable);
373 
374         //evaluate nested symbols
375         AbstractCommand.realizeSymbols(clayContext);
376 
377         clayContext.setRootElement(getDisplayElementRoot());
378         clayContext.setParent(this);
379 
380         Catalog catalog = null;
381         try {
382             catalog = getCatalog();
383         } catch (Exception e) {
384             log.error(e);
385         }
386         Command command = catalog
387                  .getCommand(Globals.ADD_COMPONENT_COMMAND_NAME);
388 
389         try {
390             command.execute(clayContext);
391         } catch (Exception e) {
392             log.error(e);
393             throw new RuntimeException(e);
394         }
395 
396     }
397 
398     /***
399      * <p>
400      * Recursively invokes the rendering of the sub component tree.
401      * </p>
402      *
403      * @param child faces component
404      * @param context faces context
405      * @exception IOException render error
406      */
407     protected void recursiveRenderChildren(UIComponent child,
408             FacesContext context) throws IOException {
409 
410         if (!child.getRendersChildren() || child == this) {
411             Iterator ci = child.getChildren().iterator();
412             while (ci.hasNext()) {
413                 UIComponent c = (UIComponent) ci.next();
414                 if (c.isRendered()) {
415                     c.encodeBegin(context);
416 
417                     if (!c.getRendersChildren()) {
418                         recursiveRenderChildren(c, context);
419                     } else {
420                         c.encodeChildren(context);
421                     }
422 
423                     c.encodeEnd(context);
424                     c = null;
425                 }
426             }
427         } else {
428             // let the component handle iterating over the children
429             child.encodeChildren(context);
430         }
431     }
432 
433     /***
434      * <p>
435      * Called by JSF, this method delegates to <code>recursiveRenderChildren</code>.
436      * </p>
437      *
438      * @param context faces context
439      * @exception IOException render error
440      */
441     public void encodeChildren(FacesContext context) throws IOException {
442 
443         if (log.isTraceEnabled()) {
444             log.trace("encodeChildren(FacesContext)");
445         }
446 
447         recursiveRenderChildren(this, context);
448 
449     }
450 
451     /***
452      * <p>
453      * Called by JSF, this method simply emits a logging statement
454      * if tracing is enabled.
455      * </p>
456      *
457      * @param context faces context
458      * @exception IOException render error
459      */
460     public void encodeEnd(FacesContext context) throws IOException {
461 
462         if (log.isTraceEnabled()) {
463             log.trace("encodeEnd(FacesContext)");
464         }
465 
466     }
467 
468     /***
469      * <p>
470      * Returns the root {@link ComponentBean} used to build the clay subtree
471      * component.
472      * </p>
473      *
474      * @return root config bean
475      */
476     protected ComponentBean getDisplayElementRoot() {
477         return displayElementRoot;
478     }
479 
480     /***
481      * <p>
482      * Sets the root {@link ComponentBean} used to build the clay subtree
483      * component.
484      * </p>
485      *
486      * @param displayElementRoot root config bean
487      */
488     protected void setDisplayElementRoot(ComponentBean displayElementRoot) {
489         this.displayElementRoot = displayElementRoot;
490     }
491 
492     /***
493      * <p>
494      * Restores a component's state.
495      * </p>
496      *
497      * @param context faces context
498      * @param obj component state
499      */
500     public void restoreState(FacesContext context, Object obj) {
501 
502         final int superState = 0;
503         final int jsfidState = 1;
504         final int shapeValidatorState = 2;
505         final int displayElementState = 3;
506         final int symbolsState = 4;
507 
508         Object[] aobj = (Object[]) obj;
509         super.restoreState(context, aobj[superState]);
510 
511         jsfid = ((String) aobj[jsfidState]);
512         shapeValidator = ((String) aobj[shapeValidatorState]);
513         displayElementRoot = ((ComponentBean) aobj[displayElementState]);
514         symbols = ((Map) aobj[symbolsState]);
515 
516     }
517 
518     /***
519      * <p>
520      * Saves a component's state.
521      * </p>
522      *
523      * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext)
524      * @param context faces context
525      * @return subtree state
526      */
527     public Object saveState(FacesContext context) {
528         final int superState = 0;
529         final int jsfidState = 1;
530         final int shapeValidatorState = 2;
531         final int displayElementState = 3;
532         final int symbolsState = 4;
533         final int size = 5;
534 
535         Object[] aobj = new Object[size];
536         aobj[superState] = super.saveState(context);
537         aobj[jsfidState] = jsfid;
538         aobj[shapeValidatorState] = shapeValidator;
539         aobj[displayElementState] = displayElementRoot;
540         aobj[symbolsState] = symbols;
541 
542         return aobj;
543     }
544 
545     /***
546      * <p>Returns <code>true</code> indicating that this
547      * component renders it's children. That means JSF will
548      * invoke <code>encodeChildren()</code> method.</p>
549      *
550      * @return <code>true</code>
551      */
552     public boolean getRendersChildren() {
553         return true;
554     }
555 
556     /***
557      * @return Returns the component's family.
558      */
559     public String getFamily() {
560         return "org.apache.shale.clay";
561     }
562 
563 
564 }