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.maildir;
020
021import java.io.File;
022import java.io.IOException;
023import java.util.Locale;
024
025import org.apache.james.mailbox.MailboxPathLocker;
026import org.apache.james.mailbox.MailboxSession;
027import org.apache.james.mailbox.exception.MailboxException;
028import org.apache.james.mailbox.exception.MailboxNotFoundException;
029import org.apache.james.mailbox.maildir.mail.model.MaildirMailbox;
030import org.apache.james.mailbox.model.MailboxConstants;
031import org.apache.james.mailbox.model.MailboxPath;
032import org.apache.james.mailbox.store.JVMMailboxPathLocker;
033import org.apache.james.mailbox.store.mail.ModSeqProvider;
034import org.apache.james.mailbox.store.mail.UidProvider;
035import org.apache.james.mailbox.store.mail.model.Mailbox;
036
037public class MaildirStore implements UidProvider<Integer>, ModSeqProvider<Integer>{
038
039    public static final String PATH_USER = "%user";
040    public static final String PATH_DOMAIN = "%domain";
041    public static final String PATH_FULLUSER = "%fulluser";
042    public static final String WILDCARD = "%";
043    
044    public static final String maildirDelimiter = ".";
045    
046    private String maildirLocation;
047    
048    private File maildirRootFile;
049    private final MailboxPathLocker locker;
050
051    private boolean messageNameStrictParse = false;
052
053    /**
054     * Construct a MaildirStore with a location. The location String
055     * currently may contain the
056     * %user,
057     * %domain,
058     * %fulluser
059     * variables.
060     * @param maildirLocation A String with variables
061     * @param locker
062     */
063    public MaildirStore(String maildirLocation, MailboxPathLocker locker) {
064        this.maildirLocation = maildirLocation;
065        this.locker = locker;
066    }
067    
068    public MaildirStore(String maildirLocation) {
069        this(maildirLocation, new JVMMailboxPathLocker());
070    }
071    
072    
073    public String getMaildirLocation() {
074        return maildirLocation;
075    }
076    /**
077     * Create a {@link MaildirFolder} for a mailbox
078     * @param mailbox
079     * @return The MaildirFolder
080     */
081    public MaildirFolder createMaildirFolder(Mailbox<Integer> mailbox) {
082        MaildirFolder mf = new MaildirFolder(getFolderName(mailbox), new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName()), locker);
083        mf.setMessageNameStrictParse(isMessageNameStrictParse());
084        return mf;
085    }
086
087    /**
088     * Creates a Mailbox object with data loaded from the file system
089     * @param root The main maildir folder containing the mailbox to load
090     * @param namespace The namespace to use
091     * @param user The owner of this mailbox
092     * @param folderName The name of the mailbox folder
093     * @return The Mailbox object populated with data from the file system
094     * @throws MailboxException If the mailbox folder doesn't exist or can't be read
095     */
096    public Mailbox<Integer> loadMailbox(MailboxSession session, File root, String namespace, String user, String folderName) throws MailboxException {
097        String mailboxName = getMailboxNameFromFolderName(folderName);
098        return loadMailbox(session, new File(root, folderName), new MailboxPath(namespace, user, mailboxName));
099    }
100
101    /**
102     * Creates a Mailbox object with data loaded from the file system
103     * @param mailboxPath The path of the mailbox
104     * @return The Mailbox object populated with data from the file system
105     * @throws MailboxNotFoundException If the mailbox folder doesn't exist
106     * @throws MailboxException If the mailbox folder can't be read
107     */
108    public Mailbox<Integer> loadMailbox(MailboxSession session, MailboxPath mailboxPath)
109    throws MailboxNotFoundException, MailboxException {
110        MaildirFolder folder = new MaildirFolder(getFolderName(mailboxPath), mailboxPath, locker);
111        folder.setMessageNameStrictParse(isMessageNameStrictParse());
112        if (!folder.exists())
113            throw new MailboxNotFoundException(mailboxPath);
114        return loadMailbox(session, folder.getRootFile(), mailboxPath);
115    }
116
117    /**
118     * Creates a Mailbox object with data loaded from the file system
119     * @param mailboxFile File object referencing the folder for the mailbox
120     * @param mailboxPath The path of the mailbox
121     * @return The Mailbox object populated with data from the file system
122     * @throws MailboxException If the mailbox folder doesn't exist or can't be read
123     */
124    private Mailbox<Integer> loadMailbox(MailboxSession session, File mailboxFile, MailboxPath mailboxPath) throws MailboxException {
125        MaildirFolder folder = new MaildirFolder(mailboxFile.getAbsolutePath(), mailboxPath, locker);
126        folder.setMessageNameStrictParse(isMessageNameStrictParse());
127        try {
128            return new MaildirMailbox<Integer>(session, mailboxPath, folder);
129        } catch (IOException e) {
130            throw new MailboxException("Unable to load Mailbox " + mailboxPath, e);
131        }
132    }
133    
134    /**
135     * Inserts the user name parts in the general maildir location String
136     * @param user The user to get the root for.
137     * @return The name of the folder which contains the specified user's mailbox
138     */
139    public String userRoot(String user) {
140        String path = maildirLocation.replace(PATH_FULLUSER, user);
141        String[] userParts = user.split("@");
142        String userName = user;
143        if (userParts.length == 2) {
144            userName = userParts[0];
145            // At least the domain part should not handled in a case-sensitive manner
146            // See MAILBOX-58
147            path = path.replace(PATH_DOMAIN, userParts[1].toLowerCase(Locale.US));
148        }
149        path = path.replace(PATH_USER, userName);
150        return path;
151    }
152    
153    /**
154     * The main maildir folder containing all mailboxes for one user
155     * @param user The user name of a mailbox
156     * @return A File object referencing the main maildir folder
157     * @throws MailboxException If the folder does not exist or is no directory
158     */
159    public File getMailboxRootForUser(String user) throws MailboxException {
160        String path = userRoot(user);
161        File root = new File(path);
162        if (!root.isDirectory())
163            throw new MailboxException("Unable to load Mailbox for user " + user);
164        return root;
165    }
166    
167    /**
168     * Return a File which is the root of all Maidirs.
169     * The returned maidirRootFile is lazilly constructured.
170     * 
171     * @return maidirRootFile
172     */
173    public File getMaildirRoot() {
174        if (maildirRootFile == null) {
175            String maildirRootLocation = maildirLocation.replaceAll(PATH_FULLUSER, "");
176            maildirRootLocation = maildirRootLocation.replaceAll(PATH_DOMAIN, "");
177            maildirRootLocation = maildirRootLocation.replaceAll(PATH_USER, "");
178            maildirRootFile = new File(maildirRootLocation);
179        }        
180        return maildirRootFile;
181    }
182
183    /**
184     * Transforms a folder name into a mailbox name
185     * @param folderName The name of the mailbox folder
186     * @return The complete (namespace) name of a mailbox
187     */
188    public String getMailboxNameFromFolderName(String folderName) {
189        String mName;
190        if (folderName.equals("")) mName = MailboxConstants.INBOX;
191        else
192        // remove leading dot
193            mName = folderName.substring(1);
194        // they are equal, anyways, this might change someday...
195        //if (maildirDelimiter != MailboxConstants.DEFAULT_DELIMITER_STRING)
196        //    mName = mName.replace(maildirDelimiter, MailboxConstants.DEFAULT_DELIMITER_STRING);
197        return mName;
198    }
199    
200    /**
201     * Get the absolute name of the folder for a specific mailbox
202     * @param namespace The namespace of the mailbox
203     * @param user The user of the mailbox
204     * @param name The name of the mailbox
205     * @return absolute name
206     */
207    public String getFolderName(String namespace, String user, String name) {
208        String root = userRoot(user);
209        // if INBOX => location == maildirLocation
210        if (name.equals(MailboxConstants.INBOX))
211            return root;
212        StringBuilder folder = new StringBuilder(root);
213        if (!root.endsWith(File.pathSeparator))
214            folder.append(File.separator);
215        folder.append(".");
216        folder.append(name);
217        return folder.toString();
218    }
219    
220    /**
221     * Get the absolute name of the folder for a specific mailbox
222     * @param mailbox The mailbox
223     * @return The absolute path to the folder containing the mailbox
224     */
225    public String getFolderName(Mailbox<Integer> mailbox) {
226        return getFolderName(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName());
227    }
228    
229    /**
230     * Get the absolute name of the folder for a specific mailbox
231     * @param mailboxPath The MailboxPath
232     * @return The absolute path to the folder containing the mailbox
233     */
234    public String getFolderName(MailboxPath mailboxPath) {
235        return getFolderName(mailboxPath.getNamespace(), mailboxPath.getUser(), mailboxPath.getName());
236    }
237
238    /**
239     * @see org.apache.james.mailbox.store.mail.UidProvider#nextUid(org.apache.james.mailbox.MailboxSession, org.apache.james.mailbox.store.mail.model.Mailbox)
240     */
241    @Override
242    public long nextUid(MailboxSession session, Mailbox<Integer> mailbox) throws MailboxException {
243        try {
244            return createMaildirFolder(mailbox).getLastUid(session) +1;
245        } catch (MailboxException e) {
246            throw new MailboxException("Unable to generate next uid", e);
247        }
248    }
249
250    @Override
251    public long nextModSeq(MailboxSession session, Mailbox<Integer> mailbox) throws MailboxException {
252        return System.currentTimeMillis();
253    }
254
255    @Override
256    public long highestModSeq(MailboxSession session, Mailbox<Integer> mailbox) throws MailboxException {
257        try {
258            return createMaildirFolder(mailbox).getHighestModSeq();
259        } catch (IOException e) {
260            throw new MailboxException("Unable to get highest mod-sequence for mailbox", e);
261        }
262    }
263
264    @Override
265    public long lastUid(MailboxSession session, Mailbox<Integer> mailbox) throws MailboxException {
266       return createMaildirFolder(mailbox).getLastUid(session);
267    }
268
269    /**
270     * Returns whether the names of message files in this store are parsed in
271     * a strict manner ({@code true}), which means a size field and flags are
272     * expected.
273     * @return
274     */
275    public boolean isMessageNameStrictParse() {
276        return messageNameStrictParse;
277    }
278
279    /**
280     * Specifies whether the names of message files in this store are parsed in
281     * a strict manner ({@code true}), which means a size field and flags are
282     * expected.
283     *
284     * Default is {@code false}.
285     *
286     * @param messageNameStrictParse
287     */
288    public void setMessageNameStrictParse(boolean messageNameStrictParse) {
289        this.messageNameStrictParse = messageNameStrictParse;
290    }
291}