001/**************************************************************** 002 * Licensed to the Apache Software Foundation (ASF) under one * 003 * or more contributor license agreements. See the NOTICE file * 004 * distributed with this work for additional information * 005 * regarding copyright ownership. The ASF licenses this file * 006 * to you under the Apache License, Version 2.0 (the * 007 * "License"); you may not use this file except in compliance * 008 * with the License. You may obtain a copy of the License at * 009 * * 010 * http://www.apache.org/licenses/LICENSE-2.0 * 011 * * 012 * Unless required by applicable law or agreed to in writing, * 013 * software distributed under the License is distributed on an * 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * 015 * KIND, either express or implied. See the License for the * 016 * specific language governing permissions and limitations * 017 * under the License. * 018 ****************************************************************/ 019package org.apache.james.mailbox.jpa.mail.model.openjpa; 020 021import java.io.Serializable; 022import java.util.ArrayList; 023import java.util.Date; 024import java.util.List; 025 026import javax.mail.Flags; 027import javax.persistence.Basic; 028import javax.persistence.CascadeType; 029import javax.persistence.Column; 030import javax.persistence.FetchType; 031import javax.persistence.Id; 032import javax.persistence.IdClass; 033import javax.persistence.ManyToOne; 034import javax.persistence.MappedSuperclass; 035import javax.persistence.NamedQueries; 036import javax.persistence.NamedQuery; 037import javax.persistence.OneToMany; 038import javax.persistence.OrderBy; 039 040import org.apache.james.mailbox.exception.MailboxException; 041import org.apache.james.mailbox.jpa.mail.model.JPAMailbox; 042import org.apache.james.mailbox.jpa.mail.model.JPAProperty; 043import org.apache.james.mailbox.jpa.mail.model.JPAUserFlag; 044import org.apache.james.mailbox.store.mail.model.AbstractMessage; 045import org.apache.james.mailbox.store.mail.model.Message; 046import org.apache.james.mailbox.store.mail.model.Property; 047import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; 048import org.apache.openjpa.persistence.jdbc.ElementJoinColumn; 049import org.apache.openjpa.persistence.jdbc.ElementJoinColumns; 050import org.apache.openjpa.persistence.jdbc.Index; 051 052/** 053 * Abstract base class for JPA based implementations of {@link AbstractMessage} 054 */ 055@IdClass(AbstractJPAMessage.MailboxIdUidKey.class) 056@NamedQueries({ 057 @NamedQuery(name="findRecentMessageUidsInMailbox", 058 query="SELECT message.uid FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.recent = TRUE"), 059 @NamedQuery(name="findUnseenMessagesInMailboxOrderByUid", 060 query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.seen = FALSE ORDER BY message.uid ASC"), 061 @NamedQuery(name="findMessagesInMailbox", 062 query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam"), 063 @NamedQuery(name="findMessagesInMailboxBetweenUIDs", 064 query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid BETWEEN :fromParam AND :toParam"), 065 @NamedQuery(name="findMessagesInMailboxWithUID", 066 query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid=:uidParam"), 067 @NamedQuery(name="findMessagesInMailboxAfterUID", 068 query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid>=:uidParam"), 069 @NamedQuery(name="findDeletedMessagesInMailbox", 070 query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.deleted=TRUE"), 071 @NamedQuery(name="findDeletedMessagesInMailboxBetweenUIDs", 072 query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid BETWEEN :fromParam AND :toParam AND message.deleted=TRUE"), 073 @NamedQuery(name="findDeletedMessagesInMailboxWithUID", 074 query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid=:uidParam AND message.deleted=TRUE"), 075 @NamedQuery(name="findDeletedMessagesInMailboxAfterUID", 076 query="SELECT message FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid>=:uidParam AND message.deleted=TRUE"), 077 078 @NamedQuery(name="deleteDeletedMessagesInMailbox", 079 query="DELETE FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.deleted=TRUE"), 080 @NamedQuery(name="deleteDeletedMessagesInMailboxBetweenUIDs", 081 query="DELETE FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid BETWEEN :fromParam AND :toParam AND message.deleted=TRUE"), 082 @NamedQuery(name="deleteDeletedMessagesInMailboxWithUID", 083 query="DELETE FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid=:uidParam AND message.deleted=TRUE"), 084 @NamedQuery(name="deleteDeletedMessagesInMailboxAfterUID", 085 query="DELETE FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.uid>=:uidParam AND message.deleted=TRUE"), 086 087 @NamedQuery(name="countUnseenMessagesInMailbox", 088 query="SELECT COUNT(message) FROM Message message WHERE message.mailbox.mailboxId = :idParam AND message.seen=FALSE"), 089 @NamedQuery(name="countMessagesInMailbox", 090 query="SELECT COUNT(message) FROM Message message WHERE message.mailbox.mailboxId = :idParam"), 091 @NamedQuery(name="deleteMessages", 092 query="DELETE FROM Message message WHERE message.mailbox.mailboxId = :idParam"), 093 @NamedQuery(name="findLastUidInMailbox", 094 query="SELECT message.uid FROM Message message WHERE message.mailbox.mailboxId = :idParam ORDER BY message.uid DESC"), 095 @NamedQuery(name="findHighestModSeqInMailbox", 096 query="SELECT message.modSeq FROM Message message WHERE message.mailbox.mailboxId = :idParam ORDER BY message.modSeq DESC"), 097 @NamedQuery(name="deleteAllMemberships", 098 query="DELETE FROM Message message") 099}) 100@MappedSuperclass 101public abstract class AbstractJPAMessage extends AbstractMessage<Long> { 102 103 104 105 private static final String TOSTRING_SEPARATOR = " "; 106 107 /** Identifies composite key */ 108 public static class MailboxIdUidKey implements Serializable { 109 110 private static final long serialVersionUID = 7847632032426660997L; 111 112 public MailboxIdUidKey() {} 113 114 /** The value for the mailbox field */ 115 public long mailbox; 116 117 /** The value for the uid field */ 118 public long uid; 119 120 @Override 121 public int hashCode() { 122 final int PRIME = 31; 123 int result = 1; 124 result = PRIME * result + (int) (mailbox ^ (mailbox >>> 32)); 125 result = PRIME * result + (int) (uid ^ (uid >>> 32)); 126 return result; 127 } 128 129 @Override 130 public boolean equals(Object obj) { 131 if (this == obj) 132 return true; 133 if (obj == null) 134 return false; 135 if (getClass() != obj.getClass()) 136 return false; 137 final MailboxIdUidKey other = (MailboxIdUidKey) obj; 138 if (mailbox != other.mailbox) 139 return false; 140 if (uid != other.uid) 141 return false; 142 return true; 143 } 144 145 } 146 147 /** The value for the mailboxId field */ 148 @Id 149 @ManyToOne( 150 cascade = { 151 CascadeType.PERSIST, 152 CascadeType.REFRESH, 153 CascadeType.MERGE}, 154 fetch=FetchType.LAZY) 155 @Column(name = "MAILBOX_ID", nullable = true) 156 private JPAMailbox mailbox; 157 158 /** The value for the uid field */ 159 @Id 160 @Column(name = "MAIL_UID") 161 private long uid; 162 163 /** The value for the modSeq field */ 164 @Index 165 @Column(name = "MAIL_MODSEQ") 166 private long modSeq; 167 168 /** The value for the internalDate field */ 169 @Basic(optional = false) 170 @Column(name = "MAIL_DATE") 171 private Date internalDate; 172 173 /** The value for the answered field */ 174 @Basic(optional = false) 175 @Column(name = "MAIL_IS_ANSWERED", nullable = false) 176 private boolean answered = false; 177 178 /** The value for the deleted field */ 179 @Basic(optional = false) 180 @Column(name = "MAIL_IS_DELETED", nullable = false) 181 @Index 182 private boolean deleted = false; 183 184 /** The value for the draft field */ 185 @Basic(optional = false) 186 @Column(name = "MAIL_IS_DRAFT", nullable = false) 187 private boolean draft = false; 188 189 /** The value for the flagged field */ 190 @Basic(optional = false) 191 @Column(name = "MAIL_IS_FLAGGED", nullable = false) 192 private boolean flagged = false; 193 194 /** The value for the recent field */ 195 @Basic(optional = false) 196 @Column(name = "MAIL_IS_RECENT", nullable = false) 197 @Index 198 private boolean recent = false; 199 200 /** The value for the seen field */ 201 @Basic(optional = false) 202 @Column(name = "MAIL_IS_SEEN", nullable = false) 203 @Index 204 private boolean seen = false; 205 206 207 /** The first body octet */ 208 @Basic(optional = false) 209 @Column(name = "MAIL_BODY_START_OCTET", nullable = false) 210 private int bodyStartOctet; 211 212 /** Number of octets in the full document content */ 213 @Basic(optional = false) 214 @Column(name = "MAIL_CONTENT_OCTETS_COUNT", nullable = false) 215 private long contentOctets; 216 217 /** MIME media type */ 218 @Basic(optional = true) 219 @Column(name = "MAIL_MIME_TYPE", nullable = true, length = 200) 220 private String mediaType; 221 222 /** MIME sub type */ 223 @Basic(optional = true) 224 @Column(name = "MAIL_MIME_SUBTYPE", nullable = true, length = 200) 225 private String subType; 226 227 /** THE CRFL count when this document is textual, null otherwise */ 228 @Basic(optional = true) 229 @Column(name = "MAIL_TEXTUAL_LINE_COUNT", nullable = true) 230 private Long textualLineCount; 231 232 233 /** Meta data for this message */ 234 @OneToMany(cascade = CascadeType.ALL, fetch=FetchType.LAZY) 235 @OrderBy("line") 236 @ElementJoinColumns({@ElementJoinColumn(name="MAILBOX_ID", referencedColumnName="MAILBOX_ID"), 237 @ElementJoinColumn(name="MAIL_UID", referencedColumnName="MAIL_UID")}) 238 private List<JPAProperty> properties; 239 240 @OneToMany(cascade = CascadeType.ALL, fetch=FetchType.LAZY) 241 @OrderBy("id") 242 @ElementJoinColumns({@ElementJoinColumn(name="MAILBOX_ID", referencedColumnName="MAILBOX_ID"), 243 @ElementJoinColumn(name="MAIL_UID", referencedColumnName="MAIL_UID")}) 244 private List<JPAUserFlag> userFlags; 245 246 @Deprecated 247 public AbstractJPAMessage() {} 248 249 public AbstractJPAMessage(JPAMailbox mailbox, Date internalDate, Flags flags, final long contentOctets, final int bodyStartOctet, final PropertyBuilder propertyBuilder) { 250 super(); 251 this.mailbox = mailbox; 252 this.internalDate = internalDate; 253 userFlags = new ArrayList<JPAUserFlag>(); 254 255 setFlags(flags); 256 this.contentOctets = contentOctets; 257 this.bodyStartOctet = bodyStartOctet; 258 this.textualLineCount = propertyBuilder.getTextualLineCount(); 259 this.mediaType = propertyBuilder.getMediaType(); 260 this.subType = propertyBuilder.getSubType(); 261 final List<Property> properties = propertyBuilder.toProperties(); 262 this.properties = new ArrayList<JPAProperty>(properties.size()); 263 int order = 0; 264 for (final Property property:properties) { 265 this.properties.add(new JPAProperty(property, order++)); 266 } 267 268 } 269 270 /** 271 * Constructs a copy of the given message. 272 * All properties are cloned except mailbox and UID. 273 * @param mailbox new mailbox 274 * @param uid new UID 275 * @param modSeq new modSeq 276 * @param original message to be copied, not null 277 * @throws IOException 278 */ 279 public AbstractJPAMessage(JPAMailbox mailbox, long uid, long modSeq, Message<?> original) throws MailboxException { 280 super(); 281 this.mailbox = mailbox; 282 this.uid = uid; 283 this.modSeq = modSeq; 284 this.userFlags = new ArrayList<JPAUserFlag>(); 285 setFlags(original.createFlags()); 286 287 // A copy of a message is recent 288 // See MAILBOX-85 289 this.recent = true; 290 291 this.contentOctets = original.getFullContentOctets(); 292 this.bodyStartOctet = (int) (original.getFullContentOctets() - original.getBodyOctets()); 293 this.internalDate = original.getInternalDate(); 294 295 296 PropertyBuilder pBuilder = new PropertyBuilder(original.getProperties()); 297 this.textualLineCount = original.getTextualLineCount(); 298 this.mediaType = original.getMediaType(); 299 this.subType = original.getSubType(); 300 final List<Property> properties = pBuilder.toProperties(); 301 this.properties = new ArrayList<JPAProperty>(properties.size()); 302 int order = 0; 303 for (final Property property:properties) { 304 this.properties.add(new JPAProperty(property, order++)); 305 } 306 } 307 308 @Override 309 public int hashCode() { 310 final int PRIME = 31; 311 int result = 1; 312 result = PRIME * result + (int) (getMailboxId() ^ (getMailboxId() >>> 32)); 313 result = PRIME * result + (int) (uid ^ (uid >>> 32)); 314 return result; 315 } 316 317 @Override 318 public boolean equals(Object obj) { 319 if (this == obj) 320 return true; 321 if (obj == null) 322 return false; 323 if (getClass() != obj.getClass()) 324 return false; 325 final AbstractJPAMessage other = (AbstractJPAMessage) obj; 326 if (getMailboxId() != null) { 327 if (!getMailboxId().equals(other.getMailboxId())) 328 return false; 329 } else { 330 if (other.getMailboxId() != null) 331 return false; 332 } 333 if (uid != other.uid) 334 return false; 335 return true; 336 } 337 338 /** 339 * @see org.apache.james.mailbox.store.mail.model.Message#getModSeq() 340 */ 341 public long getModSeq() { 342 return modSeq; 343 } 344 345 /** 346 * @see org.apache.james.mailbox.store.mail.model.Message#setModSeq(long) 347 */ 348 public void setModSeq(long modSeq) { 349 this.modSeq = modSeq; 350 } 351 352 /** 353 * Gets the top level MIME content media type. 354 * 355 * @return top level MIME content media type, or null if default 356 */ 357 public String getMediaType() { 358 return mediaType; 359 } 360 361 /** 362 * Gets the MIME content subtype. 363 * 364 * @return the MIME content subtype, or null if default 365 */ 366 public String getSubType() { 367 return subType; 368 } 369 370 /** 371 * Gets a read-only list of meta-data properties. 372 * For properties with multiple values, this list will contain 373 * several enteries with the same namespace and local name. 374 * @return unmodifiable list of meta-data, not null 375 */ 376 public List<Property> getProperties() { 377 return new ArrayList<Property>(properties); 378 } 379 380 /** 381 * Gets the number of CRLF in a textual document. 382 * @return CRLF count when document is textual, 383 * null otherwise 384 */ 385 public Long getTextualLineCount() { 386 return textualLineCount; 387 } 388 389 /** 390 * @see org.apache.james.mailbox.store.mail.model.Message#getFullContentOctets() 391 */ 392 public long getFullContentOctets() { 393 return contentOctets; 394 } 395 396 @Override 397 protected int getBodyStartOctet() { 398 return bodyStartOctet; 399 } 400 401 402 /** 403 * @see org.apache.james.mailbox.store.mail.model.Message#getInternalDate() 404 */ 405 public Date getInternalDate() { 406 return internalDate; 407 } 408 409 /** 410 * @see org.apache.james.mailbox.store.mail.model.Message#getMailboxId() 411 */ 412 public Long getMailboxId() { 413 return getMailbox().getMailboxId(); 414 } 415 416 /** 417 * @see org.apache.james.mailbox.store.mail.model.Message#getUid() 418 */ 419 public long getUid() { 420 return uid; 421 } 422 423 /** 424 * @see org.apache.james.mailbox.store.mail.model.Message#isAnswered() 425 */ 426 public boolean isAnswered() { 427 return answered; 428 } 429 430 /** 431 * @see org.apache.james.mailbox.store.mail.model.Message#isDeleted() 432 */ 433 public boolean isDeleted() { 434 return deleted; 435 } 436 437 /** 438 * @see org.apache.james.mailbox.store.mail.model.Message#isDraft() 439 */ 440 public boolean isDraft() { 441 return draft; 442 } 443 444 /** 445 * @see org.apache.james.mailbox.store.mail.model.Message#isFlagged() 446 */ 447 public boolean isFlagged() { 448 return flagged; 449 } 450 451 /** 452 * @see org.apache.james.mailbox.store.mail.model.Message#isRecent() 453 */ 454 public boolean isRecent() { 455 return recent; 456 } 457 458 /** 459 * @see org.apache.james.mailbox.store.mail.model.Message#isSeen() 460 */ 461 public boolean isSeen() { 462 return seen; 463 } 464 465 public void setUid(long uid) { 466 this.uid = uid; 467 } 468 469 /** 470 * @see org.apache.james.mailbox.store.mail.model.Message#setFlags(javax.mail.Flags) 471 */ 472 public void setFlags(Flags flags) { 473 answered = flags.contains(Flags.Flag.ANSWERED); 474 deleted = flags.contains(Flags.Flag.DELETED); 475 draft = flags.contains(Flags.Flag.DRAFT); 476 flagged = flags.contains(Flags.Flag.FLAGGED); 477 recent = flags.contains(Flags.Flag.RECENT); 478 seen = flags.contains(Flags.Flag.SEEN); 479 480 /* 481 // Loop over the user flags and check which of them needs to get added / removed 482 List<String> uFlags = Arrays.asList(flags.getUserFlags()); 483 for (int i = 0; i < userFlags.size(); i++) { 484 JPAUserFlag f = userFlags.get(i); 485 if (uFlags.contains(f.getName()) == false) { 486 userFlags.remove(f); 487 i++; 488 } 489 } 490 for (int i = 0; i < uFlags.size(); i++) { 491 boolean found = false; 492 String uFlag = uFlags.get(i); 493 for (int a = 0; a < userFlags.size(); a++) { 494 String userFlag = userFlags.get(a).getName(); 495 if (userFlag.equals(uFlag)) { 496 found = true; 497 break; 498 } 499 } 500 if (found == false) { 501 userFlags.add(new JPAUserFlag(uFlag)); 502 } 503 504 505 } 506 */ 507 String[] userflags = flags.getUserFlags(); 508 userFlags.clear(); 509 for (int i = 0 ; i< userflags.length; i++) { 510 userFlags.add(new JPAUserFlag(userflags[i])); 511 } 512 } 513 514 /** 515 * Utility getter on Mailbox. 516 */ 517 public JPAMailbox getMailbox() { 518 return mailbox; 519 } 520 521 /** 522 * This implementation supports user flags 523 * 524 * 525 */ 526 @Override 527 protected String[] createUserFlags() { 528 String[] flags = new String[userFlags.size()]; 529 for (int i = 0; i < userFlags.size(); i++) { 530 flags[i] = userFlags.get(i).getName(); 531 } 532 return flags; 533 } 534 535 /** 536 * Utility setter on Mailbox. 537 */ 538 public void setMailbox(JPAMailbox mailbox) { 539 this.mailbox = mailbox; 540 } 541 542 public String toString() { 543 final String retValue = 544 "message(" 545 + "mailboxId = " + this.getMailboxId() + TOSTRING_SEPARATOR 546 + "uid = " + this.uid + TOSTRING_SEPARATOR 547 + "internalDate = " + this.internalDate + TOSTRING_SEPARATOR 548 + "answered = " + this.answered + TOSTRING_SEPARATOR 549 + "deleted = " + this.deleted + TOSTRING_SEPARATOR 550 + "draft = " + this.draft + TOSTRING_SEPARATOR 551 + "flagged = " + this.flagged + TOSTRING_SEPARATOR 552 + "recent = " + this.recent + TOSTRING_SEPARATOR 553 + "seen = " + this.seen + TOSTRING_SEPARATOR 554 + " )"; 555 return retValue; 556 } 557 558 559}