/** * $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 * SMACK jabber library.
*
* You should refer to the * JEP-54 documentation.*
* 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.*
* Usage: *
*
* // To save VCard:
*
* 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");
*
* vCard.setField("TITLE", "Mr");
* vCard.setAddressFieldHome("STREET", "Some street");
* vCard.setAddressFieldWork("CTRY", "US");
* vCard.setPhoneWork("FAX", "3443233");
*
* vCard.save(connection);
*
* // To load VCard:
*
* VCard vCard = new VCard();
* vCard.load(conn); // load own VCard
* vCard.load(conn, "joe@foo.bar"); // load someone's VCard
*
*
* @author Kirill Maximov (kir@maxkir.com)
*/
public class VCard extends IQ {
private static final String DEFAULT_MIME_TYPE = "image/jpeg";
/**
* Phone types:
* VOICE?, FAX?, PAGER?, MSG?, CELL?, VIDEO?, BBS?, MODEM?, ISDN?, PCS?, PREF?
*/
private Map
* // Load Avatar from VCard
* byte[] avatarBytes = vCard.getAvatar();
*
* // To create an ImageIcon for Swing applications
* ImageIcon icon = new ImageIcon(avatar);
*
* // 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();
* }
*
*
* @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.*
* 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