/*
* Copyright 2006-2010 Daniel Henninger. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package net.sf.kraken.avatars;
import org.apache.log4j.Logger;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.util.Base64;
import org.jivesoftware.util.NotFoundException;
import org.jivesoftware.util.StringUtils;
import org.xmpp.packet.JID;
import java.io.ByteArrayInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
/**
* @author Daniel Henninger
*/
public class Avatar {
static Logger Log = Logger.getLogger(Avatar.class);
private static final String INSERT_AVATAR =
"INSERT INTO ofGatewayAvatars(jid, xmppHash, legacyIdentifier, createDate, lastUpdate, imageType, imageData) " +
"VALUES (?,?,?,?,?,?,?)";
private static final String DELETE_AVATAR =
"DELETE FROM ofGatewayAvatars WHERE jid=?";
private static final String LOAD_AVATAR =
"SELECT xmppHash, legacyIdentifier, createDate, lastUpdate, imageType " +
"FROM ofGatewayAvatars WHERE jid=?";
private static final String RETRIEVE_IMAGE =
"SELECT imageData FROM ofGatewayAvatars WHERE jid=?";
private static final String UPDATE_LEGACY_ID =
"UPDATE ofGatewayAvatars SET legacyIdentifier=? WHERE jid=?";
private JID jid;
private String xmppHash;
private String legacyIdentifier;
private Date createDate;
private Date lastUpdate;
private String mimeType;
/**
* Creates a new avatar entry.
*
* @param jid JID of the avatar.
* @param imageData Binary image data.
* @throws IllegalArgumentException if any of the arguments are null.
*/
public Avatar(JID jid, byte[] imageData) throws IllegalArgumentException {
if (jid == null || imageData == null) {
throw new IllegalArgumentException("Avatar: Passed null argument.");
}
this.jid = jid;
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(imageData);
this.xmppHash = StringUtils.encodeHex(md.digest());
}
catch (NoSuchAlgorithmException e) {
Log.error("Avatar: Unable to find support for SHA algorithm?");
}
this.createDate = new Date();
this.lastUpdate = new Date();
ImageInfo imageInfo = new ImageInfo();
imageInfo.setInput(new ByteArrayInputStream(imageData));
this.mimeType = imageInfo.getMimeType();
try {
insertIntoDb(Base64.encodeBytes(imageData));
}
catch (SQLException e) {
Log.error("Avatar: SQL exception while inserting avatar: ", e);
}
}
/**
* Creates a new avatar entry.
*
* @param jid JID of the avatar.
* @param legacyIdentifier Hash or whatever is necessary to identify the avatar on the legacy network.
* @param imageData Binary image data.
* @throws IllegalArgumentException if any of the arguments are null.
*/
public Avatar(JID jid, String legacyIdentifier, byte[] imageData) throws IllegalArgumentException {
if (jid == null || legacyIdentifier == null || imageData == null) {
throw new IllegalArgumentException("Avatar: Passed null argument.");
}
this.jid = jid;
this.legacyIdentifier = legacyIdentifier;
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(imageData);
this.xmppHash = StringUtils.encodeHex(md.digest());
}
catch (NoSuchAlgorithmException e) {
Log.error("Avatar: Unable to find support for SHA algorithm?");
}
this.createDate = new Date();
this.lastUpdate = new Date();
ImageInfo imageInfo = new ImageInfo();
imageInfo.setInput(new ByteArrayInputStream(imageData));
this.mimeType = imageInfo.getMimeType();
try {
insertIntoDb(Base64.encodeBytes(imageData));
}
catch (SQLException e) {
Log.error("Avatar: SQL exception while inserting avatar: ", e);
}
}
/**
* Loads an existing avatar.
*
* @param jid JID of the contact whose avatar we are retrieving.
* @throws NotFoundException if avatar entry was not found in database.
*/
public Avatar(JID jid) throws NotFoundException {
this.jid = jid;
loadFromDb();
Log.debug("Loaded avatar for "+this.jid+" of hash "+this.xmppHash);
}
/**
* Returns the JID of the avatar.
*
* @return JID of avatar.
*/
public JID getJid() {
return jid;
}
/**
* Returns the XMPP hash (sha1).
*
* @return SHA1 based XMPP hash.
*/
public String getXmppHash() {
return xmppHash;
}
/**
* Returns the legacy identifier of the avatar.
*
* This is completely up to the transport and is generally whatever type of hash is used on their end.
*
* @return Legacy identifier for the avatar.
*/
public String getLegacyIdentifier() {
return legacyIdentifier;
}
/**
* Sets the legacy identifier of the avatar.
*
* This is typically used for setting your own avatar information on the legacy service,
* and called after the avatar is set to store the known legacy identifier, if there is one,
* associated with your avatar.
*
* @param identifier Identifier to store.
*/
public void setLegacyIdentifier(String identifier) {
this.legacyIdentifier = identifier;
Connection con = null;
PreparedStatement pstmt = null;
boolean abortTransaction = false;
try {
con = DbConnectionManager.getTransactionConnection();
pstmt = con.prepareStatement(UPDATE_LEGACY_ID);
pstmt.setString(1, jid.toString());
pstmt.setString(2, legacyIdentifier);
pstmt.executeUpdate();
}
catch (SQLException sqle) {
abortTransaction = true;
Log.error("Avatar: Major SQL error while updating legacy identifier: ", sqle);
}
finally {
DbConnectionManager.closeTransactionConnection(pstmt, con, abortTransaction);
}
}
/**
* Returns the creation date of the avatar in the database.
*
* @return Creation date of the avatar.
*/
public Date getCreateDate() {
return createDate;
}
/**
* Returns the date the avatar was last updated.
*
* @return Last update date of the avatar.
*/
public Date getLastUpdate() {
return lastUpdate;
}
/**
* Returns the mime type of the image that is stored.
*
* @return Mime type of the avatar image.
*/
public String getMimeType() {
return mimeType;
}
/**
* Retrieves the actual image data for this avatar.
*
* @return The base64 encoded image data for the avatar.
* @throws NotFoundException if avatar entry was not found in database.
*/
public String getImageData() throws NotFoundException {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
String imageData = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(RETRIEVE_IMAGE);
pstmt.setString(1, jid.toString());
rs = pstmt.executeQuery();
if (!rs.next()) {
throw new NotFoundException("Avatar not found for " + jid);
}
imageData = rs.getString(1);
}
catch (SQLException sqle) {
Log.error(sqle);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return imageData;
}
/**
* Inserts a new avaar into the database.
*
* @param imageData Base64 encoded image data to be stored in database.
* @throws SQLException if the SQL statement is wrong for whatever reason.
*/
private void insertIntoDb(String imageData) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
boolean abortTransaction = false;
synchronized ("avatar"+jid.toString()) {
try {
con = DbConnectionManager.getTransactionConnection();
pstmt = con.prepareStatement(DELETE_AVATAR);
pstmt.setString(1, jid.toString());
pstmt.executeUpdate();
pstmt = con.prepareStatement(INSERT_AVATAR);
pstmt.setString(1, jid.toString());
pstmt.setString(2, xmppHash);
pstmt.setString(3, legacyIdentifier);
pstmt.setLong(4, createDate.getTime());
pstmt.setLong(5, lastUpdate.getTime());
pstmt.setString(6, mimeType);
pstmt.setString(7, imageData);
pstmt.executeUpdate();
}
catch (SQLException sqle) {
abortTransaction = true;
throw sqle;
}
finally {
DbConnectionManager.closeTransactionConnection(pstmt, con, abortTransaction);
}
}
}
/**
* Load avatar from database.
*
* @throws NotFoundException if avatar entry was not found in database.
*/
private void loadFromDb() throws NotFoundException {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_AVATAR);
pstmt.setString(1, jid.toString());
rs = pstmt.executeQuery();
if (!rs.next()) {
throw new NotFoundException("Avatar not found for " + jid);
}
this.xmppHash = rs.getString(1);
this.legacyIdentifier = rs.getString(2);
this.createDate = new Date(rs.getLong(3));
this.lastUpdate = new Date(rs.getLong(4));
this.mimeType = rs.getString(5);
}
catch (SQLException sqle) {
Log.error(sqle);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
}
}