blob: 9766db82404433c75202023f313343c6e8ca3356 [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001/**
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
21package org.jivesoftware.smackx.packet;
22
23import java.io.BufferedInputStream;
24import java.io.File;
25import java.io.FileInputStream;
26import java.io.IOException;
27import java.lang.reflect.Field;
28import java.lang.reflect.Modifier;
29import java.net.URL;
30import java.security.MessageDigest;
31import java.security.NoSuchAlgorithmException;
32import java.util.HashMap;
33import java.util.Iterator;
34import java.util.Map;
35import java.util.Map.Entry;
36
37import org.jivesoftware.smack.Connection;
38import org.jivesoftware.smack.PacketCollector;
39import org.jivesoftware.smack.SmackConfiguration;
40import org.jivesoftware.smack.XMPPException;
41import org.jivesoftware.smack.filter.PacketIDFilter;
42import org.jivesoftware.smack.packet.IQ;
43import org.jivesoftware.smack.packet.Packet;
44import org.jivesoftware.smack.packet.XMPPError;
45import 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 */
87public 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
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 */
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 */
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 */
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
167 public String getFirstName() {
168 return firstName;
169 }
170
171 public void setFirstName(String firstName) {
172 this.firstName = firstName;
173 // Update FN field
174 updateFN();
175 }
176
177 public String getLastName() {
178 return lastName;
179 }
180
181 public void setLastName(String lastName) {
182 this.lastName = lastName;
183 // Update FN field
184 updateFN();
185 }
186
187 public String getMiddleName() {
188 return middleName;
189 }
190
191 public void setMiddleName(String middleName) {
192 this.middleName = middleName;
193 // Update FN field
194 updateFN();
195 }
196
197 public String getNickName() {
198 return otherSimpleFields.get("NICKNAME");
199 }
200
201 public void setNickName(String nickName) {
202 otherSimpleFields.put("NICKNAME", nickName);
203 }
204
205 public String getEmailHome() {
206 return emailHome;
207 }
208
209 public void setEmailHome(String email) {
210 this.emailHome = email;
211 }
212
213 public String getEmailWork() {
214 return emailWork;
215 }
216
217 public void setEmailWork(String emailWork) {
218 this.emailWork = emailWork;
219 }
220
221 public String getJabberId() {
222 return otherSimpleFields.get("JABBERID");
223 }
224
225 public void setJabberId(String jabberId) {
226 otherSimpleFields.put("JABBERID", jabberId);
227 }
228
229 public String getOrganization() {
230 return organization;
231 }
232
233 public void setOrganization(String organization) {
234 this.organization = organization;
235 }
236
237 public String getOrganizationUnit() {
238 return organizationUnit;
239 }
240
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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
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 */
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
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 */
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 */
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 */
547 public void load(Connection connection, String user) throws XMPPException {
548 checkAuthenticated(connection, false);
549
550 setTo(user);
551 doLoad(connection, user);
552 }
553
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
580 public String getChildElementXML() {
581 StringBuilder sb = new StringBuilder();
582 new VCardWriter(sb).write();
583 return sb.toString();
584 }
585
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
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
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
630 private boolean hasNameField() {
631 return firstName != null || lastName != null || middleName != null;
632 }
633
634 private boolean hasOrganizationFields() {
635 return organization != null || organizationUnit != null;
636 }
637
638 // Used in tests:
639
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
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
706 public String toString() {
707 return getChildElementXML();
708 }
709
710 //==============================================================
711
712 private class VCardWriter {
713
714 private final StringBuilder sb;
715
716 VCardWriter(StringBuilder sb) {
717 this.sb = sb;
718 }
719
720 public void write() {
721 appendTag("vCard", "xmlns", "vcard-temp", hasContent(), new ContentBuilder() {
722 public void addTagContent() {
723 buildActualContent();
724 }
725 });
726 }
727
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
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 }
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
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
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
801 private void appendEmptyTag(Object tag) {
802 sb.append('<').append(tag).append("/>");
803 }
804
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
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
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
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
858 private void appendTag(String tag, boolean hasContent, ContentBuilder builder) {
859 appendTag(tag, null, null, hasContent, builder);
860 }
861
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
878 void addTagContent();
879 }
880
881 //==============================================================
882}
883