1 /** 2 * $RCSfile$ 3 * $Revision$ 4 * $Date$ 5 * 6 * Copyright 2003-2007 Jive Software. 7 * 8 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21 package org.jivesoftware.smackx.packet; 22 23 import java.io.BufferedInputStream; 24 import java.io.File; 25 import java.io.FileInputStream; 26 import java.io.IOException; 27 import java.lang.reflect.Field; 28 import java.lang.reflect.Modifier; 29 import java.net.URL; 30 import java.security.MessageDigest; 31 import java.security.NoSuchAlgorithmException; 32 import java.util.HashMap; 33 import java.util.Iterator; 34 import java.util.Map; 35 import java.util.Map.Entry; 36 37 import org.jivesoftware.smack.Connection; 38 import org.jivesoftware.smack.PacketCollector; 39 import org.jivesoftware.smack.SmackConfiguration; 40 import org.jivesoftware.smack.XMPPException; 41 import org.jivesoftware.smack.filter.PacketIDFilter; 42 import org.jivesoftware.smack.packet.IQ; 43 import org.jivesoftware.smack.packet.Packet; 44 import org.jivesoftware.smack.packet.XMPPError; 45 import org.jivesoftware.smack.util.StringUtils; 46 47 /** 48 * A VCard class for use with the 49 * <a href="http://www.jivesoftware.org/smack/" target="_blank">SMACK jabber library</a>.<p> 50 * <p/> 51 * You should refer to the 52 * <a href="http://www.jabber.org/jeps/jep-0054.html" target="_blank">JEP-54 documentation</a>.<p> 53 * <p/> 54 * Please note that this class is incomplete but it does provide the most commonly found 55 * information in vCards. Also remember that VCard transfer is not a standard, and the protocol 56 * may change or be replaced.<p> 57 * <p/> 58 * <b>Usage:</b> 59 * <pre> 60 * <p/> 61 * // To save VCard: 62 * <p/> 63 * VCard vCard = new VCard(); 64 * vCard.setFirstName("kir"); 65 * vCard.setLastName("max"); 66 * vCard.setEmailHome("foo@fee.bar"); 67 * vCard.setJabberId("jabber@id.org"); 68 * vCard.setOrganization("Jetbrains, s.r.o"); 69 * vCard.setNickName("KIR"); 70 * <p/> 71 * vCard.setField("TITLE", "Mr"); 72 * vCard.setAddressFieldHome("STREET", "Some street"); 73 * vCard.setAddressFieldWork("CTRY", "US"); 74 * vCard.setPhoneWork("FAX", "3443233"); 75 * <p/> 76 * vCard.save(connection); 77 * <p/> 78 * // To load VCard: 79 * <p/> 80 * VCard vCard = new VCard(); 81 * vCard.load(conn); // load own VCard 82 * vCard.load(conn, "joe@foo.bar"); // load someone's VCard 83 * </pre> 84 * 85 * @author Kirill Maximov (kir@maxkir.com) 86 */ 87 public class VCard extends IQ { 88 89 /** 90 * Phone types: 91 * VOICE?, FAX?, PAGER?, MSG?, CELL?, VIDEO?, BBS?, MODEM?, ISDN?, PCS?, PREF? 92 */ 93 private Map<String, String> homePhones = new HashMap<String, String>(); 94 private Map<String, String> workPhones = new HashMap<String, String>(); 95 96 97 /** 98 * Address types: 99 * POSTAL?, PARCEL?, (DOM | INTL)?, PREF?, POBOX?, EXTADR?, STREET?, LOCALITY?, 100 * REGION?, PCODE?, CTRY? 101 */ 102 private Map<String, String> homeAddr = new HashMap<String, String>(); 103 private Map<String, String> workAddr = new HashMap<String, String>(); 104 105 private String firstName; 106 private String lastName; 107 private String middleName; 108 109 private String emailHome; 110 private String emailWork; 111 112 private String organization; 113 private String organizationUnit; 114 115 private String photoMimeType; 116 private String photoBinval; 117 118 /** 119 * Such as DESC ROLE GEO etc.. see JEP-0054 120 */ 121 private Map<String, String> otherSimpleFields = new HashMap<String, String>(); 122 123 // fields that, as they are should not be escaped before forwarding to the server 124 private Map<String, String> otherUnescapableFields = new HashMap<String, String>(); 125 VCard()126 public VCard() { 127 } 128 129 /** 130 * Set generic VCard field. 131 * 132 * @param field value of field. Possible values: NICKNAME, PHOTO, BDAY, JABBERID, MAILER, TZ, 133 * GEO, TITLE, ROLE, LOGO, NOTE, PRODID, REV, SORT-STRING, SOUND, UID, URL, DESC. 134 */ getField(String field)135 public String getField(String field) { 136 return otherSimpleFields.get(field); 137 } 138 139 /** 140 * Set generic VCard field. 141 * 142 * @param value value of field 143 * @param field field to set. See {@link #getField(String)} 144 * @see #getField(String) 145 */ setField(String field, String value)146 public void setField(String field, String value) { 147 setField(field, value, false); 148 } 149 150 /** 151 * Set generic, unescapable VCard field. If unescabale is set to true, XML maybe a part of the 152 * value. 153 * 154 * @param value value of field 155 * @param field field to set. See {@link #getField(String)} 156 * @param isUnescapable True if the value should not be escaped, and false if it should. 157 */ setField(String field, String value, boolean isUnescapable)158 public void setField(String field, String value, boolean isUnescapable) { 159 if (!isUnescapable) { 160 otherSimpleFields.put(field, value); 161 } 162 else { 163 otherUnescapableFields.put(field, value); 164 } 165 } 166 getFirstName()167 public String getFirstName() { 168 return firstName; 169 } 170 setFirstName(String firstName)171 public void setFirstName(String firstName) { 172 this.firstName = firstName; 173 // Update FN field 174 updateFN(); 175 } 176 getLastName()177 public String getLastName() { 178 return lastName; 179 } 180 setLastName(String lastName)181 public void setLastName(String lastName) { 182 this.lastName = lastName; 183 // Update FN field 184 updateFN(); 185 } 186 getMiddleName()187 public String getMiddleName() { 188 return middleName; 189 } 190 setMiddleName(String middleName)191 public void setMiddleName(String middleName) { 192 this.middleName = middleName; 193 // Update FN field 194 updateFN(); 195 } 196 getNickName()197 public String getNickName() { 198 return otherSimpleFields.get("NICKNAME"); 199 } 200 setNickName(String nickName)201 public void setNickName(String nickName) { 202 otherSimpleFields.put("NICKNAME", nickName); 203 } 204 getEmailHome()205 public String getEmailHome() { 206 return emailHome; 207 } 208 setEmailHome(String email)209 public void setEmailHome(String email) { 210 this.emailHome = email; 211 } 212 getEmailWork()213 public String getEmailWork() { 214 return emailWork; 215 } 216 setEmailWork(String emailWork)217 public void setEmailWork(String emailWork) { 218 this.emailWork = emailWork; 219 } 220 getJabberId()221 public String getJabberId() { 222 return otherSimpleFields.get("JABBERID"); 223 } 224 setJabberId(String jabberId)225 public void setJabberId(String jabberId) { 226 otherSimpleFields.put("JABBERID", jabberId); 227 } 228 getOrganization()229 public String getOrganization() { 230 return organization; 231 } 232 setOrganization(String organization)233 public void setOrganization(String organization) { 234 this.organization = organization; 235 } 236 getOrganizationUnit()237 public String getOrganizationUnit() { 238 return organizationUnit; 239 } 240 setOrganizationUnit(String organizationUnit)241 public void setOrganizationUnit(String organizationUnit) { 242 this.organizationUnit = organizationUnit; 243 } 244 245 /** 246 * Get home address field 247 * 248 * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, 249 * LOCALITY, REGION, PCODE, CTRY 250 */ getAddressFieldHome(String addrField)251 public String getAddressFieldHome(String addrField) { 252 return homeAddr.get(addrField); 253 } 254 255 /** 256 * Set home address field 257 * 258 * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, 259 * LOCALITY, REGION, PCODE, CTRY 260 */ setAddressFieldHome(String addrField, String value)261 public void setAddressFieldHome(String addrField, String value) { 262 homeAddr.put(addrField, value); 263 } 264 265 /** 266 * Get work address field 267 * 268 * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, 269 * LOCALITY, REGION, PCODE, CTRY 270 */ getAddressFieldWork(String addrField)271 public String getAddressFieldWork(String addrField) { 272 return workAddr.get(addrField); 273 } 274 275 /** 276 * Set work address field 277 * 278 * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, 279 * LOCALITY, REGION, PCODE, CTRY 280 */ setAddressFieldWork(String addrField, String value)281 public void setAddressFieldWork(String addrField, String value) { 282 workAddr.put(addrField, value); 283 } 284 285 286 /** 287 * Set home phone number 288 * 289 * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF 290 * @param phoneNum phone number 291 */ setPhoneHome(String phoneType, String phoneNum)292 public void setPhoneHome(String phoneType, String phoneNum) { 293 homePhones.put(phoneType, phoneNum); 294 } 295 296 /** 297 * Get home phone number 298 * 299 * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF 300 */ getPhoneHome(String phoneType)301 public String getPhoneHome(String phoneType) { 302 return homePhones.get(phoneType); 303 } 304 305 /** 306 * Set work phone number 307 * 308 * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF 309 * @param phoneNum phone number 310 */ setPhoneWork(String phoneType, String phoneNum)311 public void setPhoneWork(String phoneType, String phoneNum) { 312 workPhones.put(phoneType, phoneNum); 313 } 314 315 /** 316 * Get work phone number 317 * 318 * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF 319 */ getPhoneWork(String phoneType)320 public String getPhoneWork(String phoneType) { 321 return workPhones.get(phoneType); 322 } 323 324 /** 325 * Set the avatar for the VCard by specifying the url to the image. 326 * 327 * @param avatarURL the url to the image(png,jpeg,gif,bmp) 328 */ setAvatar(URL avatarURL)329 public void setAvatar(URL avatarURL) { 330 byte[] bytes = new byte[0]; 331 try { 332 bytes = getBytes(avatarURL); 333 } 334 catch (IOException e) { 335 e.printStackTrace(); 336 } 337 338 setAvatar(bytes); 339 } 340 341 /** 342 * Removes the avatar from the vCard 343 * 344 * This is done by setting the PHOTO value to the empty string as defined in XEP-0153 345 */ removeAvatar()346 public void removeAvatar() { 347 // Remove avatar (if any) 348 photoBinval = null; 349 photoMimeType = null; 350 } 351 352 /** 353 * Specify the bytes of the JPEG for the avatar to use. 354 * If bytes is null, then the avatar will be removed. 355 * 'image/jpeg' will be used as MIME type. 356 * 357 * @param bytes the bytes of the avatar, or null to remove the avatar data 358 */ setAvatar(byte[] bytes)359 public void setAvatar(byte[] bytes) { 360 setAvatar(bytes, "image/jpeg"); 361 } 362 363 /** 364 * Specify the bytes for the avatar to use as well as the mime type. 365 * 366 * @param bytes the bytes of the avatar. 367 * @param mimeType the mime type of the avatar. 368 */ setAvatar(byte[] bytes, String mimeType)369 public void setAvatar(byte[] bytes, String mimeType) { 370 // If bytes is null, remove the avatar 371 if (bytes == null) { 372 removeAvatar(); 373 return; 374 } 375 376 // Otherwise, add to mappings. 377 String encodedImage = StringUtils.encodeBase64(bytes); 378 379 setAvatar(encodedImage, mimeType); 380 } 381 382 /** 383 * Specify the Avatar used for this vCard. 384 * 385 * @param encodedImage the Base64 encoded image as String 386 * @param mimeType the MIME type of the image 387 */ setAvatar(String encodedImage, String mimeType)388 public void setAvatar(String encodedImage, String mimeType) { 389 photoBinval = encodedImage; 390 photoMimeType = mimeType; 391 } 392 393 /** 394 * Return the byte representation of the avatar(if one exists), otherwise returns null if 395 * no avatar could be found. 396 * <b>Example 1</b> 397 * <pre> 398 * // Load Avatar from VCard 399 * byte[] avatarBytes = vCard.getAvatar(); 400 * <p/> 401 * // To create an ImageIcon for Swing applications 402 * ImageIcon icon = new ImageIcon(avatar); 403 * <p/> 404 * // To create just an image object from the bytes 405 * ByteArrayInputStream bais = new ByteArrayInputStream(avatar); 406 * try { 407 * Image image = ImageIO.read(bais); 408 * } 409 * catch (IOException e) { 410 * e.printStackTrace(); 411 * } 412 * </pre> 413 * 414 * @return byte representation of avatar. 415 */ getAvatar()416 public byte[] getAvatar() { 417 if (photoBinval == null) { 418 return null; 419 } 420 return StringUtils.decodeBase64(photoBinval); 421 } 422 423 /** 424 * Returns the MIME Type of the avatar or null if none is set 425 * 426 * @return the MIME Type of the avatar or null 427 */ getAvatarMimeType()428 public String getAvatarMimeType() { 429 return photoMimeType; 430 } 431 432 /** 433 * Common code for getting the bytes of a url. 434 * 435 * @param url the url to read. 436 */ getBytes(URL url)437 public static byte[] getBytes(URL url) throws IOException { 438 final String path = url.getPath(); 439 final File file = new File(path); 440 if (file.exists()) { 441 return getFileBytes(file); 442 } 443 444 return null; 445 } 446 getFileBytes(File file)447 private static byte[] getFileBytes(File file) throws IOException { 448 BufferedInputStream bis = null; 449 try { 450 bis = new BufferedInputStream(new FileInputStream(file)); 451 int bytes = (int) file.length(); 452 byte[] buffer = new byte[bytes]; 453 int readBytes = bis.read(buffer); 454 if (readBytes != buffer.length) { 455 throw new IOException("Entire file not read"); 456 } 457 return buffer; 458 } 459 finally { 460 if (bis != null) { 461 bis.close(); 462 } 463 } 464 } 465 466 /** 467 * Returns the SHA-1 Hash of the Avatar image. 468 * 469 * @return the SHA-1 Hash of the Avatar image. 470 */ getAvatarHash()471 public String getAvatarHash() { 472 byte[] bytes = getAvatar(); 473 if (bytes == null) { 474 return null; 475 } 476 477 MessageDigest digest; 478 try { 479 digest = MessageDigest.getInstance("SHA-1"); 480 } 481 catch (NoSuchAlgorithmException e) { 482 e.printStackTrace(); 483 return null; 484 } 485 486 digest.update(bytes); 487 return StringUtils.encodeHex(digest.digest()); 488 } 489 updateFN()490 private void updateFN() { 491 StringBuilder sb = new StringBuilder(); 492 if (firstName != null) { 493 sb.append(StringUtils.escapeForXML(firstName)).append(' '); 494 } 495 if (middleName != null) { 496 sb.append(StringUtils.escapeForXML(middleName)).append(' '); 497 } 498 if (lastName != null) { 499 sb.append(StringUtils.escapeForXML(lastName)); 500 } 501 setField("FN", sb.toString()); 502 } 503 504 /** 505 * Save this vCard for the user connected by 'connection'. Connection should be authenticated 506 * and not anonymous.<p> 507 * <p/> 508 * NOTE: the method is asynchronous and does not wait for the returned value. 509 * 510 * @param connection the Connection to use. 511 * @throws XMPPException thrown if there was an issue setting the VCard in the server. 512 */ save(Connection connection)513 public void save(Connection connection) throws XMPPException { 514 checkAuthenticated(connection, true); 515 516 setType(IQ.Type.SET); 517 setFrom(connection.getUser()); 518 PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(getPacketID())); 519 connection.sendPacket(this); 520 521 Packet response = collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); 522 523 // Cancel the collector. 524 collector.cancel(); 525 if (response == null) { 526 throw new XMPPException("No response from server on status set."); 527 } 528 if (response.getError() != null) { 529 throw new XMPPException(response.getError()); 530 } 531 } 532 533 /** 534 * Load VCard information for a connected user. Connection should be authenticated 535 * and not anonymous. 536 */ load(Connection connection)537 public void load(Connection connection) throws XMPPException { 538 checkAuthenticated(connection, true); 539 540 setFrom(connection.getUser()); 541 doLoad(connection, connection.getUser()); 542 } 543 544 /** 545 * Load VCard information for a given user. Connection should be authenticated and not anonymous. 546 */ load(Connection connection, String user)547 public void load(Connection connection, String user) throws XMPPException { 548 checkAuthenticated(connection, false); 549 550 setTo(user); 551 doLoad(connection, user); 552 } 553 doLoad(Connection connection, String user)554 private void doLoad(Connection connection, String user) throws XMPPException { 555 setType(Type.GET); 556 PacketCollector collector = connection.createPacketCollector( 557 new PacketIDFilter(getPacketID())); 558 connection.sendPacket(this); 559 560 VCard result = null; 561 try { 562 result = (VCard) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); 563 564 if (result == null) { 565 String errorMessage = "Timeout getting VCard information"; 566 throw new XMPPException(errorMessage, new XMPPError( 567 XMPPError.Condition.request_timeout, errorMessage)); 568 } 569 if (result.getError() != null) { 570 throw new XMPPException(result.getError()); 571 } 572 } 573 catch (ClassCastException e) { 574 System.out.println("No VCard for " + user); 575 } 576 577 copyFieldsFrom(result); 578 } 579 getChildElementXML()580 public String getChildElementXML() { 581 StringBuilder sb = new StringBuilder(); 582 new VCardWriter(sb).write(); 583 return sb.toString(); 584 } 585 copyFieldsFrom(VCard from)586 private void copyFieldsFrom(VCard from) { 587 Field[] fields = VCard.class.getDeclaredFields(); 588 for (Field field : fields) { 589 if (field.getDeclaringClass() == VCard.class && 590 !Modifier.isFinal(field.getModifiers())) { 591 try { 592 field.setAccessible(true); 593 field.set(this, field.get(from)); 594 } 595 catch (IllegalAccessException e) { 596 throw new RuntimeException("This cannot happen:" + field, e); 597 } 598 } 599 } 600 } 601 checkAuthenticated(Connection connection, boolean checkForAnonymous)602 private void checkAuthenticated(Connection connection, boolean checkForAnonymous) { 603 if (connection == null) { 604 throw new IllegalArgumentException("No connection was provided"); 605 } 606 if (!connection.isAuthenticated()) { 607 throw new IllegalArgumentException("Connection is not authenticated"); 608 } 609 if (checkForAnonymous && connection.isAnonymous()) { 610 throw new IllegalArgumentException("Connection cannot be anonymous"); 611 } 612 } 613 hasContent()614 private boolean hasContent() { 615 //noinspection OverlyComplexBooleanExpression 616 return hasNameField() 617 || hasOrganizationFields() 618 || emailHome != null 619 || emailWork != null 620 || otherSimpleFields.size() > 0 621 || otherUnescapableFields.size() > 0 622 || homeAddr.size() > 0 623 || homePhones.size() > 0 624 || workAddr.size() > 0 625 || workPhones.size() > 0 626 || photoBinval != null 627 ; 628 } 629 hasNameField()630 private boolean hasNameField() { 631 return firstName != null || lastName != null || middleName != null; 632 } 633 hasOrganizationFields()634 private boolean hasOrganizationFields() { 635 return organization != null || organizationUnit != null; 636 } 637 638 // Used in tests: 639 equals(Object o)640 public boolean equals(Object o) { 641 if (this == o) return true; 642 if (o == null || getClass() != o.getClass()) return false; 643 644 final VCard vCard = (VCard) o; 645 646 if (emailHome != null ? !emailHome.equals(vCard.emailHome) : vCard.emailHome != null) { 647 return false; 648 } 649 if (emailWork != null ? !emailWork.equals(vCard.emailWork) : vCard.emailWork != null) { 650 return false; 651 } 652 if (firstName != null ? !firstName.equals(vCard.firstName) : vCard.firstName != null) { 653 return false; 654 } 655 if (!homeAddr.equals(vCard.homeAddr)) { 656 return false; 657 } 658 if (!homePhones.equals(vCard.homePhones)) { 659 return false; 660 } 661 if (lastName != null ? !lastName.equals(vCard.lastName) : vCard.lastName != null) { 662 return false; 663 } 664 if (middleName != null ? !middleName.equals(vCard.middleName) : vCard.middleName != null) { 665 return false; 666 } 667 if (organization != null ? 668 !organization.equals(vCard.organization) : vCard.organization != null) { 669 return false; 670 } 671 if (organizationUnit != null ? 672 !organizationUnit.equals(vCard.organizationUnit) : vCard.organizationUnit != null) { 673 return false; 674 } 675 if (!otherSimpleFields.equals(vCard.otherSimpleFields)) { 676 return false; 677 } 678 if (!workAddr.equals(vCard.workAddr)) { 679 return false; 680 } 681 if (photoBinval != null ? !photoBinval.equals(vCard.photoBinval) : vCard.photoBinval != null) { 682 return false; 683 } 684 685 return workPhones.equals(vCard.workPhones); 686 } 687 hashCode()688 public int hashCode() { 689 int result; 690 result = homePhones.hashCode(); 691 result = 29 * result + workPhones.hashCode(); 692 result = 29 * result + homeAddr.hashCode(); 693 result = 29 * result + workAddr.hashCode(); 694 result = 29 * result + (firstName != null ? firstName.hashCode() : 0); 695 result = 29 * result + (lastName != null ? lastName.hashCode() : 0); 696 result = 29 * result + (middleName != null ? middleName.hashCode() : 0); 697 result = 29 * result + (emailHome != null ? emailHome.hashCode() : 0); 698 result = 29 * result + (emailWork != null ? emailWork.hashCode() : 0); 699 result = 29 * result + (organization != null ? organization.hashCode() : 0); 700 result = 29 * result + (organizationUnit != null ? organizationUnit.hashCode() : 0); 701 result = 29 * result + otherSimpleFields.hashCode(); 702 result = 29 * result + (photoBinval != null ? photoBinval.hashCode() : 0); 703 return result; 704 } 705 toString()706 public String toString() { 707 return getChildElementXML(); 708 } 709 710 //============================================================== 711 712 private class VCardWriter { 713 714 private final StringBuilder sb; 715 VCardWriter(StringBuilder sb)716 VCardWriter(StringBuilder sb) { 717 this.sb = sb; 718 } 719 write()720 public void write() { 721 appendTag("vCard", "xmlns", "vcard-temp", hasContent(), new ContentBuilder() { 722 public void addTagContent() { 723 buildActualContent(); 724 } 725 }); 726 } 727 buildActualContent()728 private void buildActualContent() { 729 if (hasNameField()) { 730 appendN(); 731 } 732 733 appendOrganization(); 734 appendGenericFields(); 735 appendPhoto(); 736 737 appendEmail(emailWork, "WORK"); 738 appendEmail(emailHome, "HOME"); 739 740 appendPhones(workPhones, "WORK"); 741 appendPhones(homePhones, "HOME"); 742 743 appendAddress(workAddr, "WORK"); 744 appendAddress(homeAddr, "HOME"); 745 } 746 appendPhoto()747 private void appendPhoto() { 748 if (photoBinval == null) 749 return; 750 751 appendTag("PHOTO", true, new ContentBuilder() { 752 public void addTagContent() { 753 appendTag("BINVAL", photoBinval); // No need to escape photoBinval, as it's already Base64 encoded 754 appendTag("TYPE", StringUtils.escapeForXML(photoMimeType)); 755 } 756 }); 757 } appendEmail(final String email, final String type)758 private void appendEmail(final String email, final String type) { 759 if (email != null) { 760 appendTag("EMAIL", true, new ContentBuilder() { 761 public void addTagContent() { 762 appendEmptyTag(type); 763 appendEmptyTag("INTERNET"); 764 appendEmptyTag("PREF"); 765 appendTag("USERID", StringUtils.escapeForXML(email)); 766 } 767 }); 768 } 769 } 770 appendPhones(Map<String, String> phones, final String code)771 private void appendPhones(Map<String, String> phones, final String code) { 772 Iterator<Map.Entry<String, String>> it = phones.entrySet().iterator(); 773 while (it.hasNext()) { 774 final Map.Entry<String,String> entry = it.next(); 775 appendTag("TEL", true, new ContentBuilder() { 776 public void addTagContent() { 777 appendEmptyTag(entry.getKey()); 778 appendEmptyTag(code); 779 appendTag("NUMBER", StringUtils.escapeForXML(entry.getValue())); 780 } 781 }); 782 } 783 } 784 appendAddress(final Map<String, String> addr, final String code)785 private void appendAddress(final Map<String, String> addr, final String code) { 786 if (addr.size() > 0) { 787 appendTag("ADR", true, new ContentBuilder() { 788 public void addTagContent() { 789 appendEmptyTag(code); 790 791 Iterator<Map.Entry<String, String>> it = addr.entrySet().iterator(); 792 while (it.hasNext()) { 793 final Entry<String, String> entry = it.next(); 794 appendTag(entry.getKey(), StringUtils.escapeForXML(entry.getValue())); 795 } 796 } 797 }); 798 } 799 } 800 appendEmptyTag(Object tag)801 private void appendEmptyTag(Object tag) { 802 sb.append('<').append(tag).append("/>"); 803 } 804 appendGenericFields()805 private void appendGenericFields() { 806 Iterator<Map.Entry<String, String>> it = otherSimpleFields.entrySet().iterator(); 807 while (it.hasNext()) { 808 Map.Entry<String, String> entry = it.next(); 809 appendTag(entry.getKey().toString(), 810 StringUtils.escapeForXML(entry.getValue())); 811 } 812 813 it = otherUnescapableFields.entrySet().iterator(); 814 while (it.hasNext()) { 815 Map.Entry<String, String> entry = it.next(); 816 appendTag(entry.getKey().toString(),entry.getValue()); 817 } 818 } 819 appendOrganization()820 private void appendOrganization() { 821 if (hasOrganizationFields()) { 822 appendTag("ORG", true, new ContentBuilder() { 823 public void addTagContent() { 824 appendTag("ORGNAME", StringUtils.escapeForXML(organization)); 825 appendTag("ORGUNIT", StringUtils.escapeForXML(organizationUnit)); 826 } 827 }); 828 } 829 } 830 appendN()831 private void appendN() { 832 appendTag("N", true, new ContentBuilder() { 833 public void addTagContent() { 834 appendTag("FAMILY", StringUtils.escapeForXML(lastName)); 835 appendTag("GIVEN", StringUtils.escapeForXML(firstName)); 836 appendTag("MIDDLE", StringUtils.escapeForXML(middleName)); 837 } 838 }); 839 } 840 appendTag(String tag, String attr, String attrValue, boolean hasContent, ContentBuilder builder)841 private void appendTag(String tag, String attr, String attrValue, boolean hasContent, 842 ContentBuilder builder) { 843 sb.append('<').append(tag); 844 if (attr != null) { 845 sb.append(' ').append(attr).append('=').append('\'').append(attrValue).append('\''); 846 } 847 848 if (hasContent) { 849 sb.append('>'); 850 builder.addTagContent(); 851 sb.append("</").append(tag).append(">\n"); 852 } 853 else { 854 sb.append("/>\n"); 855 } 856 } 857 appendTag(String tag, boolean hasContent, ContentBuilder builder)858 private void appendTag(String tag, boolean hasContent, ContentBuilder builder) { 859 appendTag(tag, null, null, hasContent, builder); 860 } 861 appendTag(String tag, final String tagText)862 private void appendTag(String tag, final String tagText) { 863 if (tagText == null) return; 864 final ContentBuilder contentBuilder = new ContentBuilder() { 865 public void addTagContent() { 866 sb.append(tagText.trim()); 867 } 868 }; 869 appendTag(tag, true, contentBuilder); 870 } 871 872 } 873 874 //============================================================== 875 876 private interface ContentBuilder { 877 addTagContent()878 void addTagContent(); 879 } 880 881 //============================================================== 882 } 883 884