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 ****************************************************************/
019 package org.apache.james.mailbox.jpa.mail.model.openjpa;
020
021 import java.io.Serializable;
022 import java.util.ArrayList;
023 import java.util.Date;
024 import java.util.List;
025
026 import javax.mail.Flags;
027 import javax.persistence.Basic;
028 import javax.persistence.CascadeType;
029 import javax.persistence.Column;
030 import javax.persistence.FetchType;
031 import javax.persistence.Id;
032 import javax.persistence.IdClass;
033 import javax.persistence.ManyToOne;
034 import javax.persistence.MappedSuperclass;
035 import javax.persistence.NamedQueries;
036 import javax.persistence.NamedQuery;
037 import javax.persistence.OneToMany;
038 import javax.persistence.OrderBy;
039
040 import org.apache.james.mailbox.exception.MailboxException;
041 import org.apache.james.mailbox.jpa.mail.model.JPAMailbox;
042 import org.apache.james.mailbox.jpa.mail.model.JPAProperty;
043 import org.apache.james.mailbox.jpa.mail.model.JPAUserFlag;
044 import org.apache.james.mailbox.store.mail.model.AbstractMessage;
045 import org.apache.james.mailbox.store.mail.model.Message;
046 import org.apache.james.mailbox.store.mail.model.Property;
047 import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
048 import org.apache.openjpa.persistence.jdbc.ElementJoinColumn;
049 import org.apache.openjpa.persistence.jdbc.ElementJoinColumns;
050 import 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
101 public 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 }