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.page;
018
019import java.io.Serializable;
020import java.time.Duration;
021
022import org.apache.wicket.pageStore.IPageStore;
023import org.apache.wicket.util.lang.Args;
024import org.slf4j.Logger;
025import org.slf4j.LoggerFactory;
026
027/**
028 * Synchronizes access to page instances from multiple threads
029 * 
030 * @author Igor Vaynberg (ivaynberg)
031 */
032public class PageAccessSynchronizer implements Serializable
033{
034        private static final long serialVersionUID = 1L;
035
036        private static final Logger logger = LoggerFactory.getLogger(PageAccessSynchronizer.class);
037
038        /** lock manager responsible for locking and unlocking page instances */
039        private final IPageLockManager pageLockManager;
040
041        /**
042         * Constructor
043         * 
044         * @param timeout
045         *            timeout value for acquiring a page lock
046         */
047        public PageAccessSynchronizer(Duration timeout)
048        {
049                this(new DefaultPageLockManager(timeout));
050        }
051
052        /**
053         * Constructor
054         *
055         * @param pageLockManager the lock manager
056         */
057        public PageAccessSynchronizer(IPageLockManager pageLockManager)
058        {
059                this.pageLockManager = Args.notNull(pageLockManager, "pageLockManager");
060        }
061
062        /**
063         * Acquire a lock to a page
064         * 
065         * @param pageId
066         *            page id
067         * @throws CouldNotLockPageException
068         *             if lock could not be acquired
069         */
070        public void lockPage(int pageId) throws CouldNotLockPageException
071        {
072                pageLockManager.lockPage(pageId);
073        }
074
075        /**
076         * Unlocks all pages locked by this thread
077         */
078        public void unlockAllPages()
079        {
080                pageLockManager.unlockAllPages();
081        }
082
083        /**
084         * Unlocks a single page locked by the current thread.
085         * 
086         * @param pageId
087         *            the id of the page which should be unlocked.
088         */
089        public void unlockPage(int pageId)
090        {
091                pageLockManager.unlockPage(pageId);
092        }
093
094        /**
095         * Wraps a page manager with this synchronizer
096         * 
097         * @param manager
098         * @return wrapped page manager
099         */
100        public IPageManager adapt(final IPageManager manager)
101        {
102                return new IPageManager()
103                {
104                        @Override
105                        public boolean supportsVersioning()
106                        {
107                                return manager.supportsVersioning();
108                        }
109
110                        @Override
111                        public IManageablePage getPage(int pageId)
112                        {
113                                IManageablePage page = null;
114                                try
115                                {
116                                        lockPage(pageId);
117                                        page = manager.getPage(pageId);
118                                }
119                                finally
120                                {
121                                        if (page == null)
122                                        {
123                                                unlockPage(pageId);
124                                        }
125                                }
126                                return page;
127                        }
128
129                        @Override
130                        public void removePage(IManageablePage page)
131                        {
132                                if (page != null)
133                                {
134                                        try
135                                        {
136                                                manager.removePage(page);
137                                        }
138                                        finally
139                                        {
140                                                unlockPage(page.getPageId());
141                                        }
142                                }
143                        }
144
145                        @Override
146                        public void touchPage(IManageablePage page)
147                        {
148                                lockPage(page.getPageId());
149
150                                manager.touchPage(page);
151                        }
152
153                        @Override
154                        public void clear()
155                        {
156                                manager.clear();
157                        }
158
159                        @Override
160                        public void untouchPage(IManageablePage page)
161                        {
162                                manager.untouchPage(page);
163                        }
164
165                        @Override
166                        public void detach()
167                        {
168                                try
169                                {
170                                        manager.detach();
171                                }
172                                finally
173                                {
174                                        unlockAllPages();
175                                }
176                        }
177
178                        @Override
179                        public IPageStore getPageStore()
180                        {
181                                return manager.getPageStore();
182                        }
183
184                        @Override
185                        public void destroy()
186                        {
187                                manager.destroy();
188                        }
189                };
190        }
191
192        /**
193         * Thread's lock on a page
194         * 
195         * @author igor
196         */
197        public static class PageLock
198        {
199                /** page id */
200                private final int pageId;
201
202                /** thread that owns the lock */
203                private final Thread thread;
204
205                private volatile boolean released = false;
206
207                /**
208                 * Constructor
209                 * 
210                 * @param pageId
211                 * @param thread
212                 */
213                public PageLock(int pageId, Thread thread)
214                {
215                        this.pageId = pageId;
216                        this.thread = thread;
217                }
218
219                /**
220                 * @return page id of locked page
221                 */
222                public int getPageId()
223                {
224                        return pageId;
225                }
226
227                /**
228                 * @return thread that owns the lock
229                 */
230                public Thread getThread()
231                {
232                        return thread;
233                }
234
235                public final synchronized void waitForRelease(long remaining, boolean isDebugEnabled)
236                {
237                        if (released)
238                        {
239                                // the thread holding the lock released it before we were able to wait for the
240                                // release
241                                if (isDebugEnabled)
242                                {
243                                        logger.debug(
244                                                "lock for page with id {} no longer locked by {}, falling through", pageId,
245                                                thread.getName());
246                                }
247                                return;
248                        }
249
250                        if (isDebugEnabled)
251                        {
252                                logger.debug("{} waiting for lock to page {} for {}",
253                                        thread.getName(), pageId, Duration.ofMillis(remaining));
254                        }
255                        try
256                        {
257                                wait(remaining);
258                        }
259                        catch (InterruptedException e)
260                        {
261                                throw new RuntimeException(e);
262                        }
263                }
264
265                public final synchronized void markReleased(boolean isDebugEnabled)
266                {
267                        if (isDebugEnabled)
268                        {
269                                logger.debug("'{}' notifying blocked threads", thread.getName());
270                        }
271                        released = true;
272                        notifyAll();
273                }
274        }
275}