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}