Package org.apache.wicket.page

Source Code of org.apache.wicket.page.PageAccessSynchronizer

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.wicket.page;

import java.util.Date;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.wicket.util.IProvider;
import org.apache.wicket.util.ValueProvider;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.time.Duration;
import org.apache.wicket.util.time.Time;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Synchronizes access to page instances from multiple threads
*
* @author Igor Vaynberg (ivaynberg)
*/
public class PageAccessSynchronizer
{
  private static final Logger logger = LoggerFactory.getLogger(PageAccessSynchronizer.class);

  /** map of which pages are owned by which threads */
  private final ConcurrentHashMap<Integer, PageLock> locks = new ConcurrentHashMap<Integer, PageLock>();

  /** used to synchronize various code points */
  private final Object semaphore = new Object();

  /** timeout value for acquiring a page lock */
  private final IProvider<Duration> timeout;

  /**
   * Constructor
   *
   * @param timeout
   *            timeout value for acquiring a page lock
   */
  public PageAccessSynchronizer(Duration timeout)
  {
    this(ValueProvider.of(timeout));
  }

  /**
   * Constructor
   *
   * @param timeout
   *            timeout value for acquiring a page lock
   */
  public PageAccessSynchronizer(IProvider<Duration> timeout)
  {
    Args.notNull(timeout, "timeout");
    this.timeout = timeout;
  }

  private static long remaining(Time start, Duration timeout)
  {
    return Math.max(0, timeout.subtract(start.elapsedSince()).getMilliseconds());
  }

  /**
   * Acquire a lock to a page
   *
   * @param pageId
   *            page id
   * @throws CouldNotLockPageException
   *             if lock could not be acquired
   */
  public void lockPage(int pageId) throws CouldNotLockPageException
  {
    final Duration timeout = this.timeout.get();
    final Thread thread = Thread.currentThread();
    final PageLock lock = new PageLock(pageId, thread);
    final Time start = Time.now();

    boolean locked = false;

    while (!locked && start.elapsedSince().lessThan(timeout))
    {
      logger.debug("{} attempting to acquire lock to page {}", thread.getName(), pageId);

      PageLock previous = locks.putIfAbsent(pageId, lock);
      if (previous == null || previous.getThread() == thread)
      {
        // first thread to acquire lock or lock is already owned by this thread
        locked = true;
      }
      else
      {
        // wait for a lock to become available
        long remaining = remaining(start, timeout);
        if (remaining > 0)
        {
          synchronized (semaphore)
          {
            if (logger.isDebugEnabled())
            {
              logger.debug("{} waiting for lock to page {} for {}", new Object[] {
                  thread.getName(), pageId, Duration.milliseconds(remaining) });
            }
            try
            {
              semaphore.wait(remaining);
            }
            catch (InterruptedException e)
            {
              // TODO better exception
              throw new RuntimeException(e);
            }
          }
        }
      }
    }
    if (locked)
    {
      logger.debug("{} acquired lock to page {}", thread.getName(), pageId);
    }
    else
    {
      logger.warn("{} failed to acquire lock to page {}, attempted for {} out of allowed {}",
        new Object[] { thread.getName(), pageId, start.elapsedSince(), timeout });
      throw new CouldNotLockPageException(pageId, thread.getName(), timeout);
    }
  }

  /**
   * Unlocks all pages locked by this thread
   */
  public void unlockAllPages()
  {
    final Thread thread = Thread.currentThread();
    final Iterator<PageLock> locks = this.locks.values().iterator();

    boolean changed = false;
    while (locks.hasNext())
    {
      // remove all locks held by this thread
      final PageLock lock = locks.next();
      if (lock.getThread() == thread)
      {
        locks.remove();
        logger.debug("{} released lock to page {}", thread.getName(), lock.getPageId());
        changed = true;
      }
    }

    if (changed)
    {
      // if any locks were removed notify threads waiting for a lock
      synchronized (semaphore)
      {
        logger.debug("{} notifying blocked threads", thread.getName());
        semaphore.notifyAll();
      }
    }
  }

  /**
   * Wraps a page manager with this synchronizer
   *
   * @param pagemanager
   * @return wrapped page manager
   */
  public IPageManager adapt(IPageManager pagemanager)
  {
    return new PageManagerDecorator(pagemanager)
    {
      @Override
      public IManageablePage getPage(int id)
      {
        lockPage(id);
        IManageablePage page = super.getPage(id);
        return page;
      }

      @Override
      public void touchPage(IManageablePage page)
      {
        lockPage(page.getPageId());
        super.touchPage(page);
      }

      @Override
      public void commitRequest()
      {
        try
        {
          super.commitRequest();
        }
        finally
        {
          unlockAllPages();
        }
      }
    };
  }

  /**
   * Thread's lock on a page
   *
   * @author igor
   */
  public static class PageLock
  {
    /** page id */
    private final int pageId;

    /** timestamp when lock was created */
    private final Date created;

    /** thread that owns the lock */
    private final Thread thread;

    /**
     * Constructor
     *
     * @param pageId
     * @param thread
     */
    public PageLock(int pageId, Thread thread)
    {
      this.pageId = pageId;
      this.thread = thread;
      created = new Date();
    }

    /**
     * @return page id of locked page
     */
    public int getPageId()
    {
      return pageId;
    }

    /**
     * @return timestamp lock was created
     */
    public Date getCreated()
    {
      return created;
    }

    /**
     * @return thread that owns the lock
     */
    public Thread getThread()
    {
      return thread;
    }


  }
}
TOP

Related Classes of org.apache.wicket.page.PageAccessSynchronizer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.