Commit 74074cc7 authored by Ryan Graham's avatar Ryan Graham Committed by ryang

initial check-in


git-svn-id: http://svn.igniterealtime.org/svn/repos/messenger/trunk@1197 b35dd754-fafc-0310-a699-88a17e54d16e
parent 0b81d768
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="JiveMessenger">
<xs:complexType>
<xs:sequence>
<xs:element ref="User" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="User">
<xs:complexType>
<xs:sequence>
<xs:element ref="Jid" use="required"/>
<xs:element ref="Password" use="required"/>
<xs:element ref="Email"/>
<xs:element ref="Name"/>
<xs:element ref="CreationDate"/>
<xs:element ref="ModifiedDate"/>
<xs:element ref="Roster"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Jid" type="xs:string"/>
<xs:element name="Password" type="xs:string"/>
<xs:element name="Email" type="xs:string"/>
<xs:element name="Name" type="xs:string"/>
<xs:element name="CreationDate" type="jive-date"/>
<xs:element name="ModifiedDate" type="jive-date"/>
<xs:element name="Roster">
<xs:complexType>
<xs:sequence>
<xs:element ref="Item" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Item">
<xs:complexType>
<xs:sequence>
<xs:element ref="Group" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute ref="jid" use="required"/>
<xs:attribute name="askstatus" use="required"/>
<xs:attribute name="recvstatus" use="required"/>
<xs:attribute name="substatus" use="required"/>
<xs:attribute name="name"/>
</xs:complexType>
</xs:element>
<xs:element name="Group" type="xs:string"/>
<xs:attribute name="jid" type="xs:string"/>
<xs:attribute name="name" type="xs:string"/>
<xs:attribute name="askstatus">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="-1"/>
<xs:maxInclusive value="3"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="recvstatus">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="-1"/>
<xs:maxInclusive value="3"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="substatus">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="-1"/>
<xs:maxInclusive value="3"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:simpleType name="jive-date">
<xs:restriction base="xs:string">
</xs:restriction>
</xs:simpleType>
</xs:schema>
\ No newline at end of file
* The classes/messenger-user-schema.xsd.xml file is contained within lib/messenger-user-schema.jar file so it can be picked up during the plugin build process
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<class>org.jivesoftware.messenger.plugin.ImportExportPlugin</class>
<name>User Import Export</name>
<description>Allows the importing and exporting of Messenger user data.</description>
<author>Ryan Graham</author>
<version>1.0</version>
<minServerVersion>2.1.3</minServerVersion>
<adminconsole>
<tab id="tab-users">
<sidebar id="user-import-export" name="Import &amp; Export">
<item id="import-export-selection" name="User Import &amp; Export"
url="import-export-selection.jsp"
description="Allows the importing and exporting of Messenger user data." />
</sidebar>
</tab>
</adminconsole>
</plugin>
\ No newline at end of file
package org.jivesoftware.messenger.plugin;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.jivesoftware.messenger.JiveGlobals;
import org.jivesoftware.messenger.XMPPServer;
import org.jivesoftware.messenger.container.Plugin;
import org.jivesoftware.messenger.container.PluginManager;
import org.jivesoftware.messenger.roster.RosterItem;
import org.jivesoftware.messenger.roster.RosterItemProvider;
import org.jivesoftware.messenger.user.User;
import org.jivesoftware.messenger.user.UserAlreadyExistsException;
import org.jivesoftware.messenger.user.UserManager;
import org.jivesoftware.messenger.user.UserNotFoundException;
import org.jivesoftware.messenger.user.UserProvider;
import org.jivesoftware.util.Log;
import org.xmpp.packet.JID;
public class ImportExportPlugin implements Plugin {
private XMPPServer server;
private UserManager userManager;
private PluginManager pluginManager;
private static UserProvider provider;
private static String serverName;
public ImportExportPlugin() {
server = XMPPServer.getInstance();
userManager = server.getUserManager();
provider = userManager.getUserProvider();
serverName = server.getServerInfo().getName();
}
public String getName() {
return pluginManager.getName(this);
}
public String getDescription() {
return pluginManager.getDescription(this);
}
public String getAuthor() {
return pluginManager.getAuthor(this);
}
public String getVersion() {
return pluginManager.getVersion(this);
}
public void initializePlugin(PluginManager manager, File pluginDirectory) {
pluginManager = manager;
}
public void destroyPlugin() {
server = null;
userManager = null;
pluginManager = null;
provider = null;
}
public boolean isUserProviderReadOnly() {
return provider.isReadOnly();
}
public boolean exportUserData(String file) throws IOException {
if (!createExportDirectory()) {
return false;
}
if (!file.endsWith(".xml")) {
file += ".xml";
}
String exportFilePath = exportDirectory() + File.separator + file;
XMLWriter writer = new XMLWriter(new FileWriter(exportFilePath), OutputFormat.createPrettyPrint());
writer.write(exportUsers());
writer.close();
return true;
}
private boolean createExportDirectory() {
boolean isDirReady = true;
if (!(new File(exportDirectory())).exists()) {
isDirReady = (new File(exportDirectory())).mkdirs();
}
return isDirReady;
}
public boolean validateImportFile(String file) {
String importFilePath = exportDirectory() + File.separator + file;
try {
UserSchemaValidator validator = new UserSchemaValidator(importFilePath, "messenger-user-schema.xsd.xml");
return validator.validate();
}
catch (Exception e) {
Log.error(e);
return false;
}
}
public List importUserData(String file) throws MalformedURLException, DocumentException {
String importFilePath = exportDirectory() + File.separator + file;
SAXReader reader = new SAXReader();
Document document = reader.read(new File(importFilePath).toURL());
return importUsers(document);
}
private Document exportUsers() {
Document document = DocumentHelper.createDocument();
Element root = document.addElement("JiveMessenger");
Iterator users = userManager.getUsers().iterator();
while (users.hasNext()) {
User user = (User) users.next();
Element userElement = root.addElement("User");
String userName = user.getUsername();
userElement.addElement("Jid").addText(userName);
try {
userElement.addElement("Password").addText(provider.getPassword(userName));
}
catch (UserNotFoundException e) {
//this should never happen
Log.info("User not found: " + userName + ", setting password to their username");
userElement.addElement("Password").addText(userName);
}
userElement.addElement("Email").addText(user.getEmail() == null ? "" : user.getEmail());
userElement.addElement("Name").addText(user.getName() == null ? "" : user.getName());
//creation and modified datte are not used as part of the import process but are exported
//for historical purposes, should they be formatted differently?
userElement.addElement("CreationDate").addText(String.valueOf(user.getCreationDate().getTime()));
userElement.addElement("ModifiedDate").addText(String.valueOf(user.getModificationDate().getTime()));
Element rosterElement = userElement.addElement("Roster");
Iterator roster = user.getRoster().getRosterItems().iterator();
while (roster.hasNext()) {
RosterItem ri = (RosterItem) roster.next();
Element itemElement = rosterElement.addElement("Item");
itemElement.addAttribute("jid", removeDoman(ri.getJid()));
itemElement.addAttribute("askstatus", String.valueOf(ri.getAskStatus().getValue()));
itemElement.addAttribute("recvstatus", String.valueOf(ri.getRecvStatus().getValue()));
itemElement.addAttribute("substatus", String.valueOf(ri.getSubStatus().getValue()));
itemElement.addAttribute("name", ri.getNickname());
Element groupElement = itemElement.addElement("Group");
Iterator groups = ri.getGroups().iterator();
while (groups.hasNext()) {
String group = (String) groups.next();
groupElement.addText(group);
}
}
}
return document;
}
public List<String> importUsers(Document document) throws DocumentException {
List<String> duplicateUsers = new ArrayList<String>();
UserManager userManager = UserManager.getInstance();
RosterItemProvider rosterItemProvider = RosterItemProvider.getInstance();
Map<String, List> rosterMap = new HashMap<String, List>();
Element users = document.getRootElement();
Iterator usersIter = users.elementIterator("User");
while (usersIter.hasNext()) {
Element user = (Element) usersIter.next();
String userName = null;
String password = null;
String email = null;
String name = null;
List<RosterItem> rosterItems = new ArrayList<RosterItem>();
Iterator userElements = user.elementIterator();
while (userElements.hasNext()) {
Element userElement = (Element) userElements.next();
String nameElement = userElement.getName();
if ("Jid".equals(nameElement)) {
userName = userElement.getText();
}
else if ("Password".equals(nameElement)) {
password = userElement.getText();
}
else if ("Name".equals(nameElement)) {
name = userElement.getText();
}
else if ("Email".equals(nameElement)) {
email = userElement.getText();
}
else if ("Roster".equals(nameElement)) {
Iterator rosterIter = userElement.elementIterator("Item");
while (rosterIter.hasNext()) {
Element rosterElement = (Element) rosterIter.next();
String jid = rosterElement.attributeValue("jid");
String askstatus = rosterElement.attributeValue("askstatus");
String recvstatus = rosterElement.attributeValue("recvstatus");
String substatus = rosterElement.attributeValue("substatus");
String nickname = rosterElement.attributeValue("name");
List<String> groups = new ArrayList<String>();
Iterator groupIter = rosterElement.elementIterator("Group");
while (groupIter.hasNext()) {
Element group = (Element) groupIter.next();
groups.add(group.getText());
}
rosterItems.add(new RosterItem(new JID(jid + "@" + serverName),
RosterItem.SubType.getTypeFromInt(Integer.parseInt(substatus)),
RosterItem.AskType.getTypeFromInt(Integer.parseInt(askstatus)),
RosterItem.RecvType.getTypeFromInt(Integer.parseInt(recvstatus)),
nickname,
groups));
}
}
}
if ((userName != null) && (password != null)) {
try {
userManager.createUser(userName, password, name, email);
rosterMap.put(userName, rosterItems);
}
catch (UserAlreadyExistsException e) {
Log.info("User already exists: " + userName);
duplicateUsers.add(userName);
}
}
}
//this prevents a user from adding a non-existent user to their roster
Iterator i = rosterMap.keySet().iterator();
while (i.hasNext()) {
String userName = (String) i.next();
Iterator rosterIter = rosterMap.get(userName).iterator();
while (rosterIter.hasNext()) {
RosterItem ri = (RosterItem) rosterIter.next();
try {
userManager.getUser(removeDoman(ri.getJid()));
rosterItemProvider.createItem(userName, ri);
}
catch (UserNotFoundException e) {
Log.info("User '" + removeDoman(ri.getJid()) + "' not found, will not be added to '" + userName + "' roster.");
}
catch (UserAlreadyExistsException e) {
Log.info("User '" + removeDoman(ri.getJid()) + "' already belongs to '" + userName + "' roster.");
}
}
}
return duplicateUsers;
}
private static String removeDoman(JID jid) {
StringTokenizer tokens = new StringTokenizer(jid.toBareJID(), "@");
if (tokens.hasMoreTokens()) {
return tokens.nextToken();
}
return null;
}
public static String exportDirectory() {
return JiveGlobals.getMessengerHome() + File.separator + "export";
}
}
package org.jivesoftware.messenger.plugin;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.parsers.SAXParserFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import org.dom4j.io.SAXWriter;
import org.jivesoftware.util.Log;
import org.xml.sax.ContentHandler;
import org.xml.sax.ErrorHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXParseException;
import com.sun.msv.reader.util.GrammarLoader;
import com.sun.msv.reader.util.IgnoreController;
import com.sun.msv.verifier.DocumentDeclaration;
import com.sun.msv.verifier.Verifier;
public class UserSchemaValidator {
private Document doc;
private String schema;
public UserSchemaValidator(String usersFile, String schemaFile) throws MalformedURLException, DocumentException {
URL usersURL = new File(usersFile).toURL();
URL schemaURL = this.getClass().getClassLoader().getResource(schemaFile);
SAXReader reader = new SAXReader();
doc = reader.read(usersURL);
schema = schemaURL.toExternalForm();
}
public boolean validate() {
try {
SAXParserFactory saxFactory = SAXParserFactory.newInstance();
saxFactory.setNamespaceAware(true);
DocumentDeclaration docDeclaration = GrammarLoader.loadVGM(schema, new IgnoreController() {
public void error(Locator[] locations,
String message,
Exception exception) {
Log.error("ERROR: " + message);
}
public void error(Locator[] locations, String message) {
Log.error("WARNING: " + message);
}
}, saxFactory);
ValidatorErrorHandler validatorErrorHandler = new ValidatorErrorHandler();
Verifier verifier = new Verifier(docDeclaration, validatorErrorHandler);
SAXWriter writer = new SAXWriter((ContentHandler) verifier);
writer.setErrorHandler(validatorErrorHandler);
writer.write(doc);
if (verifier.isValid()) {
return true;
} else {
Log.error(doc.getName() + " is invalid.");
return false;
}
} catch (Exception e) {
Log.error(e);
return false;
}
}
private class ValidatorErrorHandler implements ErrorHandler {
public void error(SAXParseException e) {
Log.error("ERROR:" + e);
}
public void fatalError(SAXParseException e) {
Log.error("Fatal:" + e);
}
public void warning(SAXParseException e) {
Log.error("Warning:" + e);
}
}
}
<%@ page import="java.io.IOException,
java.util.*,
org.jivesoftware.admin.AdminPageBean,
org.jivesoftware.messenger.plugin.ImportExportPlugin,
org.jivesoftware.messenger.XMPPServer,
org.jivesoftware.util.ParamUtils"
%>
<%-- Define Administration Bean --%>
<jsp:useBean id="admin" class="org.jivesoftware.util.WebManager" />
<c:set var="admin" value="${admin.manager}" />
<% admin.init(request, response, session, application, out ); %>
<%
boolean exportUsers = request.getParameter("exportUsers") != null;
boolean success = request.getParameter("success") != null;
ImportExportPlugin plugin = (ImportExportPlugin) XMPPServer.getInstance().getPluginManager().getPlugin("userimportexport");
Map errors = new HashMap();
if (exportUsers) {
String file = ParamUtils.getParameter(request, "exportFile");
if ((file == null) || (file.length() <= 0)) {
errors.put("missingFile","missingFile");
}
else {
try {
//todo this could take some, redirect to a progress page?
if (plugin.exportUserData(file)) {
response.sendRedirect("export-user-data.jsp?success=true");
return;
}
else {
errors.put("fileNotCreated","fileNotCreated");
}
}
catch (IOException e) {
errors.put("IOException","IOException");
}
}
}
%>
<jsp:useBean id="pageinfo" scope="request" class="org.jivesoftware.admin.AdminPageBean" />
<% // Title of this page and breadcrumbs
String title = "Export User Data";
pageinfo.setTitle(title);
pageinfo.getBreadcrumbs().add(new AdminPageBean.Breadcrumb("Main", "index.jsp"));
pageinfo.getBreadcrumbs().add(new AdminPageBean.Breadcrumb(title, "export-user-data.jsp"));
pageinfo.setPageID("import-export-selection");
%>
<jsp:include page="top.jsp" flush="true" />
<jsp:include page="title.jsp" flush="true" />
<% if (errors.size() > 0) { %>
<div class="jive-error">
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td class="jive-icon"><img src="images/error-16x16.gif" width="16" height="16" border="0"></td>
<td class="jive-icon-label">
<% if (errors.containsKey("missingFile")) { %>
Missing or bad file name.
<% } else if (errors.containsKey("IOException") || errors.containsKey("fileNotCreated")) { %>
Couldn't create export file.
<% } %>
</td>
</tr>
</tbody>
</table>
</div>
<br>
<% } else if ("true".equals(request.getParameter("success"))) { %>
<div class="jive-success">
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td class="jive-icon"><img src="images/success-16x16.gif" width="16" height="16" border="0"></td>
<td class="jive-icon-label">User data successfully exported.</td>
</tr>
</tbody>
</table>
</div>
<br>
<% } %>
<form action="export-user-data.jsp?exportUsers" method="post">
<div class="jive-table">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<thead>
<tr>
<th>Export Properties</th>
</tr>
</thead>
<tr class="jive-even">
<td style="border-right:1px #ccc solid;">Export Location:</td>
</tr>
<tr class="jive-odd">
<td style="border-right:1px #ccc solid;"><%= plugin.exportDirectory() %></td>
</tr>
<tr class="jive-even">
<td style="border-right:1px #ccc solid;">Export File Name:</td>
</tr>
<tr class="jive-odd">
<td style="border-right:1px #ccc solid;">
<input type="text" size="30" maxlength="150" name="exportFile">
</td>
</tr>
</table>
</div>
<br><br>
<input type="submit" value="Export">
</form>
<jsp:include page="bottom.jsp" flush="true" />
\ No newline at end of file
<%@ page import="org.jivesoftware.admin.AdminPageBean,
org.jivesoftware.messenger.plugin.ImportExportPlugin,
org.jivesoftware.messenger.XMPPServer"
%>
<jsp:useBean id="pageinfo" scope="request" class="org.jivesoftware.admin.AdminPageBean" />
<% // Title of this page and breadcrumbs
String title = "Import Export Selection";
pageinfo.setTitle(title);
pageinfo.getBreadcrumbs().add(new AdminPageBean.Breadcrumb("Main", "index.jsp"));
pageinfo.getBreadcrumbs().add(new AdminPageBean.Breadcrumb(title, "import-export-selection.jsp"));
pageinfo.setPageID("import-export-selection");
ImportExportPlugin plugin = (ImportExportPlugin) XMPPServer.getInstance().getPluginManager().getPlugin("userimportexport");
%>
<jsp:include page="top.jsp" flush="true" />
<jsp:include page="title.jsp" flush="true" />
<p>
<% if (plugin.isUserProviderReadOnly()) { %>
Sorry, this feature does not work with your Messenger configuration (LDAP).
<% } else { %>
The import and export functions allow you to read data into and write user
data from your Jive Messenger installation.
<ul>
<li><a href="import-user-data.jsp">Import User Data</a></li>
<li><a href="export-user-data.jsp">Export User Data</a></li>
</ul>
<% } %>
<jsp:include page="bottom.jsp" flush="true" />
\ No newline at end of file
<%@ page import="java.io.IOException,
java.net.MalformedURLException,
java.util.*,
org.dom4j.DocumentException,
org.jivesoftware.admin.AdminPageBean,
org.jivesoftware.messenger.plugin.ImportExportPlugin,
org.jivesoftware.messenger.XMPPServer,
org.jivesoftware.util.ParamUtils"
%>
<%-- Define Administration Bean --%>
<jsp:useBean id="admin" class="org.jivesoftware.util.WebManager" />
<c:set var="admin" value="${admin.manager}" />
<% admin.init(request, response, session, application, out ); %>
<%
boolean importUsers = request.getParameter("importUsers") != null;
boolean success = request.getParameter("success") != null;
ImportExportPlugin plugin = (ImportExportPlugin) XMPPServer.getInstance().getPluginManager().getPlugin("userimportexport");
List duplicateUsers = new ArrayList();
Map errors = new HashMap();
if (importUsers) {
String file = ParamUtils.getParameter(request, "importFile");
if ((file == null) || (file.length() <= 0)) {
errors.put("badFile", "badFile");
}
else {
try {
//todo this could take some, redirect to a progress page?
if (plugin.validateImportFile(file)) {
duplicateUsers.addAll(plugin.importUserData(file));
if (duplicateUsers.size() == 0) {
response.sendRedirect("import-user-data.jsp?success=true");
return;
}
errors.put("userAlreadyExists", "userAlreadyExists");
}
else {
errors.put("invalidUserFile", "invalidUserFile");
}
}
catch (MalformedURLException e) {
errors.put("MalformedURLException", "MalformedURLException");
}
catch (DocumentException e) {
errors.put("DocumentException", "DocumentException");
}
}
}
%>
<jsp:useBean id="pageinfo" scope="request" class="org.jivesoftware.admin.AdminPageBean" />
<% // Title of this page and breadcrumbs
String title = "Import User Data";
pageinfo.setTitle(title);
pageinfo.getBreadcrumbs().add(new AdminPageBean.Breadcrumb("Main", "index.jsp"));
pageinfo.getBreadcrumbs().add(new AdminPageBean.Breadcrumb(title, "import-user-data.jsp"));
pageinfo.setPageID("import-export-selection");
%>
<jsp:include page="top.jsp" flush="true" />
<jsp:include page="title.jsp" flush="true" />
<% if (errors.size() > 0) { %>
<div class="jive-error">
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td class="jive-icon"><img src="images/error-16x16.gif" width="16" height="16" border="0"></td>
<td class="jive-icon-label">
<% if (errors.containsKey("MalformedURLException") || errors.containsKey("badFile")) { %>
Missing or bad file name.
<% } else if (errors.containsKey("DocumentException")) { %>
Import failed.
<% } else if (errors.containsKey("invalidUserFile")) { %>
The import file does not match the user schema.
<% } else if (errors.containsKey("userAlreadyExists")) { %>
The following users are already exist in the system and were not loaded:<br>
<%
Iterator iter = duplicateUsers.iterator();
while (iter.hasNext()) {
String username = (String) iter.next();
%><%= username %><%
if (iter.hasNext()) {
%>,&nbsp;<%
} else {
%>.<%
}
}
} %>
</td>
</tr>
</tbody>
</table>
</div>
<br>
<% } else if ("true".equals(request.getParameter("success"))) { %>
<div class="jive-success">
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td class="jive-icon"><img src="images/success-16x16.gif" width="16" height="16" border="0"></td>
<td class="jive-icon-label">All users added successfully.</td>
</tr>
</tbody>
</table>
</div>
<br>
<% } %>
<form action="import-user-data.jsp?importUsers" method="post">
<div class="jive-table">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<thead>
<tr>
<th>Import Properties</th>
</tr>
</thead>
<tr class="jive-even">
<td style="border-right:1px #ccc solid;">Import Location:</td>
</tr>
<tr class="jive-odd">
<td style="border-right:1px #ccc solid;"><%= plugin.exportDirectory() %></td>
</tr>
<tr class="jive-even">
<td style="border-right:1px #ccc solid;">Import File Name:</td>
</tr>
<tr class="jive-odd">
<td style="border-right:1px #ccc solid;"><input type="text" size="30" maxlength="150" name="importFile"></td>
</tr>
</table>
</div>
<br><br>
<input type="submit" value="Import">
</form>
<jsp:include page="bottom.jsp" flush="true" />
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment