001/*
002 * Copyright 2002-2016 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.context.request;
018
019import java.lang.reflect.Method;
020import java.util.Map;
021import javax.faces.context.ExternalContext;
022import javax.faces.context.FacesContext;
023import javax.portlet.PortletSession;
024
025import org.apache.commons.logging.Log;
026import org.apache.commons.logging.LogFactory;
027
028import org.springframework.util.Assert;
029import org.springframework.util.ClassUtils;
030import org.springframework.util.ReflectionUtils;
031import org.springframework.util.StringUtils;
032import org.springframework.web.util.WebUtils;
033
034/**
035 * {@link RequestAttributes} adapter for a JSF {@link javax.faces.context.FacesContext}.
036 * Used as default in a JSF environment, wrapping the current FacesContext.
037 *
038 * <p><b>NOTE:</b> In contrast to {@link ServletRequestAttributes}, this variant does
039 * <i>not</i> support destruction callbacks for scoped attributes, neither for the
040 * request scope nor for the session scope. If you rely on such implicit destruction
041 * callbacks, consider defining a Spring {@link RequestContextListener} in your
042 * {@code web.xml}.
043 *
044 * <p>Requires JSF 2.0 or higher, as of Spring 4.0.
045 *
046 * @author Juergen Hoeller
047 * @since 2.5.2
048 * @see javax.faces.context.FacesContext#getExternalContext()
049 * @see javax.faces.context.ExternalContext#getRequestMap()
050 * @see javax.faces.context.ExternalContext#getSessionMap()
051 * @see RequestContextHolder#currentRequestAttributes()
052 */
053public class FacesRequestAttributes implements RequestAttributes {
054
055        private static final boolean portletApiPresent =
056                        ClassUtils.isPresent("javax.portlet.PortletSession", FacesRequestAttributes.class.getClassLoader());
057
058        /**
059         * We'll create a lot of these objects, so we don't want a new logger every time.
060         */
061        private static final Log logger = LogFactory.getLog(FacesRequestAttributes.class);
062
063        private final FacesContext facesContext;
064
065
066        /**
067         * Create a new FacesRequestAttributes adapter for the given FacesContext.
068         * @param facesContext the current FacesContext
069         * @see javax.faces.context.FacesContext#getCurrentInstance()
070         */
071        public FacesRequestAttributes(FacesContext facesContext) {
072                Assert.notNull(facesContext, "FacesContext must not be null");
073                this.facesContext = facesContext;
074        }
075
076
077        /**
078         * Return the JSF FacesContext that this adapter operates on.
079         */
080        protected final FacesContext getFacesContext() {
081                return this.facesContext;
082        }
083
084        /**
085         * Return the JSF ExternalContext that this adapter operates on.
086         * @see javax.faces.context.FacesContext#getExternalContext()
087         */
088        protected final ExternalContext getExternalContext() {
089                return getFacesContext().getExternalContext();
090        }
091
092        /**
093         * Return the JSF attribute Map for the specified scope
094         * @param scope constant indicating request or session scope
095         * @return the Map representation of the attributes in the specified scope
096         * @see #SCOPE_REQUEST
097         * @see #SCOPE_SESSION
098         */
099        protected Map<String, Object> getAttributeMap(int scope) {
100                if (scope == SCOPE_REQUEST) {
101                        return getExternalContext().getRequestMap();
102                }
103                else {
104                        return getExternalContext().getSessionMap();
105                }
106        }
107
108
109        @Override
110        public Object getAttribute(String name, int scope) {
111                if (scope == SCOPE_GLOBAL_SESSION && portletApiPresent) {
112                        return PortletSessionAccessor.getAttribute(name, getExternalContext());
113                }
114                else {
115                        return getAttributeMap(scope).get(name);
116                }
117        }
118
119        @Override
120        public void setAttribute(String name, Object value, int scope) {
121                if (scope == SCOPE_GLOBAL_SESSION && portletApiPresent) {
122                        PortletSessionAccessor.setAttribute(name, value, getExternalContext());
123                }
124                else {
125                        getAttributeMap(scope).put(name, value);
126                }
127        }
128
129        @Override
130        public void removeAttribute(String name, int scope) {
131                if (scope == SCOPE_GLOBAL_SESSION && portletApiPresent) {
132                        PortletSessionAccessor.removeAttribute(name, getExternalContext());
133                }
134                else {
135                        getAttributeMap(scope).remove(name);
136                }
137        }
138
139        @Override
140        public String[] getAttributeNames(int scope) {
141                if (scope == SCOPE_GLOBAL_SESSION && portletApiPresent) {
142                        return PortletSessionAccessor.getAttributeNames(getExternalContext());
143                }
144                else {
145                        return StringUtils.toStringArray(getAttributeMap(scope).keySet());
146                }
147        }
148
149        @Override
150        public void registerDestructionCallback(String name, Runnable callback, int scope) {
151                if (logger.isWarnEnabled()) {
152                        logger.warn("Could not register destruction callback [" + callback + "] for attribute '" + name +
153                                        "' because FacesRequestAttributes does not support such callbacks");
154                }
155        }
156
157        @Override
158        public Object resolveReference(String key) {
159                if (REFERENCE_REQUEST.equals(key)) {
160                        return getExternalContext().getRequest();
161                }
162                else if (REFERENCE_SESSION.equals(key)) {
163                        return getExternalContext().getSession(true);
164                }
165                else if ("application".equals(key)) {
166                        return getExternalContext().getContext();
167                }
168                else if ("requestScope".equals(key)) {
169                        return getExternalContext().getRequestMap();
170                }
171                else if ("sessionScope".equals(key)) {
172                        return getExternalContext().getSessionMap();
173                }
174                else if ("applicationScope".equals(key)) {
175                        return getExternalContext().getApplicationMap();
176                }
177                else if ("facesContext".equals(key)) {
178                        return getFacesContext();
179                }
180                else if ("cookie".equals(key)) {
181                        return getExternalContext().getRequestCookieMap();
182                }
183                else if ("header".equals(key)) {
184                        return getExternalContext().getRequestHeaderMap();
185                }
186                else if ("headerValues".equals(key)) {
187                        return getExternalContext().getRequestHeaderValuesMap();
188                }
189                else if ("param".equals(key)) {
190                        return getExternalContext().getRequestParameterMap();
191                }
192                else if ("paramValues".equals(key)) {
193                        return getExternalContext().getRequestParameterValuesMap();
194                }
195                else if ("initParam".equals(key)) {
196                        return getExternalContext().getInitParameterMap();
197                }
198                else if ("view".equals(key)) {
199                        return getFacesContext().getViewRoot();
200                }
201                else if ("viewScope".equals(key)) {
202                        return getFacesContext().getViewRoot().getViewMap();
203                }
204                else if ("flash".equals(key)) {
205                        return getExternalContext().getFlash();
206                }
207                else if ("resource".equals(key)) {
208                        return getFacesContext().getApplication().getResourceHandler();
209                }
210                else {
211                        return null;
212                }
213        }
214
215        @Override
216        public String getSessionId() {
217                Object session = getExternalContext().getSession(true);
218                try {
219                        // Both HttpSession and PortletSession have a getId() method.
220                        Method getIdMethod = session.getClass().getMethod("getId");
221                        return ReflectionUtils.invokeMethod(getIdMethod, session).toString();
222                }
223                catch (NoSuchMethodException ex) {
224                        throw new IllegalStateException("Session object [" + session + "] does not have a getId() method");
225                }
226        }
227
228        @Override
229        public Object getSessionMutex() {
230                // Enforce presence of a session first to allow listeners to create the mutex attribute
231                ExternalContext externalContext = getExternalContext();
232                Object session = externalContext.getSession(true);
233                Object mutex = externalContext.getSessionMap().get(WebUtils.SESSION_MUTEX_ATTRIBUTE);
234                if (mutex == null) {
235                        mutex = (session != null ? session : externalContext);
236                }
237                return mutex;
238        }
239
240
241        /**
242         * Inner class to avoid hard-coded Portlet API dependency.
243         */
244        private static class PortletSessionAccessor {
245
246                public static Object getAttribute(String name, ExternalContext externalContext) {
247                        Object session = externalContext.getSession(false);
248                        if (session instanceof PortletSession) {
249                                return ((PortletSession) session).getAttribute(name, PortletSession.APPLICATION_SCOPE);
250                        }
251                        else if (session != null) {
252                                return externalContext.getSessionMap().get(name);
253                        }
254                        else {
255                                return null;
256                        }
257                }
258
259                public static void setAttribute(String name, Object value, ExternalContext externalContext) {
260                        Object session = externalContext.getSession(true);
261                        if (session instanceof PortletSession) {
262                                ((PortletSession) session).setAttribute(name, value, PortletSession.APPLICATION_SCOPE);
263                        }
264                        else {
265                                externalContext.getSessionMap().put(name, value);
266                        }
267                }
268
269                public static void removeAttribute(String name, ExternalContext externalContext) {
270                        Object session = externalContext.getSession(false);
271                        if (session instanceof PortletSession) {
272                                ((PortletSession) session).removeAttribute(name, PortletSession.APPLICATION_SCOPE);
273                        }
274                        else if (session != null) {
275                                externalContext.getSessionMap().remove(name);
276                        }
277                }
278
279                public static String[] getAttributeNames(ExternalContext externalContext) {
280                        Object session = externalContext.getSession(false);
281                        if (session instanceof PortletSession) {
282                                return StringUtils.toStringArray(
283                                                ((PortletSession) session).getAttributeNames(PortletSession.APPLICATION_SCOPE));
284                        }
285                        else if (session != null) {
286                                return StringUtils.toStringArray(externalContext.getSessionMap().keySet());
287                        }
288                        else {
289                                return new String[0];
290                        }
291                }
292        }
293
294}