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.portlet.context;
018
019import java.util.Map;
020import java.util.concurrent.ConcurrentHashMap;
021import javax.portlet.PortletRequest;
022import javax.portlet.PortletResponse;
023import javax.portlet.PortletSession;
024
025import org.springframework.util.Assert;
026import org.springframework.util.StringUtils;
027import org.springframework.web.context.request.AbstractRequestAttributes;
028import org.springframework.web.context.request.DestructionCallbackBindingListener;
029import org.springframework.web.context.request.RequestAttributes;
030import org.springframework.web.portlet.util.PortletUtils;
031
032/**
033 * Portlet-based implementation of the
034 * {@link org.springframework.web.context.request.RequestAttributes} interface.
035 *
036 * <p>Accesses objects from portlet request and portlet session scope,
037 * with a distinction between "session" (the PortletSession's "portlet scope")
038 * and "global session" (the PortletSession's "application scope").
039 *
040 * @author Juergen Hoeller
041 * @since 2.0
042 * @see javax.portlet.PortletRequest#getAttribute
043 * @see javax.portlet.PortletSession#getAttribute
044 * @see javax.portlet.PortletSession#PORTLET_SCOPE
045 * @see javax.portlet.PortletSession#APPLICATION_SCOPE
046 * @see RequestAttributes#SCOPE_SESSION
047 * @see RequestAttributes#SCOPE_GLOBAL_SESSION
048 */
049public class PortletRequestAttributes extends AbstractRequestAttributes {
050
051        /**
052         * Constant identifying the {@link String} prefixed to the name of a
053         * destruction callback when it is stored in a {@link PortletSession}.
054         */
055        public static final String DESTRUCTION_CALLBACK_NAME_PREFIX =
056                        PortletRequestAttributes.class.getName() + ".DESTRUCTION_CALLBACK.";
057
058
059        private final PortletRequest request;
060
061        private PortletResponse response;
062
063        private volatile PortletSession session;
064
065        private final Map<String, Object> sessionAttributesToUpdate = new ConcurrentHashMap<String, Object>(1);
066
067        private final Map<String, Object> globalSessionAttributesToUpdate = new ConcurrentHashMap<String, Object>(1);
068
069
070        /**
071         * Create a new PortletRequestAttributes instance for the given request.
072         * @param request current portlet request
073         */
074        public PortletRequestAttributes(PortletRequest request) {
075                Assert.notNull(request, "Request must not be null");
076                this.request = request;
077        }
078
079        /**
080         * Create a new PortletRequestAttributes instance for the given request.
081         * @param request current portlet request
082         * @param response current portlet response (for optional exposure)
083         */
084        public PortletRequestAttributes(PortletRequest request, PortletResponse response) {
085                this(request);
086                this.response = response;
087        }
088
089
090        /**
091         * Exposes the native {@link PortletRequest} that we're wrapping.
092         */
093        public final PortletRequest getRequest() {
094                return this.request;
095        }
096
097        /**
098         * Exposes the native {@link PortletResponse} that we're wrapping (if any).
099         */
100        public final PortletResponse getResponse() {
101                return this.response;
102        }
103
104        /**
105         * Exposes the {@link PortletSession} that we're wrapping.
106         * @param allowCreate whether to allow creation of a new session if none exists yet
107         */
108        protected final PortletSession getSession(boolean allowCreate) {
109                if (isRequestActive()) {
110                        PortletSession session = this.request.getPortletSession(allowCreate);
111                        this.session = session;
112                        return session;
113                }
114                else {
115                        // Access through stored session reference, if any...
116                        PortletSession session = this.session;
117                        if (session == null) {
118                                if (allowCreate) {
119                                        throw new IllegalStateException(
120                                                        "No session found and request already completed - cannot create new session!");
121                                }
122                                else {
123                                        session = this.request.getPortletSession(false);
124                                        this.session = session;
125                                }
126                        }
127                        return session;
128                }
129        }
130
131
132        @Override
133        public Object getAttribute(String name, int scope) {
134                if (scope == SCOPE_REQUEST) {
135                        if (!isRequestActive()) {
136                                throw new IllegalStateException(
137                                                "Cannot ask for request attribute - request is not active anymore!");
138                        }
139                        return this.request.getAttribute(name);
140                }
141                else {
142                        PortletSession session = getSession(false);
143                        if (session != null) {
144                                if (scope == SCOPE_GLOBAL_SESSION) {
145                                        Object value = session.getAttribute(name, PortletSession.APPLICATION_SCOPE);
146                                        if (value != null) {
147                                                this.globalSessionAttributesToUpdate.put(name, value);
148                                        }
149                                        return value;
150                                }
151                                else {
152                                        Object value = session.getAttribute(name);
153                                        if (value != null) {
154                                                this.sessionAttributesToUpdate.put(name, value);
155                                        }
156                                        return value;
157                                }
158                        }
159                        return null;
160                }
161        }
162
163        @Override
164        public void setAttribute(String name, Object value, int scope) {
165                if (scope == SCOPE_REQUEST) {
166                        if (!isRequestActive()) {
167                                throw new IllegalStateException(
168                                                "Cannot set request attribute - request is not active anymore!");
169                        }
170                        this.request.setAttribute(name, value);
171                }
172                else {
173                        PortletSession session = getSession(true);
174                        if (scope == SCOPE_GLOBAL_SESSION) {
175                                session.setAttribute(name, value, PortletSession.APPLICATION_SCOPE);
176                                this.globalSessionAttributesToUpdate.remove(name);
177                        }
178                        else {
179                                session.setAttribute(name, value);
180                                this.sessionAttributesToUpdate.remove(name);
181                        }
182                }
183        }
184
185        @Override
186        public void removeAttribute(String name, int scope) {
187                if (scope == SCOPE_REQUEST) {
188                        if (isRequestActive()) {
189                                this.request.removeAttribute(name);
190                                removeRequestDestructionCallback(name);
191                        }
192                }
193                else {
194                        PortletSession session = getSession(false);
195                        if (session != null) {
196                                if (scope == SCOPE_GLOBAL_SESSION) {
197                                        session.removeAttribute(name, PortletSession.APPLICATION_SCOPE);
198                                        this.globalSessionAttributesToUpdate.remove(name);
199                                }
200                                else {
201                                        session.removeAttribute(name);
202                                        this.sessionAttributesToUpdate.remove(name);
203                                }
204                        }
205                }
206        }
207
208        @Override
209        public String[] getAttributeNames(int scope) {
210                if (scope == SCOPE_REQUEST) {
211                        if (!isRequestActive()) {
212                                throw new IllegalStateException(
213                                                "Cannot ask for request attributes - request is not active anymore!");
214                        }
215                        return StringUtils.toStringArray(this.request.getAttributeNames());
216                }
217                else {
218                        PortletSession session = getSession(false);
219                        if (session != null) {
220                                if (scope == SCOPE_GLOBAL_SESSION) {
221                                        return StringUtils.toStringArray(session.getAttributeNames(PortletSession.APPLICATION_SCOPE));
222                                }
223                                else {
224                                        return StringUtils.toStringArray(session.getAttributeNames());
225                                }
226                        }
227                        return new String[0];
228                }
229        }
230
231        @Override
232        public void registerDestructionCallback(String name, Runnable callback, int scope) {
233                if (scope == SCOPE_REQUEST) {
234                        registerRequestDestructionCallback(name, callback);
235                }
236                else {
237                        registerSessionDestructionCallback(name, callback);
238                }
239        }
240
241        @Override
242        public Object resolveReference(String key) {
243                if (REFERENCE_REQUEST.equals(key)) {
244                        return this.request;
245                }
246                else if (REFERENCE_SESSION.equals(key)) {
247                        return getSession(true);
248                }
249                else {
250                        return null;
251                }
252        }
253
254        @Override
255        public String getSessionId() {
256                return getSession(true).getId();
257        }
258
259        @Override
260        public Object getSessionMutex() {
261                return PortletUtils.getSessionMutex(getSession(true));
262        }
263
264
265        /**
266         * Update all accessed session attributes through {@code session.setAttribute}
267         * calls, explicitly indicating to the container that they might have been modified.
268         */
269        @Override
270        protected void updateAccessedSessionAttributes() {
271                if (!this.sessionAttributesToUpdate.isEmpty() || !this.globalSessionAttributesToUpdate.isEmpty()) {
272                        PortletSession session = getSession(false);
273                        if (session != null) {
274                                try {
275                                        for (Map.Entry<String, Object> entry : this.sessionAttributesToUpdate.entrySet()) {
276                                                String name = entry.getKey();
277                                                Object newValue = entry.getValue();
278                                                Object oldValue = session.getAttribute(name);
279                                                if (oldValue == newValue) {
280                                                        session.setAttribute(name, newValue);
281                                                }
282                                        }
283                                        for (Map.Entry<String, Object> entry : this.globalSessionAttributesToUpdate.entrySet()) {
284                                                String name = entry.getKey();
285                                                Object newValue = entry.getValue();
286                                                Object oldValue = session.getAttribute(name, PortletSession.APPLICATION_SCOPE);
287                                                if (oldValue == newValue) {
288                                                        session.setAttribute(name, newValue, PortletSession.APPLICATION_SCOPE);
289                                                }
290                                        }
291                                }
292                                catch (IllegalStateException ex) {
293                                        // Session invalidated - shouldn't usually happen.
294                                }
295                        }
296                        this.sessionAttributesToUpdate.clear();
297                        this.globalSessionAttributesToUpdate.clear();
298                }
299        }
300
301        /**
302         * Register the given callback as to be executed after session termination.
303         * <p>Note: The callback object should be serializable in order to survive
304         * web app restarts.
305         * @param name the name of the attribute to register the callback for
306         * @param callback the callback to be executed for destruction
307         */
308        protected void registerSessionDestructionCallback(String name, Runnable callback) {
309                PortletSession session = getSession(true);
310                session.setAttribute(DESTRUCTION_CALLBACK_NAME_PREFIX + name,
311                                new DestructionCallbackBindingListener(callback));
312        }
313
314
315        @Override
316        public String toString() {
317                return this.request.toString();
318        }
319
320}