001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.jxpath.servlet;
018
019import javax.servlet.ServletContext;
020import javax.servlet.ServletRequest;
021import javax.servlet.http.HttpServletRequest;
022import javax.servlet.http.HttpSession;
023import javax.servlet.jsp.PageContext;
024import org.apache.commons.jxpath.JXPathContext;
025import org.apache.commons.jxpath.JXPathContextFactory;
026import org.apache.commons.jxpath.JXPathIntrospector;
027
028/**
029 * Static methods that allocate and cache JXPathContexts bound to
030 * {@link PageContext}, {@link ServletRequest}, {@link HttpSession}
031 * and {@link ServletContext}.
032 * <p>
033 * The {@link JXPathContext} returned by {@link #getPageContext getPageContext()}
034 * provides access to all scopes via the PageContext.findAttribute()
035 * method.  Thus, an expression like "foo" will first look for the attribute
036 * named "foo" in the "page" context, then the "request" context, then
037 * the "session" one and finally in the "application" context.
038 * <p>
039 * If you need to limit the attibute lookup to just one scope, you can use the
040 * pre-definded variables "page", "request", "session" and "application".
041 * For example, the expression "$session/foo" extracts the value of the
042 * session attribute named "foo".
043 * <p>
044 * Following are some implementation details. There is a separate JXPathContext
045 * for each of the four scopes. These contexts are chained according to the
046 * nesting of the scopes.  So, the parent of the "page" JXPathContext is a
047 * "request" JXPathContext, whose parent is a "session" JXPathContext (that is
048 * if there is a session), whose parent is an "application" context.
049 * <p>
050 * The  XPath context node for each context is the corresponding object:
051 * PageContext, ServletRequest, HttpSession or ServletContext.  This feature can
052 * be used by servlets.  A servlet can use one of the methods declared by this
053 * class and work with a specific JXPathContext for any scope.
054 * <p>
055 * Since JXPath chains lookups for variables and extension functions, variables
056 * and extension function declared in the outer scopes are also available in
057 * the inner scopes.
058 * <p>
059 * Each  of the four context declares exactly one variable, the value of which
060 * is the corresponding object: PageContext, etc.
061 * <p>
062 * The  "session" variable will be undefined if there is no session for this
063 * servlet. JXPath does not automatically create sessions.
064 *
065 * @author Dmitri Plotnikov
066 * @version $Revision: 652845 $ $Date: 2008-05-02 13:46:46 -0400 (Fri, 02 May 2008) $
067 */
068public final class JXPathServletContexts {
069
070    private static JXPathContextFactory factory;
071
072    static {
073        JXPathIntrospector.registerDynamicClass(
074                PageScopeContext.class,
075                PageScopeContextHandler.class);
076        JXPathIntrospector.registerDynamicClass(
077                PageContext.class,
078                PageContextHandler.class);
079        JXPathIntrospector.registerDynamicClass(
080                ServletContext.class,
081                ServletContextHandler.class);
082        JXPathIntrospector.registerDynamicClass(
083                ServletRequestAndContext.class,
084                ServletRequestHandler.class);
085        JXPathIntrospector.registerDynamicClass(
086                HttpSessionAndServletContext.class,
087                HttpSessionHandler.class);
088        factory = JXPathContextFactory.newInstance();
089    }
090
091    /**
092     * Returns a JXPathContext bound to the "page" scope. Caches that context
093     * within the PageContext itself.
094     * @param pageContext as described
095     * @return JXPathContext
096     */
097    public static JXPathContext getPageContext(PageContext pageContext) {
098        JXPathContext context =
099            (JXPathContext) pageContext.getAttribute(Constants.JXPATH_CONTEXT);
100        if (context == null) {
101            JXPathContext parentContext =
102                getRequestContext(
103                    pageContext.getRequest(),
104                    pageContext.getServletContext());
105            context = factory.newContext(parentContext, pageContext);
106            context.setVariables(
107                new KeywordVariables(
108                    Constants.PAGE_SCOPE,
109                    new PageScopeContext(pageContext)));
110            pageContext.setAttribute(Constants.JXPATH_CONTEXT, context);
111        }
112        return context;
113    }
114
115    /**
116     * Returns a JXPathContext bound to the "request" scope. Caches that context
117     * within the request itself.
118     * @param request as described
119     * @param servletContext operative
120     * @return JXPathContext
121     */
122    public static JXPathContext getRequestContext(ServletRequest request,
123            ServletContext servletContext) {
124        JXPathContext context =
125            (JXPathContext) request.getAttribute(Constants.JXPATH_CONTEXT);
126        // If we are in an included JSP or Servlet, the request parameter
127        // will represent the included URL, but the JXPathContext we have
128        // just acquired will represent the outer request.
129        if (context != null) {
130            ServletRequestAndContext handle =
131                (ServletRequestAndContext) context.getContextBean();
132            if (handle.getServletRequest() == request) {
133                return context;
134            }
135        }
136
137        JXPathContext parentContext = null;
138        if (request instanceof HttpServletRequest) {
139            HttpSession session =
140                ((HttpServletRequest) request).getSession(false);
141            if (session != null) {
142                parentContext = getSessionContext(session, servletContext);
143            }
144            else {
145                parentContext = getApplicationContext(servletContext);
146            }
147        }
148        ServletRequestAndContext handle =
149            new ServletRequestAndContext(request, servletContext);
150        context = factory.newContext(parentContext, handle);
151        context.setVariables(
152            new KeywordVariables(Constants.REQUEST_SCOPE, handle));
153        request.setAttribute(Constants.JXPATH_CONTEXT, context);
154        return context;
155    }
156
157    /**
158     * Returns a JXPathContext bound to the "session" scope. Caches that context
159     * within the session itself.
160     * @param session as described
161     * @param servletContext operative
162     * @return JXPathContext
163     */
164    public static JXPathContext getSessionContext(HttpSession session,
165            ServletContext servletContext) {
166        JXPathContext context =
167            (JXPathContext) session.getAttribute(Constants.JXPATH_CONTEXT);
168        if (context == null) {
169            JXPathContext parentContext = getApplicationContext(servletContext);
170            HttpSessionAndServletContext handle =
171                new HttpSessionAndServletContext(session, servletContext);
172            context = factory.newContext(parentContext, handle);
173            context.setVariables(
174                new KeywordVariables(Constants.SESSION_SCOPE, handle));
175            session.setAttribute(Constants.JXPATH_CONTEXT, context);
176        }
177        return context;
178    }
179
180    /**
181     * Returns  a JXPathContext bound to the "application" scope. Caches that
182     * context within the servlet context itself.
183     * @param servletContext operative
184     * @return JXPathContext
185     */
186    public static JXPathContext getApplicationContext(
187            ServletContext servletContext) {
188        JXPathContext context =
189            (JXPathContext) servletContext.getAttribute(
190                Constants.JXPATH_CONTEXT);
191        if (context == null) {
192            context = factory.newContext(null, servletContext);
193            context.setVariables(
194                new KeywordVariables(
195                    Constants.APPLICATION_SCOPE,
196                    servletContext));
197            servletContext.setAttribute(Constants.JXPATH_CONTEXT, context);
198        }
199        return context;
200    }
201}