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.wicket.pageStore;
018
019import java.util.ArrayList;
020import java.util.LinkedList;
021
022import org.apache.wicket.MetaDataKey;
023import org.apache.wicket.page.IManageablePage;
024import org.apache.wicket.request.cycle.RequestCycle;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028/**
029 * Buffers storage of added pages until the end of the request, when they are delegated to the next store in
030 * the identical order they where added.
031 */
032public class RequestPageStore extends DelegatingPageStore
033{
034
035        private static final Logger log = LoggerFactory.getLogger(RequestPageStore.class);
036
037        private static final MetaDataKey<RequestData> KEY = new MetaDataKey<>()
038        {
039                private static final long serialVersionUID = 1L;
040        };
041
042        public RequestPageStore(IPageStore delegate)
043        {
044                super(delegate);
045        }
046
047        @Override
048        public IManageablePage getPage(IPageContext context, int id)
049        {
050                IManageablePage page = getRequestData(context).get(id);
051                if (page != null)
052                {
053                        return page;
054                }
055
056                return getDelegate().getPage(context, id);
057        }
058
059        @Override
060        public void addPage(IPageContext context, IManageablePage page)
061        {
062                getRequestData(context).add(page);
063        }
064
065        @Override
066        public void removePage(IPageContext context, IManageablePage page)
067        {
068                getRequestData(context).remove(page);
069
070                getDelegate().removePage(context, page);
071        }
072
073        @Override
074        public void removeAllPages(IPageContext context)
075        {
076                getRequestData(context).removeAll();
077
078                getDelegate().removeAllPages(context);
079        }
080
081        @Override
082        public void revertPage(IPageContext context, IManageablePage page)
083        {
084                getRequestData(context).remove(page);
085                
086                getDelegate().revertPage(context, page);
087        }
088        
089        @Override
090        public void end(IPageContext context)
091        {
092                getDelegate().end(context);
093                
094                RequestData requestData = getRequestData(context);
095                for (IManageablePage page : requestData.pages())
096                {
097                        if (isPageStateless(page) == false)
098                        {
099                                // last opportunity to create a session
100                                context.getSessionId(true);
101                                break;
102                        }
103                }
104        }
105        
106        @Override
107        public void detach(IPageContext context)
108        {
109                RequestData requestData = getRequestData(context);
110                for (IManageablePage page : requestData.pages())
111                {
112                        if (isPageStateless(page) == false)
113                        {
114                                getDelegate().addPage(context, page);
115                        }
116                }
117                requestData.removeAll();
118
119                getDelegate().detach(context);
120        }
121
122        private boolean isPageStateless(final IManageablePage page) {
123                boolean isPageStateless;
124                try
125                {
126                        isPageStateless = page.isPageStateless();
127                }
128                catch (Exception x)
129                {
130                        log.warn("An error occurred while checking whether a page is stateless. Assuming it is stateful.", x);
131                        isPageStateless = false;
132                }
133                return isPageStateless;
134        }
135
136        private RequestData getRequestData(IPageContext context)
137        {
138                return context.getRequestData(KEY, RequestData::new);
139        }
140        
141        /**
142         * Data kept in the {@link RequestCycle}.
143         */
144        static class RequestData
145        {
146                private final LinkedList<IManageablePage> pages = new LinkedList<>();
147                
148                public void add(IManageablePage page)
149                {
150                        // add as last
151                        pages.remove(page);
152                        pages.addLast(page);
153                }
154
155                public Iterable<IManageablePage> pages()
156                {
157                        // must work on copy to prevent concurrent modification when page is re-added during detaching 
158                        return new ArrayList<>(pages);
159                }
160
161                public IManageablePage get(int id)
162                {
163                        for (IManageablePage page : pages)
164                        {
165                                if (page.getPageId() == id)
166                                {
167                                        return page;
168                                }
169                        }
170                        return null;
171                }
172
173                public void remove(IManageablePage page)
174                {
175                        pages.remove(page);
176                }
177
178                public void removeAll()
179                {
180                        pages.clear();
181                }               
182        }
183}