| /**************************************************************** |
| * Licensed to the Apache Software Foundation (ASF) under one * |
| * or more contributor license agreements. See the NOTICE file * |
| * distributed with this work for additional information * |
| * regarding copyright ownership. The ASF licenses this file * |
| * to you under the Apache License, Version 2.0 (the * |
| * "License"); you may not use this file except in compliance * |
| * with the License. You may obtain a copy of the License at * |
| * * |
| * http://www.apache.org/licenses/LICENSE-2.0 * |
| * * |
| * Unless required by applicable law or agreed to in writing, * |
| * software distributed under the License is distributed on an * |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * |
| * KIND, either express or implied. See the License for the * |
| * specific language governing permissions and limitations * |
| * under the License. * |
| ****************************************************************/ |
| |
| package org.apache.hupa.server.preferences; |
| |
| import gwtupload.server.UploadServlet; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.util.HashMap; |
| import java.util.Hashtable; |
| |
| import javax.mail.BodyPart; |
| import javax.mail.Flags.Flag; |
| import javax.mail.Folder; |
| import javax.mail.Message; |
| import javax.mail.MessagingException; |
| import javax.mail.Multipart; |
| import javax.mail.Session; |
| import javax.mail.internet.MimeBodyPart; |
| import javax.mail.internet.MimeMessage; |
| import javax.mail.internet.MimeMultipart; |
| import javax.servlet.http.HttpSession; |
| |
| import org.apache.commons.fileupload.FileItem; |
| import org.apache.commons.fileupload.FileItemFactory; |
| import org.apache.commons.fileupload.disk.DiskFileItemFactory; |
| import org.apache.commons.io.output.ByteArrayOutputStream; |
| import org.apache.commons.logging.Log; |
| import org.apache.hupa.server.IMAPStoreCache; |
| import org.apache.hupa.server.utils.MessageUtils; |
| import org.apache.hupa.shared.SConsts; |
| import org.apache.hupa.shared.domain.User; |
| import org.apache.hupa.shared.rpc.ContactsResult.Contact; |
| |
| import com.google.inject.Inject; |
| import com.google.inject.Provider; |
| import com.sun.mail.imap.IMAPFolder; |
| import com.sun.mail.imap.IMAPStore; |
| |
| /** |
| * A user preferences storage which uses IMAP as repository data |
| * |
| * @author manolo |
| */ |
| public class InImapUserPreferencesStorage extends UserPreferencesStorage { |
| |
| |
| // User preferences are saved in IMAP but there is a delay between a new |
| // contact is added an the save action. It saves number of operations in |
| // the IMAP server. |
| // It's not final in order to override in tests to make them run faster |
| protected static int IMAP_SAVE_DELAY = 10000; |
| |
| protected static final String MAGIC_SUBJECT_CONTACTS = "Hupa-Contacts"; |
| |
| private static final String HUPA_DATA_MIME_TYPE = "application/hupa-data"; |
| |
| |
| private static Hashtable<User, Thread> threads = new Hashtable<User, Thread>(); |
| |
| /** |
| * Opens the IMAP folder and read messages until it founds the magic subject, |
| * then gets the attachment which contains the data and return the serialized object stored. |
| */ |
| protected static Object readUserPreferencesFromIMAP(Log logger, User user, IMAPStore iStore, String folderName, String magicType) |
| throws MessagingException, IOException, ClassNotFoundException { |
| Folder folder = iStore.getFolder(folderName); |
| if (folder.exists()) { |
| if (!folder.isOpen()) { |
| folder.open(Folder.READ_WRITE); |
| } |
| Message message = null; |
| Message[] msgs = folder.getMessages(); |
| for (Message msg : msgs) { |
| if (magicType.equals(msg.getSubject())) { |
| message = msg; |
| break; |
| } |
| } |
| if (message != null) { |
| Object con = message.getContent(); |
| if (con instanceof Multipart) { |
| Multipart mp = (Multipart) con; |
| for(int i=0; i<mp.getCount(); i++) { |
| BodyPart part = mp.getBodyPart(i); |
| if (part.getContentType().toLowerCase().startsWith(HUPA_DATA_MIME_TYPE)) { |
| ObjectInputStream ois = new ObjectInputStream(part.getInputStream()); |
| Object o = ois.readObject(); |
| ois.close(); |
| logger.info("Returning user preferences of type " + magicType + " from imap folder + " + folderName + " for user " + user); |
| return o; |
| } |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Opens the IMAP folder, deletes all messages which match the magic subject and |
| * creates a new message with an attachment which contains the object serialized |
| */ |
| protected static void saveUserPreferencesInIMAP(Log logger, User user, Session session, IMAPStore iStore, String folderName, String subject, Object object) |
| throws MessagingException, IOException, InterruptedException { |
| IMAPFolder folder = (IMAPFolder) iStore.getFolder(folderName); |
| |
| if (folder.exists() || folder.create(IMAPFolder.HOLDS_MESSAGES)) { |
| if (!folder.isOpen()) { |
| folder.open(Folder.READ_WRITE); |
| } |
| |
| // Serialize the object |
| ByteArrayOutputStream fout = new ByteArrayOutputStream(); |
| ObjectOutputStream oos = new ObjectOutputStream(fout); |
| oos.writeObject(object); |
| oos.close(); |
| ByteArrayInputStream is = new ByteArrayInputStream(fout.toByteArray()); |
| |
| // Create a new message with an attachment which has the serialized object |
| MimeMessage message = new MimeMessage(session); |
| message.setSubject(subject); |
| |
| Multipart multipart = new MimeMultipart(); |
| MimeBodyPart txtPart = new MimeBodyPart(); |
| txtPart.setContent("This message contains configuration used by Hupa, do not delete it", "text/plain"); |
| multipart.addBodyPart(txtPart); |
| FileItem item = createPreferencesFileItem(is, subject, HUPA_DATA_MIME_TYPE); |
| multipart.addBodyPart(MessageUtils.fileitemToBodypart(item)); |
| message.setContent(multipart); |
| message.saveChanges(); |
| |
| // It seems it's not possible to modify the content of an existing message using the API |
| // So I delete the previous message storing the preferences and I create a new one |
| Message[] msgs = folder.getMessages(); |
| for (Message msg : msgs) { |
| if (subject.equals(msg.getSubject())) { |
| msg.setFlag(Flag.DELETED, true); |
| } |
| } |
| |
| // It is necessary to copy the message before saving it (the same problem in AbstractSendMessageHandler) |
| message = new MimeMessage((MimeMessage)message); |
| message.setFlag(Flag.SEEN, true); |
| folder.appendMessages(new Message[] { message }); |
| folder.close(true); |
| logger.info("Saved preferences " + subject + " in imap folder " + folderName + " for user " + user); |
| } else { |
| logger.error("Unable to save preferences " + subject + " in imap folder " + folderName + " for user " + user); |
| } |
| } |
| |
| /** |
| * Right now, using the same approach present in upload attachments to create the attachment |
| */ |
| private static FileItem createPreferencesFileItem(InputStream is, String filename, String contentType) throws IOException { |
| FileItemFactory f = new DiskFileItemFactory(); |
| FileItem item = f.createItem(filename, contentType, false, filename); |
| UploadServlet.copyFromInputStreamToOutputStream(is, item.getOutputStream()); |
| return item; |
| } |
| |
| private Log logger; |
| |
| private final IMAPStoreCache cache; |
| |
| private final Provider<HttpSession> sessionProvider; |
| |
| /** |
| * Constructor |
| */ |
| @Inject |
| public InImapUserPreferencesStorage(IMAPStoreCache cache, Log logger, Provider<HttpSession> sessionProvider) { |
| this.sessionProvider = sessionProvider; |
| this.cache = cache; |
| this.logger = logger; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.hupa.server.preferences.UserPreferencesStorage#addContact(org.apache.hupa.shared.rpc.ContactsResult.Contact[]) |
| */ |
| @Override |
| public void addContact(Contact... contacts) { |
| HashMap<String, Contact> contactsHash = getContactsHash(); |
| |
| for (Contact contact : contacts) { |
| if (!contactsHash.containsKey(contact.toKey())) { |
| contactsHash.put(contact.toKey(), contact); |
| saveContactsAsync((User) sessionProvider.get().getAttribute(SConsts.USER_SESS_ATTR)); |
| } |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.hupa.server.preferences.UserPreferencesStorage#getContacts() |
| */ |
| @Override |
| public Contact[] getContacts() { |
| HashMap<String, Contact> sessionContacts = getContactsHash(); |
| return sessionContacts.values().toArray(new Contact[sessionContacts.size()]); |
| } |
| |
| /** |
| * Returns the Hash of contacts getting it from the session if available, or from |
| * the IMAP repository if it is the first time. |
| */ |
| @SuppressWarnings("unchecked") |
| private HashMap<String, Contact> getContactsHash() { |
| HttpSession session = sessionProvider.get(); |
| HashMap<String, Contact> contactHash = (HashMap<String, Contact>) session.getAttribute(SConsts.CONTACTS_SESS_ATTR); |
| if (contactHash == null) { |
| try { |
| User user = (User) sessionProvider.get().getAttribute(SConsts.USER_SESS_ATTR); |
| IMAPStore iStore = cache.get(user); |
| Object o = readUserPreferencesFromIMAP(logger, user, iStore, user.getSettings().getDraftsFolderName(), MAGIC_SUBJECT_CONTACTS); |
| contactHash = o != null ? (HashMap<String, Contact>) o : new HashMap<String, Contact>(); |
| session.setAttribute(SConsts.CONTACTS_SESS_ATTR, contactHash); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| return contactHash; |
| } |
| |
| /** |
| * Saves the contacts list in IMAP asynchronously, It is so because of two reasons: |
| * 1.- User processes don't wait for it |
| * 2.- It saves number of save operations, because the method addContact |
| * is called frequently when fetching a folder, so add these contacts are |
| * added to the session list, and a thread is delayed to store |
| * all the block. |
| */ |
| private void saveContactsAsync(User user) { |
| Thread thread = threads.get(user); |
| if (thread == null || !thread.isAlive()) { |
| thread = new SavePreferencesThread(user, MAGIC_SUBJECT_CONTACTS, getContactsHash()); |
| threads.put(user, thread); |
| thread.start(); |
| } |
| } |
| |
| /** |
| * The thread class which saves asynchronously the user preferences |
| */ |
| private class SavePreferencesThread extends Thread { |
| private String folderName = null; |
| private Object object = null; |
| private String subject = null; |
| private User user = null; |
| |
| public SavePreferencesThread(User user, String subject, Object object) { |
| this.user = user; |
| this.folderName = user.getSettings().getDraftsFolderName(); |
| this.subject = subject; |
| this.object = object; |
| } |
| |
| public void run(){ |
| try { |
| sleep(IMAP_SAVE_DELAY); |
| saveUserPreferencesInIMAP(logger, user, cache.getMailSession(user), cache.get(user), folderName, subject, object); |
| } catch (Exception e) { |
| logger.error("Error saving user's preferences: ", e); |
| } |
| } |
| } |
| } |