/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/courier/branches/sakai-2.8.1/courier-impl/impl/src/java/org/sakaiproject/courier/impl/BasicCourierService.java $ * $Id: BasicCourierService.java 59674 2009-04-03 23:05:58Z arwhyte@umich.edu $ *********************************************************************************** * * Copyright (c) 2005, 2006, 2007, 2008 The Sakai Foundation * * Licensed under the Educational Community 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.osedu.org/licenses/ECL-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.sakaiproject.courier.impl; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.courier.api.CourierService; import org.sakaiproject.courier.api.Delivery; import org.sakaiproject.util.StringUtil; /** * <p> * BasicCourierService is the implementation for the CourierService. * </p> */ public class BasicCourierService implements CourierService { /** Our logger. */ private static final Log M_log = LogFactory.getLog(BasicCourierService.class); protected static final int nLocks = 100; /** Stores a List of Deliveries for each address, keyed by address. */ protected Map<String, List<Delivery>> m_addresses = new ConcurrentHashMap<String, List<Delivery>>(nLocks, 0.75f, nLocks); /** ** Array of objects for locking. You take the hash MOD the size of the ** array to find the element in the array to lock. This allows better ** concurrency than a global lock on the hash table. I still use ** ConcurrentHashMap because otherwise I worry about two attempts ** to add an entry that happen to use the same lock. So I'm ** depending upon ConcurrentHashMap to maintain the soundness of ** the data structure. Unfortunately the synchronization in it doesn't ** help against situations where I get a value and then test or ** modify it. That needs real locks. **/ protected Object[] locks; /********************************************************************************************************************************************************************************************************************************************************** * Dependencies and their setter methods *********************************************************************************************************************************************************************************************************************************************************/ /********************************************************************************************************************************************************************************************************************************************************** * Init and Destroy *********************************************************************************************************************************************************************************************************************************************************/ /** * Final initialization, once all dependencies are set. */ public void init() { M_log.info("init()"); m_addresses.clear(); locks = new Object[nLocks]; for (int i = 0; i < nLocks; i++) locks[i] = new Object(); } /** * Returns to uninitialized state. */ public void destroy() { M_log.info("destroy()"); m_addresses.clear(); locks = null; } /********************************************************************************************************************************************************************************************************************************************************** * CourierService implementation *********************************************************************************************************************************************************************************************************************************************************/ protected int slot(String s) { int hash = Math.abs(s.hashCode()); return hash % nLocks; } /** * Queue up a delivery for the client window identified in the Delivery * object. The particular form of delivery is determined by the type of * Delivery object sent. * * @param delivery * The Delivery (or extension) object to deliver. */ public void deliver(Delivery delivery) { if (M_log.isDebugEnabled()) M_log.debug("deliver(Delivery " + delivery + ")"); final String address = delivery.getAddress(); synchronized(locks[slot(address)]) { // find the entry in m_addresses List<Delivery> deliveries = m_addresses.get(address); // create if needed if (deliveries == null) { deliveries = new ArrayList<Delivery>(); m_addresses.put(address, deliveries); } // if this doesn't exist in the list already, add it if (!deliveries.contains(delivery)) { deliveries.add(delivery); } } } /** * Clear any pending delivery requests to the particular client window for * this element. * * @param address * The address of the client window. * @param elementId * The id of the html element. */ public void clear(String address, String elementId) { if (M_log.isDebugEnabled()) M_log.debug("clear(String " + address + ", String " + elementId + ")"); synchronized(locks[slot(address)]) { // find the entry in m_addresses List<Delivery> deliveries = m_addresses.get(address); // if not there we are done if (deliveries == null) return; // remove any Deliveries with this elementId for (Iterator<Delivery> it = deliveries.iterator(); it.hasNext();) { Delivery delivery = it.next(); if (!StringUtil.different(delivery.getElement(), elementId)) { it.remove(); } } // if none left, remove it from the list if (deliveries.isEmpty()) { m_addresses.remove(address); } } } /** * Clear any pending delivery requests to this session client window. * * @param address * The address of client window. */ public void clear(String address) { if (M_log.isDebugEnabled()) M_log.debug("clear(String " + address + ")"); synchronized(locks[slot(address)]) { // remove this portal from m_addresses m_addresses.remove(address); } } /** * Access and de-queue the Deliveries queued up for a particular session client window. * * @param address * The address of client window. * @return a List of Delivery objects addressed to this session client window. */ @SuppressWarnings("unchecked") public List getDeliveries(String address) { if (M_log.isDebugEnabled()) M_log.debug("getDeliveries(String " + address + ")"); List<Delivery> deliveries; synchronized(locks[slot(address)]) { // find the entry in m_addresses deliveries = m_addresses.get(address); if (deliveries == null) { // if null, return something return Collections.EMPTY_LIST; // this should return null! } else { m_addresses.remove(address); } } // do this outside of the sync. no reason to hold // other operations now that we've atomically gotten the list // I'm worried about delays and maybe even deadlocks if we // do arbitrary code while holding a lock. // "act" all the deliveries for (Delivery delivery : deliveries) { delivery.act(); } return deliveries; } /** * Check to see if there are any deliveries queued up for a particular * session client window. * * @param address * The address of the client window. * @return true if there are deliveries for this client window, false if * not. * * WARNING: This method is almost certainly not what you want. * I can see no way to use it that won't have synchronization problems. */ public boolean hasDeliveries(String address) { if (M_log.isDebugEnabled()) M_log.debug("hasDeliveries(String " + address + ")"); List<Delivery> deliveries; // find the entry in m_addresses synchronized(locks[slot(address)]) { deliveries = m_addresses.get(address); } if (deliveries == null) return false; return (!deliveries.isEmpty()); } }