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}