org.sakaiproject.chat2.model.impl.ChatManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.chat2.model.impl.ChatManagerImpl.java

Source

/**********************************************************************************
 * $URL: https://source.sakaiproject.org/svn/portal/trunk/portal-util/util/src/java/org/sakaiproject/portal/util/PortalSiteHelper.java $
 * $Id: PortalSiteHelper.java 21708 2007-02-18 21:59:28Z ian@caret.cam.ac.uk $
 ***********************************************************************************
 *
 * Copyright (c) 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.opensource.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.chat2.model.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;

import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Expression;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.sakaiproject.authz.cover.FunctionManager;
import org.sakaiproject.authz.cover.SecurityService;
import org.sakaiproject.chat2.model.ChatChannel;
import org.sakaiproject.chat2.model.ChatManager;
import org.sakaiproject.chat2.model.RoomObserver;
import org.sakaiproject.chat2.model.ChatFunctions;
import org.sakaiproject.chat2.model.ChatMessage;
import org.sakaiproject.component.cover.ServerConfigurationService;
import org.sakaiproject.entity.api.EntityManager;
import org.sakaiproject.entity.api.Reference;
import org.sakaiproject.entity.api.Summary;
import org.sakaiproject.event.api.Event;
import org.sakaiproject.event.cover.EventTrackingService;
import org.sakaiproject.exception.IdInvalidException;
import org.sakaiproject.exception.IdUsedException;
import org.sakaiproject.exception.PermissionException;

import org.sakaiproject.util.FormattedText;
import org.sakaiproject.site.cover.SiteService;
import org.sakaiproject.time.api.Time;
import org.sakaiproject.time.cover.TimeService;
import org.sakaiproject.tool.cover.SessionManager;
import org.sakaiproject.user.api.User;
import org.sakaiproject.user.api.UserNotDefinedException;
import org.sakaiproject.user.cover.UserDirectoryService;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
 * 
 * @author andersjb
 *
 */
public class ChatManagerImpl extends HibernateDaoSupport implements ChatManager, Observer {

    private int messagesMax = 100;

    protected final transient Log logger = LogFactory.getLog(getClass());

    /** the clients listening to the various rooms */
    protected Map<String, List<RoomObserver>> roomListeners = new HashMap<String, List<RoomObserver>>();

    private EntityManager entityManager;

    private ChatChannel defaultChannelSettings;

    static Comparator<ChatMessage> channelComparatorAsc = new Comparator<ChatMessage>() {
        public int compare(ChatMessage o1, ChatMessage o2) {
            return (o1.getMessageDate().compareTo(o2.getMessageDate()));
        }
    };

    static Comparator<ChatMessage> channelComparatorDesc = new Comparator<ChatMessage>() {
        public int compare(ChatMessage o1, ChatMessage o2) {
            return -1 * (o1.getMessageDate().compareTo(o2.getMessageDate()));
        }
    };

    // part of HibernateDaoSupport; this is the only context in which it is OK                                             
    // to modify the template configuration                                                                                
    protected void initDao() throws Exception {
        super.initDao();
        getHibernateTemplate().setCacheQueries(true);
        logger.info("initDao template " + getHibernateTemplate());
    }

    /**
     * Called on after the startup of the singleton.  This sets the global
     * list of functions which will have permission managed by sakai
     * @throws Exception
     */
    protected void init() throws Exception {
        logger.info("init()");

        try {

            EventTrackingService.addObserver(this);

            // register functions
            if (FunctionManager.getRegisteredFunctions(ChatFunctions.CHAT_FUNCTION_PREFIX).size() == 0) {
                FunctionManager.registerFunction(ChatFunctions.CHAT_FUNCTION_READ);
                FunctionManager.registerFunction(ChatFunctions.CHAT_FUNCTION_NEW);
                FunctionManager.registerFunction(ChatFunctions.CHAT_FUNCTION_DELETE_ANY);
                FunctionManager.registerFunction(ChatFunctions.CHAT_FUNCTION_DELETE_OWN);
                FunctionManager.registerFunction(ChatFunctions.CHAT_FUNCTION_DELETE_CHANNEL);
                FunctionManager.registerFunction(ChatFunctions.CHAT_FUNCTION_NEW_CHANNEL);
                FunctionManager.registerFunction(ChatFunctions.CHAT_FUNCTION_EDIT_CHANNEL);
            }

        } catch (Exception e) {
            logger.warn("Error with ChatManager.init()", e);
        }

    }

    /**
     * Destroy
     */
    public void destroy() {
        EventTrackingService.deleteObserver(this);

        logger.info("destroy()");
    }

    /**
     * {@inheritDoc}
     */
    public ChatChannel createNewChannel(String context, String title, boolean placementDefaultChannel,
            boolean checkAuthz, String placement) throws PermissionException {
        if (checkAuthz)
            checkPermission(ChatFunctions.CHAT_FUNCTION_NEW_CHANNEL, context);

        ChatChannel channel = new ChatChannel(getDefaultChannelSettings());

        channel.setCreationDate(new Date());
        channel.setContext(context);
        channel.setTitle(title);
        channel.setPlacementDefaultChannel(placementDefaultChannel);
        if (placementDefaultChannel) {
            channel.setPlacement(placement);
        }

        return channel;
    }

    /**
     * {@inheritDoc}
     */
    public void updateChannel(ChatChannel channel, boolean checkAuthz) throws PermissionException {

        if (channel == null)
            return;

        if (checkAuthz)
            checkPermission(ChatFunctions.CHAT_FUNCTION_EDIT_CHANNEL, channel.getContext());

        getHibernateTemplate().saveOrUpdate(channel);
    }

    /**
     * {@inheritDoc}
     */
    public void deleteChannel(ChatChannel channel) throws PermissionException {

        if (channel == null)
            return;

        checkPermission(ChatFunctions.CHAT_FUNCTION_DELETE_CHANNEL, channel.getContext());
        getHibernateTemplate().delete(channel);

        sendDeleteChannel(channel);
    }

    /**
     * {@inheritDoc}
     */
    public ChatChannel getChatChannel(String chatChannelId) {
        return (ChatChannel) getHibernateTemplate().get(ChatChannel.class, chatChannelId);
    }

    /**
     * {@inheritDoc}
     */
    public Date calculateDateByOffset(int offset) {
        Calendar tmpDate = Calendar.getInstance();
        tmpDate.set(Calendar.DAY_OF_MONTH, tmpDate.get(Calendar.DAY_OF_MONTH) - offset);
        return new Date(tmpDate.getTimeInMillis());
    }

    /* (non-Javadoc)
     * @see org.sakaiproject.chat2.model.ChatManager#getChannelMessages(org.sakaiproject.chat2.model.ChatChannel, java.lang.String, java.util.Date, int, boolean)
     */
    public List<ChatMessage> getChannelMessages(ChatChannel channel, String context, Date date, int start, int max,
            boolean sortAsc) throws PermissionException {
        if (channel == null) {
            List<ChatMessage> allMessages = new ArrayList<ChatMessage>();
            List<ChatChannel> channels = getContextChannels(context, true);
            for (Iterator<ChatChannel> i = channels.iterator(); i.hasNext();) {
                ChatChannel tmpChannel = i.next();
                allMessages.addAll(getChannelMessages(tmpChannel, date, start, max, sortAsc));
            }

            // Why resorting here? -AZ
            if (sortAsc) {
                Collections.sort(allMessages, channelComparatorAsc);
            } else {
                Collections.sort(allMessages, channelComparatorDesc);
            }
            return allMessages;
        } else
            return getChannelMessages(channel, date, start, max, sortAsc);
    }

    /**
     * 
     * @see getChannelMessages
     */
    @SuppressWarnings("unchecked")
    protected List<ChatMessage> getChannelMessages(ChatChannel channel, Date date, int start, int max,
            boolean sortAsc) throws PermissionException {

        List<ChatMessage> messages = new ArrayList<ChatMessage>();
        if (channel == null || max == 0) {
            // no channel or no items causes nothing to be returned
            return messages;
        }

        checkPermission(ChatFunctions.CHAT_FUNCTION_READ, channel.getContext());
        int localMax = max;
        int localStart = start;
        Date localDate = date;

        // Find out which values to use.
        // If the settings of the channel have more strict values then the passed info, use them instead.
        if (channel.getFilterType().equals(ChatChannel.FILTER_BY_NUMBER)
                || channel.getFilterType().equals(ChatChannel.FILTER_NONE)) {
            if (!channel.isEnableUserOverride()) {
                localMax = Math.min(localMax, channel.getFilterParam());
            }
        } else if (channel.getFilterType().equals(ChatChannel.FILTER_BY_TIME)) {
            int days = channel.getFilterParam();
            Date tmpDate = calculateDateByOffset(days);
            if (!channel.isEnableUserOverride()) {
                localDate = tmpDate;
            }
        }

        // enforce maximum number of messages returned
        if (localStart < 0) {
            localStart = 0;
        }
        if (localMax < 0 || localMax > messagesMax) {
            localMax = messagesMax;
        }

        Criteria c = this.getSession().createCriteria(ChatMessage.class);
        c.add(Expression.eq("chatChannel", channel));
        if (localDate != null) {
            c.add(Expression.ge("messageDate", localDate));
        }

        // Always sort desc so we get the newest messages, reorder after we get the final list
        c.addOrder(Order.desc("messageDate"));

        if (localMax != 0) {
            if (localMax > 0) {
                c.setMaxResults(localMax);
            }
            if (localStart > 0) {
                c.setFirstResult(localStart);
            }
            messages = c.list();
        }

        //Reorder the list
        if (sortAsc) {
            Collections.sort(messages, channelComparatorAsc);
        } else {
            Collections.sort(messages, channelComparatorDesc);
        }

        return messages;
    }

    /* (non-Javadoc)
     * @see org.sakaiproject.chat2.model.ChatManager#countChannelMessages(org.sakaiproject.chat2.model.ChatChannel)
     */
    public int countChannelMessages(ChatChannel channel) {
        return getChannelMessagesCount(channel, null, null);
        // use getChannelMessagesCount since it is more efficient
        //        Criteria c = this.getSession().createCriteria(ChatMessage.class);
        //        if (channel != null) {
        //            c.add(Expression.eq("chatChannel", channel));      
        //        }
        //        List<ChatMessage> messages = c.list();
        //        return messages.size();
    }

    /* (non-Javadoc)
     * @see org.sakaiproject.chat2.model.ChatManager#getChannelMessagesCount(org.sakaiproject.chat2.model.ChatChannel, java.lang.String, java.util.Date)
     */
    public int getChannelMessagesCount(ChatChannel channel, String context, Date date) {
        if (channel == null) {
            // default to the first one
            List<ChatChannel> channels = getContextChannels(context, true);
            if (channels != null && channels.size() > 0) {
                channel = channels.iterator().next();
            }
        }
        int count = 0;
        if (channel != null) {
            Criteria c = this.getSession().createCriteria(ChatMessage.class);
            c.add(Expression.eq("chatChannel", channel));
            if (date != null) {
                c.add(Expression.ge("messageDate", date));
            }
            c.setProjection(Projections.rowCount());
            Integer countInt = (Integer) c.uniqueResult();
            if (countInt != null) {
                count = countInt.intValue();
            }
        }
        return count;
    }

    /**
     * {@inheritDoc}
     */
    public ChatMessage createNewMessage(ChatChannel channel, String owner) throws PermissionException {

        if (channel == null) {
            throw new IllegalArgumentException("Must specify a channel");
        }

        // We don't support posting by anonymous users
        if (owner == null) {
            throw new PermissionException(null, ChatFunctions.CHAT_FUNCTION_NEW, channel.getContext());
        }

        checkPermission(ChatFunctions.CHAT_FUNCTION_NEW, channel.getContext());

        ChatMessage message = new ChatMessage();

        message.setChatChannel(channel);
        message.setOwner(owner);
        message.setMessageDate(new Date());

        return message;
    }

    /**
     * {@inheritDoc}
     */
    public void updateMessage(ChatMessage message) {
        getHibernateTemplate().saveOrUpdate(message);
    }

    /**
     * {@inheritDoc}
     */
    public void migrateMessage(String sql, Object[] values) {

        //String statement = "insert into CHAT2_MESSAGE (MESSAGE_ID, CHANNEL_ID, OWNER, MESSAGE_DATE, BODY, migratedMessageId) " +
        //   "select ?, ?, ?, ?, ?, ? from dual where not exists " +  
        //   "(select * from CHAT2_MESSAGE m2 where m2.migratedMessageId=?)";

        try {

            String messageId = (String) values[0];
            String channelId = (String) values[1];
            String owner = (String) values[2];
            Date messageDate = (Date) values[3];
            String body = (String) values[4];
            String migratedId = (String) values[5];

            logger.debug("migrate message: " + messageId + ", " + channelId);

            ChatMessage message = getMigratedMessage(messageId);

            if (owner == null) {
                logger.warn("can't migrate message, owner is null. messageId: [" + messageId + "] channelId: ["
                        + channelId + "]");
                return;
            }

            if (message == null && body != null && !body.equals("")) {

                ChatChannel channel = getChatChannel(channelId);

                message = new ChatMessage();

                message.setId(messageId);
                message.setChatChannel(channel);
                message.setOwner(owner);
                message.setMessageDate(messageDate);
                message.setBody(FormattedText.convertPlaintextToFormattedText(body));
                message.setMigratedMessageId(migratedId);

                getHibernateTemplate().save(message);

            }

        } catch (Exception e) {

            logger.error("migrateMessage: " + e);

        }

    }

    /**
     * {@inheritDoc}
     */
    public boolean getCanDelete(ChatMessage message) {

        if (message == null)
            return false;

        String context = message.getChatChannel().getContext();

        boolean canDeleteAny = can(ChatFunctions.CHAT_FUNCTION_DELETE_ANY, context);
        boolean canDeleteOwn = can(ChatFunctions.CHAT_FUNCTION_DELETE_OWN, context);
        boolean isOwner = SessionManager.getCurrentSessionUserId() != null
                ? SessionManager.getCurrentSessionUserId().equals(message.getOwner())
                : false;

        boolean canDelete = canDeleteAny;

        if (canDeleteOwn && isOwner)
            canDelete = true;

        return canDelete;
    }

    /**
     * {@inheritDoc}
     */
    public boolean getCanDeleteAnyMessage(String context) {
        return can(ChatFunctions.CHAT_FUNCTION_DELETE_ANY, context);
    }

    /**
     * {@inheritDoc}
     */
    public boolean getCanDelete(ChatChannel channel) {
        return channel == null ? false : can(ChatFunctions.CHAT_FUNCTION_DELETE_CHANNEL, channel.getContext());
    }

    /**
     * {@inheritDoc}
     */
    public boolean getCanEdit(ChatChannel channel) {
        return channel == null ? false : can(ChatFunctions.CHAT_FUNCTION_EDIT_CHANNEL, channel.getContext());
    }

    /**
     * {@inheritDoc}
     */
    public boolean getCanCreateChannel(String context) {
        return can(ChatFunctions.CHAT_FUNCTION_NEW_CHANNEL, context);
    }

    /**
     * {@inheritDoc}
     */
    public boolean getCanReadMessage(ChatChannel channel) {
        return channel == null ? false : can(ChatFunctions.CHAT_FUNCTION_READ, channel.getContext());
    }

    /**
     * {@inheritDoc}
     */
    public boolean getCanPostMessage(ChatChannel channel) {
        // We don't currently support posting messages by anonymous users
        if (SessionManager.getCurrentSessionUserId() == null)
            return false;

        boolean allowed = false;
        if (channel != null) {
            allowed = can(ChatFunctions.CHAT_FUNCTION_NEW, channel.getContext());
            if (allowed) {
                // check the dates if they are set (https://jira.sakaiproject.org/browse/SAK-24207)
                Date today = new Date();
                Date start = channel.getStartDate();
                if (start == null) {
                    start = today;
                } else {
                    // fix up the date to shift to be beginning or end of the day (drop any time component)
                    start = DateUtils.truncate(start, Calendar.DATE);
                }
                Date end = channel.getEndDate();
                if (end == null) {
                    end = today;
                } else {
                    // fix up the date to shift to be beginning or end of the day (drop any time component)
                    end = DateUtils.truncate(end, Calendar.DATE);
                    end = DateUtils.addSeconds(end, 86398); // just short of a full day in seconds
                }
                if (today.before(start) || today.after(end)) {
                    // today is outside the configured dates so no posting allowed
                    allowed = false;
                }
            }
        }
        return allowed;
    }

    /**
     * delete a Chat Message
     * @param ChatMessage the message to delete
     */
    public void deleteMessage(ChatMessage message) throws PermissionException {
        if (message == null)
            return;
        if (!getCanDelete(message))
            checkPermission(ChatFunctions.CHAT_FUNCTION_DELETE_ANY, message.getChatChannel().getContext());

        getHibernateTemplate().delete(message);

        sendDeleteMessage(message);
    }

    /**
     * {@inheritDoc}
     */
    public void deleteChannelMessages(ChatChannel channel) throws PermissionException {

        if (channel == null)
            return;

        if (!getCanDeleteAnyMessage(channel.getContext()))
            checkPermission(ChatFunctions.CHAT_FUNCTION_DELETE_ANY, channel.getContext());

        channel = getChatChannel(channel.getId());

        if (channel != null) {
            channel.getMessages().size();
            channel.getMessages().clear();
            updateChannel(channel, false);
        }
        sendDeleteChannelMessages(channel);
    }

    /**
     * {@inheritDoc}
     */
    public ChatMessage getMessage(String chatMessageId) {
        return (ChatMessage) getHibernateTemplate().get(ChatMessage.class, chatMessageId);
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    protected ChatMessage getMigratedMessage(String migratedMessageId) {
        List<ChatMessage> messages = (List<ChatMessage>) getHibernateTemplate()
                .findByNamedQuery("findMigratedMessage", migratedMessageId);
        ChatMessage message = null;
        if (messages.size() > 0)
            message = messages.get(0);
        return message;
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public List<ChatChannel> getContextChannels(String context, boolean lazy) {
        List<ChatChannel> channels = (List<ChatChannel>) getHibernateTemplate()
                .findByNamedQuery("findChannelsInContext", context);
        if (!lazy) {
            for (Iterator<ChatChannel> i = channels.iterator(); i.hasNext();) {
                ChatChannel channel = i.next();
                channel.getMessages().size();
            }
        }
        return channels;
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public List<ChatChannel> getContextChannels(String context, String defaultNewTitle, String placement) {
        List<ChatChannel> channels = (List<ChatChannel>) getHibernateTemplate()
                .findByNamedQuery("findChannelsInContext", context);

        if (channels.size() == 0) {
            try {
                ChatChannel channel = createNewChannel(context, defaultNewTitle, true, false, placement);
                getHibernateTemplate().save(channel);
                channels.add(channel);
            } catch (PermissionException e) {
                logger.debug("Ignoring exception since it shouldn't be thrown here as we're not checking");
            }

        }

        return channels;
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public ChatChannel getDefaultChannel(String contextId, String placement) {
        List<ChatChannel> channels = (List<ChatChannel>) getHibernateTemplate()
                .findByNamedQuery("findDefaultChannelsInContext", new Object[] { contextId, placement });
        if (channels.size() == 0) {
            channels = getContextChannels(contextId, "", placement);
        }
        if (channels.size() >= 1)
            return (ChatChannel) channels.get(0);

        return null;
    }

    /**
     * {@inheritDoc}
     */
    public void addRoomListener(RoomObserver observer, String roomId) {
        List<RoomObserver> roomObservers;
        synchronized (roomListeners) {
            if (roomListeners.get(roomId) == null)
                roomListeners.put(roomId, new ArrayList<RoomObserver>());
            roomObservers = roomListeners.get(roomId);
        }
        synchronized (roomObservers) {
            roomObservers.add(observer);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("after add roomObservers " + roomObservers);
        }

    }

    /**
     * {@inheritDoc}
     */
    public void removeRoomListener(RoomObserver observer, String roomId) {

        if (roomListeners.get(roomId) != null) {
            List<RoomObserver> roomObservers = roomListeners.get(roomId);

            if (roomObservers != null) {
                synchronized (roomObservers) {

                    roomObservers.remove(observer);
                    if (roomObservers.size() == 0) {

                        synchronized (roomListeners) {
                            roomListeners.remove(roomId);
                        }

                    }

                }
            } // end if(roomObservers != null)

            if (logger.isDebugEnabled()) {
                logger.debug("after remove roomObservers " + roomObservers);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void sendMessage(ChatMessage message) {
        ChatMessageTxSync txSync = new ChatMessageTxSync(message);

        getHibernateTemplate().flush();
        txSync.afterCompletion(ChatMessageTxSync.STATUS_COMMITTED);
    }

    /**
     * {@inheritDoc}
     */
    public void sendDeleteMessage(ChatMessage message) {
        ChatMessageDeleteTxSync txSync = new ChatMessageDeleteTxSync(message);

        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(txSync);
        } else {
            txSync.afterCompletion(ChatMessageDeleteTxSync.STATUS_COMMITTED);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void sendDeleteChannel(ChatChannel channel) {
        ChatChannelDeleteTxSync txSync = new ChatChannelDeleteTxSync(channel);

        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(txSync);
        } else {
            txSync.afterCompletion(ChatChannelDeleteTxSync.STATUS_COMMITTED);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void sendDeleteChannelMessages(ChatChannel channel) {
        ChatChannelMessagesDeleteTxSync txSync = new ChatChannelMessagesDeleteTxSync(channel);

        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(txSync);
        } else {
            txSync.afterCompletion(ChatChannelMessagesDeleteTxSync.STATUS_COMMITTED);
        }
    }

    /**
     * This helps to send out the message when the record is placed in the database
     * @author andersjb
     *
     */
    private class ChatMessageDeleteTxSync extends TransactionSynchronizationAdapter {
        private ChatMessage message;

        public ChatMessageDeleteTxSync(ChatMessage message) {
            this.message = message;
        }

        public void afterCompletion(int status) {
            Event event = null;
            String function = ChatFunctions.CHAT_FUNCTION_DELETE_ANY;
            if (message.getOwner().equals(SessionManager.getCurrentSessionUserId())) {
                // own or any
                function = ChatFunctions.CHAT_FUNCTION_DELETE_OWN;
            }

            event = EventTrackingService.newEvent(function, message.getReference(), false);

            if (event != null)
                EventTrackingService.post(event);
        }
    }

    /**
     * This helps to send out the message when the record is placed in the database
     * @author andersjb
     *
     */
    private class ChatMessageTxSync extends TransactionSynchronizationAdapter {
        private ChatMessage message;

        public ChatMessageTxSync(ChatMessage message) {
            this.message = message;
        }

        public void afterCompletion(int status) {
            Event event = null;
            event = EventTrackingService.newEvent(ChatFunctions.CHAT_FUNCTION_NEW, message.getReference(), false);

            if (event != null)
                EventTrackingService.post(event);
        }
    }

    /**
     * This helps to send out the message when the record is placed in the database
     * @author andersjb
     *
     */
    private class ChatChannelDeleteTxSync extends TransactionSynchronizationAdapter {
        private ChatChannel channel;

        public ChatChannelDeleteTxSync(ChatChannel channel) {
            this.channel = channel;
        }

        public void afterCompletion(int status) {
            Event event = null;
            event = EventTrackingService.newEvent(ChatFunctions.CHAT_FUNCTION_DELETE_CHANNEL,
                    channel.getReference(), false);

            if (event != null)
                EventTrackingService.post(event);
        }
    }

    /**
     * This helps to send out the message when the messages are all deleted
     * @author andersjb
     *
     */
    private class ChatChannelMessagesDeleteTxSync extends TransactionSynchronizationAdapter {
        private ChatChannel channel;

        public ChatChannelMessagesDeleteTxSync(ChatChannel channel) {
            this.channel = channel;
        }

        public void afterCompletion(int status) {
            Event event = null;
            event = EventTrackingService.newEvent(ChatFunctions.CHAT_FUNCTION_DELETE_ANY, channel.getReference(),
                    false);

            if (event != null)
                EventTrackingService.post(event);
        }
    }

    /**
     * This method is called whenever the observed object is changed. An
     * application calls an <tt>Observable</tt> object's
     * <code>notifyObservers</code> method to have all the object's
     * observers notified of the change.
     * 
     * This operates within its own Thread so normal rules and conditions don't apply
     *
     * @param o   the observable object.
     * @param arg an argument passed to the <code>notifyObservers</code>
     *            method.
     */
    @SuppressWarnings("unchecked")
    public void update(Observable o, Object arg) {
        if (arg instanceof Event) {
            Event event = (Event) arg;

            Reference ref = getEntityManager().newReference(event.getResource());

            if (event.getEvent().equals(ChatFunctions.CHAT_FUNCTION_NEW)) {

                // get the actual message and distribute it. Otherwise each
                // observer will fetch their own copy of the message.

                String id = ref.getId();
                if (id == null)
                    return;
                ChatMessage message = getMessage(ref.getId());
                if (message == null)
                    return;

                //String[] messageParams = event.getResource().split(":");

                ArrayList<RoomObserver> observers = (ArrayList<RoomObserver>) roomListeners.get(ref.getContainer());

                // originally we did the iteration inside synchronized.
                // however that turns out to hold the lock too long
                // a shallow copy of an arraylist shouldn't be bad.
                // we currently call removeRoom from receivedMessage in
                // some cases, so it can't be locked or we will deadlock
                if (observers != null) {
                    synchronized (observers) {
                        observers = (ArrayList) observers.clone();
                    }
                    for (Iterator<RoomObserver> i = observers.iterator(); i.hasNext();) {
                        RoomObserver observer = i.next();

                        observer.receivedMessage(ref.getContainer(), message);
                    }
                }

            } else if (event.getEvent().equals(ChatFunctions.CHAT_FUNCTION_DELETE_CHANNEL)) {
                //String chatChannelId = event.getResource();

                ArrayList<RoomObserver> observers = (ArrayList<RoomObserver>) roomListeners.get(ref.getId());

                if (observers != null) {
                    synchronized (observers) {
                        observers = (ArrayList) observers.clone();
                    }
                    for (Iterator<RoomObserver> i = observers.iterator(); i.hasNext();) {
                        RoomObserver observer = i.next();

                        observer.roomDeleted(ref.getId());
                    }
                }
            }
        }
    }

    /**
     * Resets the passed context's default channel
     *
     */
    protected void resetPlacementDefaultChannel(String context, String placement) {
        Session session = null;
        Connection conn = null;
        PreparedStatement statement = null;

        String query = "update CHAT2_CHANNEL c set c.placementDefaultChannel=?, c.PLACEMENT_ID=? "
                + "WHERE c.context=? and c.PLACEMENT_ID=?";

        try {
            session = getSession();
            conn = session.connection();

            statement = conn.prepareStatement(query);
            statement.setBoolean(1, false);
            statement.setString(2, null);
            statement.setString(3, context);
            statement.setString(4, placement);
            statement.executeUpdate();
        } catch (Exception e) {
            logger.warn(e.getMessage());
        } finally {
            if (statement != null) {
                //ensure the statement is closed
                try {
                    statement.close();
                } catch (Exception e) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(e);
                    }
                }
            }
            try {
                if (conn != null)
                    conn.close();
            } catch (Exception ex) {
                logger.warn(ex.getMessage());
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void makeDefaultContextChannel(ChatChannel channel, String placement) {
        //reset context's defaults
        if (isMaintainer(channel.getContext())) {
            try {
                resetPlacementDefaultChannel(channel.getContext(), placement);

                //set new one as default
                channel.setPlacementDefaultChannel(true);
                channel.setPlacement(placement);
                updateChannel(channel, false);
            } catch (PermissionException e) {
                logger.debug("Ignoring PermissionException since it is unchecked here.");
            }
        }
    }

    protected void checkPermission(String function, String context) throws PermissionException {

        if (!SecurityService.unlock(function, SiteService.siteReference(context))) {
            String user = SessionManager.getCurrentSessionUserId();
            throw new PermissionException(user, function, context);
        }
    }

    protected boolean can(String function, String context) {
        return SecurityService.unlock(function, SiteService.siteReference(context));
    }

    /**
     * {@inheritDoc}
     */
    public boolean isMaintainer(String context) {
        return SecurityService.unlock(SiteService.SECURE_UPDATE_SITE, SiteService.siteReference(context));
    }

    protected String getSummaryFromHeader(ChatMessage item) throws UserNotDefinedException {
        String body = item.getBody();
        if (body.length() > 50)
            body = body.substring(1, 49);
        User user = UserDirectoryService.getUser(item.getOwner());
        Time messageTime = TimeService.newTime(item.getMessageDate().getTime());
        String newText = body + ", " + user.getDisplayName() + ", " + messageTime.toStringLocalFull();
        return newText;
    }

    /**********************************************************************************************************************************************************************************************************************************************************
     * getSummary implementation
     *********************************************************************************************************************************************************************************************************************************************************/
    public Map<String, String> getSummary(String channel, int items, int days)
            throws IdUsedException, IdInvalidException, PermissionException {
        long startTime = System.currentTimeMillis() - (days * 24l * 60l * 60l * 1000l);

        List<ChatMessage> messages = getChannelMessages(getChatChannel(channel), new Date(startTime), 0, items,
                true);

        Iterator<ChatMessage> iMsg = messages.iterator();
        Time pubDate = null;
        String summaryText = null;
        Map<String, String> m = new HashMap<String, String>();
        while (iMsg.hasNext()) {
            ChatMessage item = iMsg.next();
            //MessageHeader header = item.getHeader();
            Time newTime = TimeService.newTime(item.getMessageDate().getTime());
            if (pubDate == null || newTime.before(pubDate))
                pubDate = newTime;
            try {
                String newText = getSummaryFromHeader(item);
                if (summaryText == null) {
                    summaryText = newText;
                } else {
                    summaryText = summaryText + "<br>\r\n" + newText;
                }
            } catch (UserNotDefinedException e) {
                logger.warn(
                        "Skipping the chat message for user: " + item.getOwner() + " since they cannot be found");
            }
        }
        if (pubDate != null) {
            m.put(Summary.PROP_PUBDATE, pubDate.toStringRFC822Local());
        }
        if (summaryText != null) {
            m.put(Summary.PROP_DESCRIPTION, summaryText);
            return m;
        }
        return null;
    }

    /**
     * @return the entityManager
     */
    public EntityManager getEntityManager() {
        return entityManager;
    }

    /**
     * @param entityManager the entityManager to set
     */
    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    /**
     * Access the partial URL that forms the root of resource URLs.
     * 
     * @param relative
     *        if true, form within the access path only (i.e. starting with /msg)
     * @return the partial URL that forms the root of resource URLs.
     */
    protected String getAccessPoint(boolean relative) {
        return (relative ? "" : ServerConfigurationService.getAccessUrl()) + REFERENCE_ROOT;
    } // getAccessPoint

    /**
     * {@inheritDoc}
     */
    public String[] summarizableToolIds() {
        String[] toolIds = { CHAT_TOOL_ID };
        return toolIds;
    }

    /**
     * {@inheritDoc}
     */
    public String getSummarizableReference(String siteId, String toolIdentifier) {
        //I think this should just return null so we get all channels.
        return null;
    }

    /**
     * {@inheritDoc}
     */
    public String getLabel() {
        return CHAT;
    }

    /**
     * {@inheritDoc}
     */
    public ChatChannel getDefaultChannelSettings() {
        return defaultChannelSettings;
    }

    /**
     * {@inheritDoc}
     */
    public void setDefaultChannelSettings(ChatChannel defaultChannelSettings) {
        this.defaultChannelSettings = defaultChannelSettings;
    }

    public void setMessagesMax(int messagesMax) {
        this.messagesMax = messagesMax;
    }

    public int getMessagesMax() {
        return messagesMax;
    }
}