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.mail; 020 021import java.io.File; 022import java.io.FileFilter; 023import java.io.FilenameFilter; 024import java.io.IOException; 025import java.util.ArrayList; 026import java.util.List; 027import java.util.regex.Pattern; 028 029import org.apache.commons.io.FileUtils; 030import org.apache.james.mailbox.MailboxSession; 031import org.apache.james.mailbox.exception.MailboxException; 032import org.apache.james.mailbox.exception.MailboxExistsException; 033import org.apache.james.mailbox.exception.MailboxNotFoundException; 034import org.apache.james.mailbox.maildir.MaildirFolder; 035import org.apache.james.mailbox.maildir.MaildirMessageName; 036import org.apache.james.mailbox.maildir.MaildirStore; 037import org.apache.james.mailbox.model.MailboxConstants; 038import org.apache.james.mailbox.model.MailboxPath; 039import org.apache.james.mailbox.store.mail.MailboxMapper; 040import org.apache.james.mailbox.store.mail.model.Mailbox; 041import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox; 042import org.apache.james.mailbox.store.transaction.NonTransactionalMapper; 043 044public class MaildirMailboxMapper extends NonTransactionalMapper implements MailboxMapper<Integer> { 045 046 /** 047 * The {@link MaildirStore} the mailboxes reside in 048 */ 049 private final MaildirStore maildirStore; 050 051 /** 052 * A request-scoped list of mailboxes in order to refer to them via id 053 */ 054 private ArrayList<Mailbox<Integer>> mailboxCache = new ArrayList<Mailbox<Integer>>(); 055 056 private final MailboxSession session; 057 058 public MaildirMailboxMapper(MaildirStore maildirStore, MailboxSession session) { 059 this.maildirStore = maildirStore; 060 this.session = session; 061 } 062 063 /** 064 * @see org.apache.james.mailbox.store.mail.MailboxMapper#delete(org.apache.james.mailbox.store.mail.model.Mailbox) 065 */ 066 @Override 067 public void delete(Mailbox<Integer> mailbox) throws MailboxException { 068 069 String folderName = maildirStore.getFolderName(mailbox); 070 File folder = new File(folderName); 071 if (folder.isDirectory()) { 072 try { 073 if (mailbox.getName().equals(MailboxConstants.INBOX)) { 074 // We must only delete cur, new, tmp and metadata for top INBOX mailbox. 075 FileUtils.deleteDirectory(new File(folder, MaildirFolder.CUR)); 076 FileUtils.deleteDirectory(new File(folder, MaildirFolder.NEW)); 077 FileUtils.deleteDirectory(new File(folder, MaildirFolder.TMP)); 078 File uidListFile = new File(folder, MaildirFolder.UIDLIST_FILE); 079 uidListFile.delete(); 080 File validityFile = new File(folder, MaildirFolder.VALIDITY_FILE); 081 validityFile.delete(); 082 } 083 else { 084 // We simply delete all the folder for non INBOX mailboxes. 085 FileUtils.deleteDirectory(folder); 086 } 087 } catch (IOException e) { 088 e.printStackTrace(); 089 throw new MailboxException("Unable to delete Mailbox " + mailbox, e); 090 } 091 } 092 else 093 throw new MailboxNotFoundException(mailbox.getName()); 094 } 095 096 097 /** 098 * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxByPath(org.apache.james.mailbox.model.MailboxPath) 099 */ 100 @Override 101 public Mailbox<Integer> findMailboxByPath(MailboxPath mailboxPath) 102 throws MailboxException, MailboxNotFoundException { 103 Mailbox<Integer> mailbox = maildirStore.loadMailbox(session, mailboxPath); 104 return cacheMailbox(mailbox); 105 } 106 107 /** 108 * @see org.apache.james.mailbox.store.mail.MailboxMapper#findMailboxWithPathLike(org.apache.james.mailbox.model.MailboxPath) 109 */ 110 @Override 111 public List<Mailbox<Integer>> findMailboxWithPathLike(MailboxPath mailboxPath) 112 throws MailboxException { 113 final Pattern searchPattern = Pattern.compile("[" + MaildirStore.maildirDelimiter + "]" 114 + mailboxPath.getName().replace(".", "\\.").replace(MaildirStore.WILDCARD, ".*")); 115 FilenameFilter filter = MaildirMessageName.createRegexFilter(searchPattern); 116 File root = maildirStore.getMailboxRootForUser(mailboxPath.getUser()); 117 File[] folders = root.listFiles(filter); 118 ArrayList<Mailbox<Integer>> mailboxList = new ArrayList<Mailbox<Integer>>(); 119 for (File folder : folders) 120 if (folder.isDirectory()) { 121 Mailbox<Integer> mailbox = maildirStore.loadMailbox(session, root, mailboxPath.getNamespace(), mailboxPath.getUser(), folder.getName()); 122 mailboxList.add(cacheMailbox(mailbox)); 123 } 124 // INBOX is in the root of the folder 125 if (Pattern.matches(mailboxPath.getName().replace(MaildirStore.WILDCARD, ".*"), MailboxConstants.INBOX)) { 126 Mailbox<Integer> mailbox = maildirStore.loadMailbox(session, root, mailboxPath.getNamespace(), mailboxPath.getUser(), ""); 127 mailboxList.add(0, cacheMailbox(mailbox)); 128 } 129 return mailboxList; 130 } 131 132 /** 133 * @see org.apache.james.mailbox.store.mail.MailboxMapper#hasChildren(org.apache.james.mailbox.store.mail.model.Mailbox, char) 134 */ 135 @Override 136 public boolean hasChildren(Mailbox<Integer> mailbox, char delimiter) throws MailboxException, MailboxNotFoundException { 137 String searchString = mailbox.getName() + MaildirStore.maildirDelimiter + MaildirStore.WILDCARD; 138 List<Mailbox<Integer>> mailboxes = findMailboxWithPathLike( 139 new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), searchString)); 140 return (mailboxes.size() > 0); 141 } 142 143 /** 144 * @see org.apache.james.mailbox.store.mail.MailboxMapper#save(org.apache.james.mailbox.store.mail.model.Mailbox) 145 */ 146 @Override 147 public void save(Mailbox<Integer> mailbox) throws MailboxException { 148 try { 149 Mailbox<Integer> originalMailbox = getCachedMailbox(mailbox.getMailboxId()); 150 MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); 151 // equals with null check 152 if (originalMailbox.getName() == null ? mailbox.getName() != null : !originalMailbox.getName().equals(mailbox.getName())) { 153 if (folder.exists()) 154 throw new MailboxExistsException(mailbox.getName()); 155 156 MaildirFolder originalFolder = maildirStore.createMaildirFolder(originalMailbox); 157 // renaming the INBOX means to move its contents to the new folder 158 if (originalMailbox.getName().equals(MailboxConstants.INBOX)) { 159 try { 160 File inboxFolder = originalFolder.getRootFile(); 161 File newFolder = folder.getRootFile(); 162 if (!newFolder.mkdirs()) 163 throw new IOException("Could not create folder " + newFolder); 164 if (!originalFolder.getCurFolder().renameTo(folder.getCurFolder())) 165 throw new IOException("Could not rename folder " + originalFolder.getCurFolder() + " to " + folder.getCurFolder()); 166 if (!originalFolder.getNewFolder().renameTo(folder.getNewFolder())) 167 throw new IOException("Could not rename folder " + originalFolder.getNewFolder() + " to " + folder.getNewFolder()); 168 if (!originalFolder.getTmpFolder().renameTo(folder.getTmpFolder())) 169 throw new IOException("Could not rename folder " + originalFolder.getTmpFolder() + " to " + folder.getTmpFolder()); 170 File oldUidListFile = new File(inboxFolder, MaildirFolder.UIDLIST_FILE); 171 File newUidListFile = new File(newFolder, MaildirFolder.UIDLIST_FILE); 172 if (!oldUidListFile.renameTo(newUidListFile)) 173 throw new IOException("Could not rename file " + oldUidListFile + " to " + newUidListFile); 174 File oldValidityFile = new File(inboxFolder, MaildirFolder.VALIDITY_FILE); 175 File newValidityFile = new File(newFolder, MaildirFolder.VALIDITY_FILE); 176 if (!oldValidityFile.renameTo(newValidityFile)) 177 throw new IOException("Could not rename file " + oldValidityFile + " to " + newValidityFile); 178 // recreate the INBOX folders, uidvalidity and uidlist will 179 // automatically be recreated later 180 if (!originalFolder.getCurFolder().mkdir()) 181 throw new IOException("Could not create folder " + originalFolder.getCurFolder()); 182 if (!originalFolder.getNewFolder().mkdir()) 183 throw new IOException("Could not create folder " + originalFolder.getNewFolder()); 184 if (!originalFolder.getTmpFolder().mkdir()) 185 throw new IOException("Could not create folder " + originalFolder.getTmpFolder()); 186 } catch (IOException e) { 187 throw new MailboxException("Failed to save Mailbox " + mailbox, e); 188 } 189 } 190 else { 191 if (!originalFolder.getRootFile().renameTo(folder.getRootFile())) 192 throw new MailboxException("Failed to save Mailbox " + mailbox, 193 new IOException("Could not rename folder " + originalFolder)); 194 } 195 } 196 } catch (MailboxNotFoundException e) { 197 // it cannot be found and is thus new 198 MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); 199 if (!folder.exists()) { 200 boolean success = folder.getRootFile().exists(); 201 if (!success) success = folder.getRootFile().mkdirs(); 202 if (!success) 203 throw new MailboxException("Failed to save Mailbox " + mailbox); 204 success = folder.getCurFolder().mkdir(); 205 success = success && folder.getNewFolder().mkdir(); 206 success = success && folder.getTmpFolder().mkdir(); 207 if (!success) 208 throw new MailboxException("Failed to save Mailbox " + mailbox, new IOException("Needed folder structure can not be created")); 209 210 } 211 try { 212 folder.setUidValidity(mailbox.getUidValidity()); 213 } catch (IOException ioe) { 214 throw new MailboxException("Failed to save Mailbox " + mailbox, ioe); 215 216 } 217 } 218 219 } 220 221 /** 222 * @see org.apache.james.mailbox.store.mail.MailboxMapper#list() 223 */ 224 @Override 225 public List<Mailbox<Integer>> list() throws MailboxException { 226 227 File maildirRoot = maildirStore.getMaildirRoot(); 228 List<Mailbox<Integer>> mailboxList = new ArrayList<Mailbox<Integer>>(); 229 230 if (maildirStore.getMaildirLocation().endsWith("/" + MaildirStore.PATH_FULLUSER)) { 231 File[] users = maildirRoot.listFiles(); 232 visitUsersForMailboxList(null, users, mailboxList); 233 return mailboxList; 234 } 235 236 File[] domains = maildirRoot.listFiles(); 237 for (File domain: domains) { 238 File[] users = domain.listFiles(); 239 visitUsersForMailboxList(domain, users, mailboxList); 240 } 241 return mailboxList; 242 243 } 244 245 /** 246 * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#endRequest() 247 */ 248 @Override 249 public void endRequest() { 250 mailboxCache.clear(); 251 } 252 253 /** 254 * Stores a copy of a mailbox in a cache valid for one request. This is to enable 255 * referring to renamed mailboxes via id. 256 * @param mailbox The mailbox to cache 257 * @return The id of the cached mailbox 258 */ 259 private Mailbox<Integer> cacheMailbox(Mailbox<Integer> mailbox) { 260 mailboxCache.add(new SimpleMailbox<Integer>(mailbox)); 261 int id = mailboxCache.size() - 1; 262 ((SimpleMailbox<Integer>) mailbox).setMailboxId(id); 263 return mailbox; 264 } 265 266 /** 267 * Retrieves a mailbox from the cache 268 * @param mailboxId The id of the mailbox to retrieve 269 * @return The mailbox 270 * @throws MailboxNotFoundException If the mailboxId is not in the cache 271 */ 272 private Mailbox<Integer> getCachedMailbox(Integer mailboxId) throws MailboxNotFoundException { 273 if (mailboxId == null) 274 throw new MailboxNotFoundException("null"); 275 try { 276 return mailboxCache.get(mailboxId); 277 } catch (IndexOutOfBoundsException e) { 278 throw new MailboxNotFoundException(String.valueOf(mailboxId)); 279 } 280 } 281 282 private void visitUsersForMailboxList(File domain, File[] users, List<Mailbox<Integer>> mailboxList) throws MailboxException { 283 284 String userName = null; 285 286 for (File user: users) { 287 288 289 if (domain == null) { 290 userName = user.getName(); 291 } 292 else { 293 userName = user.getName() + "@" + domain.getName(); 294 } 295 296 // Special case for INBOX: Let's use the user's folder. 297 MailboxPath inboxMailboxPath = new MailboxPath(session.getPersonalSpace(), userName, MailboxConstants.INBOX); 298 mailboxList.add(maildirStore.loadMailbox(session, inboxMailboxPath)); 299 300 // List all INBOX sub folders. 301 302 File[] mailboxes = user.listFiles(new FileFilter() { 303 @Override 304 public boolean accept(File pathname) { 305 return pathname.getName().startsWith("."); 306 } 307 }); 308 309 for (File mailbox: mailboxes) { 310 311 312 MailboxPath mailboxPath = new MailboxPath(MailboxConstants.USER_NAMESPACE, 313 userName, 314 mailbox.getName().substring(1)); 315 mailboxList.add(maildirStore.loadMailbox(session, mailboxPath)); 316 317 } 318 319 } 320 321 } 322 323}