Java tutorial
/** * $Revision$ * $Date$ * * Copyright (C) 2005-2008 Jive Software. All rights reserved. * * 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.clearspace; import static org.jivesoftware.openfire.clearspace.ClearspaceManager.HttpType.DELETE; import static org.jivesoftware.openfire.clearspace.ClearspaceManager.HttpType.GET; import static org.jivesoftware.openfire.clearspace.ClearspaceManager.HttpType.POST; import static org.jivesoftware.openfire.clearspace.ClearspaceManager.HttpType.PUT; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.Node; import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.user.User; import org.jivesoftware.openfire.user.UserAlreadyExistsException; import org.jivesoftware.openfire.user.UserCollection; import org.jivesoftware.openfire.user.UserNotFoundException; import org.jivesoftware.openfire.user.UserProvider; import org.jivesoftware.util.LocaleUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmpp.packet.JID; /** * The ClearspaceUserProvider uses the UserService and ProfileSearchService web service inside of Clearspace * to retrieve user information and to search for users from Clearspace. * * @author Gabriel Guardincerri */ public class ClearspaceUserProvider implements UserProvider { private static final Logger Log = LoggerFactory.getLogger(ClearspaceUserProvider.class); // The UserService webservice url prefix protected static final String USER_URL_PREFIX = "userService/"; // The ProfileSearchService webservice url prefix protected static final String SEARCH_URL_PREFIX = "profileSearchService/"; // Used to know it CS is a read only user provider private Boolean readOnly; public ClearspaceUserProvider() { } /** * Loads the user using the userService/users GET service. Only loads local users. * Throws a UserNotFoundException exception if the user could not be found. * * @param username the username of the user to load * @return a user instance with the user information * @throws UserNotFoundException if the user could not be found */ public User loadUser(String username) throws UserNotFoundException { // Translate the response return translate(getUserByUsername(username)); } /** * Creates user using the userService/users POST service. If Clearspace is a read only * provider throws an UnsupportedOperationException. If there is already a user with * the username throws a UserAlreadyExistsException. * * @param username the username of the user * @param password the password of the user * @param name the name of the user (optional) * @param email the email of the user * @return an instance of the created user * @throws UserAlreadyExistsException If there is already a user with the username * @throws UnsupportedOperationException If Clearspace is a read only provider */ public User createUser(String username, String password, String name, String email) throws UserAlreadyExistsException { if (isReadOnly()) { // Reject the operation since the provider is read-only throw new UnsupportedOperationException(); } try { String path = USER_URL_PREFIX + "users/"; // Creates the XML with the data Document groupDoc = DocumentHelper.createDocument(); Element rootE = groupDoc.addElement("createUserWithUser"); Element userE = rootE.addElement("user"); // adds the username Element usernameE = userE.addElement("username"); // Un-escape username. username = JID.unescapeNode(username); // Encode potentially non-ASCII characters username = URLUTF8Encoder.encode(username); usernameE.addText(username); // adds the name if it is not empty if (name != null && !"".equals(name.trim())) { Element nameE = userE.addElement("name"); nameE.addText(name); } // adds the password Element passwordE = userE.addElement("password"); passwordE.addText(password); // adds the the email Element emailE = userE.addElement("email"); emailE.addText(email); // new user are always enabled Element enabledE = userE.addElement("enabled"); enabledE.addText("true"); Element user = ClearspaceManager.getInstance().executeRequest(POST, path, groupDoc.asXML()); return translate(user); } catch (UserAlreadyExistsException uaee) { throw uaee; } catch (Exception e) { Log.error(LocaleUtils.getLocalizedString("admin.error"), e); } return new User(username, name, email, new Date(), new Date()); } /** * Deletes a user using the userService/users DELETE service. If the user is not found returns. * * @param username the username of the user to delete */ public void deleteUser(String username) { if (isReadOnly()) { // Reject the operation since the provider is read-only throw new UnsupportedOperationException(); } try { long userID = ClearspaceManager.getInstance().getUserID(username); String path = USER_URL_PREFIX + "users/" + userID; ClearspaceManager.getInstance().executeRequest(DELETE, path); } catch (UserNotFoundException gnfe) { // it is OK, the user doesn't exist "anymore" } catch (Exception e) { Log.error(e.getMessage(), e); } } /** * Gets the user count using the userService/users/count GET service. * * @return the user count */ public int getUserCount() { try { String path = USER_URL_PREFIX + "users/count"; Element element = ClearspaceManager.getInstance().executeRequest(GET, path); return Integer.valueOf(WSUtils.getReturn(element)); } catch (Exception e) { Log.error(e.getMessage(), e); } return 0; } /** * Gets all users using the userService/userNames GET service. * * @return a list of all users */ public Collection<User> getUsers() { Collection<String> usernames = getUsernames(); return new UserCollection(usernames.toArray(new String[usernames.size()])); } /** * Gets all usernames using the userService/userNames GET service. * * @return a list of all the usernames */ public Collection<String> getUsernames() { try { String path = USER_URL_PREFIX + "userNames"; Element element = ClearspaceManager.getInstance().executeRequest(GET, path); return WSUtils.parseUsernameArray(element); } catch (Exception e) { Log.error(e.getMessage(), e); } return new ArrayList<String>(); } /** * Gets a bounded list of users using the userService/userNames GET service. * * @param startIndex the start index * @param numResults the number of result * @return a bounded list of users */ public Collection<User> getUsers(int startIndex, int numResults) { String[] usernamesAll = getUsernames().toArray(new String[0]); Collection<String> usernames = new ArrayList<String>(); // Filters the user for (int i = startIndex; (i < startIndex + numResults) && (i < usernamesAll.length); i++) { usernames.add(usernamesAll[i]); } return new UserCollection(usernames.toArray(new String[usernames.size()])); } /** * Updates the name of the user using the userService/update service. * * @param username the username of the user * @param name the new name of the user * @throws UserNotFoundException if there is no user with that username */ public void setName(String username, String name) throws UserNotFoundException { if (isReadOnly()) { // Reject the operation since the provider is read-only throw new UnsupportedOperationException(); } try { // Creates the params Element userUpdateParams = getUserUpdateParams(username); // Modifies the attribute of the user String[] path = new String[] { "user", "name" }; WSUtils.modifyElementText(userUpdateParams, path, name); // Updates the user updateUser(userUpdateParams); } catch (UserNotFoundException e) { throw e; } catch (Exception e) { throw new UserNotFoundException(e); } } /** * Updates the email of the user using the userService/update service. * * @param username the username of the user * @param email the new email of the user * @throws UserNotFoundException if the user could not be found */ public void setEmail(String username, String email) throws UserNotFoundException { if (isReadOnly()) { // Reject the operation since the provider is read-only throw new UnsupportedOperationException(); } try { // Creates the params Element userUpdateParams = getUserUpdateParams(username); // Modifies the attribute of the user String[] path = new String[] { "user", "email" }; WSUtils.modifyElementText(userUpdateParams, path, email); // Updates the user updateUser(userUpdateParams); } catch (UserNotFoundException e) { throw e; } catch (Exception e) { throw new UserNotFoundException(e); } } /** * Updates the creationDate of the user using the userService/update service. * * @param username the username of the user * @param creationDate the new email of the user * @throws UserNotFoundException if the user could not be found */ public void setCreationDate(String username, Date creationDate) throws UserNotFoundException { if (isReadOnly()) { // Reject the operation since the provider is read-only throw new UnsupportedOperationException(); } try { // Creates the params Element userUpdateParams = getUserUpdateParams(username); // Modifies the attribute of the user String[] path = new String[] { "user", "creationDate" }; String newValue = WSUtils.formatDate(creationDate); WSUtils.modifyElementText(userUpdateParams, path, newValue); // Updates the user updateUser(userUpdateParams); } catch (UserNotFoundException e) { throw e; } catch (Exception e) { throw new UserNotFoundException(e); } } /** * Updates the modificationDate of the user using the userService/update service. * * @param username the username of the user * @param modificationDate the new modificationDate of the user * @throws UserNotFoundException if the user could not be found */ public void setModificationDate(String username, Date modificationDate) throws UserNotFoundException { if (isReadOnly()) { // Reject the operation since the provider is read-only throw new UnsupportedOperationException(); } try { // Creates the params Element userUpdateParams = getUserUpdateParams(username); // Modifies the attribute of the user String[] path = new String[] { "user", "modificationDate" }; String newValue = WSUtils.formatDate(modificationDate); WSUtils.modifyElementText(userUpdateParams, path, newValue); // Updates the user updateUser(userUpdateParams); } catch (UserNotFoundException e) { throw e; } catch (Exception e) { throw new UserNotFoundException(e); } } /** * Creates the parameters to send in a update user request based on the information of <code>username</code> * * @param username the username of the user * @return the parameters to send in a update user request * @throws UserNotFoundException if the user could not be found */ protected Element getUserUpdateParams(String username) throws UserNotFoundException { // Creates the user update params element Element userUpdateParams = DocumentHelper.createDocument().addElement("updateUser"); Element newUser = userUpdateParams.addElement("user"); // Gets the current user information Element currentUser = getUserByUsername(username).element("return"); List<Element> userAttributes = currentUser.elements(); for (Element userAttribute : userAttributes) { newUser.addElement(userAttribute.getName()).setText(userAttribute.getText()); } return userUpdateParams; } /** * Updates the user using the userService/users PUT service. * * @param userUpdateParams the request parameters * @throws UserNotFoundException if the user could not be found */ protected void updateUser(Element userUpdateParams) throws UserNotFoundException { try { String path = USER_URL_PREFIX + "users"; ClearspaceManager.getInstance().executeRequest(PUT, path, userUpdateParams.asXML()); } catch (UserNotFoundException e) { throw new UserNotFoundException("User not found."); } catch (Exception e) { // It is not supported exception, wrap it into an UnsupportedOperationException throw new UnsupportedOperationException("Unexpected error", e); } } /** * Clearsapce can search using three fields: username, name and email. * * @return a list of username, name and email * @throws UnsupportedOperationException */ public Set<String> getSearchFields() throws UnsupportedOperationException { return new LinkedHashSet<String>(Arrays.asList("Username", "Name", "Email")); } /** * Search for the user using the userService/search POST method. * * @param fields the fields to search on. * @param query the query string. * @return a Collection of users that match the search. * @throws UnsupportedOperationException if the provider does not * support the operation (this is an optional operation). */ public Collection<User> findUsers(Set<String> fields, String query) throws UnsupportedOperationException { // Creates the XML with the data Element paramsE = DocumentHelper.createDocument().addElement("search"); Element queryE = paramsE.addElement("query"); queryE.addElement("keywords").addText(query); queryE.addElement("searchUsername").addText("true"); queryE.addElement("searchName").addText("true"); queryE.addElement("searchEmail").addText("true"); queryE.addElement("searchProfile").addText("false"); List<String> usernames = new ArrayList<String>(); try { //TODO create a service on CS to get only the username field String path = SEARCH_URL_PREFIX + "searchProfile"; Element element = ClearspaceManager.getInstance().executeRequest(POST, path, paramsE.asXML()); List<Node> userNodes = (List<Node>) element.selectNodes("return"); for (Node userNode : userNodes) { String username = userNode.selectSingleNode("username").getText(); // Escape the username so that it can be used as a JID. username = JID.escapeNode(username); // Encode potentially non-ASCII characters username = URLUTF8Encoder.encode(username); usernames.add(username); } } catch (Exception e) { Log.error(e.getMessage(), e); } return new UserCollection(usernames.toArray(new String[usernames.size()])); } /** * Search for the user using the userService/searchBounded POST method. * * @param fields the fields to search on. * @param query the query string. * @param startIndex the starting index in the search result to return. * @param numResults the number of users to return in the search result. * @return a Collection of users that match the search. * @throws UnsupportedOperationException if the provider does not * support the operation (this is an optional operation). */ public Collection<User> findUsers(Set<String> fields, String query, int startIndex, int numResults) throws UnsupportedOperationException { // Creates the XML with the data Element paramsE = DocumentHelper.createDocument().addElement("searchBounded"); Element queryE = paramsE.addElement("query"); queryE.addElement("keywords").addText(query); queryE.addElement("searchUsername").addText("true"); queryE.addElement("searchName").addText("true"); queryE.addElement("searchEmail").addText("true"); queryE.addElement("searchProfile").addText("false"); paramsE.addElement("startIndex").addText(String.valueOf(startIndex)); paramsE.addElement("numResults").addText(String.valueOf(numResults)); List<String> usernames = new ArrayList<String>(); try { //TODO create a service on CS to get only the username field String path = SEARCH_URL_PREFIX + "searchProfile"; Element element = ClearspaceManager.getInstance().executeRequest(POST, path, paramsE.asXML()); List<Node> userNodes = (List<Node>) element.selectNodes("return"); for (Node userNode : userNodes) { String username = userNode.selectSingleNode("username").getText(); // Escape the username so that it can be used as a JID. username = JID.escapeNode(username); // Encode potentially non-ASCII characters username = URLUTF8Encoder.encode(username); usernames.add(username); } } catch (Exception e) { Log.error(e.getMessage(), e); } return new UserCollection(usernames.toArray(new String[usernames.size()])); } /** * Returns true if Clearspace is a read only user provider. * * @return true if Clearspace is a read only user provider */ public boolean isReadOnly() { if (readOnly == null) { synchronized (this) { if (readOnly == null) { loadReadOnly(); } } } // If it is null returns the most restrictive answer. return (readOnly == null ? false : readOnly); } /** * In Clearspace name is optional. * * @return false */ public boolean isNameRequired() { return false; } /** * In Clearspace email is required * * @return true */ public boolean isEmailRequired() { return true; } /** * Tries to load the read only attribute using the userService/isReadOnly service. */ private void loadReadOnly() { try { // See if the is read only String path = USER_URL_PREFIX + "isReadOnly"; Element element = ClearspaceManager.getInstance().executeRequest(GET, path); readOnly = Boolean.valueOf(WSUtils.getReturn(element)); } catch (Exception e) { // if there is a problem, keep it null, maybe in the next call success. Log.error("Failed checking #isReadOnly with Clearspace", e); } } /** * Translates a Clearspace xml user response into a Openfire User * * @param responseNode the Clearspace response * @return a User instance with its information */ private User translate(Node responseNode) { String username; String name = null; String email = null; Date creationDate = null; Date modificationDate = null; Node userNode = responseNode.selectSingleNode("return"); Node tmpNode; // Gets the username username = userNode.selectSingleNode("username").getText(); // Escape the username so that it can be used as a JID. username = JID.escapeNode(username); // Gets the name if it is visible boolean nameVisible = Boolean.valueOf(userNode.selectSingleNode("nameVisible").getText()); // Gets the name tmpNode = userNode.selectSingleNode("name"); if (tmpNode != null) { name = tmpNode.getText(); } // Gets the email if it is visible boolean emailVisible = Boolean.valueOf(userNode.selectSingleNode("emailVisible").getText()); // Gets the email tmpNode = userNode.selectSingleNode("email"); if (tmpNode != null) { email = tmpNode.getText(); } // Gets the creation date tmpNode = userNode.selectSingleNode("creationDate"); if (tmpNode != null) { creationDate = WSUtils.parseDate(tmpNode.getText()); } // Gets the modification date tmpNode = userNode.selectSingleNode("modificationDate"); if (tmpNode != null) { modificationDate = WSUtils.parseDate(tmpNode.getText()); } // Creates the user User user = new User(username, name, email, creationDate, modificationDate); user.setNameVisible(nameVisible); user.setEmailVisible(emailVisible); return user; } /** * Gets a user using the userService/users GET service. * * @param username the username of the user * @return the user xml response * @throws UserNotFoundException The user was not found in the Clearspace database or there was an error. */ private Element getUserByUsername(String username) throws UserNotFoundException { // Checks if the user is local if (username.contains("@")) { if (!XMPPServer.getInstance().isLocal(new JID(username))) { throw new UserNotFoundException("Cannot load user of remote server: " + username); } username = username.substring(0, username.lastIndexOf("@")); } try { // Un-escape username. username = JID.unescapeNode(username); // Encode potentially non-ASCII characters username = URLUTF8Encoder.encode(username); // Requests the user String path = USER_URL_PREFIX + "users/" + username; // return the response return ClearspaceManager.getInstance().executeRequest(GET, path); } catch (UserNotFoundException unfe) { throw unfe; } catch (Exception e) { // It is not supported exception, wrap it into an UserNotFoundException throw new UserNotFoundException("Error loading the user", e); } } }