001 /* 002 * Copyright 2010 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package com.hs.mail.imap.dao; 017 018 import java.sql.Connection; 019 import java.sql.PreparedStatement; 020 import java.sql.ResultSet; 021 import java.sql.SQLException; 022 import java.sql.Statement; 023 import java.sql.Timestamp; 024 import java.util.ArrayList; 025 import java.util.Date; 026 import java.util.HashMap; 027 import java.util.List; 028 import java.util.Map; 029 030 import javax.mail.Flags; 031 032 import org.apache.commons.collections.CollectionUtils; 033 import org.apache.commons.lang.ArrayUtils; 034 import org.apache.james.mime4j.parser.Field; 035 import org.springframework.jdbc.core.PreparedStatementCreator; 036 import org.springframework.jdbc.core.RowMapper; 037 import org.springframework.jdbc.support.GeneratedKeyHolder; 038 import org.springframework.jdbc.support.KeyHolder; 039 import org.springframework.jdbc.support.rowset.SqlRowSet; 040 041 import com.hs.mail.imap.message.FetchData; 042 import com.hs.mail.imap.message.MailMessage; 043 import com.hs.mail.imap.message.MessageHeader; 044 import com.hs.mail.imap.message.PhysMessage; 045 046 /** 047 * 048 * @author Won Chul Doh 049 * @since Mar 23, 2010 050 * 051 */ 052 public class MySqlMessageDao extends AbstractDao implements MessageDao { 053 054 public List<Long> getMessageIDList(long mailboxID) { 055 String sql = "SELECT messageid FROM message WHERE mailboxid = ? ORDER BY messageid"; 056 return (List<Long>) getJdbcTemplate().queryForList(sql, 057 new Object[] { new Long(mailboxID) }, Long.class); 058 } 059 060 public void addMessage(long mailboxID, MailMessage message) { 061 if (message.getPhysMessageID() == 0) { 062 addPhysicalMessage(message); 063 } 064 addMessage(message.getPhysMessageID(), mailboxID); 065 } 066 067 private long addPhysicalMessage(final MailMessage message) { 068 final String sql = "INSERT INTO physmessage (size, internaldate, subject, sentdate, fromaddr) VALUES(?, ?, ?, ?, ?)"; 069 final MessageHeader header = message.getHeader(); 070 KeyHolder keyHolder = new GeneratedKeyHolder(); 071 getJdbcTemplate().update(new PreparedStatementCreator() { 072 public PreparedStatement createPreparedStatement(Connection con) 073 throws SQLException { 074 PreparedStatement pstmt = con.prepareStatement(sql, 075 Statement.RETURN_GENERATED_KEYS); 076 pstmt.setLong(1, message.getSize()); // size 077 pstmt.setTimestamp(2, new Timestamp(message.getInternalDate() 078 .getTime())); // internaldate 079 pstmt.setString(3, header.getSubject()); // subject 080 Date sent = header.getDate(); 081 pstmt.setTimestamp(4, (sent != null) ? new Timestamp(sent 082 .getTime()) : null); // sentdate 083 pstmt.setString(5, header.getFrom().getDisplayString()); // fromaddr 084 return pstmt; 085 } 086 }, keyHolder); 087 long physmessageid = keyHolder.getKey().longValue(); 088 message.setPhysMessageID(physmessageid); 089 addHeader(physmessageid, header); 090 return physmessageid; 091 } 092 093 private void addMessage(long physMessageID, long mailboxID) { 094 String sql = "INSERT INTO message (physmessageid, mailboxid) VALUES(?, ?)"; 095 getJdbcTemplate().update(sql, 096 new Object[] { new Long(physMessageID), new Long(mailboxID) }); 097 } 098 099 public void addMessage(long mailboxID, MailMessage message, Flags flags) { 100 if (message.getPhysMessageID() == 0) { 101 addPhysicalMessage(message); 102 } 103 addMessage(message.getPhysMessageID(), mailboxID, flags); 104 } 105 106 private void addMessage(long physMessageID, long mailboxID, Flags flags) { 107 String sql = "INSERT INTO message (physmessageid, mailboxid, seen, answered, deleted, flagged, draft) VALUES(?, ?, ?, ?, ?, ?, ?)"; 108 getJdbcTemplate().update( 109 sql, 110 new Object[] { new Long(physMessageID), new Long(mailboxID), 111 FlagUtils.getParam(flags, Flags.Flag.SEEN), 112 FlagUtils.getParam(flags, Flags.Flag.ANSWERED), 113 FlagUtils.getParam(flags, Flags.Flag.DELETED), 114 FlagUtils.getParam(flags, Flags.Flag.FLAGGED), 115 FlagUtils.getParam(flags, Flags.Flag.DRAFT) }); 116 } 117 118 public void copyMessage(long messageID, long mailboxID) { 119 // The flags and internal date of the message SHOULD be preserved, and 120 // the Recent flag SHOULD be set, in the copy. 121 String sql = "INSERT INTO message (mailboxid, physmessageid, seen, answered, deleted, flagged, draft) SELECT ?, physmessageid, seen, answered, deleted, flagged, draft FROM message WHERE messageid = ?"; 122 getJdbcTemplate().update(sql, 123 new Object[] { new Long(mailboxID), new Long(messageID) }); 124 } 125 126 public FetchData getMessageFetchData(long messageID) { 127 String sql = "SELECT * FROM message m, physmessage p WHERE m.messageid = ? AND m.physmessageid = p.id"; 128 FetchData fd = (FetchData) queryForObject(sql, new Object[] { new Long( 129 messageID) }, fetchDataRowMapper); 130 if (fd != null) { 131 List<String> ufs = getUserFlags(messageID); 132 if (CollectionUtils.isNotEmpty(ufs)) { 133 Flags flags = fd.getFlags(); 134 for (String uf : ufs) { 135 flags.add(uf); 136 } 137 } 138 } 139 return fd; 140 } 141 142 public void deleteMessage(long messageID) { 143 String sql = "DELETE m.*, k.* FROM message AS m LEFT JOIN keyword AS k ON (m.messageid = k.messageid) WHERE m.messageid = ?"; 144 getJdbcTemplate().update(sql, new Object[] { new Long(messageID) }); 145 } 146 147 public PhysMessage getDanglingMessageID(long messageID) { 148 String sql = "SELECT m.physmessageid, p.internaldate FROM message m, physmessage p WHERE m.physmessageid = (SELECT physmessageid FROM message WHERE messageid = ?) AND p.id=m.physmessageid GROUP BY m.physmessageid HAVING COUNT(m.physmessageid) = 1"; 149 return (PhysMessage) queryForObject(sql, 150 new Object[] { new Long(messageID) }, new RowMapper() { 151 public Object mapRow(ResultSet rs, int rowNum) 152 throws SQLException { 153 PhysMessage pm = new PhysMessage(); 154 pm.setPhysMessageID(rs.getLong("physmessageid")); 155 pm.setInternalDate(new Date(rs.getTimestamp("internaldate").getTime())); 156 return pm; 157 } 158 } 159 ); 160 } 161 162 public void deletePhysicalMessage(long physMessageID) { 163 String[] sql = { 164 "DELETE FROM physmessage WHERE id = ?", 165 "DELETE FROM headervalue WHERE physmessageid = ?" 166 }; 167 Object[] param = { new Long(physMessageID) }; 168 getJdbcTemplate().update(sql[0], param); 169 getJdbcTemplate().update(sql[1], param); 170 } 171 172 public List<Long> resetRecent(long mailboxID) { 173 String sql = "SELECT messageid FROM message WHERE mailboxid = ? AND recent = 'Y'"; 174 Object[] param = new Object[] { new Long(mailboxID) }; 175 List<Long> result = getJdbcTemplate().queryForList(sql, param, 176 Long.class); 177 if (CollectionUtils.isNotEmpty(result)) { 178 sql = "UPDATE message SET recent = 'N' WHERE mailboxid = ? AND recent = 'Y'"; 179 getJdbcTemplate().update(sql, param); 180 } 181 return result; 182 } 183 184 public void setFlags(long messageID, Flags flags, boolean replace, 185 boolean set) { 186 setSystemFlags(messageID, flags.getSystemFlags(), replace, set); 187 setUserFlags(messageID, flags.getUserFlags(), replace, set); 188 } 189 190 private int setSystemFlags(long messageID, Flags.Flag[] flags, 191 boolean replace, boolean set) { 192 if (ArrayUtils.isEmpty(flags)) { 193 return 0; 194 } 195 StringBuilder sql = new StringBuilder("UPDATE message SET "); 196 List params = new ArrayList(); 197 sql.append(FlagUtils.buildParams(flags, replace, set, params)); 198 sql.append(" WHERE messageid = ?"); 199 params.add(new Long(messageID)); 200 return getJdbcTemplate().update(sql.toString(), params.toArray()); 201 } 202 203 private void setUserFlags(long messageID, String[] flags, boolean replace, 204 boolean set) { 205 if (replace) { 206 String sql = "DELETE FROM keyword WHERE messageid = ?"; 207 getJdbcTemplate().update(sql, new Object[] { new Long(messageID) }); 208 } 209 if (!ArrayUtils.isEmpty(flags)) { 210 String sql = (replace || set) ? "INSERT INTO keyword (messageid, keyword) VALUES(?, ?)" 211 : "DELETE FROM keyword WHERE messageid = ? AND keyword = ?"; 212 for (int i = 0; i < flags.length; i++) { 213 getJdbcTemplate().update(sql, 214 new Object[] { new Long(messageID), flags[i] }); 215 } 216 } 217 } 218 219 public Flags getFlags(long messageID) { 220 Flags flags = getSystemFlags(messageID); 221 if (flags == null) { 222 flags = new Flags(); 223 } 224 List<String> ufs = getUserFlags(messageID); 225 if (CollectionUtils.isNotEmpty(ufs)) { 226 for (String uf : ufs) { 227 flags.add(uf); 228 } 229 } 230 return flags; 231 } 232 233 private Flags getSystemFlags(long messageID) { 234 String sql = "SELECT * FROM message WHERE messageid = ?"; 235 return (Flags) queryForObject(sql, 236 new Object[] { new Long(messageID) }, new RowMapper() { 237 public Object mapRow(ResultSet rs, int rowNum) 238 throws SQLException { 239 return FlagUtils.getFlags(rs); 240 } 241 }); 242 } 243 244 private List<String> getUserFlags(long messageID) { 245 String sql = "SELECT keyword FROM keyword WHERE messageid = ?"; 246 return getJdbcTemplate().queryForList(sql, 247 new Object[] { new Long(messageID) }, String.class); 248 } 249 250 //------------------------------------------------------------------------- 251 // Methods dealing with message header 252 //------------------------------------------------------------------------- 253 254 public Map<String, String> getHeader(long physMessageID) { 255 String sql = "SELECT headername, headervalue FROM headername n, headervalue v WHERE v.physmessageid = ? AND v.headernameid = n.id"; 256 SqlRowSet rs = getJdbcTemplate().queryForRowSet(sql, 257 new Object[] { new Long(physMessageID) }); 258 Map<String, String> results = new HashMap<String, String>(); 259 while (rs.next()) { 260 results.put(rs.getString(1), rs.getString(2)); 261 } 262 return results; 263 } 264 265 public Map<String, String> getHeader(long physMessageID, String[] fields) { 266 String sql = "SELECT headername, headervalue FROM headername n, headervalue v WHERE v.physmessageid = ? AND v.headernameid = n.id AND n.headername IN "; 267 Object[] param = new Object[fields.length + 1]; 268 param[0] = new Long(physMessageID); 269 sql += duplicate(fields, param, 1); 270 Map<String, String> results = new HashMap<String, String>(); 271 SqlRowSet rs = getJdbcTemplate().queryForRowSet(sql, param); 272 while (rs.next()) { 273 results.put(rs.getString(1), rs.getString(2)); 274 } 275 return results; 276 } 277 278 public void addHeader(long physMessageID, MessageHeader header) { 279 List<Field> fields = header.getHeader().getFields(); 280 if (!CollectionUtils.isEmpty(fields)) { 281 for (Field field : fields) { 282 addField(physMessageID, field); 283 } 284 } 285 } 286 287 private void addField(long physMessageID, Field field) { 288 long id = getHeaderNameID(field.getName()); 289 addHeaderValue(physMessageID, id, field.getBody()); 290 } 291 292 public long getHeaderNameID(String headerName) { 293 String sql = "SELECT id FROM headername WHERE headername = ?"; 294 long result = queryForLong(sql, new Object[] { headerName }); 295 if (result == 0) { 296 result = addHeaderName(headerName); 297 } 298 return result; 299 } 300 301 private long addHeaderName(final String headerName) { 302 KeyHolder keyHolder = new GeneratedKeyHolder(); 303 getJdbcTemplate().update(new PreparedStatementCreator() { 304 public PreparedStatement createPreparedStatement(Connection con) 305 throws SQLException { 306 String sql = "INSERT INTO headername (headername) VALUES(?)"; 307 PreparedStatement pstmt = con.prepareStatement(sql, 308 Statement.RETURN_GENERATED_KEYS); 309 pstmt.setString(1, headerName); 310 return pstmt; 311 } 312 }, keyHolder); 313 return keyHolder.getKey().longValue(); 314 } 315 316 private void addHeaderValue(long physMessageID, long headerNameID, 317 String headerValue) { 318 String sql = "INSERT INTO headervalue (physmessageid, headernameid, headervalue) VALUES(?, ?, ?)"; 319 getJdbcTemplate().update( 320 sql, 321 new Object[] { new Long(physMessageID), new Long(headerNameID), 322 headerValue }); 323 } 324 325 private static RowMapper fetchDataRowMapper = new RowMapper() { 326 public Object mapRow(ResultSet rs, int rowNum) throws SQLException { 327 FetchData fd = new FetchData(); 328 fd.setMessageID(rs.getLong("messageid")); 329 fd.setPhysMessageID(rs.getLong("physmessageid")); 330 fd.setSize(rs.getLong("size")); 331 fd.setFlags(FlagUtils.getFlags(rs)); 332 fd.setInternalDate(new Date(rs.getTimestamp("internaldate").getTime())); 333 return fd; 334 } 335 }; 336 337 }