org.jivesoftware.openfire.plugin.Xep227Exporter.java Source code

Java tutorial

Introduction

Here is the source code for org.jivesoftware.openfire.plugin.Xep227Exporter.java

Source

/**
 *
 * Copyright 2016 Anno van Vliet
 *
 * 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.jivesoftware.openfire.plugin;

import java.io.InputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Namespace;
import org.dom4j.QName;
import org.dom4j.io.DOMReader;
import org.jivesoftware.openfire.OfflineMessage;
import org.jivesoftware.openfire.OfflineMessageStore;
import org.jivesoftware.openfire.PrivateStorage;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthFactory;
import org.jivesoftware.openfire.roster.RosterItem;
import org.jivesoftware.openfire.roster.RosterItem.AskType;
import org.jivesoftware.openfire.roster.RosterItem.RecvType;
import org.jivesoftware.openfire.roster.RosterItem.SubType;
import org.jivesoftware.openfire.roster.RosterItemProvider;
import org.jivesoftware.openfire.roster.RosterManager;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.openfire.vcard.VCardManager;
import org.jivesoftware.util.XMPPDateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;

import gnu.inet.encoding.Stringprep;
import gnu.inet.encoding.StringprepException;

/**
 * A In and Exporter which conforms to XEP-0227.
 *
 * @author Anno van Vliet
 *
 */
public class Xep227Exporter implements InExporter {

    /**
     * constants defining field and attribute names
     */
    private static final String V_CARD_NAME = "vCard";
    private static final String ASK_SUBSCRIBE_ENUM = "subscribe";
    private static final String STAMP_NAME = "stamp";
    private static final String DELAY_ELEMENT_NAME = "delay";
    private static final String FROM_NAME = "from";
    private static final String MESSAGE_ELEMENT_NAME = "message";
    private static final String OFFLINE_MESSAGES_ELEMENT_NAME = "offline-messages";
    private static final String GROUP_ELEMENT_NAME = "group";
    private static final String SUBSCRIPTION_NAME = "subscription";
    private static final String ASK_NAME = "ask";
    private static final String ITEM_ELEMENT_NAME = "item";
    private static final String QUERY_ELEMENT_NAME = "query";
    private static final String PASSWORD_NAME = "password";
    private static final String NAME_NAME = "name";
    private static final String USER_ELEMENT_NAME = "user";
    private static final String JID_NAME = "jid";
    private static final String HOST_ELEMENT_NAME = "host";
    private static final String SERVER_DATA_ELEMENT_NAME = "server-data";

    /**
     * the relevant namespaces
     */
    private static final String JABBER_CLIENT_NS = "jabber:client";
    private static final String VCARD_TEMP_NS = "vcard-temp";
    private static final String JABBER_IQ_ROSTER_NS = "jabber:iq:roster";
    private static final String URN_XMPP_PIE_0_NS = "urn:xmpp:pie:0";

    private static final Namespace JABBER_MSG_NS = new Namespace("", JABBER_CLIENT_NS);

    private static final Logger Log = LoggerFactory.getLogger(Xep227Exporter.class);

    private final String serverName;
    private final OfflineMessageStore offlineMessagesStore;
    private final VCardManager vCardManager;
    //private final PrivateStorage privateStorage;
    private final UserManager userManager;
    private final RosterItemProvider rosterItemProvider;

    private final DateFormat dateformater = new SimpleDateFormat(XMPPDateTimeFormat.XMPP_DATETIME_FORMAT,
            Locale.US);

    /**
     * 
     */
    public Xep227Exporter() {
        offlineMessagesStore = XMPPServer.getInstance().getOfflineMessageStore();
        serverName = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
        //TODO not yet implemented
        //privateStorage = XMPPServer.getInstance().getPrivateStorage();
        vCardManager = VCardManager.getInstance();

        userManager = UserManager.getInstance();
        rosterItemProvider = RosterManager.getRosterItemProvider();

    }

    /**
     * Constructor used for testing purposes.
     * 
     * @param serverName
     * @param offlineMessagesStore
     * @param vCardManager
     * @param privateStorage
     * @param userManager
     * @param rosterItemProvider
     */
    public Xep227Exporter(String serverName, OfflineMessageStore offlineMessagesStore, VCardManager vCardManager,
            PrivateStorage privateStorage, UserManager userManager, RosterItemProvider rosterItemProvider) {
        super();
        this.serverName = serverName;
        this.offlineMessagesStore = offlineMessagesStore;
        this.vCardManager = vCardManager;
        //this.privateStorage = privateStorage;
        this.userManager = userManager;
        this.rosterItemProvider = rosterItemProvider;
    }

    /* (non-Javadoc)
     * @see org.jivesoftware.openfire.plugin.Exporter#exportUsers(org.jivesoftware.openfire.user.UserManager)
     */
    @Override
    public Document exportUsers() {
        Log.debug("exportUsers");
        Document document = DocumentHelper.createDocument();

        Element root = document.addElement(SERVER_DATA_ELEMENT_NAME, URN_XMPP_PIE_0_NS);
        Element host = root.addElement(HOST_ELEMENT_NAME);

        host.addAttribute(JID_NAME, serverName);

        Collection<User> users = userManager.getUsers();
        for (User user : users) {

            String userName = user.getUsername();

            Element userElement = host.addElement(USER_ELEMENT_NAME);

            exportUser(userElement, user);

            exportOfflineMessages(serverName, userElement, userName);

            exportRoster(userElement, user);

            exportVCard(userElement, userName);

            exportPrivateStorage(userName, userElement);

        }

        return document;
    }

    /**
     * Adding heading of an user and its parameters
     * 
     * @param userElement
     *            DOM element
     * @param user
     *            User object
     */
    private void exportUser(Element userElement, User user) {

        String userName = user.getUsername();
        userElement.addAttribute(NAME_NAME, userName);

        try {
            String pw = AuthFactory.getPassword(userName);
            userElement.addAttribute(PASSWORD_NAME, pw);

        } catch (UserNotFoundException e) {
            Log.info("User " + userName + " not found, setting their password to their username");
            userElement.addAttribute(PASSWORD_NAME, userName);
        } catch (UnsupportedOperationException e) {
            Log.info("Unable to retrieve " + userName + " password, setting their password to their username");
            userElement.addAttribute(PASSWORD_NAME, userName);
        }

    }

    /**
     * Add roster and its groups to a DOM element
     * 
     * @param userElement
     *            DOM element
     * @param user
     *            User
     */
    private void exportRoster(Element userElement, User user) {
        Element rosterElement = userElement.addElement(QUERY_ELEMENT_NAME, JABBER_IQ_ROSTER_NS);

        Collection<RosterItem> roster = user.getRoster().getRosterItems();
        for (RosterItem ri : roster) {
            Element itemElement = rosterElement.addElement(ITEM_ELEMENT_NAME);

            itemElement.addAttribute(JID_NAME, ri.getJid().toBareJID());
            itemElement.addAttribute(NAME_NAME, ri.getNickname());
            itemElement.addAttribute(SUBSCRIPTION_NAME, ri.getSubStatus().getName());
            if (ri.getAskStatus() == AskType.SUBSCRIBE) {
                itemElement.addAttribute(ASK_NAME, ASK_SUBSCRIBE_ENUM);
            }

            /**
             * Adding groups
             */
            Element groupElement = itemElement.addElement(GROUP_ELEMENT_NAME);
            List<String> groups = ri.getGroups();
            for (String group : groups) {
                groupElement.addText(group);
            }

        }
    }

    /**
     * Adding offline messages, if there are some.
     * 
     * @param hostname
     *            host name
     * @param userElement
     *            DOM element
     * @param userName
     *            user name
     */
    @SuppressWarnings("unchecked")
    private void exportOfflineMessages(String hostname, Element userElement, String userName) {
        Collection<OfflineMessage> offlineMessages = offlineMessagesStore.getMessages(userName, false);

        if (!offlineMessages.isEmpty()) {
            Element offlineElement = userElement.addElement(OFFLINE_MESSAGES_ELEMENT_NAME);

            for (OfflineMessage offMessage : offlineMessages) {

                Element messageElement = offlineElement.addElement(new QName(MESSAGE_ELEMENT_NAME, JABBER_MSG_NS));
                for (Object att : offMessage.getElement().attributes()) {
                    Attribute attribute = (Attribute) att;
                    messageElement.addAttribute(attribute.getQName(), attribute.getValue());
                }

                for (Iterator<Element> iterator = offMessage.getElement().elementIterator(); iterator.hasNext();) {
                    Element element = iterator.next();
                    messageElement.add(element.createCopy(new QName(element.getName(),
                            (element.getNamespace() == Namespace.NO_NAMESPACE ? JABBER_MSG_NS
                                    : element.getNamespace()))));

                }

                /**
                 * Adding delay element
                 */
                Element delayElement = messageElement.addElement("delay", "urn:xmpp:delay");
                delayElement.addAttribute(FROM_NAME, hostname);
                delayElement.addAttribute("stamp", XMPPDateTimeFormat.format(offMessage.getCreationDate()));
                delayElement.addText("Offline Storage");
            }

        }

    }

    /**
     * Adding vcard element
     * 
     * @param userElement
     *            DOM element
     * @param userName
     *            user name
     */
    @SuppressWarnings("unchecked")
    private void exportVCard(Element userElement, String userName) {
        Element vCard = vCardManager.getVCard(userName);
        if (vCard != null) {
            Element vCardElement = userElement.addElement(V_CARD_NAME, VCARD_TEMP_NS);
            for (Iterator<Element> iterator = vCard.elementIterator(); iterator.hasNext();) {
                Element element = iterator.next();
                vCardElement.add(element.createCopy());

            }
        }
    }

    /**
     * Add all the private stored information (XEP-0049)
     * <b>Note: this method is not supported in the available openfire releases.
       *  
     * </b>  
     * @param userName User name
     * @param userElement User element
     */
    private void exportPrivateStorage(String userName, Element userElement) {
        //    Element result = privateStorage.getAll(userName);
        //    if (result.elements().size() > 0) {
        //      userElement.add(result.createCopy());
        //    }
    }

    /* (non-Javadoc)
     * @see org.jivesoftware.openfire.plugin.InExporter#validate()
     */
    @Override
    public boolean validate(InputStream file) {
        Log.debug("validate");

        org.w3c.dom.Document doc = new UserSchemaValidator(file, "pie.xsd", "jabber-iq-roster.xsd",
                "jabber-iq-private.xsd", "xml.xsd", "stanzaerror.xsd", "jabber-client.xsd").validateAndParse();

        return (doc != null);
    }

    /* (non-Javadoc)
     * @see org.jivesoftware.openfire.plugin.InExporter#importUsers(java.io.InputStream, java.lang.String, boolean)
     */
    @Override
    public List<String> importUsers(InputStream inputStream, String previousDomain,
            boolean isUserProviderReadOnly) {
        Log.debug("importUsers");

        DOMReader xmlReader = new DOMReader();
        Document doc = xmlReader.read(new UserSchemaValidator(inputStream).validateAndParse());

        return importUsers(doc, previousDomain, isUserProviderReadOnly);
    }

    /* (non-Javadoc)
     * @see org.jivesoftware.openfire.plugin.InExporter#importUsers(org.dom4j.Document, java.lang.String, boolean)
     */
    @SuppressWarnings("unchecked")
    private List<String> importUsers(Document document, String previousDomain, boolean isUserProviderReadOnly) {

        List<String> invalidUsers = new ArrayList<String>();

        Element hosts = document.getRootElement();

        Iterator<Element> hostsIter = hosts.elementIterator(HOST_ELEMENT_NAME);
        while (hostsIter.hasNext()) {
            Element host = hostsIter.next();

            Iterator<Element> usersIter = host.elementIterator(USER_ELEMENT_NAME);
            while (usersIter.hasNext()) {
                Element user = usersIter.next();

                importUser(user, previousDomain, isUserProviderReadOnly, invalidUsers);
            }
        }

        return invalidUsers;
    }

    /**
     * @param user
     * @param previousDomain
     * @param isUserProviderReadOnly
     * @param invalidUsers
     */
    @SuppressWarnings("unchecked")
    private void importUser(Element user, String previousDomain, boolean isUserProviderReadOnly,
            List<String> invalidUsers) {
        Log.debug("importUser");

        List<RosterItem> rosterItems = new ArrayList<RosterItem>();
        List<OfflineMessage> offlineMessages = new ArrayList<OfflineMessage>();
        Element vCardElement = null;

        String userName = user.attributeValue(NAME_NAME);
        String password = user.attributeValue(PASSWORD_NAME);

        Iterator<Element> userElements = user.elementIterator();
        while (userElements.hasNext()) {
            Element userElement = userElements.next();

            String nameElement = userElement.getName();
            if (OFFLINE_MESSAGES_ELEMENT_NAME.equals(nameElement)) {

                importOffLineMessages(userElement, offlineMessages);

            } else if (QUERY_ELEMENT_NAME.equals(nameElement)
                    && JABBER_IQ_ROSTER_NS.equals(userElement.getNamespaceURI())) {

                importUserRoster(userElement, rosterItems, previousDomain);

            } else if (V_CARD_NAME.equals(nameElement) && VCARD_TEMP_NS.equals(userElement.getNamespaceURI())) {

                vCardElement = userElement;

            }
        }

        if (userName != null) {
            try {
                userName = Stringprep.nodeprep(userName);

                if (!isUserProviderReadOnly && (password != null)) {
                    userManager.createUser(userName, password, userName, null);
                }

                if (!isUserProviderReadOnly && vCardElement != null) {
                    try {
                        vCardManager.setVCard(userName, vCardElement);
                    } catch (Exception e) {
                        Log.warn("Error updating VCard:" + userName + ":" + e.getMessage());
                        Log.debug("", e);

                    }
                }

                // Check to see user exists before adding their roster, this is for
                // read-only user providers.
                userManager.getUser(userName);
                for (RosterItem ri : rosterItems) {
                    rosterItemProvider.createItem(userName, ri);
                }
                for (OfflineMessage offlineMessage : offlineMessages) {
                    offlineMessagesStore.addMessage(offlineMessage);
                }
            } catch (StringprepException se) {
                Log.info("Invalid username " + userName);
                invalidUsers.add(userName);
            } catch (UserAlreadyExistsException e) {
                Log.info("User already exists " + userName);
                invalidUsers.add(userName);
            } catch (UserNotFoundException e) {
                Log.info("User not found " + userName);
                invalidUsers.add(userName);
            } catch (Exception e) {
                Log.warn("Error updating User:" + userName + ":" + e.getLocalizedMessage());
                invalidUsers.add(userName);
            }
        }

    }

    /**
     * @param userElement
     * @param rosterItems
     * @param previousDomain
     */
    @SuppressWarnings("unchecked")
    private void importUserRoster(Element userElement, List<RosterItem> rosterItems, String previousDomain) {
        Log.debug("importUserRoster");

        Iterator<Element> rosterIter = userElement.elementIterator(ITEM_ELEMENT_NAME);

        while (rosterIter.hasNext()) {
            Element rosterElement = rosterIter.next();

            String jid = rosterElement.attributeValue(JID_NAME);
            String nickname = rosterElement.attributeValue(NAME_NAME);
            String substatus = rosterElement.attributeValue(SUBSCRIPTION_NAME);
            String askstatus = rosterElement.attributeValue(ASK_NAME);

            List<String> groups = new ArrayList<String>();
            Iterator<Element> groupIter = rosterElement.elementIterator(GROUP_ELEMENT_NAME);
            while (groupIter.hasNext()) {
                Element group = groupIter.next();
                String groupName = group.getText();
                if (groupName != null && groupName.trim().length() > 0) {
                    groups.add(groupName);
                }
            }

            // used for migration
            if (previousDomain != null && jid != null) {
                jid = jid.replace(previousDomain, serverName);
            }

            try {
                rosterItems.add(new RosterItem(new JID(jid),
                        (substatus != null ? SubType.valueOf(substatus.toUpperCase()) : SubType.BOTH),
                        (ASK_SUBSCRIBE_ENUM.equals(askstatus) ? AskType.SUBSCRIBE : AskType.NONE), RecvType.NONE,
                        nickname, groups));

            } catch (Exception e) {
                Log.warn("Adding User Roster failed:" + e.getLocalizedMessage());
                Log.debug("", e);

            }
        }

    }

    /**
     * @param userElement
     * @param offlineMessages
     */
    @SuppressWarnings("unchecked")
    private void importOffLineMessages(Element userElement, List<OfflineMessage> offlineMessages) {
        Log.debug("importOffLineMessages");
        // TODO Auto-generated method stub

        Iterator<Element> messageIter = userElement.elementIterator(MESSAGE_ELEMENT_NAME);

        while (messageIter.hasNext()) {
            Element msgElement = messageIter.next();

            String creationDateStr = null;
            if (msgElement.element(DELAY_ELEMENT_NAME) != null) {
                creationDateStr = msgElement.element(DELAY_ELEMENT_NAME).attributeValue(STAMP_NAME);
            }

            Date creationDate = null;
            try {
                if (creationDateStr != null) {
                    creationDate = dateformater.parse(creationDateStr);
                }
            } catch (ParseException e) {
                Log.warn("Date not parsable:" + e.getLocalizedMessage());
            }

            offlineMessages.add(new OfflineMessage(creationDate, msgElement));
        }
    }

}