001    /*******************************************************************************
002     * Copyright (C) PicoContainer Organization. All rights reserved. 
003     * ---------------------------------------------------------------------------
004     * The software in this package is published under the terms of the BSD style
005     * license a copy of which has been included with this distribution in the
006     * LICENSE.txt file.
007     ******************************************************************************/
008    package org.picocontainer.web.jsf;
009    
010    import java.util.Map;
011    
012    import javax.faces.context.FacesContext;
013    import javax.faces.el.EvaluationException;
014    import javax.faces.el.VariableResolver;
015    
016    import org.picocontainer.PicoContainer;
017    import org.picocontainer.MutablePicoContainer;
018    import org.picocontainer.web.PicoServletContainerFilter;
019    
020    /**
021     * This is a variable resolver implementation for Java ServerFaces.
022     * <h2>Installation</h2>
023     * <p>
024     * Add install this variable resolver by adding setting the application's
025     * variable resolver to
026     * <em>org.picocontainer.web.jsf.NanoWarDelegatingVariableResolver</em>. An
027     * example follows:
028     * </p>
029     * <hr/>
030     * 
031     * <pre>
032     *   &lt;faces-config&gt;
033     *      &lt;application&gt;
034     *          &lt;strong&gt;
035     *          &lt;variable-resolver&gt;
036     *              org.picocontainer.web.jsf.NanoWarDelegatingVariableResolver
037     *          &lt;/variable-resolver&gt;
038     *          &lt;/strong&gt;
039     *      &lt;/application&gt;
040     *      ....
041     *   &lt;/faces-config&gt;
042     * </pre>
043     * 
044     * <hr/>
045     * <h2>Usage</h2>
046     * <h4>Part 1 - Write your Constructor Dependency Injection (CDI) - based
047     * backing bean:</h4>
048     * <p>
049     * Even though you are writing a backing bean, you can utilize PicoContainers
050     * CDI capabilities to the fullest. Example:
051     * </p>
052     * 
053     * <pre>
054     *    //Imports and variables...
055     *    
056     *    public ListCheeseController(&lt;strong&gt;CheeseService service&lt;/strong&gt;) {
057     *       this.service = service;       
058     *    }
059     *    
060     *    //The rest of the class.
061     * </pre>
062     * 
063     * <h4>Part 2 - Set up your NanoWAR services.</h4>
064     * <p>
065     * (This assumes you have installed NanoWAR properly. Please see the NanoWAR
066     * documentation for specific instructions)
067     * </p>
068     * <p>
069     * You need to name your services with the name you will be giving your
070     * <tt>Backing Bean</tt>. Example:
071     * 
072     * <pre>
073     *    pico = builder.container(parent: parent) {
074     *        if(assemblyScope instanceof javax.servlet.ServletContext) {
075     *          // Application Services would go here.
076     *        } else if (assemblyScope instanceof javax.servlet.ServletRequest) {
077     *            &lt;strong&gt;addComponent(key: 'cheeseBean', class: 'org.picocontainer.web.samples.jsf.ListCheeseController')&lt;/strong&gt;
078     *        }
079     *    }
080     * </pre>
081     * 
082     * <h4>Part 3 - Set up your managed beans for JSF</h4>
083     * <p>
084     * Set the managed bean names in your <tt>faces-config</tt> to equal the names
085     * given to the backing beans in the nanowar composition script. Example:
086     * </p>
087     * 
088     * <pre>
089     *    &lt;managed-bean&gt;
090     *        &lt;description&gt;CDI Injected Bean&lt;/description&gt;
091     *        &lt;strong&gt;&lt;managed-bean-name&gt;cheeseBean&lt;/managed-bean-name&gt;&lt;/strong&gt;
092     *        &lt;managed-bean-class&gt;
093     *            org.picocontainer.web.samples.jsf.CheeseController
094     *        &lt;/managed-bean-class&gt;
095     *        &lt;managed-bean-scope&gt;request&lt;/managed-bean-scope&gt;
096     *    &lt;/managed-bean&gt;
097     * </pre>
098     * 
099     * <p>
100     * Notice how the same names were used in the <tt>faces-config</tt> as in the
101     * nanowar configuration. When the JSF page asks for the bean named
102     * 'addCheeseBean', the Nano variable resolver will take that name and check
103     * nanocontainer for an object of that instance. If it finds one, it will send
104     * it back to the page.
105     * </p>
106     * <em>Note:</em>
107     * <p>
108     * This class currently has only been tested using MyFaces. There are reports
109     * that this technique doesn't work on all reference implementation versions. We
110     * welcome success or failure feedback!
111     * </p>
112     * 
113     * @author Michael Rimov
114     */
115    public class PicoVariableResolver extends VariableResolver {
116    
117        public static class ServletFilter extends PicoServletContainerFilter {
118            private static ThreadLocal<MutablePicoContainer> currentRequestContainer = new ThreadLocal<MutablePicoContainer>();
119            private static ThreadLocal<MutablePicoContainer> currentSessionContainer = new ThreadLocal<MutablePicoContainer>();
120            private static ThreadLocal<MutablePicoContainer> currentAppContainer = new ThreadLocal<MutablePicoContainer>();
121    
122            protected void setAppContainer(MutablePicoContainer container) {
123                currentAppContainer.set(container);
124            }
125            protected void setRequestContainer(MutablePicoContainer container) {
126                currentRequestContainer.set(container);
127            }
128            protected void setSessionContainer(MutablePicoContainer container) {
129                currentSessionContainer.set(container);
130            }
131    
132            protected static MutablePicoContainer getRequestContainerForThread() {
133                return currentRequestContainer.get();
134            }
135            protected static MutablePicoContainer getSessionContainerForThread() {
136                return currentSessionContainer.get();
137            }
138            protected static MutablePicoContainer getApplicationContainerForThread() {
139                return currentAppContainer.get();
140            }
141    
142        }
143        /**
144         * The nested variable resolver.
145         */
146        private VariableResolver nested;
147    
148        /**
149         * Decorated Variable resolver.
150         * 
151         * @param decorated
152         */
153        public PicoVariableResolver(VariableResolver decorated) {
154            super();
155            if (decorated == null) {
156                throw new NullPointerException("decorated");
157            }
158            nested = decorated;
159        }
160    
161        /**
162         * Retrieve the delegated value.
163         * 
164         * @return the wrapped variable resolver.
165         */
166        protected VariableResolver getNested() {
167            return nested;
168        }
169    
170        /**
171         * {@inheritDoc}
172         * 
173         * @param facesContext
174         * @param name
175         * @return the resulting object, either resolved through NanoWAR, or passed
176         *         onto the delegate resolver.
177         * @throws EvaluationException
178         * @see javax.faces.el.VariableResolver#resolveVariable(javax.faces.context.FacesContext,
179         *      java.lang.String)
180         */
181        public Object resolveVariable(FacesContext facesContext, String name) {
182    
183            PicoContainer nano = getPicoContainer(facesContext);
184    
185            Object result = nano.getComponent(name);
186            if (result == null) {
187                return nested.resolveVariable(facesContext, name);
188            }
189    
190            return result;
191        }
192    
193        /**
194         * Tries to locate the nanocontainer first at request level, and then if it
195         * doesn't find it there. (Filter might not be installed), it tries
196         * Application level. If that fails it throws an exception since you
197         * wouldn't expect the NanoWarDelegatingVariableResolver
198         * 
199         * @param facesContext
200         * @return NanoContainer instance.
201         * @throws EvaluationException if it cannot find a NanoWAR instance.
202         */
203        protected PicoContainer getPicoContainer(FacesContext facesContext) {
204            Map requestAttributeMap = facesContext.getExternalContext().getRequestMap();
205    
206            PicoContainer container = null;
207    
208            // First check request map.
209            if (requestAttributeMap != null) {
210                container = ServletFilter.getRequestContainerForThread();
211            }
212    
213            if (requestAttributeMap == null || container == null) {
214    
215                // If that fails, check session for container.
216                Map sessionMap = facesContext.getExternalContext().getSessionMap();
217                if (sessionMap != null) {
218                    // If there is a session.
219                    container = ServletFilter.getSessionContainerForThread();
220                }
221    
222                if (sessionMap == null || container == null) {
223    
224                    // If that fails, check for App level container.
225                    container = ServletFilter.getApplicationContainerForThread();
226                    if (container == null) {
227                        // If that fails... Fail.
228                        throw new EvaluationException(
229                                "The NanoWar delegating variable resolver is installed, however no NanoWar "
230                                        + "container was found in the request or application scope.");
231                    }
232                }
233            }
234    
235            return container;
236        }
237    
238    }