aboutsummaryrefslogtreecommitdiff
path: root/src/org/jivesoftware/smackx/packet/VCard.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/jivesoftware/smackx/packet/VCard.java')
-rw-r--r--src/org/jivesoftware/smackx/packet/VCard.java883
1 files changed, 883 insertions, 0 deletions
diff --git a/src/org/jivesoftware/smackx/packet/VCard.java b/src/org/jivesoftware/smackx/packet/VCard.java
new file mode 100644
index 0000000..9766db8
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/VCard.java
@@ -0,0 +1,883 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.smackx.packet;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.PacketCollector;
+import org.jivesoftware.smack.SmackConfiguration;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.filter.PacketIDFilter;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.packet.XMPPError;
+import org.jivesoftware.smack.util.StringUtils;
+
+/**
+ * A VCard class for use with the
+ * <a href="http://www.jivesoftware.org/smack/" target="_blank">SMACK jabber library</a>.<p>
+ * <p/>
+ * You should refer to the
+ * <a href="http://www.jabber.org/jeps/jep-0054.html" target="_blank">JEP-54 documentation</a>.<p>
+ * <p/>
+ * Please note that this class is incomplete but it does provide the most commonly found
+ * information in vCards. Also remember that VCard transfer is not a standard, and the protocol
+ * may change or be replaced.<p>
+ * <p/>
+ * <b>Usage:</b>
+ * <pre>
+ * <p/>
+ * // To save VCard:
+ * <p/>
+ * VCard vCard = new VCard();
+ * vCard.setFirstName("kir");
+ * vCard.setLastName("max");
+ * vCard.setEmailHome("foo@fee.bar");
+ * vCard.setJabberId("jabber@id.org");
+ * vCard.setOrganization("Jetbrains, s.r.o");
+ * vCard.setNickName("KIR");
+ * <p/>
+ * vCard.setField("TITLE", "Mr");
+ * vCard.setAddressFieldHome("STREET", "Some street");
+ * vCard.setAddressFieldWork("CTRY", "US");
+ * vCard.setPhoneWork("FAX", "3443233");
+ * <p/>
+ * vCard.save(connection);
+ * <p/>
+ * // To load VCard:
+ * <p/>
+ * VCard vCard = new VCard();
+ * vCard.load(conn); // load own VCard
+ * vCard.load(conn, "joe@foo.bar"); // load someone's VCard
+ * </pre>
+ *
+ * @author Kirill Maximov (kir@maxkir.com)
+ */
+public class VCard extends IQ {
+
+ /**
+ * Phone types:
+ * VOICE?, FAX?, PAGER?, MSG?, CELL?, VIDEO?, BBS?, MODEM?, ISDN?, PCS?, PREF?
+ */
+ private Map<String, String> homePhones = new HashMap<String, String>();
+ private Map<String, String> workPhones = new HashMap<String, String>();
+
+
+ /**
+ * Address types:
+ * POSTAL?, PARCEL?, (DOM | INTL)?, PREF?, POBOX?, EXTADR?, STREET?, LOCALITY?,
+ * REGION?, PCODE?, CTRY?
+ */
+ private Map<String, String> homeAddr = new HashMap<String, String>();
+ private Map<String, String> workAddr = new HashMap<String, String>();
+
+ private String firstName;
+ private String lastName;
+ private String middleName;
+
+ private String emailHome;
+ private String emailWork;
+
+ private String organization;
+ private String organizationUnit;
+
+ private String photoMimeType;
+ private String photoBinval;
+
+ /**
+ * Such as DESC ROLE GEO etc.. see JEP-0054
+ */
+ private Map<String, String> otherSimpleFields = new HashMap<String, String>();
+
+ // fields that, as they are should not be escaped before forwarding to the server
+ private Map<String, String> otherUnescapableFields = new HashMap<String, String>();
+
+ public VCard() {
+ }
+
+ /**
+ * Set generic VCard field.
+ *
+ * @param field value of field. Possible values: NICKNAME, PHOTO, BDAY, JABBERID, MAILER, TZ,
+ * GEO, TITLE, ROLE, LOGO, NOTE, PRODID, REV, SORT-STRING, SOUND, UID, URL, DESC.
+ */
+ public String getField(String field) {
+ return otherSimpleFields.get(field);
+ }
+
+ /**
+ * Set generic VCard field.
+ *
+ * @param value value of field
+ * @param field field to set. See {@link #getField(String)}
+ * @see #getField(String)
+ */
+ public void setField(String field, String value) {
+ setField(field, value, false);
+ }
+
+ /**
+ * Set generic, unescapable VCard field. If unescabale is set to true, XML maybe a part of the
+ * value.
+ *
+ * @param value value of field
+ * @param field field to set. See {@link #getField(String)}
+ * @param isUnescapable True if the value should not be escaped, and false if it should.
+ */
+ public void setField(String field, String value, boolean isUnescapable) {
+ if (!isUnescapable) {
+ otherSimpleFields.put(field, value);
+ }
+ else {
+ otherUnescapableFields.put(field, value);
+ }
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ // Update FN field
+ updateFN();
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ // Update FN field
+ updateFN();
+ }
+
+ public String getMiddleName() {
+ return middleName;
+ }
+
+ public void setMiddleName(String middleName) {
+ this.middleName = middleName;
+ // Update FN field
+ updateFN();
+ }
+
+ public String getNickName() {
+ return otherSimpleFields.get("NICKNAME");
+ }
+
+ public void setNickName(String nickName) {
+ otherSimpleFields.put("NICKNAME", nickName);
+ }
+
+ public String getEmailHome() {
+ return emailHome;
+ }
+
+ public void setEmailHome(String email) {
+ this.emailHome = email;
+ }
+
+ public String getEmailWork() {
+ return emailWork;
+ }
+
+ public void setEmailWork(String emailWork) {
+ this.emailWork = emailWork;
+ }
+
+ public String getJabberId() {
+ return otherSimpleFields.get("JABBERID");
+ }
+
+ public void setJabberId(String jabberId) {
+ otherSimpleFields.put("JABBERID", jabberId);
+ }
+
+ public String getOrganization() {
+ return organization;
+ }
+
+ public void setOrganization(String organization) {
+ this.organization = organization;
+ }
+
+ public String getOrganizationUnit() {
+ return organizationUnit;
+ }
+
+ public void setOrganizationUnit(String organizationUnit) {
+ this.organizationUnit = organizationUnit;
+ }
+
+ /**
+ * Get home address field
+ *
+ * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET,
+ * LOCALITY, REGION, PCODE, CTRY
+ */
+ public String getAddressFieldHome(String addrField) {
+ return homeAddr.get(addrField);
+ }
+
+ /**
+ * Set home address field
+ *
+ * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET,
+ * LOCALITY, REGION, PCODE, CTRY
+ */
+ public void setAddressFieldHome(String addrField, String value) {
+ homeAddr.put(addrField, value);
+ }
+
+ /**
+ * Get work address field
+ *
+ * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET,
+ * LOCALITY, REGION, PCODE, CTRY
+ */
+ public String getAddressFieldWork(String addrField) {
+ return workAddr.get(addrField);
+ }
+
+ /**
+ * Set work address field
+ *
+ * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET,
+ * LOCALITY, REGION, PCODE, CTRY
+ */
+ public void setAddressFieldWork(String addrField, String value) {
+ workAddr.put(addrField, value);
+ }
+
+
+ /**
+ * Set home phone number
+ *
+ * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF
+ * @param phoneNum phone number
+ */
+ public void setPhoneHome(String phoneType, String phoneNum) {
+ homePhones.put(phoneType, phoneNum);
+ }
+
+ /**
+ * Get home phone number
+ *
+ * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF
+ */
+ public String getPhoneHome(String phoneType) {
+ return homePhones.get(phoneType);
+ }
+
+ /**
+ * Set work phone number
+ *
+ * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF
+ * @param phoneNum phone number
+ */
+ public void setPhoneWork(String phoneType, String phoneNum) {
+ workPhones.put(phoneType, phoneNum);
+ }
+
+ /**
+ * Get work phone number
+ *
+ * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF
+ */
+ public String getPhoneWork(String phoneType) {
+ return workPhones.get(phoneType);
+ }
+
+ /**
+ * Set the avatar for the VCard by specifying the url to the image.
+ *
+ * @param avatarURL the url to the image(png,jpeg,gif,bmp)
+ */
+ public void setAvatar(URL avatarURL) {
+ byte[] bytes = new byte[0];
+ try {
+ bytes = getBytes(avatarURL);
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ setAvatar(bytes);
+ }
+
+ /**
+ * Removes the avatar from the vCard
+ *
+ * This is done by setting the PHOTO value to the empty string as defined in XEP-0153
+ */
+ public void removeAvatar() {
+ // Remove avatar (if any)
+ photoBinval = null;
+ photoMimeType = null;
+ }
+
+ /**
+ * Specify the bytes of the JPEG for the avatar to use.
+ * If bytes is null, then the avatar will be removed.
+ * 'image/jpeg' will be used as MIME type.
+ *
+ * @param bytes the bytes of the avatar, or null to remove the avatar data
+ */
+ public void setAvatar(byte[] bytes) {
+ setAvatar(bytes, "image/jpeg");
+ }
+
+ /**
+ * Specify the bytes for the avatar to use as well as the mime type.
+ *
+ * @param bytes the bytes of the avatar.
+ * @param mimeType the mime type of the avatar.
+ */
+ public void setAvatar(byte[] bytes, String mimeType) {
+ // If bytes is null, remove the avatar
+ if (bytes == null) {
+ removeAvatar();
+ return;
+ }
+
+ // Otherwise, add to mappings.
+ String encodedImage = StringUtils.encodeBase64(bytes);
+
+ setAvatar(encodedImage, mimeType);
+ }
+
+ /**
+ * Specify the Avatar used for this vCard.
+ *
+ * @param encodedImage the Base64 encoded image as String
+ * @param mimeType the MIME type of the image
+ */
+ public void setAvatar(String encodedImage, String mimeType) {
+ photoBinval = encodedImage;
+ photoMimeType = mimeType;
+ }
+
+ /**
+ * Return the byte representation of the avatar(if one exists), otherwise returns null if
+ * no avatar could be found.
+ * <b>Example 1</b>
+ * <pre>
+ * // Load Avatar from VCard
+ * byte[] avatarBytes = vCard.getAvatar();
+ * <p/>
+ * // To create an ImageIcon for Swing applications
+ * ImageIcon icon = new ImageIcon(avatar);
+ * <p/>
+ * // To create just an image object from the bytes
+ * ByteArrayInputStream bais = new ByteArrayInputStream(avatar);
+ * try {
+ * Image image = ImageIO.read(bais);
+ * }
+ * catch (IOException e) {
+ * e.printStackTrace();
+ * }
+ * </pre>
+ *
+ * @return byte representation of avatar.
+ */
+ public byte[] getAvatar() {
+ if (photoBinval == null) {
+ return null;
+ }
+ return StringUtils.decodeBase64(photoBinval);
+ }
+
+ /**
+ * Returns the MIME Type of the avatar or null if none is set
+ *
+ * @return the MIME Type of the avatar or null
+ */
+ public String getAvatarMimeType() {
+ return photoMimeType;
+ }
+
+ /**
+ * Common code for getting the bytes of a url.
+ *
+ * @param url the url to read.
+ */
+ public static byte[] getBytes(URL url) throws IOException {
+ final String path = url.getPath();
+ final File file = new File(path);
+ if (file.exists()) {
+ return getFileBytes(file);
+ }
+
+ return null;
+ }
+
+ private static byte[] getFileBytes(File file) throws IOException {
+ BufferedInputStream bis = null;
+ try {
+ bis = new BufferedInputStream(new FileInputStream(file));
+ int bytes = (int) file.length();
+ byte[] buffer = new byte[bytes];
+ int readBytes = bis.read(buffer);
+ if (readBytes != buffer.length) {
+ throw new IOException("Entire file not read");
+ }
+ return buffer;
+ }
+ finally {
+ if (bis != null) {
+ bis.close();
+ }
+ }
+ }
+
+ /**
+ * Returns the SHA-1 Hash of the Avatar image.
+ *
+ * @return the SHA-1 Hash of the Avatar image.
+ */
+ public String getAvatarHash() {
+ byte[] bytes = getAvatar();
+ if (bytes == null) {
+ return null;
+ }
+
+ MessageDigest digest;
+ try {
+ digest = MessageDigest.getInstance("SHA-1");
+ }
+ catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ digest.update(bytes);
+ return StringUtils.encodeHex(digest.digest());
+ }
+
+ private void updateFN() {
+ StringBuilder sb = new StringBuilder();
+ if (firstName != null) {
+ sb.append(StringUtils.escapeForXML(firstName)).append(' ');
+ }
+ if (middleName != null) {
+ sb.append(StringUtils.escapeForXML(middleName)).append(' ');
+ }
+ if (lastName != null) {
+ sb.append(StringUtils.escapeForXML(lastName));
+ }
+ setField("FN", sb.toString());
+ }
+
+ /**
+ * Save this vCard for the user connected by 'connection'. Connection should be authenticated
+ * and not anonymous.<p>
+ * <p/>
+ * NOTE: the method is asynchronous and does not wait for the returned value.
+ *
+ * @param connection the Connection to use.
+ * @throws XMPPException thrown if there was an issue setting the VCard in the server.
+ */
+ public void save(Connection connection) throws XMPPException {
+ checkAuthenticated(connection, true);
+
+ setType(IQ.Type.SET);
+ setFrom(connection.getUser());
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(getPacketID()));
+ connection.sendPacket(this);
+
+ Packet response = collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server on status set.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ }
+
+ /**
+ * Load VCard information for a connected user. Connection should be authenticated
+ * and not anonymous.
+ */
+ public void load(Connection connection) throws XMPPException {
+ checkAuthenticated(connection, true);
+
+ setFrom(connection.getUser());
+ doLoad(connection, connection.getUser());
+ }
+
+ /**
+ * Load VCard information for a given user. Connection should be authenticated and not anonymous.
+ */
+ public void load(Connection connection, String user) throws XMPPException {
+ checkAuthenticated(connection, false);
+
+ setTo(user);
+ doLoad(connection, user);
+ }
+
+ private void doLoad(Connection connection, String user) throws XMPPException {
+ setType(Type.GET);
+ PacketCollector collector = connection.createPacketCollector(
+ new PacketIDFilter(getPacketID()));
+ connection.sendPacket(this);
+
+ VCard result = null;
+ try {
+ result = (VCard) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ if (result == null) {
+ String errorMessage = "Timeout getting VCard information";
+ throw new XMPPException(errorMessage, new XMPPError(
+ XMPPError.Condition.request_timeout, errorMessage));
+ }
+ if (result.getError() != null) {
+ throw new XMPPException(result.getError());
+ }
+ }
+ catch (ClassCastException e) {
+ System.out.println("No VCard for " + user);
+ }
+
+ copyFieldsFrom(result);
+ }
+
+ public String getChildElementXML() {
+ StringBuilder sb = new StringBuilder();
+ new VCardWriter(sb).write();
+ return sb.toString();
+ }
+
+ private void copyFieldsFrom(VCard from) {
+ Field[] fields = VCard.class.getDeclaredFields();
+ for (Field field : fields) {
+ if (field.getDeclaringClass() == VCard.class &&
+ !Modifier.isFinal(field.getModifiers())) {
+ try {
+ field.setAccessible(true);
+ field.set(this, field.get(from));
+ }
+ catch (IllegalAccessException e) {
+ throw new RuntimeException("This cannot happen:" + field, e);
+ }
+ }
+ }
+ }
+
+ private void checkAuthenticated(Connection connection, boolean checkForAnonymous) {
+ if (connection == null) {
+ throw new IllegalArgumentException("No connection was provided");
+ }
+ if (!connection.isAuthenticated()) {
+ throw new IllegalArgumentException("Connection is not authenticated");
+ }
+ if (checkForAnonymous && connection.isAnonymous()) {
+ throw new IllegalArgumentException("Connection cannot be anonymous");
+ }
+ }
+
+ private boolean hasContent() {
+ //noinspection OverlyComplexBooleanExpression
+ return hasNameField()
+ || hasOrganizationFields()
+ || emailHome != null
+ || emailWork != null
+ || otherSimpleFields.size() > 0
+ || otherUnescapableFields.size() > 0
+ || homeAddr.size() > 0
+ || homePhones.size() > 0
+ || workAddr.size() > 0
+ || workPhones.size() > 0
+ || photoBinval != null
+ ;
+ }
+
+ private boolean hasNameField() {
+ return firstName != null || lastName != null || middleName != null;
+ }
+
+ private boolean hasOrganizationFields() {
+ return organization != null || organizationUnit != null;
+ }
+
+ // Used in tests:
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final VCard vCard = (VCard) o;
+
+ if (emailHome != null ? !emailHome.equals(vCard.emailHome) : vCard.emailHome != null) {
+ return false;
+ }
+ if (emailWork != null ? !emailWork.equals(vCard.emailWork) : vCard.emailWork != null) {
+ return false;
+ }
+ if (firstName != null ? !firstName.equals(vCard.firstName) : vCard.firstName != null) {
+ return false;
+ }
+ if (!homeAddr.equals(vCard.homeAddr)) {
+ return false;
+ }
+ if (!homePhones.equals(vCard.homePhones)) {
+ return false;
+ }
+ if (lastName != null ? !lastName.equals(vCard.lastName) : vCard.lastName != null) {
+ return false;
+ }
+ if (middleName != null ? !middleName.equals(vCard.middleName) : vCard.middleName != null) {
+ return false;
+ }
+ if (organization != null ?
+ !organization.equals(vCard.organization) : vCard.organization != null) {
+ return false;
+ }
+ if (organizationUnit != null ?
+ !organizationUnit.equals(vCard.organizationUnit) : vCard.organizationUnit != null) {
+ return false;
+ }
+ if (!otherSimpleFields.equals(vCard.otherSimpleFields)) {
+ return false;
+ }
+ if (!workAddr.equals(vCard.workAddr)) {
+ return false;
+ }
+ if (photoBinval != null ? !photoBinval.equals(vCard.photoBinval) : vCard.photoBinval != null) {
+ return false;
+ }
+
+ return workPhones.equals(vCard.workPhones);
+ }
+
+ public int hashCode() {
+ int result;
+ result = homePhones.hashCode();
+ result = 29 * result + workPhones.hashCode();
+ result = 29 * result + homeAddr.hashCode();
+ result = 29 * result + workAddr.hashCode();
+ result = 29 * result + (firstName != null ? firstName.hashCode() : 0);
+ result = 29 * result + (lastName != null ? lastName.hashCode() : 0);
+ result = 29 * result + (middleName != null ? middleName.hashCode() : 0);
+ result = 29 * result + (emailHome != null ? emailHome.hashCode() : 0);
+ result = 29 * result + (emailWork != null ? emailWork.hashCode() : 0);
+ result = 29 * result + (organization != null ? organization.hashCode() : 0);
+ result = 29 * result + (organizationUnit != null ? organizationUnit.hashCode() : 0);
+ result = 29 * result + otherSimpleFields.hashCode();
+ result = 29 * result + (photoBinval != null ? photoBinval.hashCode() : 0);
+ return result;
+ }
+
+ public String toString() {
+ return getChildElementXML();
+ }
+
+ //==============================================================
+
+ private class VCardWriter {
+
+ private final StringBuilder sb;
+
+ VCardWriter(StringBuilder sb) {
+ this.sb = sb;
+ }
+
+ public void write() {
+ appendTag("vCard", "xmlns", "vcard-temp", hasContent(), new ContentBuilder() {
+ public void addTagContent() {
+ buildActualContent();
+ }
+ });
+ }
+
+ private void buildActualContent() {
+ if (hasNameField()) {
+ appendN();
+ }
+
+ appendOrganization();
+ appendGenericFields();
+ appendPhoto();
+
+ appendEmail(emailWork, "WORK");
+ appendEmail(emailHome, "HOME");
+
+ appendPhones(workPhones, "WORK");
+ appendPhones(homePhones, "HOME");
+
+ appendAddress(workAddr, "WORK");
+ appendAddress(homeAddr, "HOME");
+ }
+
+ private void appendPhoto() {
+ if (photoBinval == null)
+ return;
+
+ appendTag("PHOTO", true, new ContentBuilder() {
+ public void addTagContent() {
+ appendTag("BINVAL", photoBinval); // No need to escape photoBinval, as it's already Base64 encoded
+ appendTag("TYPE", StringUtils.escapeForXML(photoMimeType));
+ }
+ });
+ }
+ private void appendEmail(final String email, final String type) {
+ if (email != null) {
+ appendTag("EMAIL", true, new ContentBuilder() {
+ public void addTagContent() {
+ appendEmptyTag(type);
+ appendEmptyTag("INTERNET");
+ appendEmptyTag("PREF");
+ appendTag("USERID", StringUtils.escapeForXML(email));
+ }
+ });
+ }
+ }
+
+ private void appendPhones(Map<String, String> phones, final String code) {
+ Iterator<Map.Entry<String, String>> it = phones.entrySet().iterator();
+ while (it.hasNext()) {
+ final Map.Entry<String,String> entry = it.next();
+ appendTag("TEL", true, new ContentBuilder() {
+ public void addTagContent() {
+ appendEmptyTag(entry.getKey());
+ appendEmptyTag(code);
+ appendTag("NUMBER", StringUtils.escapeForXML(entry.getValue()));
+ }
+ });
+ }
+ }
+
+ private void appendAddress(final Map<String, String> addr, final String code) {
+ if (addr.size() > 0) {
+ appendTag("ADR", true, new ContentBuilder() {
+ public void addTagContent() {
+ appendEmptyTag(code);
+
+ Iterator<Map.Entry<String, String>> it = addr.entrySet().iterator();
+ while (it.hasNext()) {
+ final Entry<String, String> entry = it.next();
+ appendTag(entry.getKey(), StringUtils.escapeForXML(entry.getValue()));
+ }
+ }
+ });
+ }
+ }
+
+ private void appendEmptyTag(Object tag) {
+ sb.append('<').append(tag).append("/>");
+ }
+
+ private void appendGenericFields() {
+ Iterator<Map.Entry<String, String>> it = otherSimpleFields.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, String> entry = it.next();
+ appendTag(entry.getKey().toString(),
+ StringUtils.escapeForXML(entry.getValue()));
+ }
+
+ it = otherUnescapableFields.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, String> entry = it.next();
+ appendTag(entry.getKey().toString(),entry.getValue());
+ }
+ }
+
+ private void appendOrganization() {
+ if (hasOrganizationFields()) {
+ appendTag("ORG", true, new ContentBuilder() {
+ public void addTagContent() {
+ appendTag("ORGNAME", StringUtils.escapeForXML(organization));
+ appendTag("ORGUNIT", StringUtils.escapeForXML(organizationUnit));
+ }
+ });
+ }
+ }
+
+ private void appendN() {
+ appendTag("N", true, new ContentBuilder() {
+ public void addTagContent() {
+ appendTag("FAMILY", StringUtils.escapeForXML(lastName));
+ appendTag("GIVEN", StringUtils.escapeForXML(firstName));
+ appendTag("MIDDLE", StringUtils.escapeForXML(middleName));
+ }
+ });
+ }
+
+ private void appendTag(String tag, String attr, String attrValue, boolean hasContent,
+ ContentBuilder builder) {
+ sb.append('<').append(tag);
+ if (attr != null) {
+ sb.append(' ').append(attr).append('=').append('\'').append(attrValue).append('\'');
+ }
+
+ if (hasContent) {
+ sb.append('>');
+ builder.addTagContent();
+ sb.append("</").append(tag).append(">\n");
+ }
+ else {
+ sb.append("/>\n");
+ }
+ }
+
+ private void appendTag(String tag, boolean hasContent, ContentBuilder builder) {
+ appendTag(tag, null, null, hasContent, builder);
+ }
+
+ private void appendTag(String tag, final String tagText) {
+ if (tagText == null) return;
+ final ContentBuilder contentBuilder = new ContentBuilder() {
+ public void addTagContent() {
+ sb.append(tagText.trim());
+ }
+ };
+ appendTag(tag, true, contentBuilder);
+ }
+
+ }
+
+ //==============================================================
+
+ private interface ContentBuilder {
+
+ void addTagContent();
+ }
+
+ //==============================================================
+}
+