public class OswPlugin implements Plugin {

	private static EntityManagerFactory emFactory;

	private static File pluginDirectory;

	private Map<String, String> connProperties = new Hashtable<String, String>();

	private IQProfileQueryHandler iqProfileQueryHandler;
	private IQProfilePublishHandler iqProfileUpdateHandler;
	private IQRelationSetupHandler iqRelationSetupHandler;
	private IQRelationUpdateHandler iqRelationUpdateHandler;
	private IQRelationQueryHandler iqRelationQueryHandler;
	private IQPEPHandler iqPEPHandler;
	private MessageEventInterceptor messageInterceptor;
	private IQSubscribeInterceptor iqSubscribeInterceptor;

	public static EntityManagerFactory getEmFactory() {
		return emFactory;
	}

	public static File getPublicDirectory() {
		return pluginDirectory;
	}

	@Override
	public void initializePlugin(PluginManager manager, File directory) {
		// Save the plugin directory for later use
		pluginDirectory = directory;

		// set the connection properties
		setConnectionProperties();

		// prepare the required folders
		prepareFolders();

		// Prepare the entity manager factory
		emFactory = Persistence.createEntityManagerFactory("onesocialweb", connProperties);

		// Create the IQ handlers
		iqProfileQueryHandler = new IQProfileQueryHandler();
		iqProfileUpdateHandler = new IQProfilePublishHandler();
		iqRelationSetupHandler = new IQRelationSetupHandler();
		iqRelationQueryHandler = new IQRelationQueryHandler();
		iqRelationUpdateHandler = new IQRelationUpdateHandler();

		// Create the PEP handlers
		iqPEPHandler = new IQPEPHandler();

		// Create the message interceptors
		messageInterceptor = new MessageEventInterceptor();
		iqSubscribeInterceptor = new IQSubscribeInterceptor();
		

		// Add the IQ handlers to the router. This will trigger their
		// initialize method.
		IQRouter iqRouter = XMPPServer.getInstance().getIQRouter();
		iqRouter.addHandler(iqProfileQueryHandler);
		iqRouter.addHandler(iqProfileUpdateHandler);
		iqRouter.addHandler(iqRelationSetupHandler);
		iqRouter.addHandler(iqRelationUpdateHandler);
		iqRouter.addHandler(iqRelationQueryHandler);
		iqRouter.addHandler(iqPEPHandler);

		// Add the interceptor to process incoming notification messages
		InterceptorManager.getInstance().addInterceptor(messageInterceptor);
		InterceptorManager.getInstance().addInterceptor(iqSubscribeInterceptor);

		// Add the PEP handlers, this will trigger their initialize method
		iqPEPHandler.addHandler(new PEPActivityHandler());
		iqPEPHandler.addHandler(new PEPInboxHandler());

		Log.info("OneSocialWeb plugin has been loaded");
	}

	@Override
	public void destroyPlugin() {
		InterceptorManager.getInstance().removeInterceptor(messageInterceptor);

		IQRouter iqRouter = XMPPServer.getInstance().getIQRouter();
		iqRouter.removeHandler(iqPEPHandler);
		iqRouter.removeHandler(iqProfileQueryHandler);
		iqRouter.removeHandler(iqProfileUpdateHandler);
		iqRouter.removeHandler(iqRelationQueryHandler);
		iqRouter.removeHandler(iqRelationSetupHandler);
		iqRouter.removeHandler(iqRelationUpdateHandler);

		if (emFactory != null) {
			emFactory.close();
		}

		Log.info("OneSocialWeb plugin has been destroyed.");
	}

	/**
	 * Tries to identify the type of DB used in the Openfire server and to
	 * obtain the connection parameters. This parameters can later be passed
	 * when creating the entity manager factory to avoid reading this from the
	 * persistance.xml file.
	 * 
	 */

	private void setConnectionProperties() {
		String driver = "";
		String serverURL = "";
		String username = "";
		String password = "";

		String connectionProvider = JiveGlobals.getXMLProperty("connectionProvider.className");

		// Check if the Database in use is an external DB - default
		// configuration
		if (connectionProvider.trim().equalsIgnoreCase(DefaultConnectionProvider.class.getName())) {
			driver = JiveGlobals.getXMLProperty("database.defaultProvider.driver");
			serverURL = JiveGlobals.getXMLProperty("database.defaultProvider.serverURL");
			username = JiveGlobals.getXMLProperty("database.defaultProvider.username");
			password = JiveGlobals.getXMLProperty("database.defaultProvider.password");
		}
		// if not, it must be an embedded DB ...
		else {
			try {
				DatabaseMetaData metadata = DbConnectionManager.getConnection().getMetaData();

				driver = "org.hsqldb.jdbcDriver";
				serverURL = metadata.getURL();
				username = metadata.getUserName();
				password = "";
			} catch (SQLException e) {
				Log.error("Cannot recognise the Database.." + e.getMessage());
			}
		}
		// save the properties...
		connProperties.put("openjpa.ConnectionURL", serverURL);
		connProperties.put("openjpa.ConnectionDriverName", driver);
		connProperties.put("openjpa.ConnectionUserName", username);
		connProperties.put("openjpa.ConnectionPassword", password);

		connProperties.put("openjpa.Log", "DefaultLevel=INFO, Tool=INFO");
		connProperties.put("openjpa.jdbc.SynchronizeMappings", "buildSchema(ForeignKeys=true)");
		connProperties.put("openjpa.Multithreaded", "true");
	}

	private void prepareFolders() {
		prepareTempFolder();
		prepareUploadFolder();
	}
	
	private void prepareUploadFolder() {
		final String uploadPath = JiveGlobals.getProperty("onesocialweb.path.upload");
		if (uploadPath != null) {
			File uploadFolder = new File(uploadPath);
			if (uploadFolder.exists() && uploadFolder.canWrite()) {
				return;
			}
			JiveGlobals.deleteProperty("onesocialweb.path.upload");
			Log.error("Specified upload folder does not exist or read-only (" + uploadPath + ")");
		}

		File uploadFolder = new File(pluginDirectory, "upload");
		if (!uploadFolder.exists()) {
			if (uploadFolder.mkdirs() && uploadFolder.canWrite()) {
				Log.info("Created the upload folder at " + uploadFolder.getAbsolutePath());
				JiveGlobals.setProperty("onesocialweb.path.upload", uploadFolder.getAbsolutePath());
			}
		}
	}

	private void prepareTempFolder() {
		final String tempPath = JiveGlobals.getProperty("onesocialweb.path.temp");
		if (tempPath != null) {
			File tempFolder = new File(tempPath);
			if (tempFolder.exists() && tempFolder.canWrite()) {
				return;
			}

			JiveGlobals.deleteProperty("onesocialweb.path.temp");
			Log.error("Specified temp folder does not exist or is readonly (" + tempPath + ")");
		}

		File tempFolder = new File(pluginDirectory, "temp");
		if (!tempFolder.exists()) {
			if (tempFolder.mkdirs() && tempFolder.canWrite()) {
				Log.info("Created the temp folder at " + tempFolder.getAbsolutePath());
				JiveGlobals.setProperty("onesocialweb.path.temp", tempFolder.getAbsolutePath());
			}
		}
	}
} @SuppressWarnings("serial")
public class AccessDeniedException extends Exception {

	public AccessDeniedException(String message) {
		super(message);
	}
	
} public class PEPActivityHandler extends PEPNodeHandler {

	public static String NODE = "urn:xmpp:microblog:0";
	
	private XMPPServer server;
	
	private Map<String, PEPCommandHandler> handlers = new ConcurrentHashMap<String, PEPCommandHandler>();

	public PEPActivityHandler() {
		super("Handler for PEP microbloging PEP node");
	}
		
	@Override
	public String getNode() {
		return NODE;
	}

	@Override
	public void initialize(XMPPServer server) {
		super.initialize(server);
		this.server = server;
		addHandler(new ActivityPublishHandler());
		addHandler(new ActivityQueryHandler());
		addHandler(new ActivityDeleteHandler());
		addHandler(new ActivitySubscribeHandler());
		addHandler(new ActivityUnsubscribeHandler());
		addHandler(new ActivitySubscribersHandler());
		addHandler(new ActivitySubscriptionsHandler());
	}

	@SuppressWarnings("unchecked")
	@Override
	public IQ handleIQ(IQ packet) throws UnauthorizedException {
		// We search for a handler based on the element name
		// and process the packet with the handler if found.
        final Element childElement = packet.getChildElement();
        final List<Element> pubsubElements = childElement.elements();	

        if (pubsubElements != null && pubsubElements.size() > 0) {
        	Element actionElement = pubsubElements.get(0);
        	PEPCommandHandler handler = getHandler(actionElement.getName());
			if (handler != null) {
				return handler.handleIQ(packet);
			}
		}

		// No valid hanlder found. Return a feature not implemented
		// error.
		IQ result = IQ.createResultIQ(packet);
		result.setChildElement(packet.getChildElement().createCopy());
		result.setError(PacketError.Condition.feature_not_implemented);
		return result;
	}

	private void addHandler(PEPCommandHandler handler) {
		handler.initialize(server);
		handlers.put(handler.getCommand(), handler);
	}

	private PEPCommandHandler getHandler(String name) {
		return handlers.get(name);
	}

} public interface ActivityMessage {

	public String getSender();
	
	public void setSender(String fromJID);
	
	public String getRecipient();

	public void setRecipient(String toJID);

	public Date getReceived();

	public void setReceived(Date received);

	public ActivityEntry getActivity();

	public void setActivity(ActivityEntry activity);

} @Entity(name = "Messages")
public class PersistentActivityMessage implements ActivityMessage {

	@Basic
	private String sender;

	@Basic
	private String recipient;

	@Temporal(TemporalType.TIMESTAMP)
	private Date received;

	@OneToOne(cascade = CascadeType.ALL, targetEntity = PersistentActivityEntry.class, fetch = FetchType.EAGER)
	private ActivityEntry activity;

	@Override
	public String getSender() {
		return sender;
	}

	@Override
	public void setSender(String fromJID) {
		this.sender = fromJID;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.onesocialweb.openfire.model.ActivityMessage#getRecipient()
	 */
	@Override
	public String getRecipient() {
		return recipient;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.onesocialweb.openfire.model.ActivityMessage#setRecipient(java.lang
	 * .String)
	 */
	@Override
	public void setRecipient(String userJID) {
		this.recipient = userJID;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.onesocialweb.openfire.model.ActivityMessage#getReceived()
	 */
	public Date getReceived() {
		return received;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.onesocialweb.openfire.model.ActivityMessage#setReceived(java.util
	 * .Date)
	 */
	public void setReceived(Date received) {
		this.received = received;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.onesocialweb.openfire.model.ActivityMessage#getActivity()
	 */
	public ActivityEntry getActivity() {
		return activity;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.onesocialweb.openfire.model.ActivityMessage#setActivity(org.onesocialweb
	 * .model.activity.ActivityEntry)
	 */
	public void setActivity(ActivityEntry activity) {
		this.activity = activity;
	}

} @Entity(name="Subscriptions")
public class PersistentSubscription implements Subscription {
	
	@Basic
	private String subscriber;
	
	@Basic
	private String target;
	
	@Temporal(TemporalType.TIMESTAMP)
	private Date created;

	/* (non-Javadoc)
	 * @see org.onesocialweb.openfire.database.model.Subscription#getCreated()
	 */
	public Date getCreated() {
		return created;
	}

	/* (non-Javadoc)
	 * @see org.onesocialweb.openfire.database.model.Subscription#setCreated(java.util.Date)
	 */
	public void setCreated(Date created) {
		this.created = created;
	}

	/* (non-Javadoc)
	 * @see org.onesocialweb.openfire.database.model.Subscription#getSubscriber()
	 */
	public String getSubscriber() {
		return subscriber;
	}

	/* (non-Javadoc)
	 * @see org.onesocialweb.openfire.database.model.Subscription#setSubscriber(java.lang.String)
	 */
	public void setSubscriber(String subscriber) {
		this.subscriber = subscriber;
	}

	/* (non-Javadoc)
	 * @see org.onesocialweb.openfire.database.model.Subscription#getTarget()
	 */
	public String getTarget() {
		return target;
	}

	/* (non-Javadoc)
	 * @see org.onesocialweb.openfire.database.model.Subscription#setTarget(java.lang.String)
	 */
	public void setTarget(String target) {
		this.target = target;
	}

} public interface Subscription {

	public Date getCreated();

	public void setCreated(Date created);

	public String getSubscriber();

	public void setSubscriber(String subscriber);

	public String getTarget();

	public void setTarget(String target);

} public class PersistentAclFactory extends AclFactory {

	@Override
	public AclAction aclAction() {
		return new PersistentAclAction();
	}

	@Override
	public AclRule aclRule() {
		return new PersistentAclRule();
	}

	@Override
	public AclSubject aclSubject() {
		return new PersistentAclSubject();
	}

} @Entity(name="ActivityEntry")
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
@Table(name="Activities")
public class PersistentActivityEntry extends PersistentAtomEntry implements ActivityEntry {

	@OneToOne(cascade=CascadeType.ALL, targetEntity=PersistentActivityActor.class, fetch=FetchType.EAGER)
	private ActivityActor actor;
	
	@OneToMany(cascade=CascadeType.ALL, targetEntity=PersistentAclRule.class, fetch=FetchType.EAGER)
	private List<AclRule> rules = new ArrayList<AclRule>();

	@OneToMany(cascade=CascadeType.ALL, targetEntity=PersistentActivityObject.class, fetch=FetchType.EAGER)
	private List<ActivityObject> objects = new ArrayList<ActivityObject>();
	
	@OneToMany(cascade=CascadeType.ALL, targetEntity=PersistentActivityVerb.class, fetch=FetchType.EAGER)
	private List<ActivityVerb> verbs = new ArrayList<ActivityVerb>();
	
	@Override
	public void addObject(ActivityObject object) {
		this.objects.add(object);
	}

	@Override
	public void addVerb(ActivityVerb verb) {
		this.verbs.add(verb);
	}

	@Override
	public void addAclRule(AclRule rule) {
		this.rules.add(rule);
	}
	
	@Override
	public List<AclRule> getAclRules() {
		return rules;
	}

	@Override
	public ActivityActor getActor() {
		return actor;
	}

	@Override
	public List<ActivityObject> getObjects() {
		return objects;
	}

	@Override
	public List<ActivityVerb> getVerbs() {
		return verbs;
	}
	
	@Override
	public void removeObject(ActivityObject object) {
		this.objects.remove(object);
	}
	
	@Override
	public void removeVerb(ActivityVerb verb) {
		this.verbs.remove(verb);
	}
	
	@Override
	public void removeAclRule(AclRule rule) {
		this.rules.remove(rule);
	}

	@Override
	public void setAclRules(List<AclRule> rules) {
		this.rules = rules;
	}

	@Override
	public void setActor(final ActivityActor actor) {
		this.actor = actor;
	}

	@Override
	public void setObjects(List<ActivityObject> objects) {
		this.objects = objects;
	}

	@Override
	public void setVerbs(final List<ActivityVerb> verbs) {
		this.verbs = verbs;
	}
	
	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append("[ActivityEntry ");
		buffer.append(super.toString());
		if (actor != null) {
			buffer.append("actor:" + actor + " ");
		}
		for (ActivityVerb verb : verbs) {
			buffer.append(verb.toString());
		}
		for (ActivityObject object : objects) {
			buffer.append(object.toString());
		}
		for (AclRule rule : rules) {
			buffer.append(rule.toString());
		}
		buffer.append("]");
		return buffer.toString();
	}

	@Override
	public boolean hasAclRules() {
		return (rules != null && rules.size() > 0);
	}

	@Override
	public boolean hasActor() {
		return (actor != null);
	}

	@Override
	public boolean hasObjects() {
		return (objects != null && objects.size() > 0);
	}

	@Override
	public boolean hasVerbs() {
		return (verbs != null && verbs.size() > 0);
	}

} public class PersistentActivityFactory extends ActivityFactory {

	@Override
	public ActivityActor actor() {
		return new PersistentActivityActor();
	}

	@Override
	public ActivityEntry entry() {
		return new PersistentActivityEntry();
	}

	@Override
	public ActivityObject object() {
		return new PersistentActivityObject();
	}

	@Override
	public ActivityVerb verb() {
		return new PersistentActivityVerb();
	}

}
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;

	}

	/**
	 * 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();