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

Java tutorial

Introduction

Here is the source code for org.onesocialweb.openfire.manager.RelationManager.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.List;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.dom4j.dom.DOMDocument;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.onesocialweb.model.atom.DefaultAtomHelper;
import org.onesocialweb.model.relation.Relation;
import org.onesocialweb.openfire.OswPlugin;
import org.onesocialweb.openfire.exception.InvalidRelationException;
import org.onesocialweb.openfire.model.relation.PersistentRelation;
import org.onesocialweb.xml.dom.RelationDomWriter;
import org.onesocialweb.xml.dom.imp.DefaultRelationDomWriter;
import org.onesocialweb.xml.namespace.Onesocialweb;
import org.w3c.dom.Element;
import org.xmpp.packet.Message;

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

    public static final String NODE = "http://onesocialweb.org/spec/1.0/relations";

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

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

    /**
     * Retrieves the relation of the target user as can be seen by the requesting user.
     * 
     * TODO ACL is not yet implemented. All relations are returned at this stage.
     * 
     * @param requestorJID the entity requesting the relations.
     * @param targetJID the entity whose relations are requested.
     * @return the list of relations of the target user, as can be seen by the requestor 
     * @throws UserNotFoundException
     */
    @SuppressWarnings("unchecked")
    public List<Relation> getRelations(String requestorJID, String targetJID) throws UserNotFoundException {
        final EntityManager em = OswPlugin.getEmFactory().createEntityManager();
        Query query = em
                .createQuery("SELECT DISTINCT relation FROM Relation relation WHERE relation.owner = :target");

        // Parametrize the query
        query.setParameter("target", targetJID);
        List<Relation> result = query.getResultList();
        em.close();

        return result;
    }

    /**
     * Setup a new relation.
     * 
     * @param userJID the user seting up the new relation
     * @param relation the relation to setup
     * @throws InvalidRelationException
     */
    public void setupRelation(String userJID, PersistentRelation relation) throws InvalidRelationException {
        // Validate the relation request
        // TODO More should be validated here
        if (!relation.hasFrom() || !relation.hasTo() || !relation.hasNature()) {
            throw new InvalidRelationException("Relation is missing required elements");
        }

        // Verify that the from or to is the user making the request
        if (!(relation.getFrom().equals(userJID) || relation.getTo().equals(userJID))) {
            throw new InvalidRelationException("Must be part of the relation to create it");
        }

        // Assign a unique ID to this new relation
        relation.setId(DefaultAtomHelper.generateId());

        // Set the request status
        relation.setStatus(Relation.Status.REQUEST);

        // We store the relation for requestor
        relation.setOwner(userJID);

        // Persist the relation
        final EntityManager em = OswPlugin.getEmFactory().createEntityManager();
        em.getTransaction().begin();
        em.persist(relation);
        em.getTransaction().commit();
        em.close();

        // We cleanup and notifiy the relation for the recipient
        relation.setAclRules(null);
        relation.setComment(null);
        notify(userJID, relation);
    }

    /**
     * Update an existing relation.
     * 
     * @param userJID the user seting up the new relation
     * @param relation the relation to setup
     * @throws InvalidRelationException
     */
    @SuppressWarnings("unchecked")
    public void updateRelation(String userJID, PersistentRelation relation) throws InvalidRelationException {
        // Validate the relation request
        // TODO More should be validated here
        if (!relation.hasId() || !relation.hasStatus()) {
            throw new InvalidRelationException("Relation is missing required elements");
        }

        // Search for an existing relation with the given ID
        final EntityManager em = OswPlugin.getEmFactory().createEntityManager();
        Query query = em.createQuery("SELECT x FROM Relation x WHERE x.owner = ?1 AND x.guid = ?2");
        query.setParameter(1, userJID);
        query.setParameter(2, relation.getId());
        List<PersistentRelation> relations = query.getResultList();

        // If no match, or more than one, we have an issue
        if (relations.size() != 1) {
            throw new InvalidRelationException("Could not find relationship with id " + relation.getId());
        }

        // We update the persisted relation
        em.getTransaction().begin();
        PersistentRelation storedRelation = relations.get(0);
        storedRelation.setStatus(relation.getStatus());
        em.getTransaction().commit();
        em.close();

        // We cleanup and notifiy the relation for the recipient
        storedRelation.setAclRules(null);
        storedRelation.setComment(null);
        notify(userJID, storedRelation);
    }

    /**
     * Handles a relation notification message.
     * 
     * @param remoteJID the entity sending the message
     * @param localJID the entity having received the message
     * @param relation the relation being notified
     * @throws InvalidRelationException
     */
    public void handleMessage(String remoteJID, String localJID, Relation relation)
            throws InvalidRelationException {
        // We need at least a status field
        if (!relation.hasStatus()) {
            throw new InvalidRelationException("Relation is missing a status field");
        }

        // Is this a new request ?
        if (relation.getStatus().equals(Relation.Status.REQUEST)) {
            handleRequestMessage(remoteJID, localJID, relation);
        } else {
            handleUpdateMessage(remoteJID, localJID, relation);
        }
    }

    @SuppressWarnings("unchecked")
    private void handleRequestMessage(String remoteJID, String localJID, Relation relation)
            throws InvalidRelationException {
        // Are required fields for a new relation setup present ?
        if (!relation.hasNature() || !relation.hasStatus() || !relation.hasFrom() || !relation.hasTo()
                || !relation.hasId()) {
            throw new InvalidRelationException("Relation is missing required elements");
        }

        // The relation should be between the sender and the receiver
        if (getDirection(relation, remoteJID, localJID) == 0) {
            throw new InvalidRelationException("Relation from/to do not match message from/to");
        }

        // Cannot add a relation to yourself
        if (relation.getFrom().equals(relation.getTo())) {
            throw new InvalidRelationException("Cannot add relation to yourself");
        }

        // Verify that this relation is not already here
        final EntityManager em = OswPlugin.getEmFactory().createEntityManager();
        Query query = em.createQuery("SELECT x FROM Relation x WHERE x.owner = ?1 AND x.guid = ?2");
        query.setParameter(1, localJID);
        query.setParameter(2, relation.getId());
        List<PersistentRelation> relations = query.getResultList();

        // If there is a match, we give up
        // TODO Not that fast. The other end may jut have not received any
        // answer and wants to try again
        // we should deal with all these recovery features.
        if (relations.size() > 0) {
            throw new InvalidRelationException("This relation has already been requested");
        }

        // Save the relation
        PersistentRelation persistentRelation = (PersistentRelation) relation;
        persistentRelation.setOwner(localJID);

        em.getTransaction().begin();
        em.persist(persistentRelation);
        em.getTransaction().commit();
        em.close();
    }

    @SuppressWarnings("unchecked")
    private void handleUpdateMessage(String remoteJID, String localJID, Relation relation)
            throws InvalidRelationException {
        // Search for the stored relation
        final EntityManager em = OswPlugin.getEmFactory().createEntityManager();
        Query query = em.createQuery("SELECT x FROM Relation x WHERE x.owner = ?1 AND x.guid = ?2");
        query.setParameter(1, localJID);
        query.setParameter(2, relation.getId());
        List<PersistentRelation> relations = query.getResultList();

        // If no match, or more than one, we have an issue
        if (relations.size() != 1) {
            throw new InvalidRelationException("Could not find matching relationship");
        }

        // We update the persisted relation
        em.getTransaction().begin();
        PersistentRelation previous = relations.get(0);
        previous.setStatus(relation.getStatus());
        em.getTransaction().commit();
        em.close();
    }

    private void notify(String localJID, Relation relation) {
        final DOMDocument domDocument = new DOMDocument();
        final Element entryElement = (Element) domDocument
                .appendChild(domDocument.createElementNS(Onesocialweb.NAMESPACE, Onesocialweb.RELATION_ELEMENT));
        final RelationDomWriter writer = new DefaultRelationDomWriter();
        writer.write(relation, entryElement);
        domDocument.removeChild(entryElement);

        final Message message = new Message();
        message.setFrom(localJID);
        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", NODE);
        org.dom4j.Element itemElement = itemsElement.addElement("item");
        itemElement.addAttribute("id", relation.getId());
        itemElement.add((org.dom4j.Element) entryElement);

        // Send to this user
        message.setTo(getOtherEnd(relation, localJID));
        server.getMessageRouter().route(message);
    }

    private String getOtherEnd(Relation relation, String userJID) {
        if (!relation.hasFrom() || !relation.hasTo()) {
            return null;
        }

        if (relation.getFrom().equals(userJID)) {
            return relation.getTo();
        }

        if (relation.getTo().equals(userJID)) {
            return relation.getFrom();
        }

        // The given JID is no part of this relation
        return null;
    }

    private int getDirection(Relation relation, String fromJID, String toJID) {
        if (!relation.hasFrom() || !relation.hasTo()) {
            return 0;
        }

        if (relation.getFrom().equals(fromJID) && relation.getTo().equals(toJID)) {
            return 1;
        }

        if (relation.getFrom().equals(toJID) && relation.getTo().equals(fromJID)) {
            return -1;
        }

        // If we are here, the relation does not concern from & to
        return 0;
    }

    /**
     * Class dependencies (should be true dependency injection someday)
     */

    private final XMPPServer server;

    /**
     * Private constructor to enforce the singleton
     */
    private RelationManager() {
        server = XMPPServer.getInstance();
    }

}