org.onesocialweb.openfire.manager.ActivityManager.java Source code

Java tutorial

Introduction

Here is the source code for org.onesocialweb.openfire.manager.ActivityManager.java

Source

/*
 *  Copyright 2010 Vodafone Group Services Ltd.
 *
 *  Licensed 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.onesocialweb.openfire.manager;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import javax.activity.InvalidActivityException;
import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.dom4j.dom.DOMDocument;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.roster.Roster;
import org.jivesoftware.openfire.roster.RosterItem;
import org.jivesoftware.openfire.roster.RosterManager;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.onesocialweb.model.acl.AclAction;
import org.onesocialweb.model.acl.AclFactory;
import org.onesocialweb.model.acl.AclRule;
import org.onesocialweb.model.acl.AclSubject;
import org.onesocialweb.model.activity.ActivityActor;
import org.onesocialweb.model.activity.ActivityEntry;
import org.onesocialweb.model.activity.ActivityFactory;
import org.onesocialweb.model.activity.ActivityObject;
import org.onesocialweb.model.atom.AtomReplyTo;
import org.onesocialweb.model.atom.DefaultAtomHelper;
import org.onesocialweb.openfire.OswPlugin;
import org.onesocialweb.openfire.exception.AccessDeniedException;
import org.onesocialweb.openfire.handler.activity.PEPActivityHandler;
import org.onesocialweb.openfire.model.ActivityMessage;
import org.onesocialweb.openfire.model.PersistentActivityMessage;
import org.onesocialweb.openfire.model.PersistentSubscription;
import org.onesocialweb.openfire.model.Subscription;
import org.onesocialweb.openfire.model.acl.PersistentAclFactory;
import org.onesocialweb.openfire.model.activity.PersistentActivityEntry;
import org.onesocialweb.openfire.model.activity.PersistentActivityFactory;
import org.onesocialweb.xml.dom.ActivityDomWriter;
import org.onesocialweb.xml.dom.imp.DefaultActivityDomWriter;
import org.onesocialweb.xml.namespace.Atom;
import org.w3c.dom.Element;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;

/**
 * The activity manager is a singleton class taking care of all the business
 * logic related to querying, creating, updating and deleting activities.
 * 
 * @author eschenal
 * 
 */
public class ActivityManager {

    /**
     * Singleton: keep a static reference to teh only instance
     */
    private static ActivityManager instance;

    public static ActivityManager getInstance() {
        if (instance == null) {
            // Carefull, we are in a threaded environment !
            synchronized (ActivityManager.class) {
                instance = new ActivityManager();
            }
        }
        return instance;
    }

    /**
     * Class dependencies 
     * TODO Make this a true dependency injection
     */
    private final ActivityFactory activityFactory;

    private final AclFactory aclFactory;

    /**
     * Publish a new activity to the activity stream of the given user.
     * activity-actor element is overwrittern using the user profile data to
     * avoid spoofing. Notifications messages are sent to the users subscribed
     * to this user activities.
     * 
     * @param user
     *            The user who the activity belongs to
     * @param entry
     *            The activity entry to publish
     * @throws UserNotFoundException
     */
    public void publishActivity(String userJID, ActivityEntry entry) throws UserNotFoundException {
        // Overide the actor to avoid spoofing
        User user = UserManager.getInstance().getUser(new JID(userJID).getNode());
        ActivityActor actor = activityFactory.actor();
        actor.setUri(userJID);
        actor.setName(user.getName());
        actor.setEmail(user.getEmail());

        // Persist the activities
        final EntityManager em = OswPlugin.getEmFactory().createEntityManager();
        em.getTransaction().begin();
        entry.setId(DefaultAtomHelper.generateId());
        for (ActivityObject object : entry.getObjects()) {
            object.setId(DefaultAtomHelper.generateId());
        }
        entry.setActor(actor);
        entry.setPublished(Calendar.getInstance().getTime());
        em.persist(entry);
        em.getTransaction().commit();
        em.close();

        // Broadcast the notifications
        notify(userJID, entry);
    }

    /**
     * Updates an activity in the activity stream of the given user.
     * activity-actor element is overwrittern using the user profile data to
     * avoid spoofing. Notifications messages are sent to the users subscribed
     * to this user activities.
     * 
     * @param user
     *            The user who the activity belongs to
     * @param entry
     *            The activity entry to update
     * @throws UserNotFoundException
     */
    public void updateActivity(String userJID, ActivityEntry entry)
            throws UserNotFoundException, UnauthorizedException {
        // Overide the actor to avoid spoofing
        User user = UserManager.getInstance().getUser(new JID(userJID).getNode());
        ActivityActor actor = activityFactory.actor();
        actor.setUri(userJID);
        actor.setName(user.getName());
        actor.setEmail(user.getEmail());

        // Persist the activities
        final EntityManager em = OswPlugin.getEmFactory().createEntityManager();
        em.getTransaction().begin();
        PersistentActivityEntry oldEntry = em.find(PersistentActivityEntry.class, entry.getId());

        if ((oldEntry == null) || (!oldEntry.getActor().getUri().equalsIgnoreCase(userJID)))
            throw new UnauthorizedException();

        Date published = oldEntry.getPublished();
        entry.setPublished(published);
        entry.setUpdated(Calendar.getInstance().getTime());

        em.remove(oldEntry);

        entry.setActor(actor);
        em.persist(entry);
        em.getTransaction().commit();
        em.close();

        // Broadcast the notifications
        notify(userJID, entry);
    }

    public void deleteActivity(String fromJID, String activityId) throws UnauthorizedException {

        final EntityManager em = OswPlugin.getEmFactory().createEntityManager();
        em.getTransaction().begin();

        PersistentActivityEntry activity = em.find(PersistentActivityEntry.class, activityId);

        if ((activity == null) || (!activity.getActor().getUri().equalsIgnoreCase(fromJID)))
            throw new UnauthorizedException();

        em.remove(activity);

        em.getTransaction().commit();
        em.close();

        notifyDelete(fromJID, activityId);
    }

    public void deleteMessage(String activityId) {

        final EntityManager em = OswPlugin.getEmFactory().createEntityManager();
        em.getTransaction().begin();

        Query query = em.createQuery("SELECT x FROM Messages x WHERE x.activity.id = ?1");
        query.setParameter(1, activityId);
        List<ActivityMessage> messages = query.getResultList();
        for (ActivityMessage message : messages) {
            em.remove(message);
        }

        em.getTransaction().commit();
        em.close();

    }

    /**
     * Retrieve the last activities of the target user, taking into account the
     * access rights of the requesting user.
     * 
     * @param requestorJID
     *            the user requesting the activities
     * @param targetJID
     *            the user whose activities are requested
     * @return an immutable list of the last activities of the target entity that can be seen by the
     *         requesting entity
     * @throws UserNotFoundException
     */
    @SuppressWarnings("unchecked")
    public List<ActivityEntry> getActivities(String requestorJID, String targetJID) throws UserNotFoundException {
        final EntityManager em = OswPlugin.getEmFactory().createEntityManager();
        Query query = em.createQuery("SELECT DISTINCT entry FROM ActivityEntry entry"
                + "             JOIN entry.rules rule " + "             JOIN rule.actions action "
                + "             JOIN rule.subjects subject " + "             WHERE entry.actor.uri = :target "
                + "             AND action.name = :view " + "             AND action.permission = :grant "
                + "             AND (subject.type = :everyone "
                + "                  OR (subject.type = :group_type "
                + "                     AND subject.name IN (:groups)) "
                + "                  OR (subject.type = :person "
                + "                      AND subject.name = :jid)) ORDER BY entry.published DESC");

        // Parametrize the query
        query.setParameter("target", targetJID);
        query.setParameter("view", AclAction.ACTION_VIEW);
        query.setParameter("grant", AclAction.PERMISSION_GRANT);
        query.setParameter("everyone", AclSubject.EVERYONE);
        query.setParameter("group_type", AclSubject.GROUP);
        query.setParameter("groups", getGroups(targetJID, requestorJID));
        query.setParameter("person", AclSubject.PERSON);
        query.setParameter("jid", requestorJID);
        query.setMaxResults(20);
        List<ActivityEntry> result = query.getResultList();
        em.close();

        return Collections.unmodifiableList(result);
    }

    /**
     * Handle an activity pubsub event. Such a message is usually
     * received by a user in these conditions: - the local user has subscribed
     * to the remote user activities - the local user is "mentionned" in this
     * activity - this activity relates to another activity of the local user
     * 
     * @param remoteJID
     *            the entity sending the message
     * @param localJID
     *            the entity having received the message
     * @param activity
     *            the activity contained in the message
     * @throws InvalidActivityException
     * @throws AccessDeniedException
     */
    public synchronized void handleMessage(String remoteJID, String localJID, ActivityEntry activity)
            throws InvalidActivityException, AccessDeniedException {

        // Validate the activity
        if (activity == null || !activity.hasId()) {
            throw new InvalidActivityException();
        }

        // Create a message for the recipient
        ActivityMessage message = new PersistentActivityMessage();
        message.setSender(remoteJID);
        message.setRecipient(localJID);
        //in case of an activity update we keep the received date as the date of
        //original publish, otherwise the updated posts will start showing on top of
        // the inbox..which we agreed we don't want...
        message.setReceived(activity.getPublished());

        // Search if the activity exists in the database
        final EntityManager em = OswPlugin.getEmFactory().createEntityManager();
        PersistentActivityEntry previousActivity = em.find(PersistentActivityEntry.class, activity.getId());

        // Assign the activity to the existing one if it exists
        if (previousActivity != null) {
            message.setActivity(previousActivity);
        } else {
            message.setActivity(activity);
        }

        //in case of an update the message will already exist in the DB
        Query query = em.createQuery("SELECT x FROM Messages x WHERE x.activity.id = ?1");
        query.setParameter(1, activity.getId());
        List<ActivityMessage> messages = query.getResultList();

        em.getTransaction().begin();
        for (ActivityMessage oldMessage : messages) {
            if (oldMessage.getRecipient().equalsIgnoreCase(localJID))
                em.remove(oldMessage);
        }
        em.getTransaction().commit();

        // We go ahead and post the message to the recipient mailbox
        em.getTransaction().begin();
        em.persist(message);
        em.getTransaction().commit();
        em.close();

    }

    /**
     * Subscribe an entity to another entity activities.
     * 
     * @param from the subscriber
     * @param to entity being subscribed to
     * @throws AlreadySubscribed
     */
    @SuppressWarnings("unchecked")
    public synchronized void subscribe(String from, String to) {

        // Check if it already exist
        final EntityManager em = OswPlugin.getEmFactory().createEntityManager();
        Query query = em.createQuery("SELECT x FROM Subscriptions x WHERE x.subscriber = ?1 AND x.target = ?2");
        query.setParameter(1, from);
        query.setParameter(2, to);
        List<Subscription> subscriptions = query.getResultList();

        // If already exist, we don't have anything left to do
        if (subscriptions != null && subscriptions.size() > 0) {
            em.close();
            return;
        }

        // Add the subscription
        Subscription subscription = new PersistentSubscription();
        subscription.setSubscriber(from);
        subscription.setTarget(to);
        subscription.setCreated(Calendar.getInstance().getTime());

        // Store
        em.getTransaction().begin();
        em.persist(subscription);
        em.getTransaction().commit();
        em.close();
    }

    /**
     * Delete a subscription.
     * 
     * @param from the entity requesting to unsubscribe
     * @param to the subscription target
     * @throws SubscriptionNotFound
     */
    @SuppressWarnings("unchecked")
    public synchronized void unsubscribe(String from, String to) {
        EntityManager em = OswPlugin.getEmFactory().createEntityManager();

        // Check if it already exist
        Query query = em.createQuery("SELECT x FROM Subscriptions x WHERE x.subscriber = ?1 AND x.target = ?2");
        query.setParameter(1, from);
        query.setParameter(2, to);
        List<Subscription> subscriptions = query.getResultList();

        // If it does not exist, we don't have anything left to do 
        if (subscriptions == null || subscriptions.size() == 0) {
            em.close();
            return;
        }

        // Remove the subscriptions
        // There should never be more than one.. but better safe than sorry
        em.getTransaction().begin();
        for (Subscription activitySubscription : subscriptions) {
            em.remove(activitySubscription);
        }
        em.getTransaction().commit();
        em.close();
    }

    @SuppressWarnings("unchecked")
    public List<Subscription> getSubscribers(String targetJID) {
        // Get a list of people who are interested by this stuff
        final EntityManager em = OswPlugin.getEmFactory().createEntityManager();
        Query query = em.createQuery("SELECT x FROM Subscriptions x WHERE x.target = ?1");
        query.setParameter(1, targetJID);
        List<Subscription> subscriptions = query.getResultList();
        em.close();
        return subscriptions;
    }

    @SuppressWarnings("unchecked")
    public List<Subscription> getSubscriptions(String subscriberJID) {
        // Get a list of people who are interested by this stuff
        final EntityManager em = OswPlugin.getEmFactory().createEntityManager();
        Query query = em.createQuery("SELECT x FROM Subscriptions x WHERE x.subscriber = ?1");
        query.setParameter(1, subscriberJID);
        List<Subscription> subscriptions = query.getResultList();
        em.close();
        return subscriptions;
    }

    private void notify(String fromJID, ActivityEntry entry) throws UserNotFoundException {

        // TODO We may want to do some cleaning of activities before
        // forwarding them (e.g. remove the acl, it is no one business)
        final ActivityDomWriter writer = new DefaultActivityDomWriter();
        final XMPPServer server = XMPPServer.getInstance();
        final List<Subscription> subscriptions = getSubscribers(fromJID);
        //   final Roster roster = XMPPServer.getInstance().getRosterManager().getRoster(new JID(fromJID).getNode());
        final DOMDocument domDocument = new DOMDocument();

        // Prepare the message
        final Element entryElement = (Element) domDocument
                .appendChild(domDocument.createElementNS(Atom.NAMESPACE, Atom.ENTRY_ELEMENT));
        writer.write(entry, entryElement);
        domDocument.removeChild(entryElement);

        final Message message = new Message();
        message.setFrom(fromJID);
        message.setBody("New activity: " + entry.getTitle());
        message.setType(Message.Type.headline);
        org.dom4j.Element eventElement = message.addChildElement("event",
                "http://jabber.org/protocol/pubsub#event");
        org.dom4j.Element itemsElement = eventElement.addElement("items");
        itemsElement.addAttribute("node", PEPActivityHandler.NODE);
        org.dom4j.Element itemElement = itemsElement.addElement("item");
        itemElement.addAttribute("id", entry.getId());
        itemElement.add((org.dom4j.Element) entryElement);

        // Keep a list of people we sent it to avoid duplicates
        List<String> alreadySent = new ArrayList<String>();

        // Send to this user
        alreadySent.add(fromJID);
        message.setTo(fromJID);
        server.getMessageRouter().route(message);

        // Send to all subscribers
        for (Subscription activitySubscription : subscriptions) {
            String recipientJID = activitySubscription.getSubscriber();
            if (!canSee(fromJID, entry, recipientJID)) {
                continue;
            }
            alreadySent.add(recipientJID);
            message.setTo(recipientJID);
            server.getMessageRouter().route(message);
        }

        // Send to recipients, if they can see it and have not already received it
        if (entry.hasRecipients()) {
            for (AtomReplyTo recipient : entry.getRecipients()) {
                //TODO This is dirty, the recipient should be an IRI etc...
                String recipientJID = recipient.getHref();
                if (!alreadySent.contains(recipientJID) && canSee(fromJID, entry, recipientJID)) {
                    alreadySent.add(fromJID);

                    message.setTo(recipientJID);
                    server.getMessageRouter().route(message);
                }
            }
        }
    }

    private void notifyDelete(String fromJID, String activityId) {

        final XMPPServer server = XMPPServer.getInstance();
        final List<Subscription> subscriptions = getSubscribers(fromJID);

        // Prepare the message

        final Message message = new Message();
        message.setFrom(fromJID);
        message.setBody("Delete activity: " + activityId);
        message.setType(Message.Type.headline);

        org.dom4j.Element eventElement = message.addChildElement("event",
                "http://jabber.org/protocol/pubsub#event");
        org.dom4j.Element itemsElement = eventElement.addElement("items");
        itemsElement.addAttribute("node", PEPActivityHandler.NODE);
        org.dom4j.Element retractElement = itemsElement.addElement("retract");
        retractElement.addAttribute("id", activityId);

        // Keep a list of people we sent it to avoid duplicates
        List<String> alreadySent = new ArrayList<String>();

        // Send to this user
        alreadySent.add(fromJID);
        message.setTo(fromJID);
        server.getMessageRouter().route(message);

        // Send to all subscribers
        for (Subscription activitySubscription : subscriptions) {
            String recipientJID = activitySubscription.getSubscriber();
            alreadySent.add(recipientJID);
            message.setTo(recipientJID);
            server.getMessageRouter().route(message);
        }

    }

    private List<String> getGroups(String ownerJID, String userJID) {
        RosterManager rosterManager = XMPPServer.getInstance().getRosterManager();
        Roster roster;
        try {
            roster = rosterManager.getRoster(new JID(ownerJID).getNode());
            RosterItem rosterItem = roster.getRosterItem(new JID(userJID));
            if (rosterItem != null) {
                return rosterItem.getGroups();
            }
        } catch (UserNotFoundException e) {
        }

        return new ArrayList<String>();
    }

    private boolean canSee(String fromJID, ActivityEntry entry, String viewer) throws UserNotFoundException {
        // Get a view action
        final AclAction viewAction = aclFactory.aclAction(AclAction.ACTION_VIEW, AclAction.PERMISSION_GRANT);
        AclRule rule = null;
        for (AclRule aclRule : entry.getAclRules()) {
            if (aclRule.hasAction(viewAction)) {
                rule = aclRule;
                break;
            }
        }

        // If no view action was found, we consider it is denied
        if (rule == null)
            return false;

        return AclManager.canSee(fromJID, rule, viewer);

    }

    /**
     * Private constructor to enforce the singleton
     */
    private ActivityManager() {
        activityFactory = new PersistentActivityFactory();
        aclFactory = new PersistentAclFactory();
    }
}