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    }