ClearspaceVCardProvider.java 20.6 KB
Newer Older
1 2 3 4
/**
 * $Revision$
 * $Date$
 *
5
 * Copyright (C) 2005-2008 Jive Software. All rights reserved.
6
 *
7 8 9 10 11 12 13 14 15 16 17
 * 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.
18 19 20
 */
package org.jivesoftware.openfire.clearspace;

21 22 23 24
import static org.jivesoftware.openfire.clearspace.ClearspaceManager.HttpType.GET;
import static org.jivesoftware.openfire.clearspace.ClearspaceManager.HttpType.POST;
import static org.jivesoftware.openfire.clearspace.ClearspaceVCardTranslator.Action.DELETE;
import static org.jivesoftware.openfire.clearspace.ClearspaceVCardTranslator.Action.NO_ACTION;
25
import static org.jivesoftware.openfire.clearspace.WSUtils.getReturn;
26 27 28 29 30 31

import java.util.List;

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
32 33 34
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
35 36
import org.jivesoftware.openfire.vcard.VCardProvider;
import org.jivesoftware.util.AlreadyExistsException;
37
import org.jivesoftware.util.NotFoundException;
38 39
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
40 41

/**
42 43 44 45 46
 * The ClearspaceLockOutProvider uses the UserService web service inside of Clearspace
 * to retrieve, edit and delete user information from Clearspace.  With this information the provider
 * builds user's VCard.
 *
 * @author Gabriel Guardincerri
47 48
 */
public class ClearspaceVCardProvider implements VCardProvider {
49

50
	private static final Logger Log = LoggerFactory.getLogger(ClearspaceVCardProvider.class);
51 52

    protected static final String PROFILE_URL_PREFIX = "profileService/";
53
    protected static final String PROFILE_FIELDS_URL_PREFIX = "profileFieldService/";
54 55 56
    protected static final String AVATAR_URL_PREFIX = "avatarService/";

    private Boolean avatarReadOnly;
57
    private boolean fieldsIDLoaded;
58 59 60 61

    public ClearspaceVCardProvider() {
    }

62 63 64 65 66 67 68
    /**
     * Loads the VCard with information from CS. It uses information from the user, the user profile and the avatar.
     * With this 3 sources of informations it builds the VCard.
     *
     * @param username username of user to load VCard of
     * @return the user's VCard
     */
69
    @Override
70
    public Element loadVCard(String username) {
71 72
        // if the fields id are not loaded
        if (!fieldsIDLoaded) {
73 74 75 76 77 78 79 80 81 82
            synchronized (this) {
                if (!fieldsIDLoaded) {
                    // try to load them
                    loadDefaultProfileFields();
                    // if still not loaded then the operation could no be perform
                    if (!fieldsIDLoaded) {
                        // It is not supported exception, wrap it into an UnsupportedOperationException
                        throw new UnsupportedOperationException("Error loading the profiles IDs");
                    }
                }
83 84 85
            }
        }

86 87 88 89 90
        try {

            // Gets the user
            User user = UserManager.getInstance().getUser(username);

91
            long userID = ClearspaceManager.getInstance().getUserID(username);
92

93 94
            // Gets the profiles information
            Element profiles = getProfiles(userID);
95

96
            // Gets the avatar information
97 98 99
            Element avatar = getAvatar(userID);

            // Translate the response
100
            return ClearspaceVCardTranslator.getInstance().translateClearspaceInfo(profiles, user, avatar);
101

102 103
        } catch (UnsupportedOperationException e) {
            throw e;
104 105
        } catch (Exception e) {
            // It is not supported exception, wrap it into an UnsupportedOperationException
106
            throw new UnsupportedOperationException("Error loading the vCard", e);
107 108 109
        }
    }

110 111 112 113 114 115 116 117 118 119 120
    /**
     * Creates the user's VCard. CS always has some information of users. So creating it is actually updating.
     * Throws an UnsupportedOperationException if Clearspace can't save some changes. Returns the VCard after the change.
     *
     * @param username     the username
     * @param vCardElement the vCard to save.
     * @return vCard as it is after the provider has a chance to adjust it.
     * @throws AlreadyExistsException        it's never throw by this implementation
     * @throws UnsupportedOperationException if the provider does not support the
     *                                       operation.
     */
121
    @Override
122
    public Element createVCard(String username, Element vCardElement) throws AlreadyExistsException {
123
        return saveVCard(username, vCardElement);
124 125
    }

126 127 128 129 130 131 132 133 134 135 136
    /**
     * Updates the user vcard in Clearspace. Throws an UnsupportedOperationException if Clearspace can't
     * save some changes. Returns the VCard after the change.
     *
     * @param username     the username.
     * @param vCardElement the vCard to save.
     * @return vCard as it is after the provider has a chance to adjust it.
     * @throws NotFoundException             if the vCard to update does not exist.
     * @throws UnsupportedOperationException if the provider does not support the
     *                                       operation.
     */
137
    @Override
138
    public Element updateVCard(String username, Element vCardElement) throws NotFoundException {
139 140 141
        return saveVCard(username, vCardElement);
    }

142 143 144 145 146
    /**
     * Always return false since Clearspace always support some changes.
     *
     * @return true
     */
147
    @Override
148 149 150 151 152 153 154 155 156 157 158 159
    public boolean isReadOnly() {
        // Return always false, since some changes are always allowed
        return false;
    }

    /**
     * Returns true the user can modify the Avatar of Clearspace.
     *
     * @return if the Avatar of Clearspace can be modified.
     */
    private boolean isAvatarReadOnly() {
        if (avatarReadOnly == null) {
160 161 162 163 164
            synchronized (this) {
                if (avatarReadOnly == null) {
                    loadAvatarReadOnly();
                }
            }
165
        }
166
        return avatarReadOnly == null ? false : avatarReadOnly;
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
    }

    /**
     * Saves the vCard of the user. First check if the change can be made,
     * if not throws an UnsupportedOperationException.
     * The VCard information is divided into 3 parts. First the preferred
     * email and the user full name are stored into Clearspace user information.
     * Second the avatar is stored into Clearspace avatar information. If the avatar was
     * new or it was modified, a new avatar is created in Clearspace. If the avatar was
     * deleted, in Clearspace the user won't have an active avatar.
     *
     * @param username     the username of the user to update the avatar info to
     * @param vCardElement the vCard with the new information
     * @return the VCard with the updated information
     * @throws UnsupportedOperationException if the provider does not support some changes.
     */
183
    private Element saveVCard(String username, Element vCardElement) {
184 185 186
        if (Log.isDebugEnabled()) {
            Log.debug("Saving VCARD: " + vCardElement.asXML());
        }
187

188
        if (!fieldsIDLoaded) {
189 190 191 192 193 194 195 196 197 198
            synchronized (this) {
                if (!fieldsIDLoaded) {
                    // try to load them
                    loadDefaultProfileFields();
                    // if still not loaded then the operation could no be perform
                    if (!fieldsIDLoaded) {
                        // It is not supported exception, wrap it into an UnsupportedOperationException
                        throw new UnsupportedOperationException("Error loading the profiles IDs");
                    }
                }
199 200
            }
        }
201 202

        try {
203

204
            long userID = ClearspaceManager.getInstance().getUserID(username);
205
            ClearspaceUserProvider userProvider = (ClearspaceUserProvider) UserManager.getUserProvider();
206

207 208 209 210
            // Gets the user params that can be used to update it
            Element userUpdateParams = userProvider.getUserUpdateParams(username);
            // Gets the element that contains the user information
            Element userElement = userUpdateParams.element("user");
211

212 213 214
            // Gets the profiles params that can be used to update them
            Element profilesUpdateParams = getProfilesUpdateParams(userID);
            //Element profilesElement = profilesUpdateParams.element("profiles");
215

216 217
            // Get the avatar params that can be used to create it. It doesn't have an avatar sub element.
            Element avatarCreateParams = getAvatarCreateParams(userID);
218

219 220 221
            // Modifies the profile, user and avatar elements according to the VCard information.
            ClearspaceVCardTranslator.Action[] actions;
            actions = ClearspaceVCardTranslator.getInstance().translateVCard(vCardElement, profilesUpdateParams, userElement, avatarCreateParams);
222

223 224 225
            // Throws an exception if the changes implies to modify something that is read only
            if ((actions[1] != NO_ACTION && userProvider.isReadOnly()) || (actions[2] != NO_ACTION && isAvatarReadOnly())) {
                throw new UnsupportedOperationException("ClearspaceVCardProvider: Invalid vcard changes.");
226 227
            }

228 229 230
            // Updates the profiles
            if (actions[0] != NO_ACTION) {
                updateProfiles(profilesUpdateParams);
231 232
            }

233 234 235
            // Updates the user
            if (actions[1] != NO_ACTION) {
                userProvider.updateUser(userUpdateParams);
236 237
            }

238 239 240 241 242 243 244 245 246
            // Updates the avatar
            if (actions[2] != NO_ACTION) {
                // Set no active avatar to delete
                if (actions[2] == DELETE) {
                    setActiveAvatar(userID, -1);
                } else {
                    // else it was created or updated, on both cases it needs to be created and assigned as the active avatar.
                    long avatarID = createAvatar(avatarCreateParams);
                    setActiveAvatar(userID, avatarID);
247 248
                }
            }
249 250 251 252
        } catch (UnsupportedOperationException e) {
            throw e;
        } catch (Exception e) {
            throw new UnsupportedOperationException("Error saving the VCard", e);
253 254 255 256 257
        }

        return loadVCard(username);
    }

258 259 260 261 262
    /**
     * Deletes the profiles and avatar information of the user.
     *
     * @param username the username.
     */
263
    @Override
264 265 266 267 268 269 270 271
    public void deleteVCard(String username) {
        ClearspaceUserProvider userProvider = (ClearspaceUserProvider) UserManager.getUserProvider();
        if (userProvider.isReadOnly() || isAvatarReadOnly()) {
            // Reject the operation since the provider is read-only
            throw new UnsupportedOperationException();
        }

        long userID;
272
        try {
273
            userID = ClearspaceManager.getInstance().getUserID(username);
274 275 276 277
        } catch (UserNotFoundException gnfe) {
            // it is OK, the user doesn't exist "anymore"
            return;
        }
278

279
        deleteAvatar(userID);
280

281 282 283 284 285 286 287 288 289 290 291
        deleteProfiles(userID);
    }

    /**
     * Deletes the profiles of the user.
     *
     * @param userID the user id.
     */
    private void deleteProfiles(long userID) {
        try {
            String path = PROFILE_URL_PREFIX + "profiles/" + userID;
292
            ClearspaceManager.getInstance().executeRequest(ClearspaceManager.HttpType.DELETE, path);
293 294
        } catch (Exception e) {
            // It is not supported exception, wrap it into an UnsupportedOperationException
295
            throw new UnsupportedOperationException("Unexpected error", e);
296 297 298
        }
    }

299 300 301 302 303 304
    /**
     * Deletes the avatar of the user.
     *
     * @param userID the user id.
     */
    private void deleteAvatar(long userID) {
305
        try {
306
            String path = AVATAR_URL_PREFIX + "avatar/" + userID;
307
            ClearspaceManager.getInstance().executeRequest(ClearspaceManager.HttpType.DELETE, path);
308 309
        } catch (Exception e) {
            // It is not supported exception, wrap it into an UnsupportedOperationException
310
            throw new UnsupportedOperationException("Unexpected error", e);
311 312 313
        }
    }

314 315 316 317 318 319 320 321 322
    /**
     * Makes the request to the webservice of Clearspace to update the profiles information.
     *
     * @param profilesUpdateParams the profiles params to use with the request.
     */
    private void updateProfiles(Element profilesUpdateParams) {
        // Try to save the profile changes
        try {
            String path = PROFILE_URL_PREFIX + "profiles";
323
            ClearspaceManager.getInstance().executeRequest(POST, path, profilesUpdateParams.asXML());
324 325 326
        } catch (Exception e) {
            // It is not supported exception, wrap it into an UnsupportedOperationException
            throw new UnsupportedOperationException("Unexpected error", e);
327 328 329
        }
    }

330 331 332 333 334 335 336 337 338 339 340 341
    /**
     * Set the active avatar of the user.
     *
     * @param userID   the userID
     * @param avatarID the avatarID
     */
    private void setActiveAvatar(long userID, long avatarID) {
        try {
            Document profilesDoc = DocumentHelper.createDocument();
            Element rootE = profilesDoc.addElement("setActiveAvatar");
            rootE.addElement("userID").setText(String.valueOf(userID));
            rootE.addElement("avatarID").setText(String.valueOf(avatarID));
342

343 344
            // Requests the user active avatar
            String path = AVATAR_URL_PREFIX + "activeAvatar/" + userID;
345

346
            ClearspaceManager.getInstance().executeRequest(POST, path, rootE.asXML());
347 348
        } catch (Exception e) {
            throw new UnsupportedOperationException("Error setting the user's " + userID + " active avatar " + avatarID, e);
349 350 351
        }
    }

352 353 354 355 356 357 358
    /**
     * Creates the avatar.
     *
     * @param avatarCreateParams the avatar information
     * @return the new avatarID
     */
    private long createAvatar(Element avatarCreateParams) {
359 360
        try {

361
            // Requests the user active avatar
362
            String path = AVATAR_URL_PREFIX + "avatars";
363
            Element avatar = ClearspaceManager.getInstance().executeRequest(POST, path, avatarCreateParams.asXML());
364 365 366 367

            return Long.valueOf(avatar.element("return").element("WSAvatar").elementTextTrim("id"));
        } catch (Exception e) {
            throw new UnsupportedOperationException("Error creating the avatar", e);
368
        }
369
    }
370

371 372 373 374 375 376 377
    /**
     * Returns the profiles of the user.
     *
     * @param userID the user id.
     * @return the profiles.
     */
    private Element getProfiles(long userID) {
378
        try {
379
            // Requests the user profile
380
            String path = PROFILE_URL_PREFIX + "profiles/" + userID;
381
            return ClearspaceManager.getInstance().executeRequest(GET, path);
382
        } catch (Exception e) {
383
            throw new UnsupportedOperationException("Error getting the profiles of user: " + userID, e);
384 385 386
        }
    }

387 388 389 390 391 392 393 394 395 396
    /**
     * Return the avatar of the user.
     *
     * @param userID the user id.
     * @return the avatar.
     */
    private Element getAvatar(long userID) {
        try {
            // Requests the user active avatar
            String path = AVATAR_URL_PREFIX + "activeAvatar/" + userID;
397
            return ClearspaceManager.getInstance().executeRequest(GET, path);
398 399
        } catch (Exception e) {
            throw new UnsupportedOperationException("Error getting the avatar of user: " + userID, e);
400 401 402
        }
    }

403 404 405 406
    /**
     * Tries to load the avatar read only info.
     */
    private void loadAvatarReadOnly() {
407 408 409
        try {
            // See if the is read only
            String path = AVATAR_URL_PREFIX + "userAvatarsEnabled";
410
            Element element = ClearspaceManager.getInstance().executeRequest(GET, path);
411 412
            avatarReadOnly = !Boolean.valueOf(getReturn(element));
        } catch (Exception e) {
413 414
            // if there is a problem, keep it null, maybe next call success.
            Log.warn("Error loading the avatar read only information", e);
415 416 417
        }
    }

418 419 420 421 422 423
    /**
     * Tries to load the default profiles fields info.
     */
    private void loadDefaultProfileFields() {
        try {
            String path = PROFILE_FIELDS_URL_PREFIX + "fields";
424
            Element defaultFields = ClearspaceManager.getInstance().executeRequest(GET, path);
425

426 427 428 429 430
            ClearspaceVCardTranslator.getInstance().initClearspaceFieldsId(defaultFields);
            fieldsIDLoaded = true;
        } catch (Exception e) {
            // if there is a problem, keep it null, maybe next call success.
            Log.warn("Error loading the default profiles fields", e);
431 432 433 434
        }

    }

435 436 437 438 439 440 441 442 443 444
    /**
     * Returns an element that can be used as a parameter to create an avatar.
     * This element has the user's avatar information.
     *
     * @param userID the id of user.
     * @return the element with that can be used to create an Avatar.
     * @throws UserNotFoundException if the userID is invalid.
     * @throws Exception             if there is problem doing the request.
     */
    private Element getAvatarCreateParams(long userID) throws Exception {
445

446 447
        // Creates response element
        Element avatarCreateParams = DocumentHelper.createDocument().addElement("createAvatar");
448

449 450
        // Gets current avatar
        Element avatarResponse = getAvatar(userID);
451

452
        // Translates from the response to create params
453 454
        Element avatar = avatarResponse.element("return");
        if (avatar != null) {
455 456 457 458
            // Sets the owner
            avatarCreateParams.addElement("ownerID").setText(avatar.elementText("owner"));

            // Sets the attachment values
459 460
            Element attachment = avatar.element("attachment");
            if (attachment != null) {
461 462 463
                avatarCreateParams.addElement("name").setText(attachment.elementText("name"));
                avatarCreateParams.addElement("contentType").setText(attachment.elementText("contentType"));
                avatarCreateParams.addElement("data").setText(attachment.elementText("data"));
464 465
            }
        }
466 467

        return avatarCreateParams;
468 469
    }

470 471 472 473 474 475 476 477 478 479 480
    /**
     * Returns an element that can be used as a parameter to modify the user profiles.
     * This element has the user's avatar information.
     *
     * @param userID the id of user.
     * @return the element with that can be used to create an Avatar.
     * @throws UserNotFoundException if the userID is invalid.
     * @throws Exception             if there is problem doing the request.
     */
    private Element getProfilesUpdateParams(long userID) throws Exception {
        Element params = DocumentHelper.createDocument().addElement("setProfile");
481

482 483
        // Add the userID param
        params.addElement("userID").setText(String.valueOf(userID));
484

485 486
        // Gets current profiles to merge the information
        Element currentProfile = getProfiles(userID);
487

488 489
        // Adds the current profiles to the new profile
        addProfiles(currentProfile, params);
490

491 492
        return params;
    }
493

494 495 496 497 498 499 500
    /**
     * Adds the profiles elements from one profile to the other one.
     *
     * @param currentProfile the profile with the information.
     * @param newProfiles    the profile to copy the information to.
     */
    private void addProfiles(Element currentProfile, Element newProfiles) {
501

502 503
        // Gets current fields
        List<Element> fields = (List<Element>) currentProfile.elements("return");
504

505 506 507 508 509 510 511 512 513 514 515 516
        // Iterate over current fields
        for (Element field : fields) {

            // Get the fieldID and values
            String fieldID = field.elementText("fieldID");
            // The value name of the value field could be value or values
            Element value = field.element("value");
            boolean multiValues = false;
            if (value == null) {
                value = field.element("values");
                if (value != null) {
                    multiValues = true;
517 518
                }
            }
519

520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536
            // Don't add empty field. Field id 0 means no field.
            if ("0".equals(fieldID)) {
                continue;
            }

            // Adds the profile to the new profiles element
            Element newProfile = newProfiles.addElement("profiles");
            newProfile.addElement("fieldID").setText(fieldID);
            // adds the value if it is not empty
            if (value != null) {
                if (multiValues) {
                    newProfile.addElement("values").setText(value.getText());
                } else {
                    newProfile.addElement("value").setText(value.getText());
                }
            }
        }
537 538
    }
}