From 00db10d29b8c0b88583bf4e6de4b4d351023caf8 Mon Sep 17 00:00:00 2001 From: Jorge Bescos Gascon Date: Fri, 16 Feb 2024 07:25:14 +0100 Subject: [PATCH] Working on reentrant lock migration Signed-off-by: Jorge Bescos Gascon --- .../angus/mail/util/BASE64EncoderStream.java | 130 +- .../angus/mail/util/MailSSLSocketFactory.java | 228 +- .../angus/mail/util/WriteTimeoutSocket.java | 82 +- .../angus/mail/dsn/MultipartReport.java | 238 +- .../mail/util/logging/CollectorFormatter.java | 180 +- .../mail/util/logging/DurationFilter.java | 152 +- .../util/logging/LogManagerProperties.java | 150 +- .../angus/mail/util/logging/MailHandler.java | 528 ++- .../eclipse/angus/mail/gimap/GmailFolder.java | 85 +- .../angus/mail/gimap/GmailMessage.java | 25 +- .../org/eclipse/angus/mail/iap/Protocol.java | 265 +- .../eclipse/angus/mail/imap/IMAPFolder.java | 3231 +++++++++-------- .../eclipse/angus/mail/imap/IMAPMessage.java | 3 +- .../angus/mail/imap/IMAPNestedMessage.java | 4 +- .../angus/mail/imap/protocol/ENVELOPE.java | 9 +- .../mail/imap/protocol/IMAPProtocol.java | 666 ++-- 16 files changed, 3492 insertions(+), 2484 deletions(-) diff --git a/core/src/main/java/org/eclipse/angus/mail/util/BASE64EncoderStream.java b/core/src/main/java/org/eclipse/angus/mail/util/BASE64EncoderStream.java index 58cdf9f8..ce2c658d 100644 --- a/core/src/main/java/org/eclipse/angus/mail/util/BASE64EncoderStream.java +++ b/core/src/main/java/org/eclipse/angus/mail/util/BASE64EncoderStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -19,6 +19,7 @@ import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.util.concurrent.locks.ReentrantLock; /** * This class implements a BASE64 encoder. It is implemented as @@ -31,6 +32,7 @@ */ public class BASE64EncoderStream extends FilterOutputStream { + private final ReentrantLock lock = new ReentrantLock(); private byte[] buffer; // cache of bytes that are yet to be encoded private int bufsize = 0; // size of the cache private byte[] outbuf; // line size output buffer @@ -92,46 +94,51 @@ public BASE64EncoderStream(OutputStream out) { * @throws IOException if an I/O error occurs. */ @Override - public synchronized void write(byte[] b, int off, int len) + public void write(byte[] b, int off, int len) throws IOException { - int end = off + len; - - // finish off incomplete coding unit - while (bufsize != 0 && off < end) - write(b[off++]); - - // finish off line - int blen = ((bytesPerLine - count) / 4) * 3; - if (off + blen <= end) { - // number of bytes that will be produced in outbuf - int outlen = encodedSize(blen); - if (!noCRLF) { - outbuf[outlen++] = (byte) '\r'; - outbuf[outlen++] = (byte) '\n'; + lock.lock(); + try { + int end = off + len; + + // finish off incomplete coding unit + while (bufsize != 0 && off < end) + write(b[off++]); + + // finish off line + int blen = ((bytesPerLine - count) / 4) * 3; + if (off + blen <= end) { + // number of bytes that will be produced in outbuf + int outlen = encodedSize(blen); + if (!noCRLF) { + outbuf[outlen++] = (byte) '\r'; + outbuf[outlen++] = (byte) '\n'; + } + out.write(encode(b, off, blen, outbuf), 0, outlen); + off += blen; + count = 0; } - out.write(encode(b, off, blen, outbuf), 0, outlen); - off += blen; - count = 0; - } - - // do bulk encoding a line at a time. - for (; off + lineLimit <= end; off += lineLimit) - out.write(encode(b, off, lineLimit, outbuf)); - - // handle remaining partial line - if (off + 3 <= end) { - blen = end - off; - blen = (blen / 3) * 3; // round down - // number of bytes that will be produced in outbuf - int outlen = encodedSize(blen); - out.write(encode(b, off, blen, outbuf), 0, outlen); - off += blen; - count += outlen; + + // do bulk encoding a line at a time. + for (; off + lineLimit <= end; off += lineLimit) + out.write(encode(b, off, lineLimit, outbuf)); + + // handle remaining partial line + if (off + 3 <= end) { + blen = end - off; + blen = (blen / 3) * 3; // round down + // number of bytes that will be produced in outbuf + int outlen = encodedSize(blen); + out.write(encode(b, off, blen, outbuf), 0, outlen); + off += blen; + count += outlen; + } + + // start next coding unit + for (; off < end; off++) + write(b[off]); + } finally { + lock.unlock(); } - - // start next coding unit - for (; off < end; off++) - write(b[off]); } /** @@ -152,11 +159,16 @@ public void write(byte[] b) throws IOException { * @throws IOException if an I/O error occurs. */ @Override - public synchronized void write(int c) throws IOException { - buffer[bufsize++] = (byte) c; - if (bufsize == 3) { // Encoding unit = 3 bytes - encode(); - bufsize = 0; + public void write(int c) throws IOException { + lock.lock(); + try { + buffer[bufsize++] = (byte) c; + if (bufsize == 3) { // Encoding unit = 3 bytes + encode(); + bufsize = 0; + } + } finally { + lock.unlock(); } } @@ -167,12 +179,17 @@ public synchronized void write(int c) throws IOException { * @throws IOException if an I/O error occurs. */ @Override - public synchronized void flush() throws IOException { - if (bufsize > 0) { // If there's unencoded characters in the buffer .. - encode(); // .. encode them - bufsize = 0; + public void flush() throws IOException { + lock.lock(); + try { + if (bufsize > 0) { // If there's unencoded characters in the buffer .. + encode(); // .. encode them + bufsize = 0; + } + out.flush(); + } finally { + lock.unlock(); } - out.flush(); } /** @@ -180,13 +197,18 @@ public synchronized void flush() throws IOException { * and closes this output stream */ @Override - public synchronized void close() throws IOException { - flush(); - if (count > 0 && !noCRLF) { - out.write(newline); - out.flush(); + public void close() throws IOException { + lock.lock(); + try { + flush(); + if (count > 0 && !noCRLF) { + out.write(newline); + out.flush(); + } + out.close(); + } finally { + lock.unlock(); } - out.close(); } /** diff --git a/core/src/main/java/org/eclipse/angus/mail/util/MailSSLSocketFactory.java b/core/src/main/java/org/eclipse/angus/mail/util/MailSSLSocketFactory.java index 1bbe8d66..b8fd1a40 100644 --- a/core/src/main/java/org/eclipse/angus/mail/util/MailSSLSocketFactory.java +++ b/core/src/main/java/org/eclipse/angus/mail/util/MailSSLSocketFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -35,6 +35,7 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Arrays; +import java.util.concurrent.locks.ReentrantLock; /** * An SSL socket factory that makes it easier to specify trust. @@ -52,6 +53,8 @@ */ public class MailSSLSocketFactory extends SSLSocketFactory { + private final ReentrantLock lock = new ReentrantLock(); + /** * Should all hosts be trusted? */ @@ -130,97 +133,152 @@ public MailSSLSocketFactory(String protocol) * * @throws KeyManagementException for key manager errors */ - private synchronized void newAdapteeFactory() + private void newAdapteeFactory() throws KeyManagementException { - sslcontext.init(keyManagers, trustManagers, secureRandom); - - // Get SocketFactory and save it in our instance var - adapteeFactory = sslcontext.getSocketFactory(); + lock.lock(); + try { + sslcontext.init(keyManagers, trustManagers, secureRandom); + + // Get SocketFactory and save it in our instance var + adapteeFactory = sslcontext.getSocketFactory(); + } finally { + lock.unlock(); + } } /** * @return the keyManagers */ - public synchronized KeyManager[] getKeyManagers() { - return keyManagers.clone(); + public KeyManager[] getKeyManagers() { + lock.lock(); + try { + return keyManagers.clone(); + } finally { + lock.unlock(); + } } /** * @param keyManagers the keyManagers to set * @throws GeneralSecurityException for security errors */ - public synchronized void setKeyManagers(KeyManager... keyManagers) + public void setKeyManagers(KeyManager... keyManagers) throws GeneralSecurityException { - this.keyManagers = keyManagers.clone(); - newAdapteeFactory(); + lock.lock(); + try { + this.keyManagers = keyManagers.clone(); + newAdapteeFactory(); + } finally { + lock.unlock(); + } } /** * @return the secureRandom */ - public synchronized SecureRandom getSecureRandom() { - return secureRandom; + public SecureRandom getSecureRandom() { + lock.lock(); + try { + return secureRandom; + } finally { + lock.unlock(); + } } /** * @param secureRandom the secureRandom to set * @throws GeneralSecurityException for security errors */ - public synchronized void setSecureRandom(SecureRandom secureRandom) + public void setSecureRandom(SecureRandom secureRandom) throws GeneralSecurityException { - this.secureRandom = secureRandom; - newAdapteeFactory(); + lock.lock(); + try { + this.secureRandom = secureRandom; + newAdapteeFactory(); + } finally { + lock.unlock(); + } } /** * @return the trustManagers */ - public synchronized TrustManager[] getTrustManagers() { - return trustManagers; + public TrustManager[] getTrustManagers() { + lock.lock(); + try { + return trustManagers; + } finally { + lock.unlock(); + } } /** * @param trustManagers the trustManagers to set * @throws GeneralSecurityException for security errors */ - public synchronized void setTrustManagers(TrustManager... trustManagers) + public void setTrustManagers(TrustManager... trustManagers) throws GeneralSecurityException { - this.trustManagers = trustManagers; - newAdapteeFactory(); + lock.lock(); + try { + this.trustManagers = trustManagers; + newAdapteeFactory(); + } finally { + lock.unlock(); + } } /** * @return true if all hosts should be trusted */ - public synchronized boolean isTrustAllHosts() { - return trustAllHosts; + public boolean isTrustAllHosts() { + lock.lock(); + try { + return trustAllHosts; + } finally { + lock.unlock(); + } } /** * @param trustAllHosts should all hosts be trusted? */ - public synchronized void setTrustAllHosts(boolean trustAllHosts) { - this.trustAllHosts = trustAllHosts; + public void setTrustAllHosts(boolean trustAllHosts) { + lock.lock(); + try { + this.trustAllHosts = trustAllHosts; + } finally { + lock.unlock(); + } } /** * @return the trusted hosts */ - public synchronized String[] getTrustedHosts() { - if (trustedHosts == null) - return null; - else - return trustedHosts.clone(); + public String[] getTrustedHosts() { + lock.lock(); + try { + if (trustedHosts == null) + return null; + else + return trustedHosts.clone(); + } finally { + lock.unlock(); + } } /** * @param trustedHosts the hosts to trust */ - public synchronized void setTrustedHosts(String... trustedHosts) { - if (trustedHosts == null) - this.trustedHosts = null; - else - this.trustedHosts = trustedHosts.clone(); + public void setTrustedHosts(String... trustedHosts) { + lock.lock(); + try { + if (trustedHosts == null) + this.trustedHosts = null; + else + this.trustedHosts = trustedHosts.clone(); + } finally { + lock.unlock(); + } } /** @@ -232,22 +290,26 @@ public synchronized void setTrustedHosts(String... trustedHosts) { * is contained in the "trustedHosts" array; * @param server name of the server we connected to */ - public synchronized boolean isServerTrusted(String server, + public boolean isServerTrusted(String server, SSLSocket sslSocket) { + lock.lock(); + try { + //System.out.println("DEBUG: isServerTrusted host " + server); - //System.out.println("DEBUG: isServerTrusted host " + server); - - // If "trustAllHosts" is set to true, we return true - if (trustAllHosts) - return true; + // If "trustAllHosts" is set to true, we return true + if (trustAllHosts) + return true; - // If the socket host is contained in the "trustedHosts" array, - // we return true - if (trustedHosts != null) - return Arrays.asList(trustedHosts).contains(server); // ignore case? + // If the socket host is contained in the "trustedHosts" array, + // we return true + if (trustedHosts != null) + return Arrays.asList(trustedHosts).contains(server); // ignore case? - // If we get here, trust of the server was verified by the trust manager - return true; + // If we get here, trust of the server was verified by the trust manager + return true; + } finally { + lock.unlock(); + } } @@ -258,33 +320,53 @@ public synchronized boolean isServerTrusted(String server, * java.lang.String, int, boolean) */ @Override - public synchronized Socket createSocket(Socket socket, String s, int i, + public Socket createSocket(Socket socket, String s, int i, boolean flag) throws IOException { - return adapteeFactory.createSocket(socket, s, i, flag); + lock.lock(); + try { + return adapteeFactory.createSocket(socket, s, i, flag); + } finally { + lock.unlock(); + } } /* (non-Javadoc) * @see javax.net.ssl.SSLSocketFactory#getDefaultCipherSuites() */ @Override - public synchronized String[] getDefaultCipherSuites() { - return adapteeFactory.getDefaultCipherSuites(); + public String[] getDefaultCipherSuites() { + lock.lock(); + try { + return adapteeFactory.getDefaultCipherSuites(); + } finally { + lock.unlock(); + } } /* (non-Javadoc) * @see javax.net.ssl.SSLSocketFactory#getSupportedCipherSuites() */ @Override - public synchronized String[] getSupportedCipherSuites() { - return adapteeFactory.getSupportedCipherSuites(); + public String[] getSupportedCipherSuites() { + lock.lock(); + try { + return adapteeFactory.getSupportedCipherSuites(); + } finally { + lock.unlock(); + } } /* (non-Javadoc) * @see javax.net.SocketFactory#createSocket() */ @Override - public synchronized Socket createSocket() throws IOException { - return adapteeFactory.createSocket(); + public Socket createSocket() throws IOException { + lock.lock(); + try { + return adapteeFactory.createSocket(); + } finally { + lock.unlock(); + } } /* (non-Javadoc) @@ -292,18 +374,28 @@ public synchronized Socket createSocket() throws IOException { * java.net.InetAddress, int) */ @Override - public synchronized Socket createSocket(InetAddress inetaddress, int i, + public Socket createSocket(InetAddress inetaddress, int i, InetAddress inetaddress1, int j) throws IOException { - return adapteeFactory.createSocket(inetaddress, i, inetaddress1, j); + lock.lock(); + try { + return adapteeFactory.createSocket(inetaddress, i, inetaddress1, j); + } finally { + lock.unlock(); + } } /* (non-Javadoc) * @see javax.net.SocketFactory#createSocket(java.net.InetAddress, int) */ @Override - public synchronized Socket createSocket(InetAddress inetaddress, int i) + public Socket createSocket(InetAddress inetaddress, int i) throws IOException { - return adapteeFactory.createSocket(inetaddress, i); + lock.lock(); + try { + return adapteeFactory.createSocket(inetaddress, i); + } finally { + lock.unlock(); + } } /* (non-Javadoc) @@ -311,19 +403,29 @@ public synchronized Socket createSocket(InetAddress inetaddress, int i) * java.net.InetAddress, int) */ @Override - public synchronized Socket createSocket(String s, int i, + public Socket createSocket(String s, int i, InetAddress inetaddress, int j) throws IOException, UnknownHostException { - return adapteeFactory.createSocket(s, i, inetaddress, j); + lock.lock(); + try { + return adapteeFactory.createSocket(s, i, inetaddress, j); + } finally { + lock.unlock(); + } } /* (non-Javadoc) * @see javax.net.SocketFactory#createSocket(java.lang.String, int) */ @Override - public synchronized Socket createSocket(String s, int i) + public Socket createSocket(String s, int i) throws IOException, UnknownHostException { - return adapteeFactory.createSocket(s, i); + lock.lock(); + try { + return adapteeFactory.createSocket(s, i); + } finally { + lock.unlock(); + } } diff --git a/core/src/main/java/org/eclipse/angus/mail/util/WriteTimeoutSocket.java b/core/src/main/java/org/eclipse/angus/mail/util/WriteTimeoutSocket.java index b31d1efb..0e8a5979 100644 --- a/core/src/main/java/org/eclipse/angus/mail/util/WriteTimeoutSocket.java +++ b/core/src/main/java/org/eclipse/angus/mail/util/WriteTimeoutSocket.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -39,6 +39,7 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.ReentrantLock; /** * A special Socket that uses a ScheduledExecutorService to @@ -49,6 +50,7 @@ */ public class WriteTimeoutSocket extends Socket { + private final ReentrantLock lock = new ReentrantLock(); // delegate all operations to this socket private final Socket socket; // to schedule task to cancel write after timeout @@ -170,9 +172,14 @@ public InputStream getInputStream() throws IOException { } @Override - public synchronized OutputStream getOutputStream() throws IOException { - // wrap the returned stream to implement write timeout - return new TimeoutOutputStream(socket, ses, timeout); + public OutputStream getOutputStream() throws IOException { + lock.lock(); + try { + // wrap the returned stream to implement write timeout + return new TimeoutOutputStream(socket, ses, timeout); + } finally { + lock.unlock(); + } } @Override @@ -385,6 +392,7 @@ class TimeoutOutputStream extends OutputStream { private static final String WRITE_TIMEOUT_MESSAGE = "Write timed out"; private static final String CANNOT_GET_TIMEOUT_TASK_RESULT_MESSAGE = "Couldn't get result of timeout task"; + private final ReentrantLock lock = new ReentrantLock(); private final OutputStream os; private final ScheduledExecutorService ses; private final Callable timeoutTask; @@ -413,45 +421,55 @@ public String call() throws Exception { } @Override - public synchronized void write(int b) throws IOException { - if (b1 == null) - b1 = new byte[1]; - b1[0] = (byte) b; - this.write(b1); + public void write(int b) throws IOException { + lock.lock(); + try { + if (b1 == null) + b1 = new byte[1]; + b1[0] = (byte) b; + this.write(b1); + } finally { + lock.unlock(); + } } @Override - public synchronized void write(byte[] bs, int off, int len) + public void write(byte[] bs, int off, int len) throws IOException { - if ((off < 0) || (off > bs.length) || (len < 0) || - ((off + len) > bs.length) || ((off + len) < 0)) { - throw new IndexOutOfBoundsException(); - } else if (len == 0) { - return; - } - + lock.lock(); try { - try { - if (timeout > 0) - sf = ses.schedule(timeoutTask, - timeout, TimeUnit.MILLISECONDS); - } catch (RejectedExecutionException ex) { - if (!socket.isClosed()) { - throw new IOException("Write aborted due to timeout not enforced", ex); - } + if ((off < 0) || (off > bs.length) || (len < 0) || + ((off + len) > bs.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; } try { - os.write(bs, off, len); - } catch (IOException e) { - if (sf != null && !sf.cancel(true)) { - throw new IOException(handleTimeoutTaskResult(sf), e); + try { + if (timeout > 0) + sf = ses.schedule(timeoutTask, + timeout, TimeUnit.MILLISECONDS); + } catch (RejectedExecutionException ex) { + if (!socket.isClosed()) { + throw new IOException("Write aborted due to timeout not enforced", ex); + } + } + + try { + os.write(bs, off, len); + } catch (IOException e) { + if (sf != null && !sf.cancel(true)) { + throw new IOException(handleTimeoutTaskResult(sf), e); + } + throw e; } - throw e; + } finally { + if (sf != null) + sf.cancel(true); } } finally { - if (sf != null) - sf.cancel(true); + lock.unlock(); } } diff --git a/dsn/src/main/java/org/eclipse/angus/mail/dsn/MultipartReport.java b/dsn/src/main/java/org/eclipse/angus/mail/dsn/MultipartReport.java index c74a2f9a..b5e46239 100644 --- a/dsn/src/main/java/org/eclipse/angus/mail/dsn/MultipartReport.java +++ b/dsn/src/main/java/org/eclipse/angus/mail/dsn/MultipartReport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -28,6 +28,7 @@ import java.io.IOException; import java.util.Vector; +import java.util.concurrent.locks.ReentrantLock; /** * A multipart/report message content, as defined in @@ -54,6 +55,7 @@ * @since JavaMail 1.4 */ public class MultipartReport extends MimeMultipart { + private final ReentrantLock lock = new ReentrantLock(); protected boolean constructed; // true when done with constructor /** @@ -169,7 +171,8 @@ public MultipartReport(DataSource ds) throws MessagingException { * @return the text * @exception MessagingException for failures */ - public synchronized String getText() throws MessagingException { + public String getText() throws MessagingException { + lock.lock(); try { BodyPart bp = getBodyPart(0); if (bp.isMimeType("text/plain")) @@ -184,6 +187,8 @@ public synchronized String getText() throws MessagingException { } } catch (IOException ex) { throw new MessagingException("Exception getting text content", ex); + } finally { + lock.unlock(); } return null; } @@ -195,10 +200,15 @@ public synchronized String getText() throws MessagingException { * @param text the text * @exception MessagingException for failures */ - public synchronized void setText(String text) throws MessagingException { - MimeBodyPart mbp = new MimeBodyPart(); - mbp.setText(text); - setBodyPart(mbp, 0); + public void setText(String text) throws MessagingException { + lock.lock(); + try { + MimeBodyPart mbp = new MimeBodyPart(); + mbp.setText(text); + setBodyPart(mbp, 0); + } finally { + lock.unlock(); + } } /** @@ -208,9 +218,14 @@ public synchronized void setText(String text) throws MessagingException { * @return the body part containing the text * @exception MessagingException for failures */ - public synchronized MimeBodyPart getTextBodyPart() + public MimeBodyPart getTextBodyPart() throws MessagingException { - return (MimeBodyPart) getBodyPart(0); + lock.lock(); + try { + return (MimeBodyPart) getBodyPart(0); + } finally { + lock.unlock(); + } } /** @@ -223,9 +238,14 @@ public synchronized MimeBodyPart getTextBodyPart() * @param mbp the body part containing the text * @exception MessagingException for failures */ - public synchronized void setTextBodyPart(MimeBodyPart mbp) + public void setTextBodyPart(MimeBodyPart mbp) throws MessagingException { - setBodyPart(mbp, 0); + lock.lock(); + try { + setBodyPart(mbp, 0); + } finally { + lock.unlock(); + } } /** @@ -235,17 +255,22 @@ public synchronized void setTextBodyPart(MimeBodyPart mbp) * @exception MessagingException for failures * @since JavaMail 1.4.2 */ - public synchronized Report getReport() throws MessagingException { - if (getCount() < 2) - return null; - BodyPart bp = getBodyPart(1); + public Report getReport() throws MessagingException { + lock.lock(); try { - Object content = bp.getContent(); - if (!(content instanceof Report)) + if (getCount() < 2) return null; - return (Report) content; - } catch (IOException ex) { - throw new MessagingException("IOException getting Report", ex); + BodyPart bp = getBodyPart(1); + try { + Object content = bp.getContent(); + if (!(content instanceof Report)) + return null; + return (Report) content; + } catch (IOException ex) { + throw new MessagingException("IOException getting Report", ex); + } + } finally { + lock.unlock(); } } @@ -256,16 +281,21 @@ public synchronized Report getReport() throws MessagingException { * @exception MessagingException for failures * @since JavaMail 1.4.2 */ - public synchronized void setReport(Report report) + public void setReport(Report report) throws MessagingException { - MimeBodyPart mbp = new MimeBodyPart(); - ContentType ct = new ContentType(contentType); - String reportType = report.getType(); - ct.setParameter("report-type", reportType); - contentType = ct.toString(); - ct = new ContentType("message", reportType, null); - mbp.setContent(report, ct.toString()); - setBodyPart(mbp, 1); + lock.lock(); + try { + MimeBodyPart mbp = new MimeBodyPart(); + ContentType ct = new ContentType(contentType); + String reportType = report.getType(); + ct.setParameter("report-type", reportType); + contentType = ct.toString(); + ct = new ContentType("message", reportType, null); + mbp.setContent(report, ct.toString()); + setBodyPart(mbp, 1); + } finally { + lock.unlock(); + } } /** @@ -276,18 +306,23 @@ public synchronized void setReport(Report report) * @deprecated use getReport instead */ @Deprecated - public synchronized DeliveryStatus getDeliveryStatus() + public DeliveryStatus getDeliveryStatus() throws MessagingException { - if (getCount() < 2) - return null; - BodyPart bp = getBodyPart(1); - if (!bp.isMimeType("message/delivery-status")) - return null; + lock.lock(); try { - return (DeliveryStatus) bp.getContent(); - } catch (IOException ex) { - throw new MessagingException("IOException getting DeliveryStatus", - ex); + if (getCount() < 2) + return null; + BodyPart bp = getBodyPart(1); + if (!bp.isMimeType("message/delivery-status")) + return null; + try { + return (DeliveryStatus) bp.getContent(); + } catch (IOException ex) { + throw new MessagingException("IOException getting DeliveryStatus", + ex); + } + } finally { + lock.unlock(); } } @@ -298,14 +333,19 @@ public synchronized DeliveryStatus getDeliveryStatus() * @exception MessagingException for failures * @deprecated use setReport instead */ - public synchronized void setDeliveryStatus(DeliveryStatus status) + public void setDeliveryStatus(DeliveryStatus status) throws MessagingException { - MimeBodyPart mbp = new MimeBodyPart(); - mbp.setContent(status, "message/delivery-status"); - setBodyPart(mbp, 1); - ContentType ct = new ContentType(contentType); - ct.setParameter("report-type", "delivery-status"); - contentType = ct.toString(); + lock.lock(); + try { + MimeBodyPart mbp = new MimeBodyPart(); + mbp.setContent(status, "message/delivery-status"); + setBodyPart(mbp, 1); + ContentType ct = new ContentType(contentType); + ct.setParameter("report-type", "delivery-status"); + contentType = ct.toString(); + } finally { + lock.unlock(); + } } /** @@ -317,19 +357,24 @@ public synchronized void setDeliveryStatus(DeliveryStatus status) * @return the returned message * @exception MessagingException for failures */ - public synchronized MimeMessage getReturnedMessage() + public MimeMessage getReturnedMessage() throws MessagingException { - if (getCount() < 3) - return null; - BodyPart bp = getBodyPart(2); - if (!bp.isMimeType("message/rfc822") && - !bp.isMimeType("text/rfc822-headers")) - return null; + lock.lock(); try { - return (MimeMessage) bp.getContent(); - } catch (IOException ex) { - throw new MessagingException("IOException getting ReturnedMessage", - ex); + if (getCount() < 3) + return null; + BodyPart bp = getBodyPart(2); + if (!bp.isMimeType("message/rfc822") && + !bp.isMimeType("text/rfc822-headers")) + return null; + try { + return (MimeMessage) bp.getContent(); + } catch (IOException ex) { + throw new MessagingException("IOException getting ReturnedMessage", + ex); + } + } finally { + lock.unlock(); } } @@ -341,28 +386,38 @@ public synchronized MimeMessage getReturnedMessage() * @param msg the returned message * @exception MessagingException for failures */ - public synchronized void setReturnedMessage(MimeMessage msg) + public void setReturnedMessage(MimeMessage msg) throws MessagingException { - if (msg == null) { - super.removeBodyPart(2); - return; + lock.lock(); + try { + if (msg == null) { + super.removeBodyPart(2); + return; + } + MimeBodyPart mbp = new MimeBodyPart(); + if (msg instanceof MessageHeaders) + mbp.setContent(msg, "text/rfc822-headers"); + else + mbp.setContent(msg, "message/rfc822"); + setBodyPart(mbp, 2); + } finally { + lock.unlock(); } - MimeBodyPart mbp = new MimeBodyPart(); - if (msg instanceof MessageHeaders) - mbp.setContent(msg, "text/rfc822-headers"); - else - mbp.setContent(msg, "message/rfc822"); - setBodyPart(mbp, 2); } - private synchronized void setBodyPart(BodyPart part, int index) + private void setBodyPart(BodyPart part, int index) throws MessagingException { - if (parts == null) // XXX - can never happen? - parts = new Vector(); + lock.lock(); + try { + if (parts == null) // XXX - can never happen? + parts = new Vector(); - if (index < parts.size()) - super.removeBodyPart(index); - super.addBodyPart(part, index); + if (index < parts.size()) + super.removeBodyPart(index); + super.addBodyPart(part, index); + } finally { + lock.unlock(); + } } @@ -374,9 +429,14 @@ private synchronized void setBodyPart(BodyPart part, int index) * @param subtype Subtype * @exception MessagingException always; can't change subtype */ - public synchronized void setSubType(String subtype) + public void setSubType(String subtype) throws MessagingException { - throw new MessagingException("Can't change subtype of MultipartReport"); + lock.lock(); + try { + throw new MessagingException("Can't change subtype of MultipartReport"); + } finally { + lock.unlock(); + } } /** @@ -410,14 +470,19 @@ public void removeBodyPart(int index) throws MessagingException { * @param part The Part to be appended * @throws MessagingException always */ - public synchronized void addBodyPart(BodyPart part) + public void addBodyPart(BodyPart part) throws MessagingException { - // Once constructor is done, don't allow this anymore. - if (!constructed) - super.addBodyPart(part); - else - throw new MessagingException( - "Can't add body parts to multipart/report 1"); + lock.lock(); + try { + // Once constructor is done, don't allow this anymore. + if (!constructed) + super.addBodyPart(part); + else + throw new MessagingException( + "Can't add body parts to multipart/report 1"); + } finally { + lock.unlock(); + } } /** @@ -428,9 +493,14 @@ public synchronized void addBodyPart(BodyPart part) * @param index Location where to insert the part * @throws MessagingException always */ - public synchronized void addBodyPart(BodyPart part, int index) + public void addBodyPart(BodyPart part, int index) throws MessagingException { - throw new MessagingException( - "Can't add body parts to multipart/report 2"); + lock.lock(); + try { + throw new MessagingException( + "Can't add body parts to multipart/report 2"); + } finally { + lock.unlock(); + } } } diff --git a/mailhandler/src/main/java/org/eclipse/angus/mail/util/logging/CollectorFormatter.java b/mailhandler/src/main/java/org/eclipse/angus/mail/util/logging/CollectorFormatter.java index 39cfe7ed..8e9e155f 100644 --- a/mailhandler/src/main/java/org/eclipse/angus/mail/util/logging/CollectorFormatter.java +++ b/mailhandler/src/main/java/org/eclipse/angus/mail/util/logging/CollectorFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2013, 2023 Jason Mehrens. All rights reserved. * * This program and the accompanying materials are made available under the @@ -22,6 +22,7 @@ import java.util.Comparator; import java.util.Locale; import java.util.ResourceBundle; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.LogRecord; @@ -74,6 +75,7 @@ public class CollectorFormatter extends Formatter { * Avoid depending on JMX runtime bean to get the start time. */ private static final long INIT_TIME = System.currentTimeMillis(); + private final ReentrantLock lock = new ReentrantLock(); /** * The message format string used as the formatted output. */ @@ -82,6 +84,7 @@ public class CollectorFormatter extends Formatter { * The formatter used to format the chosen log record. */ private Formatter formatter; + private ReentrantLock formatterLock; /** * The comparator used to pick the log record to format. */ @@ -348,28 +351,33 @@ protected LogRecord apply(final LogRecord t, final LogRecord u) { * @return true if the last record was the expected record. * @throws NullPointerException if the update record is null. */ - private synchronized boolean accept(final LogRecord e, final LogRecord u) { - /** - * LogRecord methods must be called before the check of the last stored - * record to guard against subclasses of LogRecord that might attempt to - * reset the state by triggering a call to getTail. - */ - final long millis = u.getMillis(); //Null check. - final Throwable ex = u.getThrown(); - if (last == e) { //Only if the exact same reference. - if (++count != 1L) { - minMillis = Math.min(minMillis, millis); - } else { //Show single records as instant and not a time period. - minMillis = millis; - } - maxMillis = Math.max(maxMillis, millis); + private boolean accept(final LogRecord e, final LogRecord u) { + lock.lock(); + try { + /** + * LogRecord methods must be called before the check of the last stored + * record to guard against subclasses of LogRecord that might attempt to + * reset the state by triggering a call to getTail. + */ + final long millis = u.getMillis(); //Null check. + final Throwable ex = u.getThrown(); + if (last == e) { //Only if the exact same reference. + if (++count != 1L) { + minMillis = Math.min(minMillis, millis); + } else { //Show single records as instant and not a time period. + minMillis = millis; + } + maxMillis = Math.max(maxMillis, millis); - if (ex != null) { - ++thrown; + if (ex != null) { + ++thrown; + } + return true; + } else { + return false; } - return true; - } else { - return false; + } finally { + lock.unlock(); } } @@ -378,16 +386,21 @@ private synchronized boolean accept(final LogRecord e, final LogRecord u) { * * @param min the current min milliseconds. */ - private synchronized void reset(final long min) { - if (last != null) { - last = null; - ++generation; - } + private void reset(final long min) { + lock.lock(); + try { + if (last != null) { + last = null; + ++generation; + } - count = 0L; - thrown = 0L; - minMillis = min; - maxMillis = Long.MIN_VALUE; + count = 0L; + thrown = 0L; + minMillis = min; + maxMillis = Long.MIN_VALUE; + } finally { + lock.unlock(); + } } /** @@ -402,6 +415,7 @@ private synchronized void reset(final long min) { private String formatRecord(final Handler h, final boolean reset) { final String p; final Formatter f; + final ReentrantLock fLock; final LogRecord record; final long c; final long t; @@ -409,9 +423,11 @@ private String formatRecord(final Handler h, final boolean reset) { long msl; long msh; long now; - synchronized (this) { + lock.lock(); + try { p = fmt; f = formatter; + fLock = formatterLock; record = last; c = count; g = generation; @@ -426,16 +442,21 @@ record = last; if (reset) { //BUG ID 6351685 reset(msh); } + } finally { + lock.unlock(); } final String head; final String msg; final String tail; if (f != null) { - synchronized (f) { + fLock.lock(); + try { head = f.getHead(h); msg = record != null ? f.format(record) : ""; tail = f.getTail(h); + } finally { + fLock.unlock(); } } else { head = ""; @@ -483,11 +504,16 @@ public void setFormat(String format) { * @param v the format pattern or null for default pattern. * @since Angus Mail 2.0.3 */ - private synchronized void setFormat0(String v) { - if (v == null || v.isEmpty()) { - v = "{0}{1}{2}{4,choice,-1#|0#|0<... {4,number,integer} more}\n"; + private void setFormat0(String v) { + lock.lock(); + try { + if (v == null || v.isEmpty()) { + v = "{0}{1}{2}{4,choice,-1#|0#|0<... {4,number,integer} more}\n"; + } + this.fmt = v; + } finally { + lock.unlock(); } - this.fmt = v; } /** @@ -498,9 +524,14 @@ private synchronized void setFormat0(String v) { * does not have LoggingPermission("control"). * @since Angus Mail 2.0.3 */ - public synchronized String getFormat() { - LogManagerProperties.checkLogManagerAccess(); - return this.fmt; + public String getFormat() { + lock.lock(); + try { + LogManagerProperties.checkLogManagerAccess(); + return this.fmt; + } finally { + lock.unlock(); + } } /** @@ -522,12 +553,18 @@ public void setFormatter(Formatter f) { * @param f the target formatter or null to use a LogManager default. * @since Angus Mail 2.0.3 */ - private synchronized void setFormatter0(Formatter f) { - if (f == null) { - //Don't force the byte code verifier to load the formatter. - f = Formatter.class.cast(new CompactFormatter()); + private void setFormatter0(Formatter f) { + lock.lock(); + try { + if (f == null) { + //Don't force the byte code verifier to load the formatter. + f = Formatter.class.cast(new CompactFormatter()); + } + this.formatterLock = new ReentrantLock(); + this.formatter = f; + } finally { + lock.unlock(); } - this.formatter = f; } @@ -539,9 +576,14 @@ private synchronized void setFormatter0(Formatter f) { * does not have LoggingPermission("control"). * @since Angus Mail 2.0.3 */ - public synchronized Formatter getFormatter() { - LogManagerProperties.checkLogManagerAccess(); - return this.formatter; + public Formatter getFormatter() { + lock.lock(); + try { + LogManagerProperties.checkLogManagerAccess(); + return this.formatter; + } finally { + lock.unlock(); + } } /** @@ -565,8 +607,13 @@ public void setComparator(Comparator c) { * used to specify choosing the last seen record. * @since Angus Mail 2.0.3 */ - private synchronized void setComparator0(Comparator c) { - this.comparator = c; + private void setComparator0(Comparator c) { + lock.lock(); + try { + this.comparator = c; + } finally { + lock.unlock(); + } } /** @@ -590,8 +637,13 @@ public Comparator getComparator() { * most recent record. * @since Angus Mail 2.0.3 */ - private synchronized Comparator getComparator0() { - return this.comparator; + private Comparator getComparator0() { + lock.lock(); + try { + return this.comparator; + } finally { + lock.unlock(); + } } /** @@ -611,8 +663,13 @@ protected String finish(String s) { * * @return null or the current log record. */ - private synchronized LogRecord peek() { - return this.last; + private LogRecord peek() { + lock.lock(); + try { + return this.last; + } finally { + lock.unlock(); + } } /** @@ -624,12 +681,17 @@ private synchronized LogRecord peek() { * @return true if the update was performed. * @throws NullPointerException if the update record is null. */ - private synchronized boolean acceptAndUpdate(LogRecord e, LogRecord u) { - if (accept(e, u)) { - this.last = u; - return true; - } else { - return false; + private boolean acceptAndUpdate(LogRecord e, LogRecord u) { + lock.lock(); + try { + if (accept(e, u)) { + this.last = u; + return true; + } else { + return false; + } + } finally { + lock.unlock(); } } diff --git a/mailhandler/src/main/java/org/eclipse/angus/mail/util/logging/DurationFilter.java b/mailhandler/src/main/java/org/eclipse/angus/mail/util/logging/DurationFilter.java index 67007c0d..57cb958e 100644 --- a/mailhandler/src/main/java/org/eclipse/angus/mail/util/logging/DurationFilter.java +++ b/mailhandler/src/main/java/org/eclipse/angus/mail/util/logging/DurationFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2015, 2023 Jason Mehrens. All rights reserved. * * This program and the accompanying materials are made available under the @@ -17,6 +17,7 @@ package org.eclipse.angus.mail.util.logging; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Filter; import java.util.logging.LogRecord; @@ -68,6 +69,8 @@ */ public class DurationFilter implements Filter { + private final ReentrantLock lock = new ReentrantLock(); + /** * The number of expected records per duration. */ @@ -135,20 +138,26 @@ public boolean equals(final Object obj) { final long c; final long p; final long s; - synchronized (this) { + lock.lock(); + try { r = this.records; d = this.duration; c = this.count; p = this.peak; s = this.start; + } finally { + lock.unlock(); } final DurationFilter other = (DurationFilter) obj; - synchronized (other) { + other.lock.lock(); + try { if (r != other.records || d != other.duration || c != other.count || p != other.peak || s != other.start) { return false; } + } finally { + other.lock.unlock(); } return true; } @@ -171,11 +180,16 @@ public boolean isIdle() { * @return hash code for this filter. */ @Override - public synchronized int hashCode() { - int hash = 3; - hash = 89 * hash + Long.hashCode(records); - hash = 89 * hash + Long.hashCode(duration); - return hash; + public int hashCode() { + lock.lock(); + try { + int hash = 3; + hash = 89 * hash + Long.hashCode(records); + hash = 89 * hash + Long.hashCode(duration); + return hash; + } finally { + lock.unlock(); + } } /** @@ -201,8 +215,11 @@ public boolean isLoggable(final LogRecord record) { */ public boolean isLoggable() { final long r; - synchronized (this) { + lock.lock(); + try { r = records; + } finally { + lock.unlock(); } return test(r, System.currentTimeMillis()); } @@ -215,9 +232,14 @@ public boolean isLoggable() { * does not have LoggingPermission("control"). * @since Angus Mail 2.0.3 */ - public synchronized long getRecords() { - LogManagerProperties.checkLogManagerAccess(); - return this.records; + public long getRecords() { + lock.lock(); + try { + LogManagerProperties.checkLogManagerAccess(); + return this.records; + } finally { + lock.unlock(); + } } /** @@ -228,9 +250,14 @@ public synchronized long getRecords() { * does not have LoggingPermission("control"). * @since Angus Mail 2.0.3 */ - public synchronized void setRecords(long records) { - LogManagerProperties.checkLogManagerAccess(); - this.records = checkRecords(records); + public void setRecords(long records) { + lock.lock(); + try { + LogManagerProperties.checkLogManagerAccess(); + this.records = checkRecords(records); + } finally { + lock.unlock(); + } } /** @@ -241,9 +268,14 @@ public synchronized void setRecords(long records) { * does not have LoggingPermission("control"). * @since Angus Mail 2.0.3 */ - public synchronized void setDurationMillis(long duration) { - LogManagerProperties.checkLogManagerAccess(); - this.duration = checkDuration(duration); + public void setDurationMillis(long duration) { + lock.lock(); + try { + LogManagerProperties.checkLogManagerAccess(); + this.duration = checkDuration(duration); + } finally { + lock.unlock(); + } } /** @@ -254,9 +286,14 @@ public synchronized void setDurationMillis(long duration) { * does not have LoggingPermission("control"). * @since Angus Mail 2.0.3 */ - public synchronized long getDurationMillis() { - LogManagerProperties.checkLogManagerAccess(); - return this.duration; + public long getDurationMillis() { + lock.lock(); + try { + LogManagerProperties.checkLogManagerAccess(); + return this.duration; + } finally { + lock.unlock(); + } } /** @@ -272,12 +309,15 @@ public String toString() { long d; boolean idle; boolean loggable; - synchronized (this) { + lock.lock(); + try { r = this.records; d = this.duration; final long millis = System.currentTimeMillis(); idle = test(0L, millis); loggable = test(r, millis); + } finally { + lock.unlock(); } return getClass().getName() + "{records=" + r @@ -316,10 +356,13 @@ private boolean test(final long limit, final long millis) { final long d; final long c; final long s; - synchronized (this) { + lock.lock(); + try { d = duration; c = count; s = start; + } finally { + lock.unlock(); } if (c > 0L) { //If not saturated. @@ -340,42 +383,47 @@ private boolean test(final long limit, final long millis) { * @param millis the log record milliseconds. * @return true if accepted false otherwise. */ - private synchronized boolean accept(final long millis) { - //Subtraction is used to deal with numeric overflow of millis. - boolean allow; - if (count > 0L) { //If not saturated. - if ((millis - peak) > 0L) { - peak = millis; //Record the new peak. - } + private boolean accept(final long millis) { + lock.lock(); + try { + //Subtraction is used to deal with numeric overflow of millis. + boolean allow; + if (count > 0L) { //If not saturated. + if ((millis - peak) > 0L) { + peak = millis; //Record the new peak. + } - //Under the rate if the count has not been reached. - if ((records - count) > 0L) { - ++count; - allow = true; - } else { - if ((peak - start) >= duration) { - count = 1L; //Start a new duration. - start = peak; + //Under the rate if the count has not been reached. + if ((records - count) > 0L) { + ++count; allow = true; } else { - count = -1L; //Saturate for the duration. - start = peak + duration; - allow = false; + if ((peak - start) >= duration) { + count = 1L; //Start a new duration. + start = peak; + allow = true; + } else { + count = -1L; //Saturate for the duration. + start = peak + duration; + allow = false; + } } - } - } else { - //If the saturation period has expired or this is the first record - //then start a new duration and allow records. - if ((millis - start) >= 0L || count == 0L) { - count = 1L; - start = millis; - peak = millis; - allow = true; } else { - allow = false; //Remain in a saturated state. + //If the saturation period has expired or this is the first record + //then start a new duration and allow records. + if ((millis - start) >= 0L || count == 0L) { + count = 1L; + start = millis; + peak = millis; + allow = true; + } else { + allow = false; //Remain in a saturated state. + } } + return allow; + } finally { + lock.unlock(); } - return allow; } /** diff --git a/mailhandler/src/main/java/org/eclipse/angus/mail/util/logging/LogManagerProperties.java b/mailhandler/src/main/java/org/eclipse/angus/mail/util/logging/LogManagerProperties.java index 501b77f5..03d2c013 100644 --- a/mailhandler/src/main/java/org/eclipse/angus/mail/util/logging/LogManagerProperties.java +++ b/mailhandler/src/main/java/org/eclipse/angus/mail/util/logging/LogManagerProperties.java @@ -33,6 +33,7 @@ import java.util.Locale; import java.util.Objects; import java.util.Properties; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.ErrorManager; import java.util.logging.Filter; import java.util.logging.Formatter; @@ -65,6 +66,8 @@ */ final class LogManagerProperties extends Properties { + private final ReentrantLock lock = new ReentrantLock(); + /** * Generated serial id. */ @@ -953,8 +956,13 @@ private static T invokeAccessController(final PrivilegedAction a) { */ @Override @SuppressWarnings("CloneDoesntCallSuperClone") - public synchronized Object clone() { - return exportCopy(defaults); + public Object clone() { + lock.lock(); + try { + return exportCopy(defaults); + } finally { + lock.unlock(); + } } /** @@ -965,31 +973,36 @@ public synchronized Object clone() { * @return the value for that key. */ @Override - public synchronized String getProperty(final String key) { - String value = defaults.getProperty(key); - if (value == null) { - if (key.length() > 0) { - value = fromLogManager(prefix + '.' + key); - } - + public String getProperty(final String key) { + lock.lock(); + try { + String value = defaults.getProperty(key); if (value == null) { - value = fromLogManager(key); - } + if (key.length() > 0) { + value = fromLogManager(prefix + '.' + key); + } - /** - * Copy the log manager properties as we read them. If a value is no - * longer present in the LogManager read it from here. The reason - * this works is because LogManager.reset() closes all attached - * handlers therefore, stale values only exist in closed handlers. - */ - if (value != null) { - super.put(key, value); - } else { - Object v = super.get(key); //defaults are not used. - value = v instanceof String ? (String) v : null; + if (value == null) { + value = fromLogManager(key); + } + + /** + * Copy the log manager properties as we read them. If a value is no + * longer present in the LogManager read it from here. The reason + * this works is because LogManager.reset() closes all attached + * handlers therefore, stale values only exist in closed handlers. + */ + if (value != null) { + super.put(key, value); + } else { + Object v = super.get(key); //defaults are not used. + value = v instanceof String ? (String) v : null; + } } + return value; + } finally { + lock.unlock(); } - return value; } /** @@ -1017,22 +1030,27 @@ public String getProperty(final String key, final String def) { * @since JavaMail 1.4.5 */ @Override - public synchronized Object get(final Object key) { - Object value; - if (key instanceof String) { - value = getProperty((String) key); - } else { - value = null; - } + public Object get(final Object key) { + lock.lock(); + try { + Object value; + if (key instanceof String) { + value = getProperty((String) key); + } else { + value = null; + } - //Search for non-string value. - if (value == null) { - value = defaults.get(key); - if (value == null && !defaults.containsKey(key)) { - value = super.get(key); + //Search for non-string value. + if (value == null) { + value = defaults.get(key); + if (value == null && !defaults.containsKey(key)) { + value = super.get(key); + } } + return value; + } finally { + lock.unlock(); } - return value; } /** @@ -1044,13 +1062,18 @@ public synchronized Object get(final Object key) { * @since JavaMail 1.4.5 */ @Override - public synchronized Object put(final Object key, final Object value) { - if (key instanceof String && value instanceof String) { - final Object def = preWrite(key); - final Object man = super.put(key, value); - return man == null ? def : man; - } else { - return super.put(key, value); + public Object put(final Object key, final Object value) { + lock.lock(); + try { + if (key instanceof String && value instanceof String) { + final Object def = preWrite(key); + final Object man = super.put(key, value); + return man == null ? def : man; + } else { + return super.put(key, value); + } + } finally { + lock.unlock(); } } @@ -1075,13 +1098,18 @@ public Object setProperty(String key, String value) { * @since JavaMail 1.4.5 */ @Override - public synchronized boolean containsKey(final Object key) { - boolean found = key instanceof String - && getProperty((String) key) != null; - if (!found) { - found = defaults.containsKey(key) || super.containsKey(key); + public boolean containsKey(final Object key) { + lock.lock(); + try { + boolean found = key instanceof String + && getProperty((String) key) != null; + if (!found) { + found = defaults.containsKey(key) || super.containsKey(key); + } + return found; + } finally { + lock.unlock(); } - return found; } /** @@ -1093,10 +1121,15 @@ public synchronized boolean containsKey(final Object key) { * @since JavaMail 1.4.5 */ @Override - public synchronized Object remove(final Object key) { - final Object def = preWrite(key); - final Object man = super.remove(key); - return man == null ? def : man; + public Object remove(final Object key) { + lock.lock(); + try { + final Object def = preWrite(key); + final Object man = super.remove(key); + return man == null ? def : man; + } finally { + lock.unlock(); + } } /** @@ -1180,8 +1213,13 @@ private Properties exportCopy(final Properties parent) { * @return the parent properties. * @throws ObjectStreamException if there is a problem. */ - private synchronized Object writeReplace() throws ObjectStreamException { - assert false; - return exportCopy((Properties) defaults.clone()); + private Object writeReplace() throws ObjectStreamException { + lock.lock(); + try { + assert false; + return exportCopy((Properties) defaults.clone()); + } finally { + lock.unlock(); + } } } diff --git a/mailhandler/src/main/java/org/eclipse/angus/mail/util/logging/MailHandler.java b/mailhandler/src/main/java/org/eclipse/angus/mail/util/logging/MailHandler.java index 915e137c..0b114f26 100644 --- a/mailhandler/src/main/java/org/eclipse/angus/mail/util/logging/MailHandler.java +++ b/mailhandler/src/main/java/org/eclipse/angus/mail/util/logging/MailHandler.java @@ -69,6 +69,7 @@ import java.util.Properties; import java.util.ResourceBundle; import java.util.ServiceConfigurationError; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.ErrorManager; import java.util.logging.Filter; import java.util.logging.Formatter; @@ -459,6 +460,7 @@ public class MailHandler extends Handler { * This must be less than the REPORT state. */ private static final Integer MUTEX_LINKAGE = -16; + private final ReentrantLock lock = new ReentrantLock(); /** * Used to turn off security checks. */ @@ -724,7 +726,8 @@ public void publish(final LogRecord record) { private void publish0(final LogRecord record) { Message msg; boolean priority; - synchronized (this) { + lock.lock(); + try { //No need to check for sealed as long as the init method ensures //that data.length and capacity are both zero until end of init(). //size is always zero on construction. @@ -747,6 +750,8 @@ private void publish0(final LogRecord record) { priority = false; msg = null; } + } finally { + lock.unlock(); } if (msg != null) { @@ -939,7 +944,8 @@ public void close() { checkAccess(); try { Message msg = null; - synchronized (this) { + lock.lock(); + try { try { msg = writeLogRecords(ErrorManager.CLOSE_FAILURE); } finally { //Change level after formatting. @@ -961,6 +967,8 @@ public void close() { initLogRecords(1); } } + } finally { + lock.unlock(); } if (msg != null) { @@ -981,10 +989,15 @@ public void close() { * @see #setLevel(java.util.logging.Level) * @since Angus Mail 2.0.3 */ - public synchronized boolean isEnabled() { - //For a this-escape, capacity will be zero and level will be off. - //No need to check that construction completed. - return capacity > 0 && this.logLevel.intValue() != offValue; + public boolean isEnabled() { + lock.lock(); + try { + //For a this-escape, capacity will be zero and level will be off. + //No need to check that construction completed. + return capacity > 0 && this.logLevel.intValue() != offValue; + } finally { + lock.unlock(); + } } /** @@ -1000,23 +1013,28 @@ public synchronized boolean isEnabled() { * @see #isEnabled() * @since Angus Mail 2.0.3 */ - public synchronized void setEnabled(final boolean enabled) { - checkAccess(); - if (this.capacity > 0) { //handler is open - if (this.size != 0) { - push(false, ErrorManager.FLUSH_FAILURE); - } - if (enabled) { - if (this.disabledLevel != null) { //was disabled - this.logLevel = this.disabledLevel; - this.disabledLevel = null; + public void setEnabled(final boolean enabled) { + lock.lock(); + try { + checkAccess(); + if (this.capacity > 0) { //handler is open + if (this.size != 0) { + push(false, ErrorManager.FLUSH_FAILURE); } - } else { - if (this.disabledLevel == null) { - this.disabledLevel = this.logLevel; - this.logLevel = Level.OFF; + if (enabled) { + if (this.disabledLevel != null) { //was disabled + this.logLevel = this.disabledLevel; + this.disabledLevel = null; + } + } else { + if (this.disabledLevel == null) { + this.disabledLevel = this.logLevel; + this.logLevel = Level.OFF; + } } } + } finally { + lock.unlock(); } } @@ -1038,7 +1056,8 @@ public void setLevel(final Level newLevel) { checkAccess(); //Don't allow a closed handler to be opened (half way). - synchronized (this) { //Wait for writeLogRecords. + lock.lock(); + try { //Wait for writeLogRecords. if (this.capacity > 0) { //if disabled then track the new level to be used when enabled. if (this.disabledLevel != null) { @@ -1047,6 +1066,8 @@ public void setLevel(final Level newLevel) { this.logLevel = newLevel; } } + } finally { + lock.unlock(); } } @@ -1106,9 +1127,12 @@ public void setErrorManager(final ErrorManager em) { private void setErrorManager0(final ErrorManager em) { Objects.requireNonNull(em); try { - synchronized (this) { //Wait for writeLogRecords. + lock.lock(); + try { //Wait for writeLogRecords. this.errorManager = em; super.setErrorManager(em); //Try to free super error manager. + } finally { + lock.unlock(); } } catch (RuntimeException | LinkageError ignore) { } @@ -1138,11 +1162,14 @@ public Filter getFilter() { @Override public void setFilter(final Filter newFilter) { checkAccess(); - synchronized (this) { //Wait for writeLogRecords. + lock.lock(); + try { //Wait for writeLogRecords. if (newFilter != filter) { clearMatches(-1); } this.filter = newFilter; //Volatile access. + } finally { + lock.unlock(); } } @@ -1153,10 +1180,15 @@ public void setFilter(final Filter newFilter) { * encoding should be used. */ @Override - public synchronized String getEncoding() { - //For a this-escape, this value will be null. - //No need to check that construction completed. - return this.encoding; + public String getEncoding() { + lock.lock(); + try { + //For a this-escape, this value will be null. + //No need to check that construction completed. + return this.encoding; + } finally { + lock.unlock(); + } } /** @@ -1195,9 +1227,11 @@ private void setEncoding0(String e) throws UnsupportedEncodingException { throw new UnsupportedEncodingException(e); } } - - synchronized (this) { //Wait for writeLogRecords. + lock.lock(); + try { this.encoding = e; + } finally { + lock.unlock(); } } @@ -1207,8 +1241,13 @@ private void setEncoding0(String e) throws UnsupportedEncodingException { * @return the Formatter (may be null). */ @Override - public synchronized Formatter getFormatter() { - return this.formatter; + public Formatter getFormatter() { + lock.lock(); + try { + return this.formatter; + } finally { + lock.unlock(); + } } /** @@ -1224,9 +1263,14 @@ public synchronized Formatter getFormatter() { * @throws NullPointerException if the given formatter is null. */ @Override - public synchronized void setFormatter(Formatter newFormatter) throws SecurityException { - checkAccess(); - this.formatter = Objects.requireNonNull(newFormatter); + public void setFormatter(Formatter newFormatter) throws SecurityException { + lock.lock(); + try { + checkAccess(); + this.formatter = Objects.requireNonNull(newFormatter); + } finally { + lock.unlock(); + } } /** @@ -1235,8 +1279,13 @@ public synchronized void setFormatter(Formatter newFormatter) throws SecurityExc * * @return a non-null push level. */ - public final synchronized Level getPushLevel() { - return this.pushLevel; + public final Level getPushLevel() { + lock.lock(); + try { + return this.pushLevel; + } finally { + lock.unlock(); + } } /** @@ -1250,16 +1299,21 @@ public final synchronized Level getPushLevel() { * caller does not have LoggingPermission("control"). * @throws IllegalStateException if called from inside a push. */ - public final synchronized void setPushLevel(Level level) { - checkAccess(); - if (level == null) { - level = Level.OFF; - } + public final void setPushLevel(Level level) { + lock.lock(); + try { + checkAccess(); + if (level == null) { + level = Level.OFF; + } - if (isWriting) { - throw new IllegalStateException(); + if (isWriting) { + throw new IllegalStateException(); + } + this.pushLevel = level; + } finally { + lock.unlock(); } - this.pushLevel = level; } /** @@ -1267,8 +1321,13 @@ public final synchronized void setPushLevel(Level level) { * * @return the push filter or null. */ - public final synchronized Filter getPushFilter() { - return this.pushFilter; + public final Filter getPushFilter() { + lock.lock(); + try { + return this.pushFilter; + } finally { + lock.unlock(); + } } /** @@ -1283,12 +1342,17 @@ public final synchronized Filter getPushFilter() { * caller does not have LoggingPermission("control"). * @throws IllegalStateException if called from inside a push. */ - public final synchronized void setPushFilter(final Filter filter) { - checkAccess(); - if (isWriting) { - throw new IllegalStateException(); + public final void setPushFilter(final Filter filter) { + lock.lock(); + try { + checkAccess(); + if (isWriting) { + throw new IllegalStateException(); + } + this.pushFilter = filter; + } finally { + lock.unlock(); } - this.pushFilter = filter; } /** @@ -1297,8 +1361,13 @@ public final synchronized void setPushFilter(final Filter filter) { * * @return the LogRecord comparator. */ - public final synchronized Comparator getComparator() { - return this.comparator; + public final Comparator getComparator() { + lock.lock(); + try { + return this.comparator; + } finally { + lock.unlock(); + } } /** @@ -1310,12 +1379,17 @@ public final synchronized Comparator getComparator() { * caller does not have LoggingPermission("control"). * @throws IllegalStateException if called from inside a push. */ - public final synchronized void setComparator(Comparator c) { - checkAccess(); - if (isWriting) { - throw new IllegalStateException(); + public final void setComparator(Comparator c) { + lock.lock(); + try { + checkAccess(); + if (isWriting) { + throw new IllegalStateException(); + } + this.comparator = c; + } finally { + lock.unlock(); } - this.comparator = c; } /** @@ -1325,9 +1399,14 @@ public final synchronized void setComparator(Comparator c) { * * @return the capacity. */ - public final synchronized int getCapacity() { - assert capacity != Integer.MIN_VALUE : capacity; - return capacity != 0 ? Math.abs(capacity) : DEFAULT_CAPACITY; + public final int getCapacity() { + lock.lock(); + try { + assert capacity != Integer.MIN_VALUE : capacity; + return capacity != 0 ? Math.abs(capacity) : DEFAULT_CAPACITY; + } finally { + lock.unlock(); + } } /** @@ -1344,9 +1423,14 @@ public final synchronized int getCapacity() { * @see #flush() * @since Angus Mail 2.0.3 */ - public final synchronized void setCapacity(int newCapacity) { - checkAccess(); - setCapacity0(newCapacity); + public final void setCapacity(int newCapacity) { + lock.lock(); + try { + checkAccess(); + setCapacity0(newCapacity); + } finally { + lock.unlock(); + } } /** @@ -1357,9 +1441,14 @@ public final synchronized void setCapacity(int newCapacity) { * @throws SecurityException if a security manager exists and the * caller does not have LoggingPermission("control"). */ - public final synchronized Authenticator getAuthenticator() { - checkAccess(); - return this.auth; + public final Authenticator getAuthenticator() { + lock.lock(); + try { + checkAccess(); + return this.auth; + } finally { + lock.unlock(); + } } /** @@ -1410,8 +1499,13 @@ public final void setAuthenticator(final char... password) { * @see #setAuthenticator(char...) * @since Angus Mail 2.0.3 */ - public final synchronized void setAuthentication(final String auth) { - setAuthenticator0(newAuthenticator(auth)); + public final void setAuthentication(final String auth) { + lock.lock(); + try { + setAuthenticator0(newAuthenticator(auth)); + } finally { + lock.unlock(); + } } /** @@ -1426,12 +1520,15 @@ private void setAuthenticator0(final Authenticator auth) { checkAccess(); Session settings; - synchronized (this) { + lock.lock(); + try{ if (isWriting) { throw new IllegalStateException(); } this.auth = auth; settings = updateSession(); + } finally { + lock.unlock(); } verifySettings(settings); } @@ -1494,12 +1591,15 @@ private Properties copyOf(Properties props) { private boolean setMailProperties0(Properties props) { Objects.requireNonNull(props); Session settings; - synchronized (this) { + lock.lock(); + try { if (isWriting) { throw new IllegalStateException(); } this.mailProps = props; settings = updateSession(); + } finally { + lock.unlock(); } return verifySettings(settings); } @@ -1514,8 +1614,11 @@ private boolean setMailProperties0(Properties props) { public final Properties getMailProperties() { checkAccess(); final Properties props; - synchronized (this) { + lock.lock(); + try { props = this.mailProps; + } finally { + lock.unlock(); } //Null check to force an error sooner rather than later. @@ -1588,8 +1691,11 @@ public final void setMailEntries(String entries) { public final String getMailEntries() { checkAccess(); final Properties props; - synchronized (this) { + lock.lock(); + try { props = this.mailProps; + } finally { + lock.unlock(); } final StringWriter sw = new StringWriter(); @@ -1644,7 +1750,8 @@ public final void setAttachmentFilters(Filter... filters) { filters = Arrays.copyOf(filters, filters.length, Filter[].class); } - synchronized (this) { + lock.lock(); + try { if (isWriting) { throw new IllegalStateException(); } @@ -1662,6 +1769,8 @@ public final void setAttachmentFilters(Filter... filters) { this.attachmentFilters = filters; this.alignAttachmentFormatters(filters.length); this.alignAttachmentNames(filters.length); + } finally { + lock.unlock(); } } @@ -1673,8 +1782,11 @@ public final void setAttachmentFilters(Filter... filters) { */ public final Formatter[] getAttachmentFormatters() { Formatter[] formatters; - synchronized (this) { + lock.lock(); + try { formatters = this.attachmentFormatters; + } finally { + lock.unlock(); } return formatters.clone(); } @@ -1706,7 +1818,8 @@ public final void setAttachmentFormatters(Formatter... formatters) { } } - synchronized (this) { + lock.lock(); + try { if (isWriting) { throw new IllegalStateException(); } @@ -1714,6 +1827,8 @@ public final void setAttachmentFormatters(Formatter... formatters) { this.attachmentFormatters = formatters; this.alignAttachmentFilters(formatters.length); this.alignAttachmentNames(formatters.length); + } finally { + lock.unlock(); } } @@ -1727,8 +1842,11 @@ public final void setAttachmentFormatters(Formatter... formatters) { */ public final Formatter[] getAttachmentNames() { final Formatter[] formatters; - synchronized (this) { + lock.lock(); + try { formatters = this.attachmentNames; + } finally { + lock.unlock(); } return formatters.clone(); } @@ -1758,7 +1876,8 @@ public final void setAttachmentNames(final String... names) { formatters = new Formatter[names.length]; } - synchronized (this) { + lock.lock(); + try { if (isWriting) { throw new IllegalStateException(); } @@ -1774,6 +1893,8 @@ public final void setAttachmentNames(final String... names) { formatters[i] = TailNameFormatter.of(name); } this.attachmentNames = formatters; + } finally { + lock.unlock(); } } @@ -1830,7 +1951,8 @@ public final void setAttachmentNameFormatters(Formatter... formatters) { Formatter[].class); } - synchronized (this) { + lock.lock(); + try { if (isWriting) { throw new IllegalStateException(); } @@ -1843,6 +1965,8 @@ public final void setAttachmentNameFormatters(Formatter... formatters) { } } this.attachmentNames = formatters; + } finally { + lock.unlock(); } } @@ -1853,8 +1977,13 @@ public final void setAttachmentNameFormatters(Formatter... formatters) { * * @return the formatter. */ - public final synchronized Formatter getSubject() { - return getSubjectFormatter(); + public final Formatter getSubject() { + lock.lock(); + try { + return getSubjectFormatter(); + } finally { + lock.unlock(); + } } /** @@ -1865,8 +1994,13 @@ public final synchronized Formatter getSubject() { * @return the formatter. * @since Angus Mail 2.0.3 */ - public final synchronized Formatter getSubjectFormatter() { - return this.subjectFormatter; + public final Formatter getSubjectFormatter() { + lock.lock(); + try { + return this.subjectFormatter; + } finally { + lock.unlock(); + } } /** @@ -1882,12 +2016,17 @@ public final synchronized Formatter getSubjectFormatter() { * @see Character#isISOControl(char) * @see Character#isISOControl(int) */ - public synchronized final void setSubject(final String subject) { - if (subject != null) { - this.setSubjectFormatter(TailNameFormatter.of(subject)); - } else { - checkAccess(); - initSubject((String) null); + public final void setSubject(final String subject) { + lock.lock(); + try { + if (subject != null) { + this.setSubjectFormatter(TailNameFormatter.of(subject)); + } else { + checkAccess(); + initSubject((String) null); + } + } finally { + lock.unlock(); } } @@ -1934,15 +2073,20 @@ public final void setSubject(final Formatter format) { * @see Character#isISOControl(int) * @since Angus Mail 2.0.3 */ - public synchronized final void setSubjectFormatter(final Formatter format) { - checkAccess(); - if (format != null) { - if (isWriting) { - throw new IllegalStateException(); + public final void setSubjectFormatter(final Formatter format) { + lock.lock(); + try { + checkAccess(); + if (format != null) { + if (isWriting) { + throw new IllegalStateException(); + } + this.subjectFormatter = format; + } else { + initSubject((String) null); } - this.subjectFormatter = format; - } else { - initSubject((String) null); + } finally { + lock.unlock(); } } @@ -2305,27 +2449,32 @@ private String contentWithEncoding(String type, String encoding) { * the value is negative. * @throws IllegalStateException if called from inside a push. */ - private synchronized void setCapacity0(int newCapacity) { - if (isWriting) { - throw new IllegalStateException(); - } + private void setCapacity0(int newCapacity) { + lock.lock(); + try { + if (isWriting) { + throw new IllegalStateException(); + } - if (!this.sealed || this.capacity == 0) { - return; - } + if (!this.sealed || this.capacity == 0) { + return; + } - if (newCapacity <= 0) { - newCapacity = DEFAULT_CAPACITY; - } + if (newCapacity <= 0) { + newCapacity = DEFAULT_CAPACITY; + } - if (this.capacity < 0) { //If closed, remain closed. - this.capacity = -newCapacity; - } else { - push(false, ErrorManager.FLUSH_FAILURE); - this.capacity = newCapacity; - if (this.data.length > newCapacity) { - initLogRecords(1); + if (this.capacity < 0) { //If closed, remain closed. + this.capacity = -newCapacity; + } else { + push(false, ErrorManager.FLUSH_FAILURE); + this.capacity = newCapacity; + if (this.data.length > newCapacity) { + initLogRecords(1); + } } + } finally { + lock.unlock(); } } @@ -2489,74 +2638,79 @@ private void grow() { * caller does not have LoggingPermission("control"). * @see #sealed */ - private synchronized void init(final Properties props) { - //Ensure non-null even on exception. - //Zero value allows publish to not check for this-escape. - initLogRecords(0); - LogManagerProperties.checkLogManagerAccess(); + private void init(final Properties props) { + lock.lock(); + try { + //Ensure non-null even on exception. + //Zero value allows publish to not check for this-escape. + initLogRecords(0); + LogManagerProperties.checkLogManagerAccess(); - final String p = getClass().getName(); - //Assign any custom error manager first so it can detect all failures. - assert this.errorManager != null; //default set before custom object - initErrorManager(fromLogManager(p.concat(".errorManager"))); - int cap = parseCapacity(fromLogManager(p.concat(".capacity"))); - Level lvl = parseLevel(fromLogManager(p.concat(".level"))); - boolean enabled = parseEnabled(fromLogManager(p.concat(".enabled"))); - initContentTypes(); - - initFilter(fromLogManager(p.concat(".filter"))); - this.auth = newAuthenticator(fromLogManager(p.concat(".authenticator"))); - - initEncoding(fromLogManager(p.concat(".encoding"))); - initFormatter(fromLogManager(p.concat(".formatter"))); - initComparator(fromLogManager(p.concat(".comparator"))); - initComparatorReverse(fromLogManager(p.concat(".comparator.reverse"))); - initPushLevel(fromLogManager(p.concat(".pushLevel"))); - initPushFilter(fromLogManager(p.concat(".pushFilter"))); - - initSubject(fromLogManager(p.concat(".subject"))); - - initAttachmentFormaters(fromLogManager(p.concat(".attachment.formatters"))); - initAttachmentFilters(fromLogManager(p.concat(".attachment.filters"))); - initAttachmentNames(fromLogManager(p.concat(".attachment.names"))); - - //Entries are always parsed to report any errors. - Properties entries = parseProperties(fromLogManager(p.concat(".mailEntries"))); - - //Any new handler object members should be set above this line - String verify = fromLogManager(p.concat(".verify")); - boolean verified; - if (props != null) { - //Given properties do not fallback to log manager. - setMailProperties0(copyOf(props)); - verified = true; - } else if (entries != null) { - //.mailEntries should fallback to log manager when verify key not present. - verified = setMailProperties0(entries); - } else { - verified = false; - } + final String p = getClass().getName(); + //Assign any custom error manager first so it can detect all failures. + assert this.errorManager != null; //default set before custom object + initErrorManager(fromLogManager(p.concat(".errorManager"))); + int cap = parseCapacity(fromLogManager(p.concat(".capacity"))); + Level lvl = parseLevel(fromLogManager(p.concat(".level"))); + boolean enabled = parseEnabled(fromLogManager(p.concat(".enabled"))); + initContentTypes(); + + initFilter(fromLogManager(p.concat(".filter"))); + this.auth = newAuthenticator(fromLogManager(p.concat(".authenticator"))); + + initEncoding(fromLogManager(p.concat(".encoding"))); + initFormatter(fromLogManager(p.concat(".formatter"))); + initComparator(fromLogManager(p.concat(".comparator"))); + initComparatorReverse(fromLogManager(p.concat(".comparator.reverse"))); + initPushLevel(fromLogManager(p.concat(".pushLevel"))); + initPushFilter(fromLogManager(p.concat(".pushFilter"))); + + initSubject(fromLogManager(p.concat(".subject"))); + + initAttachmentFormaters(fromLogManager(p.concat(".attachment.formatters"))); + initAttachmentFilters(fromLogManager(p.concat(".attachment.filters"))); + initAttachmentNames(fromLogManager(p.concat(".attachment.names"))); + + //Entries are always parsed to report any errors. + Properties entries = parseProperties(fromLogManager(p.concat(".mailEntries"))); + + //Any new handler object members should be set above this line + String verify = fromLogManager(p.concat(".verify")); + boolean verified; + if (props != null) { + //Given properties do not fallback to log manager. + setMailProperties0(copyOf(props)); + verified = true; + } else if (entries != null) { + //.mailEntries should fallback to log manager when verify key not present. + verified = setMailProperties0(entries); + } else { + verified = false; + } - //Fallback to top level verify properties if needed. - if (!verified && verify != null) { - try { - verifySettings(initSession()); - } catch (final RuntimeException re) { - reportError("Unable to verify", re, ErrorManager.OPEN_FAILURE); - } catch (final ServiceConfigurationError sce) { - reportConfigurationError(sce, ErrorManager.OPEN_FAILURE); + //Fallback to top level verify properties if needed. + if (!verified && verify != null) { + try { + verifySettings(initSession()); + } catch (final RuntimeException re) { + reportError("Unable to verify", re, ErrorManager.OPEN_FAILURE); + } catch (final ServiceConfigurationError sce) { + reportConfigurationError(sce, ErrorManager.OPEN_FAILURE); + } } - } - intern(); //Show verify warnings first. + intern(); //Show verify warnings first. - //Mark the handler as fully constructed by setting these fields. - this.capacity = cap; - if (enabled) { - this.logLevel = lvl; - } else { - this.disabledLevel = lvl; + //Mark the handler as fully constructed by setting these fields. + this.capacity = cap; + if (enabled) { + this.logLevel = lvl; + } else { + this.disabledLevel = lvl; + } + sealed = true; + } finally { + lock.unlock(); } - sealed = true; } /** @@ -3391,7 +3545,8 @@ private void sort() { */ private Message writeLogRecords(final int code) { try { - synchronized (this) { + lock.lock(); + try { if (size > 0 && !isWriting) { isWriting = true; try { @@ -3403,6 +3558,8 @@ private Message writeLogRecords(final int code) { } } } + } finally { + lock.unlock(); } } catch (final Exception e) { reportError("Unable to create message", e, code); @@ -3653,7 +3810,8 @@ private void verifySettings0(Session session, String verify) { //Perform all of the copy actions first. String[] atn; - synchronized (this) { //Create the subject. + lock.lock(); + try { //Create the subject. appendSubject(abort, head(subjectFormatter)); appendSubject(abort, tail(subjectFormatter, "")); atn = new String[attachmentNames.length]; @@ -3665,6 +3823,8 @@ private void verifySettings0(Session session, String verify) { atn[i] = atn[i].concat(tail(attachmentNames[i], "")); } } + } finally { + lock.unlock(); } setIncompleteCopy(abort); //Original body part is never added. @@ -3811,7 +3971,8 @@ private void verifySettings0(Session session, String verify) { MimeBodyPart[] ambp = new MimeBodyPart[atn.length]; final MimeBodyPart body; final String bodyContentType; - synchronized (this) { + lock.lock(); + try { bodyContentType = contentTypeOf(getFormatter()); body = createBodyPart(); for (int i = 0; i < atn.length; ++i) { @@ -3820,6 +3981,8 @@ private void verifySettings0(Session session, String verify) { //Convert names to mime type under lock. atn[i] = getContentType(atn[i]); } + } finally { + lock.unlock(); } body.setDescription(verify); @@ -4018,10 +4181,13 @@ private void setErrorContent(MimeMessage msg, String verify, Throwable t) { final MimeBodyPart body; final String subjectType; final String msgDesc; - synchronized (this) { + lock.lock(); + try { body = createBodyPart(); msgDesc = descriptionFrom(comparator, pushLevel, pushFilter); subjectType = getClassId(subjectFormatter); + } finally { + lock.unlock(); } body.setDescription("Formatted using " diff --git a/providers/gimap/src/main/java/org/eclipse/angus/mail/gimap/GmailFolder.java b/providers/gimap/src/main/java/org/eclipse/angus/mail/gimap/GmailFolder.java index ebea74f0..b045c6e3 100644 --- a/providers/gimap/src/main/java/org/eclipse/angus/mail/gimap/GmailFolder.java +++ b/providers/gimap/src/main/java/org/eclipse/angus/mail/gimap/GmailFolder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,6 +16,8 @@ package org.eclipse.angus.mail.gimap; +import java.util.concurrent.locks.ReentrantLock; + import jakarta.mail.FetchProfile; import jakarta.mail.FolderClosedException; import jakarta.mail.Message; @@ -41,6 +43,9 @@ */ public class GmailFolder extends IMAPFolder { + + private final ReentrantLock lock = new ReentrantLock(); + /** * A fetch profile item for fetching headers. * This inner class extends the FetchProfile.Item @@ -117,29 +122,37 @@ protected FetchProfileItem(String name) { * @exception MessagingException for failures * @since JavaMail 1.5.5 */ - public synchronized void setLabels(Message[] msgs, + public void setLabels(Message[] msgs, String[] labels, boolean set) throws MessagingException { - checkOpened(); + lock.lock(); + try { + checkOpened(); - if (msgs.length == 0) // boundary condition - return; + if (msgs.length == 0) // boundary condition + return; - synchronized (messageCacheLock) { + messageCacheLock.lock.lock(); try { - IMAPProtocol ip = getProtocol(); - assert ip instanceof GmailProtocol; - GmailProtocol p = (GmailProtocol) ip; - MessageSet[] ms = Utility.toMessageSetSorted(msgs, null); - if (ms == null) - throw new MessageRemovedException( - "Messages have been removed"); - p.storeLabels(ms, labels, set); - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); + try { + IMAPProtocol ip = getProtocol(); + assert ip instanceof GmailProtocol; + GmailProtocol p = (GmailProtocol) ip; + MessageSet[] ms = Utility.toMessageSetSorted(msgs, null); + if (ms == null) + throw new MessageRemovedException( + "Messages have been removed"); + p.storeLabels(ms, labels, set); + } catch (ConnectionException cex) { + throw new FolderClosedException(this, cex.getMessage()); + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); + } + } finally { + messageCacheLock.lock.unlock(); } + } finally { + lock.unlock(); } } @@ -153,15 +166,20 @@ public synchronized void setLabels(Message[] msgs, * @exception MessagingException for failures * @since JavaMail 1.5.5 */ - public synchronized void setLabels(int start, int end, + public void setLabels(int start, int end, String[] labels, boolean set) throws MessagingException { - checkOpened(); - Message[] msgs = new Message[end - start + 1]; - int i = 0; - for (int n = start; n <= end; n++) - msgs[i++] = getMessage(n); - setLabels(msgs, labels, set); + lock.lock(); + try { + checkOpened(); + Message[] msgs = new Message[end - start + 1]; + int i = 0; + for (int n = start; n <= end; n++) + msgs[i++] = getMessage(n); + setLabels(msgs, labels, set); + } finally { + lock.unlock(); + } } /** @@ -173,14 +191,19 @@ public synchronized void setLabels(int start, int end, * @exception MessagingException for failures * @since JavaMail 1.5.5 */ - public synchronized void setLabels(int[] msgnums, + public void setLabels(int[] msgnums, String[] labels, boolean set) throws MessagingException { - checkOpened(); - Message[] msgs = new Message[msgnums.length]; - for (int i = 0; i < msgnums.length; i++) - msgs[i] = getMessage(msgnums[i]); - setLabels(msgs, labels, set); + lock.lock(); + try { + checkOpened(); + Message[] msgs = new Message[msgnums.length]; + for (int i = 0; i < msgnums.length; i++) + msgs[i] = getMessage(msgnums[i]); + setLabels(msgs, labels, set); + } finally { + lock.unlock(); + } } /** diff --git a/providers/gimap/src/main/java/org/eclipse/angus/mail/gimap/GmailMessage.java b/providers/gimap/src/main/java/org/eclipse/angus/mail/gimap/GmailMessage.java index f521e3f4..87e5728e 100644 --- a/providers/gimap/src/main/java/org/eclipse/angus/mail/gimap/GmailMessage.java +++ b/providers/gimap/src/main/java/org/eclipse/angus/mail/gimap/GmailMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -103,10 +103,12 @@ public String[] getLabels() throws MessagingException { * @exception MessagingException for failures * @since JavaMail 1.5.5 */ - public synchronized void setLabels(String[] labels, boolean set) + public void setLabels(String[] labels, boolean set) throws MessagingException { - // Acquire MessageCacheLock, to freeze seqnum. - synchronized (getMessageCacheLock()) { + lock.lock(); + try { + // Acquire MessageCacheLock, to freeze seqnum. + getMessageCacheLock().lock(); try { IMAPProtocol ip = getProtocol(); assert ip instanceof GmailProtocol; @@ -117,7 +119,11 @@ public synchronized void setLabels(String[] labels, boolean set) throw new FolderClosedException(folder, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); + } finally { + getMessageCacheLock().unlock(); } + } finally { + lock.unlock(); } } @@ -131,8 +137,13 @@ public synchronized void setLabels(String[] labels, boolean set) * * @since JavaMail 1.5.6 */ - public synchronized void clearCachedLabels() { - if (items != null) - items.remove(GmailProtocol.LABELS_ITEM.getName()); + public void clearCachedLabels() { + lock.lock(); + try { + if (items != null) + items.remove(GmailProtocol.LABELS_ITEM.getName()); + } finally { + lock.unlock(); + } } } diff --git a/providers/imap/src/main/java/org/eclipse/angus/mail/iap/Protocol.java b/providers/imap/src/main/java/org/eclipse/angus/mail/iap/Protocol.java index f6d9e1ce..cb0731f0 100644 --- a/providers/imap/src/main/java/org/eclipse/angus/mail/iap/Protocol.java +++ b/providers/imap/src/main/java/org/eclipse/angus/mail/iap/Protocol.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -40,6 +40,7 @@ import java.util.Properties; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; @@ -57,6 +58,7 @@ */ public class Protocol { + private final ReentrantLock lock = new ReentrantLock(); protected String host; private Socket socket; // in case we turn on TLS, we'll need these later @@ -353,58 +355,63 @@ public String writeCommand(String command, Argument args) * @param args the arguments * @return array of Response objects returned by the server */ - public synchronized Response[] command(String command, Argument args) { - commandStart(command); - List v = new ArrayList<>(); - boolean done = false; - String tag = null; - - // write the command + public Response[] command(String command, Argument args) { + lock.lock(); try { - tag = writeCommand(command, args); - } catch (LiteralException lex) { - v.add(lex.getResponse()); - done = true; - } catch (Exception ex) { - // Convert this into a BYE response - v.add(Response.byeResponse(ex)); - done = true; - } + commandStart(command); + List v = new ArrayList<>(); + boolean done = false; + String tag = null; - Response byeResp = null; - while (!done) { - Response r = null; + // write the command try { - r = readResponse(); - } catch (IOException ioex) { - if (byeResp == null) // convert this into a BYE response - byeResp = Response.byeResponse(ioex); - // else, connection closed after BYE was sent - break; - } catch (ProtocolException pex) { - logger.log(Level.FINE, "ignoring bad response", pex); - continue; // skip this response + tag = writeCommand(command, args); + } catch (LiteralException lex) { + v.add(lex.getResponse()); + done = true; + } catch (Exception ex) { + // Convert this into a BYE response + v.add(Response.byeResponse(ex)); + done = true; } - if (r.isBYE()) { - byeResp = r; - continue; - } + Response byeResp = null; + while (!done) { + Response r = null; + try { + r = readResponse(); + } catch (IOException ioex) { + if (byeResp == null) // convert this into a BYE response + byeResp = Response.byeResponse(ioex); + // else, connection closed after BYE was sent + break; + } catch (ProtocolException pex) { + logger.log(Level.FINE, "ignoring bad response", pex); + continue; // skip this response + } - v.add(r); + if (r.isBYE()) { + byeResp = r; + continue; + } - // If this is a matching command completion response, we are done - if (r.isTagged() && r.getTag().equals(tag)) - done = true; - } + v.add(r); - if (byeResp != null) - v.add(byeResp); // must be last - Response[] responses = new Response[v.size()]; - v.toArray(responses); - timestamp = System.currentTimeMillis(); - commandEnd(); - return responses; + // If this is a matching command completion response, we are done + if (r.isTagged() && r.getTag().equals(tag)) + done = true; + } + + if (byeResp != null) + v.add(byeResp); // must be last + Response[] responses = new Response[v.size()]; + v.toArray(responses); + timestamp = System.currentTimeMillis(); + commandEnd(); + return responses; + } finally { + lock.unlock(); + } } /** @@ -457,13 +464,18 @@ public void simpleCommand(String cmd, Argument args) * @exception IOException for I/O errors * @exception ProtocolException for protocol failures */ - public synchronized void startTLS(String cmd) + public void startTLS(String cmd) throws IOException, ProtocolException { - if (socket instanceof SSLSocket) - return; // nothing to do - simpleCommand(cmd, null); - socket = SocketFetcher.startTLS(socket, host, props, prefix); - initStreams(); + lock.lock(); + try { + if (socket instanceof SSLSocket) + return; // nothing to do + simpleCommand(cmd, null); + socket = SocketFetcher.startTLS(socket, host, props, prefix); + initStreams(); + } finally { + lock.unlock(); + } } /** @@ -475,43 +487,48 @@ public synchronized void startTLS(String cmd) * @exception IOException for I/O errors * @exception ProtocolException for protocol failures */ - public synchronized void startCompression(String cmd) + public void startCompression(String cmd) throws IOException, ProtocolException { - // XXX - check whether compression is already enabled? - simpleCommand(cmd, null); - - // need to create our own Inflater and Deflater in order to set nowrap - Inflater inf = new Inflater(true); - traceInput = new TraceInputStream(new InflaterInputStream( - socket.getInputStream(), inf), traceLogger); - traceInput.setQuote(quote); - input = new ResponseInputStream(traceInput); - - // configure the Deflater - int level = PropUtil.getIntProperty(props, prefix + ".compress.level", - Deflater.DEFAULT_COMPRESSION); - int strategy = PropUtil.getIntProperty(props, - prefix + ".compress.strategy", - Deflater.DEFAULT_STRATEGY); - if (logger.isLoggable(Level.FINE)) - logger.log(Level.FINE, - "Creating Deflater with compression level {0} and strategy {1}", - level, strategy); - Deflater def = new Deflater(Deflater.DEFAULT_COMPRESSION, true); - try { - def.setLevel(level); - } catch (IllegalArgumentException ex) { - logger.log(Level.FINE, "Ignoring bad compression level", ex); - } + lock.lock(); try { - def.setStrategy(strategy); - } catch (IllegalArgumentException ex) { - logger.log(Level.FINE, "Ignoring bad compression strategy", ex); + // XXX - check whether compression is already enabled? + simpleCommand(cmd, null); + + // need to create our own Inflater and Deflater in order to set nowrap + Inflater inf = new Inflater(true); + traceInput = new TraceInputStream(new InflaterInputStream( + socket.getInputStream(), inf), traceLogger); + traceInput.setQuote(quote); + input = new ResponseInputStream(traceInput); + + // configure the Deflater + int level = PropUtil.getIntProperty(props, prefix + ".compress.level", + Deflater.DEFAULT_COMPRESSION); + int strategy = PropUtil.getIntProperty(props, + prefix + ".compress.strategy", + Deflater.DEFAULT_STRATEGY); + if (logger.isLoggable(Level.FINE)) + logger.log(Level.FINE, + "Creating Deflater with compression level {0} and strategy {1}", + level, strategy); + Deflater def = new Deflater(Deflater.DEFAULT_COMPRESSION, true); + try { + def.setLevel(level); + } catch (IllegalArgumentException ex) { + logger.log(Level.FINE, "Ignoring bad compression level", ex); + } + try { + def.setStrategy(strategy); + } catch (IllegalArgumentException ex) { + logger.log(Level.FINE, "Ignoring bad compression strategy", ex); + } + traceOutput = new TraceOutputStream(new DeflaterOutputStream( + socket.getOutputStream(), def, true), traceLogger); + traceOutput.setQuote(quote); + output = new DataOutputStream(new BufferedOutputStream(traceOutput)); + } finally { + lock.unlock(); } - traceOutput = new TraceOutputStream(new DeflaterOutputStream( - socket.getOutputStream(), def, true), traceLogger); - traceOutput.setQuote(quote); - output = new DataOutputStream(new BufferedOutputStream(traceOutput)); } /** @@ -641,14 +658,19 @@ public boolean supportsUtf8() { /** * Disconnect. */ - protected synchronized void disconnect() { - if (socket != null) { - try { - socket.close(); - } catch (IOException e) { - // ignore it + protected void disconnect() { + lock.lock(); + try { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + // ignore it + } + socket = null; } - socket = null; + } finally { + lock.unlock(); } } @@ -660,38 +682,43 @@ protected synchronized void disconnect() { * * @return the name of the local host */ - protected synchronized String getLocalHost() { - // get our hostname and cache it for future use - if (localHostName == null || localHostName.length() <= 0) - localHostName = - props.getProperty(prefix + ".localhost"); - if (localHostName == null || localHostName.length() <= 0) - localHostName = - props.getProperty(prefix + ".localaddress"); + protected String getLocalHost() { + lock.lock(); try { - if (localHostName == null || localHostName.length() <= 0) { - InetAddress localHost = InetAddress.getLocalHost(); - localHostName = localHost.getCanonicalHostName(); - // if we can't get our name, use local address literal - if (localHostName == null) - // XXX - not correct for IPv6 - localHostName = "[" + localHost.getHostAddress() + "]"; + // get our hostname and cache it for future use + if (localHostName == null || localHostName.length() <= 0) + localHostName = + props.getProperty(prefix + ".localhost"); + if (localHostName == null || localHostName.length() <= 0) + localHostName = + props.getProperty(prefix + ".localaddress"); + try { + if (localHostName == null || localHostName.length() <= 0) { + InetAddress localHost = InetAddress.getLocalHost(); + localHostName = localHost.getCanonicalHostName(); + // if we can't get our name, use local address literal + if (localHostName == null) + // XXX - not correct for IPv6 + localHostName = "[" + localHost.getHostAddress() + "]"; + } + } catch (UnknownHostException uhex) { } - } catch (UnknownHostException uhex) { - } - // last chance, try to get our address from our socket - if (localHostName == null || localHostName.length() <= 0) { - if (socket != null && socket.isBound()) { - InetAddress localHost = socket.getLocalAddress(); - localHostName = localHost.getCanonicalHostName(); - // if we can't get our name, use local address literal - if (localHostName == null) - // XXX - not correct for IPv6 - localHostName = "[" + localHost.getHostAddress() + "]"; + // last chance, try to get our address from our socket + if (localHostName == null || localHostName.length() <= 0) { + if (socket != null && socket.isBound()) { + InetAddress localHost = socket.getLocalAddress(); + localHostName = localHost.getCanonicalHostName(); + // if we can't get our name, use local address literal + if (localHostName == null) + // XXX - not correct for IPv6 + localHostName = "[" + localHost.getHostAddress() + "]"; + } } + return localHostName; + } finally { + lock.unlock(); } - return localHostName; } /** diff --git a/providers/imap/src/main/java/org/eclipse/angus/mail/imap/IMAPFolder.java b/providers/imap/src/main/java/org/eclipse/angus/mail/imap/IMAPFolder.java index a54bcb5d..ea6f29bc 100644 --- a/providers/imap/src/main/java/org/eclipse/angus/mail/imap/IMAPFolder.java +++ b/providers/imap/src/main/java/org/eclipse/angus/mail/imap/IMAPFolder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -71,6 +71,7 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; /** @@ -127,14 +128,14 @@ /* * The folder object itself serves as a lock for the folder's state * EXCEPT for the message cache (see below), typically by using - * synchronized methods. When checking that a folder is open or + * ReentrantLock. When checking that a folder is open or * closed, the folder's lock must be held. It's important that the * folder's lock is acquired before the messageCacheLock (see below). * Thus, the locking hierarchy is that the folder lock, while optional, * must be acquired before the messageCacheLock, if it's acquired at * all. Be especially careful of callbacks that occur while holding * the messageCacheLock into (e.g.) superclass Folder methods that are - * synchronized. Note that methods in IMAPMessage will acquire the + * locked. Note that methods in IMAPMessage will acquire the * messageCacheLock without acquiring the folder lock.

* * When a folder is opened, it creates a messageCache (a Vector) of @@ -158,7 +159,8 @@ * server (i.e., anytime the corresponding IMAPProtocol method is * invoked), follow the below style: * - * synchronized (messageCacheLock) { // ACQUIRE LOCK + * messageCacheLock.lock(); // ACQUIRE LOCK + * try { * issue command () * * // The response processing is typically done within @@ -170,7 +172,10 @@ * // happens within this critical-region, surrounded by * // locks. * process responses () - * } // RELEASE LOCK + * } finally { + * // RELEASE LOCK + * messageCacheLock.unlock(); + * } * * This technique is used both by methods in IMAPFolder and by methods * in IMAPMessage and other classes that operate on data in the folder. @@ -201,7 +206,8 @@ public class IMAPFolder extends Folder implements UIDFolder, ResponseHandler { protected volatile IMAPProtocol protocol; // this folder's protocol object protected MessageCache messageCache;// message cache // accessor lock for message cache - protected final Object messageCacheLock = new Object(); + protected final ReentrantLock messageCacheLock = new ReentrantLock(); + private final ReentrantLock lock = new ReentrantLock(); protected Hashtable uidTable; // UID->Message hashtable @@ -269,10 +275,13 @@ public class IMAPFolder extends Folder implements UIDFolder, ResponseHandler { * the folder lock. This check is done by the getProtocol() * method, resulting in a typical usage pattern of: * - * synchronized (messageCacheLock) { + * messageCacheLock.lock(); + * try { * IMAPProtocol p = getProtocol(); // may block waiting for IDLE * // ... use protocol - * } + * } finally { + * messageCacheLock.unlock(); + * } */ private static final int RUNNING = 0; // not doing IDLE command private static final int IDLE = 1; // IDLE command in effect @@ -455,7 +464,7 @@ protected IMAPFolder(ListInfo li, IMAPStore store) { * Ensure that this folder exists. If 'exists' has been set to true, * we don't attempt to validate it with the server again. Note that * this can result in a possible loss of sync with the server. - * ASSERT: Must be called with this folder's synchronization lock held. + * ASSERT: Must be called with this folder's lock held. */ protected void checkExists() throws MessagingException { // If the boolean field 'exists' is false, check with the @@ -467,7 +476,7 @@ protected void checkExists() throws MessagingException { /* * Ensure the folder is closed. - * ASSERT: Must be called with this folder's synchronization lock held. + * ASSERT: Must be called with this folder's lock held. */ protected void checkClosed() { if (opened) @@ -478,10 +487,10 @@ protected void checkClosed() { /* * Ensure the folder is open. - * ASSERT: Must be called with this folder's synchronization lock held. + * ASSERT: Must be called with this folder's lock held. */ protected void checkOpened() throws FolderClosedException { - assert Thread.holdsLock(this); + assert lock.isHeldByCurrentThread(); if (!opened) { if (reallyClosed) throw new IllegalStateException( @@ -510,7 +519,8 @@ protected void checkRange(int msgno) throws MessagingException { // Out of range, let's ping the server and see if // the server has more messages for us. - synchronized (messageCacheLock) { // Acquire lock + messageCacheLock.lock(); + try { // Acquire lock try { keepConnectionAlive(false); } catch (ConnectionException cex) { @@ -519,7 +529,10 @@ protected void checkRange(int msgno) throws MessagingException { } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } - } // Release lock + } finally { + // Release lock + messageCacheLock.unlock(); + } if (msgno > total) // Still out of range ? Throw up ... throw new IndexOutOfBoundsException(msgno + " > " + total); @@ -530,7 +543,7 @@ protected void checkRange(int msgno) throws MessagingException { * and also verify that the folder allows setting flags. */ private void checkFlags(Flags flags) throws MessagingException { - assert Thread.holdsLock(this); + assert lock.isHeldByCurrentThread(); if (mode != READ_WRITE) throw new IllegalStateException( "Cannot change flags on READ_ONLY folder: " + fullName @@ -547,19 +560,24 @@ private void checkFlags(Flags flags) throws MessagingException { * Get the name of this folder. */ @Override - public synchronized String getName() { - /* Return the last component of this Folder's full name. - * Folder components are delimited by the separator character. - */ - if (name == null) { - try { - name = fullName.substring( - fullName.lastIndexOf(getSeparator()) + 1 - ); - } catch (MessagingException mex) { + public String getName() { + lock.lock(); + try { + /* Return the last component of this Folder's full name. + * Folder components are delimited by the separator character. + */ + if (name == null) { + try { + name = fullName.substring( + fullName.lastIndexOf(getSeparator()) + 1 + ); + } catch (MessagingException mex) { + } } + return name; + } finally { + lock.unlock(); } - return name; } /** @@ -574,58 +592,68 @@ public String getFullName() { * Get this folder's parent. */ @Override - public synchronized Folder getParent() throws MessagingException { - char c = getSeparator(); - int index; - if ((index = fullName.lastIndexOf(c)) != -1) - return ((IMAPStore) store).newIMAPFolder( - fullName.substring(0, index), c); - else - return new DefaultFolder((IMAPStore) store); + public Folder getParent() throws MessagingException { + lock.lock(); + try { + char c = getSeparator(); + int index; + if ((index = fullName.lastIndexOf(c)) != -1) + return ((IMAPStore) store).newIMAPFolder( + fullName.substring(0, index), c); + else + return new DefaultFolder((IMAPStore) store); + } finally { + lock.unlock(); + } } /** * Check whether this folder really exists on the server. */ @Override - public synchronized boolean exists() throws MessagingException { - // Check whether this folder exists .. - ListInfo[] li = null; - final String lname; - if (isNamespace && separator != '\0') - lname = fullName + separator; - else - lname = fullName; + public boolean exists() throws MessagingException { + lock.lock(); + try { + // Check whether this folder exists .. + ListInfo[] li = null; + final String lname; + if (isNamespace && separator != '\0') + lname = fullName + separator; + else + lname = fullName; - li = (ListInfo[]) doCommand(new ProtocolCommand() { - @Override - public Object doCommand(IMAPProtocol p) throws ProtocolException { - return p.list("", lname); - } - }); + li = (ListInfo[]) doCommand(new ProtocolCommand() { + @Override + public Object doCommand(IMAPProtocol p) throws ProtocolException { + return p.list("", lname); + } + }); - if (li != null) { - int i = findName(li, lname); - fullName = li[i].name; - separator = li[i].separator; - int len = fullName.length(); - if (separator != '\0' && len > 0 && - fullName.charAt(len - 1) == separator) { - fullName = fullName.substring(0, len - 1); + if (li != null) { + int i = findName(li, lname); + fullName = li[i].name; + separator = li[i].separator; + int len = fullName.length(); + if (separator != '\0' && len > 0 && + fullName.charAt(len - 1) == separator) { + fullName = fullName.substring(0, len - 1); + } + type = 0; + if (li[i].hasInferiors) + type |= HOLDS_FOLDERS; + if (li[i].canOpen) + type |= HOLDS_MESSAGES; + exists = true; + attributes = li[i].attrs; + } else { + exists = opened; + attributes = null; } - type = 0; - if (li[i].hasInferiors) - type |= HOLDS_FOLDERS; - if (li[i].canOpen) - type |= HOLDS_MESSAGES; - exists = true; - attributes = li[i].attrs; - } else { - exists = opened; - attributes = null; - } - return exists; + return exists; + } finally { + lock.unlock(); + } } /** @@ -665,259 +693,296 @@ public Folder[] listSubscribed(String pattern) throws MessagingException { return doList(pattern, true); } - private synchronized Folder[] doList(final String pattern, + private Folder[] doList(final String pattern, final boolean subscribed) throws MessagingException { - checkExists(); // insure that this folder does exist. + lock.lock(); + try { + checkExists(); // insure that this folder does exist. - // Why waste a roundtrip to the server? - if (attributes != null && !isDirectory()) - return new Folder[0]; + // Why waste a roundtrip to the server? + if (attributes != null && !isDirectory()) + return new Folder[0]; - final char c = getSeparator(); + final char c = getSeparator(); - ListInfo[] li = (ListInfo[]) doCommandIgnoreFailure( - new ProtocolCommand() { - @Override - public Object doCommand(IMAPProtocol p) - throws ProtocolException { - if (subscribed) - return p.lsub("", fullName + c + pattern); - else - return p.list("", fullName + c + pattern); - } - }); + ListInfo[] li = (ListInfo[]) doCommandIgnoreFailure( + new ProtocolCommand() { + @Override + public Object doCommand(IMAPProtocol p) + throws ProtocolException { + if (subscribed) + return p.lsub("", fullName + c + pattern); + else + return p.list("", fullName + c + pattern); + } + }); - if (li == null) - return new Folder[0]; + if (li == null) + return new Folder[0]; - /* - * The UW based IMAP4 servers (e.g. SIMS2.0) include - * current folder (terminated with the separator), when - * the LIST pattern is '%' or '*'. i.e, - * returns "mail/" as the first LIST response. - * - * Doesn't make sense to include the current folder in this - * case, so we filter it out. Note that I'm assuming that - * the offending response is the *first* one, my experiments - * with the UW & SIMS2.0 servers indicate that .. - */ - int start = 0; - // Check the first LIST response. - if (li.length > 0 && li[0].name.equals(fullName + c)) - start = 1; // start from index = 1 - - IMAPFolder[] folders = new IMAPFolder[li.length - start]; - IMAPStore st = (IMAPStore) store; - for (int i = start; i < li.length; i++) - folders[i - start] = st.newIMAPFolder(li[i]); - return folders; + /* + * The UW based IMAP4 servers (e.g. SIMS2.0) include + * current folder (terminated with the separator), when + * the LIST pattern is '%' or '*'. i.e, + * returns "mail/" as the first LIST response. + * + * Doesn't make sense to include the current folder in this + * case, so we filter it out. Note that I'm assuming that + * the offending response is the *first* one, my experiments + * with the UW & SIMS2.0 servers indicate that .. + */ + int start = 0; + // Check the first LIST response. + if (li.length > 0 && li[0].name.equals(fullName + c)) + start = 1; // start from index = 1 + + IMAPFolder[] folders = new IMAPFolder[li.length - start]; + IMAPStore st = (IMAPStore) store; + for (int i = start; i < li.length; i++) + folders[i - start] = st.newIMAPFolder(li[i]); + return folders; + } finally { + lock.unlock(); + } } /** * Get the separator character. */ @Override - public synchronized char getSeparator() throws MessagingException { - if (separator == UNKNOWN_SEPARATOR) { - ListInfo[] li = null; + public char getSeparator() throws MessagingException { + lock.lock(); + try { + if (separator == UNKNOWN_SEPARATOR) { + ListInfo[] li = null; - li = (ListInfo[]) doCommand(new ProtocolCommand() { - @Override - public Object doCommand(IMAPProtocol p) - throws ProtocolException { - // REV1 allows the following LIST format to obtain - // the hierarchy delimiter of non-existent folders - if (p.isREV1()) // IMAP4rev1 - return p.list(fullName, ""); - else // IMAP4, note that this folder must exist for this - // to work :( - return p.list("", fullName); - } - }); + li = (ListInfo[]) doCommand(new ProtocolCommand() { + @Override + public Object doCommand(IMAPProtocol p) + throws ProtocolException { + // REV1 allows the following LIST format to obtain + // the hierarchy delimiter of non-existent folders + if (p.isREV1()) // IMAP4rev1 + return p.list(fullName, ""); + else // IMAP4, note that this folder must exist for this + // to work :( + return p.list("", fullName); + } + }); - if (li != null) - separator = li[0].separator; - else - separator = '/'; // punt ! + if (li != null) + separator = li[0].separator; + else + separator = '/'; // punt ! + } + return separator; + } finally { + lock.unlock(); } - return separator; } /** * Get the type of this folder. */ @Override - public synchronized int getType() throws MessagingException { - if (opened) { - // never throw FolderNotFoundException if folder is open - if (attributes == null) - exists(); // try to fetch attributes - } else { - checkExists(); + public int getType() throws MessagingException { + lock.lock(); + try { + if (opened) { + // never throw FolderNotFoundException if folder is open + if (attributes == null) + exists(); // try to fetch attributes + } else { + checkExists(); + } + return type; + } finally { + lock.unlock(); } - return type; } /** * Check whether this folder is subscribed. */ @Override - public synchronized boolean isSubscribed() { - ListInfo[] li = null; - final String lname; - if (isNamespace && separator != '\0') - lname = fullName + separator; - else - lname = fullName; - + public boolean isSubscribed() { + lock.lock(); try { - li = (ListInfo[]) doProtocolCommand(new ProtocolCommand() { - @Override - public Object doCommand(IMAPProtocol p) - throws ProtocolException { - return p.lsub("", lname); - } - }); - } catch (ProtocolException pex) { - } + ListInfo[] li = null; + final String lname; + if (isNamespace && separator != '\0') + lname = fullName + separator; + else + lname = fullName; - if (li != null) { - int i = findName(li, lname); - return li[i].canOpen; - } else - return false; + try { + li = (ListInfo[]) doProtocolCommand(new ProtocolCommand() { + @Override + public Object doCommand(IMAPProtocol p) + throws ProtocolException { + return p.lsub("", lname); + } + }); + } catch (ProtocolException pex) { + } + + if (li != null) { + int i = findName(li, lname); + return li[i].canOpen; + } else + return false; + } finally { + lock.unlock(); + } } /** * Subscribe/Unsubscribe this folder. */ @Override - public synchronized void setSubscribed(final boolean subscribe) + public void setSubscribed(final boolean subscribe) throws MessagingException { - doCommandIgnoreFailure(new ProtocolCommand() { - @Override - public Object doCommand(IMAPProtocol p) throws ProtocolException { - if (subscribe) - p.subscribe(fullName); - else - p.unsubscribe(fullName); - return null; - } - }); + lock.lock(); + try { + doCommandIgnoreFailure(new ProtocolCommand() { + @Override + public Object doCommand(IMAPProtocol p) throws ProtocolException { + if (subscribe) + p.subscribe(fullName); + else + p.unsubscribe(fullName); + return null; + } + }); + } finally { + lock.unlock(); + } } /** * Create this folder, with the specified type. */ @Override - public synchronized boolean create(final int type) + public boolean create(final int type) throws MessagingException { - - char c = 0; - if ((type & HOLDS_MESSAGES) == 0) // only holds folders - c = getSeparator(); - final char sep = c; - Object ret = doCommandIgnoreFailure(new ProtocolCommand() { - @Override - public Object doCommand(IMAPProtocol p) - throws ProtocolException { - if ((type & HOLDS_MESSAGES) == 0) // only holds folders - p.create(fullName + sep); - else { - p.create(fullName); - - // Certain IMAP servers do not allow creation of folders - // that can contain messages *and* subfolders. So, if we - // were asked to create such a folder, we should verify - // that we could indeed do so. - if ((type & HOLDS_FOLDERS) != 0) { - // we want to hold subfolders and messages. Check - // whether we could create such a folder. - ListInfo[] li = p.list("", fullName); - if (li != null && !li[0].hasInferiors) { - // Hmm ..the new folder - // doesn't support Inferiors ? Fail - p.delete(fullName); - throw new ProtocolException("Unsupported type"); + lock.lock(); + try { + char c = 0; + if ((type & HOLDS_MESSAGES) == 0) // only holds folders + c = getSeparator(); + final char sep = c; + Object ret = doCommandIgnoreFailure(new ProtocolCommand() { + @Override + public Object doCommand(IMAPProtocol p) + throws ProtocolException { + if ((type & HOLDS_MESSAGES) == 0) // only holds folders + p.create(fullName + sep); + else { + p.create(fullName); + + // Certain IMAP servers do not allow creation of folders + // that can contain messages *and* subfolders. So, if we + // were asked to create such a folder, we should verify + // that we could indeed do so. + if ((type & HOLDS_FOLDERS) != 0) { + // we want to hold subfolders and messages. Check + // whether we could create such a folder. + ListInfo[] li = p.list("", fullName); + if (li != null && !li[0].hasInferiors) { + // Hmm ..the new folder + // doesn't support Inferiors ? Fail + p.delete(fullName); + throw new ProtocolException("Unsupported type"); + } } } + return Boolean.TRUE; } - return Boolean.TRUE; - } - }); - - if (ret == null) - return false; // CREATE failure, maybe this - // folder already exists ? - - // exists = true; - // this.type = type; - boolean retb = exists(); // set exists, type, and attributes - if (retb) // Notify listeners on self and our Store - notifyFolderListeners(FolderEvent.CREATED); - return retb; + }); + + if (ret == null) + return false; // CREATE failure, maybe this + // folder already exists ? + + // exists = true; + // this.type = type; + boolean retb = exists(); // set exists, type, and attributes + if (retb) // Notify listeners on self and our Store + notifyFolderListeners(FolderEvent.CREATED); + return retb; + } finally { + lock.unlock(); + } } /** * Check whether this folder has new messages. */ @Override - public synchronized boolean hasNewMessages() throws MessagingException { - synchronized (messageCacheLock) { - if (opened) { // If we are open, we already have this information - // Folder is open, make sure information is up to date - // tickle the folder and store connections. - try { - keepConnectionAlive(true); - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); + public boolean hasNewMessages() throws MessagingException { + lock.lock(); + try { + messageCacheLock.lock(); + try { + if (opened) { // If we are open, we already have this information + // Folder is open, make sure information is up to date + // tickle the folder and store connections. + try { + keepConnectionAlive(true); + } catch (ConnectionException cex) { + throw new FolderClosedException(this, cex.getMessage()); + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); + } + return recent > 0 ? true : false; } - return recent > 0 ? true : false; + } finally { + messageCacheLock.unlock(); } - } - - // First, the cheap way - use LIST and look for the \Marked - // or \Unmarked tag - ListInfo[] li = null; - final String lname; - if (isNamespace && separator != '\0') - lname = fullName + separator; - else - lname = fullName; - li = (ListInfo[]) doCommandIgnoreFailure(new ProtocolCommand() { - @Override - public Object doCommand(IMAPProtocol p) throws ProtocolException { - return p.list("", lname); - } - }); + // First, the cheap way - use LIST and look for the \Marked + // or \Unmarked tag - // if folder doesn't exist, throw exception - if (li == null) - throw new FolderNotFoundException(this, fullName + " not found"); + ListInfo[] li = null; + final String lname; + if (isNamespace && separator != '\0') + lname = fullName + separator; + else + lname = fullName; + li = (ListInfo[]) doCommandIgnoreFailure(new ProtocolCommand() { + @Override + public Object doCommand(IMAPProtocol p) throws ProtocolException { + return p.list("", lname); + } + }); - int i = findName(li, lname); - if (li[i].changeState == ListInfo.CHANGED) - return true; - else if (li[i].changeState == ListInfo.UNCHANGED) - return false; + // if folder doesn't exist, throw exception + if (li == null) + throw new FolderNotFoundException(this, fullName + " not found"); - // LIST didn't work. Try the hard way, using STATUS - try { - Status status = getStatus(); - if (status.recent > 0) + int i = findName(li, lname); + if (li[i].changeState == ListInfo.CHANGED) return true; - else + else if (li[i].changeState == ListInfo.UNCHANGED) return false; - } catch (BadCommandException bex) { - // Probably doesn't support STATUS, tough luck. - return false; - } catch (ConnectionException cex) { - throw new StoreClosedException(store, cex.getMessage()); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); + + // LIST didn't work. Try the hard way, using STATUS + try { + Status status = getStatus(); + if (status.recent > 0) + return true; + else + return false; + } catch (BadCommandException bex) { + // Probably doesn't support STATUS, tough luck. + return false; + } catch (ConnectionException cex) { + throw new StoreClosedException(store, cex.getMessage()); + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); + } + } finally { + lock.unlock(); } } @@ -925,90 +990,110 @@ else if (li[i].changeState == ListInfo.UNCHANGED) * Get the named subfolder. */ @Override - public synchronized Folder getFolder(String name) + public Folder getFolder(String name) throws MessagingException { - // If we know that this folder is *not* a directory, don't - // send the request to the server at all ... - if (attributes != null && !isDirectory()) - throw new MessagingException("Cannot contain subfolders"); + lock.lock(); + try { + // If we know that this folder is *not* a directory, don't + // send the request to the server at all ... + if (attributes != null && !isDirectory()) + throw new MessagingException("Cannot contain subfolders"); - char c = getSeparator(); - return ((IMAPStore) store).newIMAPFolder(fullName + c + name, c); + char c = getSeparator(); + return ((IMAPStore) store).newIMAPFolder(fullName + c + name, c); + } finally { + lock.unlock(); + } } /** * Delete this folder. */ @Override - public synchronized boolean delete(boolean recurse) + public boolean delete(boolean recurse) throws MessagingException { - checkClosed(); // insure that this folder is closed. + lock.lock(); + try { + checkClosed(); // insure that this folder is closed. - if (recurse) { - // Delete all subfolders. - Folder[] f = list(); - for (int i = 0; i < f.length; i++) - f[i].delete(recurse); // ignore intermediate failures - } + if (recurse) { + // Delete all subfolders. + Folder[] f = list(); + for (int i = 0; i < f.length; i++) + f[i].delete(recurse); // ignore intermediate failures + } - // Attempt to delete this folder + // Attempt to delete this folder - Object ret = doCommandIgnoreFailure(new ProtocolCommand() { - @Override - public Object doCommand(IMAPProtocol p) throws ProtocolException { - p.delete(fullName); - return Boolean.TRUE; - } - }); + Object ret = doCommandIgnoreFailure(new ProtocolCommand() { + @Override + public Object doCommand(IMAPProtocol p) throws ProtocolException { + p.delete(fullName); + return Boolean.TRUE; + } + }); - if (ret == null) - // Non-existent folder/No permission ?? - return false; + if (ret == null) + // Non-existent folder/No permission ?? + return false; - // DELETE succeeded. - exists = false; - attributes = null; + // DELETE succeeded. + exists = false; + attributes = null; - // Notify listeners on self and our Store - notifyFolderListeners(FolderEvent.DELETED); - return true; + // Notify listeners on self and our Store + notifyFolderListeners(FolderEvent.DELETED); + return true; + } finally { + lock.unlock(); + } } /** * Rename this folder. */ @Override - public synchronized boolean renameTo(final Folder f) + public boolean renameTo(final Folder f) throws MessagingException { - checkClosed(); // insure that we are closed. - checkExists(); - if (f.getStore() != store) - throw new MessagingException("Can't rename across Stores"); + lock.lock(); + try { + checkClosed(); // insure that we are closed. + checkExists(); + if (f.getStore() != store) + throw new MessagingException("Can't rename across Stores"); - Object ret = doCommandIgnoreFailure(new ProtocolCommand() { - @Override - public Object doCommand(IMAPProtocol p) throws ProtocolException { - p.rename(fullName, f.getFullName()); - return Boolean.TRUE; - } - }); + Object ret = doCommandIgnoreFailure(new ProtocolCommand() { + @Override + public Object doCommand(IMAPProtocol p) throws ProtocolException { + p.rename(fullName, f.getFullName()); + return Boolean.TRUE; + } + }); - if (ret == null) - return false; + if (ret == null) + return false; - exists = false; - attributes = null; - notifyFolderRenamedListeners(f); - return true; + exists = false; + attributes = null; + notifyFolderRenamedListeners(f); + return true; + } finally { + lock.unlock(); + } } /** * Open this folder in the given mode. */ @Override - public synchronized void open(int mode) throws MessagingException { - open(mode, null); + public void open(int mode) throws MessagingException { + lock.lock(); + try { + open(mode, null); + } finally { + lock.unlock(); + } } /** @@ -1021,149 +1106,158 @@ public synchronized void open(int mode) throws MessagingException { * @return a List of MailEvent instances, or null if none * @since JavaMail 1.5.1 */ - public synchronized List open(int mode, ResyncData rd) + public List open(int mode, ResyncData rd) throws MessagingException { - checkClosed(); // insure that we are not already open - - MailboxInfo mi = null; - // Request store for our own protocol connection. - protocol = ((IMAPStore) store).getProtocol(this); + lock.lock(); + try { + checkClosed(); // insure that we are not already open - List openEvents = null; - synchronized (messageCacheLock) { // Acquire messageCacheLock + MailboxInfo mi = null; + // Request store for our own protocol connection. + protocol = ((IMAPStore) store).getProtocol(this); - /* - * Add response handler right away so we get any alerts or - * notifications that occur during the SELECT or EXAMINE. - * Have to be sure to remove it if we fail to open the - * folder. - */ - protocol.addResponseHandler(this); + List openEvents = null; + messageCacheLock.lock(); + try { // Acquire messageCacheLock - try { /* - * Enable QRESYNC or CONDSTORE if needed and not enabled. - * QRESYNC implies CONDSTORE, but servers that support - * QRESYNC are not required to support just CONDSTORE - * per RFC 5162. + * Add response handler right away so we get any alerts or + * notifications that occur during the SELECT or EXAMINE. + * Have to be sure to remove it if we fail to open the + * folder. */ - if (rd != null) { - if (rd == ResyncData.CONDSTORE) { - if (!protocol.isEnabled("CONDSTORE") && - !protocol.isEnabled("QRESYNC")) { - if (protocol.hasCapability("CONDSTORE")) - protocol.enable("CONDSTORE"); - else + protocol.addResponseHandler(this); + + try { + /* + * Enable QRESYNC or CONDSTORE if needed and not enabled. + * QRESYNC implies CONDSTORE, but servers that support + * QRESYNC are not required to support just CONDSTORE + * per RFC 5162. + */ + if (rd != null) { + if (rd == ResyncData.CONDSTORE) { + if (!protocol.isEnabled("CONDSTORE") && + !protocol.isEnabled("QRESYNC")) { + if (protocol.hasCapability("CONDSTORE")) + protocol.enable("CONDSTORE"); + else + protocol.enable("QRESYNC"); + } + } else { + if (!protocol.isEnabled("QRESYNC")) protocol.enable("QRESYNC"); } - } else { - if (!protocol.isEnabled("QRESYNC")) - protocol.enable("QRESYNC"); } - } - if (mode == READ_ONLY) - mi = protocol.examine(fullName, rd); - else - mi = protocol.select(fullName, rd); - } catch (CommandFailedException cex) { - /* - * Handle SELECT or EXAMINE failure. - * Try to figure out why the operation failed so we can - * report a more reasonable exception. - * - * Will use our existing protocol object. - */ - try { - checkExists(); // throw exception if folder doesn't exist + if (mode == READ_ONLY) + mi = protocol.examine(fullName, rd); + else + mi = protocol.select(fullName, rd); + } catch (CommandFailedException cex) { + /* + * Handle SELECT or EXAMINE failure. + * Try to figure out why the operation failed so we can + * report a more reasonable exception. + * + * Will use our existing protocol object. + */ + try { + checkExists(); // throw exception if folder doesn't exist - if ((type & HOLDS_MESSAGES) == 0) - throw new MessagingException( - "folder cannot contain messages"); - throw new MessagingException(cex.getMessage(), cex); + if ((type & HOLDS_MESSAGES) == 0) + throw new MessagingException( + "folder cannot contain messages"); + throw new MessagingException(cex.getMessage(), cex); - } finally { - // folder not open, don't keep this information - exists = false; - attributes = null; - type = 0; - // connection still good, return it - releaseProtocol(true); - } - // NOTREACHED - } catch (ProtocolException pex) { - // got a BAD or a BYE; connection may be bad, close it - try { - throw logoutAndThrow(pex.getMessage(), pex); - } finally { - releaseProtocol(false); + } finally { + // folder not open, don't keep this information + exists = false; + attributes = null; + type = 0; + // connection still good, return it + releaseProtocol(true); + } + // NOTREACHED + } catch (ProtocolException pex) { + // got a BAD or a BYE; connection may be bad, close it + try { + throw logoutAndThrow(pex.getMessage(), pex); + } finally { + releaseProtocol(false); + } } - } - if (mi.mode != mode) { - if (mode == READ_WRITE && mi.mode == READ_ONLY && - ((IMAPStore) store).allowReadOnlySelect()) { - ; // all ok, allow it - } else { // otherwise, it's an error - ReadOnlyFolderException ife = new ReadOnlyFolderException( - this, "Cannot open in desired mode"); - throw cleanupAndThrow(ife); + if (mi.mode != mode) { + if (mode == READ_WRITE && mi.mode == READ_ONLY && + ((IMAPStore) store).allowReadOnlySelect()) { + ; // all ok, allow it + } else { // otherwise, it's an error + ReadOnlyFolderException ife = new ReadOnlyFolderException( + this, "Cannot open in desired mode"); + throw cleanupAndThrow(ife); + } } - } - // Initialize stuff. - opened = true; - reallyClosed = false; - this.mode = mi.mode; - availableFlags = mi.availableFlags; - permanentFlags = mi.permanentFlags; - total = realTotal = mi.total; - recent = mi.recent; - uidvalidity = mi.uidvalidity; - uidnext = mi.uidnext; - uidNotSticky = mi.uidNotSticky; - highestmodseq = mi.highestmodseq; - - // Create the message cache of appropriate size - messageCache = new MessageCache(this, (IMAPStore) store, total); - - // process saved responses and return corresponding events - if (mi.responses != null) { - openEvents = new ArrayList<>(); - for (IMAPResponse ir : mi.responses) { - if (ir.keyEquals("VANISHED")) { - // "VANISHED" SP ["(EARLIER)"] SP known-uids - String[] s = ir.readAtomStringList(); - // check that it really is "EARLIER" - if (s == null || s.length != 1 || - !s[0].equalsIgnoreCase("EARLIER")) - continue; // it's not, what to do with it here? - String uids = ir.readAtom(); - UIDSet[] uidset = UIDSet.parseUIDSets(uids); - long[] luid = UIDSet.toArray(uidset, uidnext); - if (luid != null && luid.length > 0) - openEvents.add( - new MessageVanishedEvent(this, luid)); - } else if (ir.keyEquals("FETCH")) { - assert ir instanceof FetchResponse : - "!ir instanceof FetchResponse"; - Message msg = processFetchResponse((FetchResponse) ir); - if (msg != null) - openEvents.add(new MessageChangedEvent(this, - MessageChangedEvent.FLAGS_CHANGED, msg)); + // Initialize stuff. + opened = true; + reallyClosed = false; + this.mode = mi.mode; + availableFlags = mi.availableFlags; + permanentFlags = mi.permanentFlags; + total = realTotal = mi.total; + recent = mi.recent; + uidvalidity = mi.uidvalidity; + uidnext = mi.uidnext; + uidNotSticky = mi.uidNotSticky; + highestmodseq = mi.highestmodseq; + + // Create the message cache of appropriate size + messageCache = new MessageCache(this, (IMAPStore) store, total); + + // process saved responses and return corresponding events + if (mi.responses != null) { + openEvents = new ArrayList<>(); + for (IMAPResponse ir : mi.responses) { + if (ir.keyEquals("VANISHED")) { + // "VANISHED" SP ["(EARLIER)"] SP known-uids + String[] s = ir.readAtomStringList(); + // check that it really is "EARLIER" + if (s == null || s.length != 1 || + !s[0].equalsIgnoreCase("EARLIER")) + continue; // it's not, what to do with it here? + String uids = ir.readAtom(); + UIDSet[] uidset = UIDSet.parseUIDSets(uids); + long[] luid = UIDSet.toArray(uidset, uidnext); + if (luid != null && luid.length > 0) + openEvents.add( + new MessageVanishedEvent(this, luid)); + } else if (ir.keyEquals("FETCH")) { + assert ir instanceof FetchResponse : + "!ir instanceof FetchResponse"; + Message msg = processFetchResponse((FetchResponse) ir); + if (msg != null) + openEvents.add(new MessageChangedEvent(this, + MessageChangedEvent.FLAGS_CHANGED, msg)); + } } } + } finally { + // Release lock + messageCacheLock.unlock(); } - } // Release lock - exists = true; // if we opened it, it must exist - attributes = null; // but we don't yet know its attributes - type = HOLDS_MESSAGES; // lacking more info, we know at least this much + exists = true; // if we opened it, it must exist + attributes = null; // but we don't yet know its attributes + type = HOLDS_MESSAGES; // lacking more info, we know at least this much - // notify listeners - notifyConnectionListeners(ConnectionEvent.OPENED); + // notify listeners + notifyConnectionListeners(ConnectionEvent.OPENED); - return openEvents; + return openEvents; + } finally { + lock.unlock(); + } } private MessagingException cleanupAndThrow(MessagingException ife) { @@ -1219,164 +1313,176 @@ private boolean isRecoverable(Throwable t) { * Prefetch attributes, based on the given FetchProfile. */ @Override - public synchronized void fetch(Message[] msgs, FetchProfile fp) + public void fetch(Message[] msgs, FetchProfile fp) throws MessagingException { - // cache this information in case connection is closed and - // protocol is set to null - boolean isRev1; - FetchItem[] fitems; - synchronized (messageCacheLock) { - checkOpened(); - isRev1 = protocol.isREV1(); - fitems = protocol.getFetchItems(); - } - - StringBuilder command = new StringBuilder(); - boolean first = true; - boolean allHeaders = false; + lock.lock(); + try { + // cache this information in case connection is closed and + // protocol is set to null + boolean isRev1; + FetchItem[] fitems; + messageCacheLock.lock(); + try { + checkOpened(); + isRev1 = protocol.isREV1(); + fitems = protocol.getFetchItems(); + } finally { + messageCacheLock.unlock(); + } - if (fp.contains(FetchProfile.Item.ENVELOPE)) { - command.append(getEnvelopeCommand()); - first = false; - } - if (fp.contains(FetchProfile.Item.FLAGS)) { - command.append(first ? "FLAGS" : " FLAGS"); - first = false; - } - if (fp.contains(FetchProfile.Item.CONTENT_INFO)) { - command.append(first ? "BODYSTRUCTURE" : " BODYSTRUCTURE"); - first = false; - } - if (fp.contains(UIDFolder.FetchProfileItem.UID)) { - command.append(first ? "UID" : " UID"); - first = false; - } - if (fp.contains(IMAPFolder.FetchProfileItem.HEADERS)) { - allHeaders = true; - if (isRev1) - command.append(first ? - "BODY.PEEK[HEADER]" : " BODY.PEEK[HEADER]"); - else - command.append(first ? "RFC822.HEADER" : " RFC822.HEADER"); - first = false; - } - if (fp.contains(IMAPFolder.FetchProfileItem.MESSAGE)) { - allHeaders = true; - if (isRev1) - command.append(first ? "BODY.PEEK[]" : " BODY.PEEK[]"); - else - command.append(first ? "RFC822" : " RFC822"); - first = false; - } - if (fp.contains(FetchProfile.Item.SIZE) || - fp.contains(IMAPFolder.FetchProfileItem.SIZE)) { - command.append(first ? "RFC822.SIZE" : " RFC822.SIZE"); - first = false; - } - if (fp.contains(IMAPFolder.FetchProfileItem.INTERNALDATE)) { - command.append(first ? "INTERNALDATE" : " INTERNALDATE"); - first = false; - } + StringBuilder command = new StringBuilder(); + boolean first = true; + boolean allHeaders = false; - // if we're not fetching all headers, fetch individual headers - String[] hdrs = null; - if (!allHeaders) { - hdrs = fp.getHeaderNames(); - if (hdrs.length > 0) { - if (!first) - command.append(" "); - command.append(createHeaderCommand(hdrs, isRev1)); + if (fp.contains(FetchProfile.Item.ENVELOPE)) { + command.append(getEnvelopeCommand()); + first = false; } - } - - /* - * Add any additional extension fetch items. - */ - for (int i = 0; i < fitems.length; i++) { - if (fp.contains(fitems[i].getFetchProfileItem())) { - if (command.length() != 0) - command.append(" "); - command.append(fitems[i].getName()); + if (fp.contains(FetchProfile.Item.FLAGS)) { + command.append(first ? "FLAGS" : " FLAGS"); + first = false; + } + if (fp.contains(FetchProfile.Item.CONTENT_INFO)) { + command.append(first ? "BODYSTRUCTURE" : " BODYSTRUCTURE"); + first = false; + } + if (fp.contains(UIDFolder.FetchProfileItem.UID)) { + command.append(first ? "UID" : " UID"); + first = false; + } + if (fp.contains(IMAPFolder.FetchProfileItem.HEADERS)) { + allHeaders = true; + if (isRev1) + command.append(first ? + "BODY.PEEK[HEADER]" : " BODY.PEEK[HEADER]"); + else + command.append(first ? "RFC822.HEADER" : " RFC822.HEADER"); + first = false; + } + if (fp.contains(IMAPFolder.FetchProfileItem.MESSAGE)) { + allHeaders = true; + if (isRev1) + command.append(first ? "BODY.PEEK[]" : " BODY.PEEK[]"); + else + command.append(first ? "RFC822" : " RFC822"); + first = false; + } + if (fp.contains(FetchProfile.Item.SIZE) || + fp.contains(IMAPFolder.FetchProfileItem.SIZE)) { + command.append(first ? "RFC822.SIZE" : " RFC822.SIZE"); + first = false; + } + if (fp.contains(IMAPFolder.FetchProfileItem.INTERNALDATE)) { + command.append(first ? "INTERNALDATE" : " INTERNALDATE"); + first = false; } - } - Utility.Condition condition = - new IMAPMessage.FetchProfileCondition(fp, fitems); + // if we're not fetching all headers, fetch individual headers + String[] hdrs = null; + if (!allHeaders) { + hdrs = fp.getHeaderNames(); + if (hdrs.length > 0) { + if (!first) + command.append(" "); + command.append(createHeaderCommand(hdrs, isRev1)); + } + } - // Acquire the Folder's MessageCacheLock. - synchronized (messageCacheLock) { + /* + * Add any additional extension fetch items. + */ + for (int i = 0; i < fitems.length; i++) { + if (fp.contains(fitems[i].getFetchProfileItem())) { + if (command.length() != 0) + command.append(" "); + command.append(fitems[i].getName()); + } + } - // check again to make sure folder is still open - checkOpened(); + Utility.Condition condition = + new IMAPMessage.FetchProfileCondition(fp, fitems); - // Apply the test, and get the sequence-number set for - // the messages that need to be prefetched. - MessageSet[] msgsets = Utility.toMessageSetSorted(msgs, condition); + // Acquire the Folder's MessageCacheLock. + messageCacheLock.lock(); + try { - if (msgsets == null) - // We already have what we need. - return; + // check again to make sure folder is still open + checkOpened(); - Response[] r = null; - // to collect non-FETCH responses & unsolicited FETCH FLAG responses - List v = new ArrayList<>(); - try { - r = getProtocol().fetch(msgsets, command.toString()); - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (CommandFailedException cfx) { - // Ignore these, as per RFC 2180 - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); - } + // Apply the test, and get the sequence-number set for + // the messages that need to be prefetched. + MessageSet[] msgsets = Utility.toMessageSetSorted(msgs, condition); - if (r == null) - return; + if (msgsets == null) + // We already have what we need. + return; - for (int i = 0; i < r.length; i++) { - if (r[i] == null) - continue; - if (!(r[i] instanceof FetchResponse)) { - v.add(r[i]); // Unsolicited Non-FETCH response - continue; + Response[] r = null; + // to collect non-FETCH responses & unsolicited FETCH FLAG responses + List v = new ArrayList<>(); + try { + r = getProtocol().fetch(msgsets, command.toString()); + } catch (ConnectionException cex) { + throw new FolderClosedException(this, cex.getMessage()); + } catch (CommandFailedException cfx) { + // Ignore these, as per RFC 2180 + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); } - // Got a FetchResponse. - FetchResponse f = (FetchResponse) r[i]; - // Get the corresponding message. - IMAPMessage msg = getMessageBySeqNumber(f.getNumber()); - - int count = f.getItemCount(); - boolean unsolicitedFlags = false; - - for (int j = 0; j < count; j++) { - Item item = f.getItem(j); - // Check for the FLAGS item - if (item instanceof Flags && - (!fp.contains(FetchProfile.Item.FLAGS) || - msg == null)) { - // Ok, Unsolicited FLAGS update. - unsolicitedFlags = true; - } else if (msg != null) - msg.handleFetchItem(item, hdrs, allHeaders); + if (r == null) + return; + + for (int i = 0; i < r.length; i++) { + if (r[i] == null) + continue; + if (!(r[i] instanceof FetchResponse)) { + v.add(r[i]); // Unsolicited Non-FETCH response + continue; + } + + // Got a FetchResponse. + FetchResponse f = (FetchResponse) r[i]; + // Get the corresponding message. + IMAPMessage msg = getMessageBySeqNumber(f.getNumber()); + + int count = f.getItemCount(); + boolean unsolicitedFlags = false; + + for (int j = 0; j < count; j++) { + Item item = f.getItem(j); + // Check for the FLAGS item + if (item instanceof Flags && + (!fp.contains(FetchProfile.Item.FLAGS) || + msg == null)) { + // Ok, Unsolicited FLAGS update. + unsolicitedFlags = true; + } else if (msg != null) + msg.handleFetchItem(item, hdrs, allHeaders); + } + if (msg != null) + msg.handleExtensionFetchItems(f.getExtensionItems()); + + // If this response contains any unsolicited FLAGS + // add it to the unsolicited response vector + if (unsolicitedFlags) + v.add(f); } - if (msg != null) - msg.handleExtensionFetchItems(f.getExtensionItems()); - // If this response contains any unsolicited FLAGS - // add it to the unsolicited response vector - if (unsolicitedFlags) - v.add(f); - } + // Dispatch any unsolicited responses + if (!v.isEmpty()) { + Response[] responses = new Response[v.size()]; + v.toArray(responses); + handleResponses(responses); + } - // Dispatch any unsolicited responses - if (!v.isEmpty()) { - Response[] responses = new Response[v.size()]; - v.toArray(responses); - handleResponses(responses); + } finally { + // Release messageCacheLock + messageCacheLock.unlock(); } - - } // Release messageCacheLock + } finally { + lock.unlock(); + } } /** @@ -1435,27 +1541,35 @@ private String createHeaderCommand(String[] hdrs, boolean isRev1) { * Set the specified flags for the given array of messages. */ @Override - public synchronized void setFlags(Message[] msgs, Flags flag, boolean value) + public void setFlags(Message[] msgs, Flags flag, boolean value) throws MessagingException { - checkOpened(); - checkFlags(flag); // validate flags + lock.lock(); + try { + checkOpened(); + checkFlags(flag); // validate flags - if (msgs.length == 0) // boundary condition - return; + if (msgs.length == 0) // boundary condition + return; - synchronized (messageCacheLock) { + messageCacheLock.lock(); try { - IMAPProtocol p = getProtocol(); - MessageSet[] ms = Utility.toMessageSetSorted(msgs, null); - if (ms == null) - throw new MessageRemovedException( - "Messages have been removed"); - p.storeFlags(ms, flag, value); - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); + try { + IMAPProtocol p = getProtocol(); + MessageSet[] ms = Utility.toMessageSetSorted(msgs, null); + if (ms == null) + throw new MessageRemovedException( + "Messages have been removed"); + p.storeFlags(ms, flag, value); + } catch (ConnectionException cex) { + throw new FolderClosedException(this, cex.getMessage()); + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); + } + } finally { + messageCacheLock.unlock(); } + } finally { + lock.unlock(); } } @@ -1463,35 +1577,50 @@ public synchronized void setFlags(Message[] msgs, Flags flag, boolean value) * Set the specified flags for the given range of message numbers. */ @Override - public synchronized void setFlags(int start, int end, + public void setFlags(int start, int end, Flags flag, boolean value) throws MessagingException { - checkOpened(); - Message[] msgs = new Message[end - start + 1]; - int i = 0; - for (int n = start; n <= end; n++) - msgs[i++] = getMessage(n); - setFlags(msgs, flag, value); + lock.lock(); + try { + checkOpened(); + Message[] msgs = new Message[end - start + 1]; + int i = 0; + for (int n = start; n <= end; n++) + msgs[i++] = getMessage(n); + setFlags(msgs, flag, value); + } finally { + lock.unlock(); + } } /** * Set the specified flags for the given array of message numbers. */ @Override - public synchronized void setFlags(int[] msgnums, Flags flag, boolean value) + public void setFlags(int[] msgnums, Flags flag, boolean value) throws MessagingException { - checkOpened(); - Message[] msgs = new Message[msgnums.length]; - for (int i = 0; i < msgnums.length; i++) - msgs[i] = getMessage(msgnums[i]); - setFlags(msgs, flag, value); + lock.lock(); + try { + checkOpened(); + Message[] msgs = new Message[msgnums.length]; + for (int i = 0; i < msgnums.length; i++) + msgs[i] = getMessage(msgnums[i]); + setFlags(msgs, flag, value); + } finally { + lock.unlock(); + } } /** * Close this folder. */ @Override - public synchronized void close(boolean expunge) throws MessagingException { - close(expunge, false); + public void close(boolean expunge) throws MessagingException { + lock.lock(); + try { + close(expunge, false); + } finally { + lock.unlock(); + } } /** @@ -1499,8 +1628,13 @@ public synchronized void close(boolean expunge) throws MessagingException { * * @exception MessagingException for failures */ - public synchronized void forceClose() throws MessagingException { - close(false, true); + public void forceClose() throws MessagingException { + lock.lock(); + try { + close(false, true); + } finally { + lock.unlock(); + } } /* @@ -1508,8 +1642,9 @@ public synchronized void forceClose() throws MessagingException { */ private void close(boolean expunge, boolean force) throws MessagingException { - assert Thread.holdsLock(this); - synchronized (messageCacheLock) { + assert lock.isHeldByCurrentThread(); + messageCacheLock.lock(); + try { /* * If we already know we're closed, this is illegal. * Can't use checkOpened() because if we were forcibly @@ -1596,6 +1731,8 @@ private void close(boolean expunge, boolean force) if (opened) cleanup(reuseProtocol); } + } finally { + messageCacheLock.unlock(); } } @@ -1605,7 +1742,7 @@ private void close(boolean expunge, boolean force) // Connection.CLOSED events are not generated. Also both // invocations are from within messageCacheLock-ed areas. private void cleanup(boolean returnToPool) { - assert Thread.holdsLock(messageCacheLock); + assert messageCacheLock.isHeldByCurrentThread(); releaseProtocol(returnToPool); messageCache = null; uidTable = null; @@ -1621,76 +1758,97 @@ private void cleanup(boolean returnToPool) { * Check whether this connection is really open. */ @Override - public synchronized boolean isOpen() { - synchronized (messageCacheLock) { - // Probe the connection to make sure its really open. - if (opened) { - try { - keepConnectionAlive(false); - } catch (ProtocolException pex) { + public boolean isOpen() { + lock.lock(); + try { + messageCacheLock.lock(); + try { + // Probe the connection to make sure its really open. + if (opened) { + try { + keepConnectionAlive(false); + } catch (ProtocolException pex) { + } } + } finally { + messageCacheLock.unlock(); } - } - return opened; + return opened; + } finally { + lock.unlock(); + } } /** * Return the permanent flags supported by the server. */ @Override - public synchronized Flags getPermanentFlags() { - if (permanentFlags == null) - return null; - return (Flags) (permanentFlags.clone()); + public Flags getPermanentFlags() { + lock.lock(); + try { + if (permanentFlags == null) + return null; + return (Flags) (permanentFlags.clone()); + } finally { + lock.unlock(); + } } /** * Get the total message count. */ @Override - public synchronized int getMessageCount() throws MessagingException { - synchronized (messageCacheLock) { - if (opened) { - // Folder is open, we know what the total message count is .. - // tickle the folder and store connections. - try { - keepConnectionAlive(true); - return total; - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); + public int getMessageCount() throws MessagingException { + lock.lock(); + try { + messageCacheLock.lock(); + try { + if (opened) { + // Folder is open, we know what the total message count is .. + // tickle the folder and store connections. + try { + keepConnectionAlive(true); + return total; + } catch (ConnectionException cex) { + throw new FolderClosedException(this, cex.getMessage()); + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); + } } + } finally { + messageCacheLock.unlock(); } - } - - // If this folder is not yet open, we use STATUS to - // get the total message count - checkExists(); - try { - Status status = getStatus(); - return status.total; - } catch (BadCommandException bex) { - // doesn't support STATUS, probably vanilla IMAP4 .. - // lets try EXAMINE - IMAPProtocol p = null; + // If this folder is not yet open, we use STATUS to + // get the total message count + checkExists(); try { - p = getStoreProtocol(); // XXX - MailboxInfo minfo = p.examine(fullName); - p.close(); - return minfo.total; + Status status = getStatus(); + return status.total; + } catch (BadCommandException bex) { + // doesn't support STATUS, probably vanilla IMAP4 .. + // lets try EXAMINE + IMAPProtocol p = null; + + try { + p = getStoreProtocol(); // XXX + MailboxInfo minfo = p.examine(fullName); + p.close(); + return minfo.total; + } catch (ProtocolException pex) { + // Give up. + throw new MessagingException(pex.getMessage(), pex); + } finally { + releaseStoreProtocol(p); + } + } catch (ConnectionException cex) { + throw new StoreClosedException(store, cex.getMessage()); } catch (ProtocolException pex) { - // Give up. throw new MessagingException(pex.getMessage(), pex); - } finally { - releaseStoreProtocol(p); } - } catch (ConnectionException cex) { - throw new StoreClosedException(store, cex.getMessage()); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); + } finally { + lock.unlock(); } } @@ -1698,48 +1856,56 @@ public synchronized int getMessageCount() throws MessagingException { * Get the new message count. */ @Override - public synchronized int getNewMessageCount() throws MessagingException { - synchronized (messageCacheLock) { - if (opened) { - // Folder is open, we know what the new message count is .. - // tickle the folder and store connections. - try { - keepConnectionAlive(true); - return recent; - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); + public int getNewMessageCount() throws MessagingException { + lock.lock(); + try { + messageCacheLock.lock(); + try { + if (opened) { + // Folder is open, we know what the new message count is .. + // tickle the folder and store connections. + try { + keepConnectionAlive(true); + return recent; + } catch (ConnectionException cex) { + throw new FolderClosedException(this, cex.getMessage()); + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); + } } + } finally { + messageCacheLock.unlock(); } - } - - // If this folder is not yet open, we use STATUS to - // get the new message count - checkExists(); - try { - Status status = getStatus(); - return status.recent; - } catch (BadCommandException bex) { - // doesn't support STATUS, probably vanilla IMAP4 .. - // lets try EXAMINE - IMAPProtocol p = null; + // If this folder is not yet open, we use STATUS to + // get the new message count + checkExists(); try { - p = getStoreProtocol(); // XXX - MailboxInfo minfo = p.examine(fullName); - p.close(); - return minfo.recent; + Status status = getStatus(); + return status.recent; + } catch (BadCommandException bex) { + // doesn't support STATUS, probably vanilla IMAP4 .. + // lets try EXAMINE + IMAPProtocol p = null; + + try { + p = getStoreProtocol(); // XXX + MailboxInfo minfo = p.examine(fullName); + p.close(); + return minfo.recent; + } catch (ProtocolException pex) { + // Give up. + throw new MessagingException(pex.getMessage(), pex); + } finally { + releaseStoreProtocol(p); + } + } catch (ConnectionException cex) { + throw new StoreClosedException(store, cex.getMessage()); } catch (ProtocolException pex) { - // Give up. throw new MessagingException(pex.getMessage(), pex); - } finally { - releaseStoreProtocol(p); } - } catch (ConnectionException cex) { - throw new StoreClosedException(store, cex.getMessage()); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); + } finally { + lock.unlock(); } } @@ -1747,41 +1913,49 @@ public synchronized int getNewMessageCount() throws MessagingException { * Get the unread message count. */ @Override - public synchronized int getUnreadMessageCount() + public int getUnreadMessageCount() throws MessagingException { - if (!opened) { - checkExists(); - // If this folder is not yet open, we use STATUS to - // get the unseen message count + lock.lock(); + try { + if (!opened) { + checkExists(); + // If this folder is not yet open, we use STATUS to + // get the unseen message count + try { + Status status = getStatus(); + return status.unseen; + } catch (BadCommandException bex) { + // doesn't support STATUS, probably vanilla IMAP4 .. + // Could EXAMINE, SEARCH for UNREAD messages and + // return the count .. bah, not worth it. + return -1; + } catch (ConnectionException cex) { + throw new StoreClosedException(store, cex.getMessage()); + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); + } + } + + // if opened, issue server-side search for messages that do + // *not* have the SEEN flag. + Flags f = new Flags(); + f.add(Flags.Flag.SEEN); try { - Status status = getStatus(); - return status.unseen; - } catch (BadCommandException bex) { - // doesn't support STATUS, probably vanilla IMAP4 .. - // Could EXAMINE, SEARCH for UNREAD messages and - // return the count .. bah, not worth it. - return -1; + messageCacheLock.lock(); + try { + int[] matches = getProtocol().search(new FlagTerm(f, false)); + return matches.length; // NOTE: 'matches' is never null + } finally { + messageCacheLock.unlock(); + } } catch (ConnectionException cex) { - throw new StoreClosedException(store, cex.getMessage()); + throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { + // Shouldn't happen throw new MessagingException(pex.getMessage(), pex); } - } - - // if opened, issue server-side search for messages that do - // *not* have the SEEN flag. - Flags f = new Flags(); - f.add(Flags.Flag.SEEN); - try { - synchronized (messageCacheLock) { - int[] matches = getProtocol().search(new FlagTerm(f, false)); - return matches.length; // NOTE: 'matches' is never null - } - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (ProtocolException pex) { - // Shouldn't happen - throw new MessagingException(pex.getMessage(), pex); + } finally { + lock.unlock(); } } @@ -1789,34 +1963,42 @@ public synchronized int getUnreadMessageCount() * Get the deleted message count. */ @Override - public synchronized int getDeletedMessageCount() + public int getDeletedMessageCount() throws MessagingException { - if (!opened) { - checkExists(); - // no way to do this on closed folders - return -1; - } - - // if opened, issue server-side search for messages that do - // have the DELETED flag. - Flags f = new Flags(); - f.add(Flags.Flag.DELETED); + lock.lock(); try { - synchronized (messageCacheLock) { - int[] matches = getProtocol().search(new FlagTerm(f, true)); - return matches.length; // NOTE: 'matches' is never null + if (!opened) { + checkExists(); + // no way to do this on closed folders + return -1; } - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (ProtocolException pex) { - // Shouldn't happen - throw new MessagingException(pex.getMessage(), pex); + + // if opened, issue server-side search for messages that do + // have the DELETED flag. + Flags f = new Flags(); + f.add(Flags.Flag.DELETED); + try { + messageCacheLock.lock(); + try { + int[] matches = getProtocol().search(new FlagTerm(f, true)); + return matches.length; // NOTE: 'matches' is never null + } finally { + messageCacheLock.unlock(); + } + } catch (ConnectionException cex) { + throw new FolderClosedException(this, cex.getMessage()); + } catch (ProtocolException pex) { + // Shouldn't happen + throw new MessagingException(pex.getMessage(), pex); + } + } finally { + lock.unlock(); } } /* * Get results of STATUS command for this folder, checking cache first. - * ASSERT: Must be called with this folder's synchronization lock held. + * ASSERT: Must be called with this folder's lock held. * ASSERT: The folder must be closed. */ private Status getStatus() throws ProtocolException { @@ -1847,74 +2029,89 @@ private Status getStatus() throws ProtocolException { * Get the specified message. */ @Override - public synchronized Message getMessage(int msgnum) + public Message getMessage(int msgnum) throws MessagingException { - checkOpened(); - checkRange(msgnum); + lock.lock(); + try { + checkOpened(); + checkRange(msgnum); - return messageCache.getMessage(msgnum); + return messageCache.getMessage(msgnum); + } finally { + lock.unlock(); + } } /** * {@inheritDoc} */ @Override - public synchronized Message[] getMessages() throws MessagingException { - /* - * Need to override Folder method to throw FolderClosedException - * instead of IllegalStateException if not really closed. - */ - checkOpened(); - int total = getMessageCount(); - Message[] msgs = new Message[total]; - for (int i = 1; i <= total; i++) - msgs[i - 1] = messageCache.getMessage(i); - return msgs; + public Message[] getMessages() throws MessagingException { + lock.lock(); + try { + /* + * Need to override Folder method to throw FolderClosedException + * instead of IllegalStateException if not really closed. + */ + checkOpened(); + int total = getMessageCount(); + Message[] msgs = new Message[total]; + for (int i = 1; i <= total; i++) + msgs[i - 1] = messageCache.getMessage(i); + return msgs; + } finally { + lock.unlock(); + } } /** * Append the given messages into this folder. */ @Override - public synchronized void appendMessages(Message[] msgs) + public void appendMessages(Message[] msgs) throws MessagingException { - checkExists(); // verify that self exists - - // XXX - have to verify that messages are in a different - // store (if any) than target folder, otherwise could - // deadlock trying to fetch messages on the same connection - // we're using for the append. + lock.lock(); + try { + checkExists(); // verify that self exists - int maxsize = ((IMAPStore) store).getAppendBufferSize(); + // XXX - have to verify that messages are in a different + // store (if any) than target folder, otherwise could + // deadlock trying to fetch messages on the same connection + // we're using for the append. - for (int i = 0; i < msgs.length; i++) { - final Message m = msgs[i]; - Date d = m.getReceivedDate(); // retain dates - if (d == null) - d = m.getSentDate(); - final Date dd = d; - final Flags f = m.getFlags(); + int maxsize = ((IMAPStore) store).getAppendBufferSize(); - final MessageLiteral mos; - try { - // if we know the message is too big, don't buffer any of it - mos = new MessageLiteral(m, - m.getSize() > maxsize ? 0 : maxsize); - } catch (IOException ex) { - throw new MessagingException( - "IOException while appending messages", ex); - } catch (MessageRemovedException mrex) { - continue; // just skip this expunged message - } + for (int i = 0; i < msgs.length; i++) { + final Message m = msgs[i]; + Date d = m.getReceivedDate(); // retain dates + if (d == null) + d = m.getSentDate(); + final Date dd = d; + final Flags f = m.getFlags(); - doCommand(new ProtocolCommand() { - @Override - public Object doCommand(IMAPProtocol p) - throws ProtocolException { - p.append(fullName, f, dd, mos); - return null; + final MessageLiteral mos; + try { + // if we know the message is too big, don't buffer any of it + mos = new MessageLiteral(m, + m.getSize() > maxsize ? 0 : maxsize); + } catch (IOException ex) { + throw new MessagingException( + "IOException while appending messages", ex); + } catch (MessageRemovedException mrex) { + continue; // just skip this expunged message } - }); + + doCommand(new ProtocolCommand() { + @Override + public Object doCommand(IMAPProtocol p) + throws ProtocolException { + p.append(fullName, f, dd, mos); + return null; + } + }); + } + } finally { + lock.unlock(); } } @@ -1936,48 +2133,53 @@ public Object doCommand(IMAPProtocol p) * @exception MessagingException for failures * @since JavaMail 1.4 */ - public synchronized AppendUID[] appendUIDMessages(Message[] msgs) + public AppendUID[] appendUIDMessages(Message[] msgs) throws MessagingException { - checkExists(); // verify that self exists + lock.lock(); + try { + checkExists(); // verify that self exists - // XXX - have to verify that messages are in a different - // store (if any) than target folder, otherwise could - // deadlock trying to fetch messages on the same connection - // we're using for the append. + // XXX - have to verify that messages are in a different + // store (if any) than target folder, otherwise could + // deadlock trying to fetch messages on the same connection + // we're using for the append. - int maxsize = ((IMAPStore) store).getAppendBufferSize(); + int maxsize = ((IMAPStore) store).getAppendBufferSize(); - AppendUID[] uids = new AppendUID[msgs.length]; - for (int i = 0; i < msgs.length; i++) { - final Message m = msgs[i]; - final MessageLiteral mos; + AppendUID[] uids = new AppendUID[msgs.length]; + for (int i = 0; i < msgs.length; i++) { + final Message m = msgs[i]; + final MessageLiteral mos; - try { - // if we know the message is too big, don't buffer any of it - mos = new MessageLiteral(m, - m.getSize() > maxsize ? 0 : maxsize); - } catch (IOException ex) { - throw new MessagingException( - "IOException while appending messages", ex); - } catch (MessageRemovedException mrex) { - continue; // just skip this expunged message - } - - Date d = m.getReceivedDate(); // retain dates - if (d == null) - d = m.getSentDate(); - final Date dd = d; - final Flags f = m.getFlags(); - AppendUID auid = (AppendUID) doCommand(new ProtocolCommand() { - @Override - public Object doCommand(IMAPProtocol p) - throws ProtocolException { - return p.appenduid(fullName, f, dd, mos); + try { + // if we know the message is too big, don't buffer any of it + mos = new MessageLiteral(m, + m.getSize() > maxsize ? 0 : maxsize); + } catch (IOException ex) { + throw new MessagingException( + "IOException while appending messages", ex); + } catch (MessageRemovedException mrex) { + continue; // just skip this expunged message } - }); - uids[i] = auid; + + Date d = m.getReceivedDate(); // retain dates + if (d == null) + d = m.getSentDate(); + final Date dd = d; + final Flags f = m.getFlags(); + AppendUID auid = (AppendUID) doCommand(new ProtocolCommand() { + @Override + public Object doCommand(IMAPProtocol p) + throws ProtocolException { + return p.appenduid(fullName, f, dd, mos); + } + }); + uids[i] = auid; + } + return uids; + } finally { + lock.unlock(); } - return uids; } /** @@ -1999,24 +2201,29 @@ public Object doCommand(IMAPProtocol p) * @exception MessagingException for failures * @since JavaMail 1.4 */ - public synchronized Message[] addMessages(Message[] msgs) + public Message[] addMessages(Message[] msgs) throws MessagingException { - checkOpened(); - Message[] rmsgs = new MimeMessage[msgs.length]; - AppendUID[] uids = appendUIDMessages(msgs); - for (int i = 0; i < uids.length; i++) { - AppendUID auid = uids[i]; - if (auid != null) { - if (auid.uidvalidity == uidvalidity) { - try { - rmsgs[i] = getMessageByUID(auid.uid); - } catch (MessagingException mex) { - // ignore errors at this stage + lock.lock(); + try { + checkOpened(); + Message[] rmsgs = new MimeMessage[msgs.length]; + AppendUID[] uids = appendUIDMessages(msgs); + for (int i = 0; i < uids.length; i++) { + AppendUID auid = uids[i]; + if (auid != null) { + if (auid.uidvalidity == uidvalidity) { + try { + rmsgs[i] = getMessageByUID(auid.uid); + } catch (MessagingException mex) { + // ignore errors at this stage + } } } } + return rmsgs; + } finally { + lock.unlock(); } - return rmsgs; } /** @@ -2024,9 +2231,14 @@ public synchronized Message[] addMessages(Message[] msgs) * specified destination. */ @Override - public synchronized void copyMessages(Message[] msgs, Folder folder) + public void copyMessages(Message[] msgs, Folder folder) throws MessagingException { - copymoveMessages(msgs, folder, false); + lock.lock(); + try { + copymoveMessages(msgs, folder, false); + } finally { + lock.unlock(); + } } /** @@ -2049,9 +2261,14 @@ public synchronized void copyMessages(Message[] msgs, Folder folder) * @exception MessagingException for failures * @since JavaMail 1.5.1 */ - public synchronized AppendUID[] copyUIDMessages(Message[] msgs, + public AppendUID[] copyUIDMessages(Message[] msgs, Folder folder) throws MessagingException { - return copymoveUIDMessages(msgs, folder, false); + lock.lock(); + try { + return copymoveUIDMessages(msgs, folder, false); + } finally { + lock.unlock(); + } } /** @@ -2066,9 +2283,14 @@ public synchronized AppendUID[] copyUIDMessages(Message[] msgs, * @exception MessagingException for failures * @since JavaMail 1.5.4 */ - public synchronized void moveMessages(Message[] msgs, Folder folder) + public void moveMessages(Message[] msgs, Folder folder) throws MessagingException { - copymoveMessages(msgs, folder, true); + lock.lock(); + try { + copymoveMessages(msgs, folder, true); + } finally { + lock.unlock(); + } } /** @@ -2093,9 +2315,14 @@ public synchronized void moveMessages(Message[] msgs, Folder folder) * @exception MessagingException for failures * @since JavaMail 1.5.4 */ - public synchronized AppendUID[] moveUIDMessages(Message[] msgs, + public AppendUID[] moveUIDMessages(Message[] msgs, Folder folder) throws MessagingException { - return copymoveUIDMessages(msgs, folder, true); + lock.lock(); + try { + return copymoveUIDMessages(msgs, folder, true); + } finally { + lock.unlock(); + } } /** @@ -2104,46 +2331,54 @@ public synchronized AppendUID[] moveUIDMessages(Message[] msgs, * * @since JavaMail 1.5.4 */ - private synchronized void copymoveMessages(Message[] msgs, Folder folder, + private void copymoveMessages(Message[] msgs, Folder folder, boolean move) throws MessagingException { - checkOpened(); + lock.lock(); + try { + checkOpened(); - if (msgs.length == 0) // boundary condition - return; + if (msgs.length == 0) // boundary condition + return; - // If the destination belongs to our same store, optimize - if (folder.getStore() == store) { - synchronized (messageCacheLock) { + // If the destination belongs to our same store, optimize + if (folder.getStore() == store) { + messageCacheLock.lock(); try { - IMAPProtocol p = getProtocol(); - MessageSet[] ms = Utility.toMessageSet(msgs, null); - if (ms == null) - throw new MessageRemovedException( - "Messages have been removed"); - if (move) - p.move(ms, folder.getFullName()); - else - p.copy(ms, folder.getFullName()); - } catch (CommandFailedException cfx) { - if (cfx.getMessage().contains("TRYCREATE")) - throw new FolderNotFoundException( - folder, - folder.getFullName() + " does not exist" - ); - else - throw new MessagingException(cfx.getMessage(), cfx); - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); + try { + IMAPProtocol p = getProtocol(); + MessageSet[] ms = Utility.toMessageSet(msgs, null); + if (ms == null) + throw new MessageRemovedException( + "Messages have been removed"); + if (move) + p.move(ms, folder.getFullName()); + else + p.copy(ms, folder.getFullName()); + } catch (CommandFailedException cfx) { + if (cfx.getMessage().contains("TRYCREATE")) + throw new FolderNotFoundException( + folder, + folder.getFullName() + " does not exist" + ); + else + throw new MessagingException(cfx.getMessage(), cfx); + } catch (ConnectionException cex) { + throw new FolderClosedException(this, cex.getMessage()); + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); + } + } finally { + messageCacheLock.unlock(); } - } - } else // destination is a different store. - if (move) - throw new MessagingException( - "Move between stores not supported"); - else - super.copyMessages(msgs, folder); + } else // destination is a different store. + if (move) + throw new MessagingException( + "Move between stores not supported"); + else + super.copyMessages(msgs, folder); + } finally { + lock.unlock(); + } } /** @@ -2169,114 +2404,122 @@ private synchronized void copymoveMessages(Message[] msgs, Folder folder, * @exception MessagingException for failures * @since JavaMail 1.5.4 */ - private synchronized AppendUID[] copymoveUIDMessages(Message[] msgs, + private AppendUID[] copymoveUIDMessages(Message[] msgs, Folder folder, boolean move) throws MessagingException { - checkOpened(); + lock.lock(); + try { + checkOpened(); - if (msgs.length == 0) // boundary condition - return null; + if (msgs.length == 0) // boundary condition + return null; - // the destination must belong to our same store - if (folder.getStore() != store) // destination is a different store. - throw new MessagingException( - move ? - "can't moveUIDMessages to a different store" : - "can't copyUIDMessages to a different store"); - - // call fetch to make sure we have all the UIDs - // necessary to interpret the COPYUID response - FetchProfile fp = new FetchProfile(); - fp.add(UIDFolder.FetchProfileItem.UID); - fetch(msgs, fp); - // XXX - could pipeline the FETCH with the COPY/MOVE below - - synchronized (messageCacheLock) { - try { - IMAPProtocol p = getProtocol(); - // XXX - messages have to be from this Folder, who checks? - MessageSet[] ms = Utility.toMessageSet(msgs, null); - if (ms == null) - throw new MessageRemovedException( - "Messages have been removed"); - CopyUID cuid; - if (move) - cuid = p.moveuid(ms, folder.getFullName()); - else - cuid = p.copyuid(ms, folder.getFullName()); + // the destination must belong to our same store + if (folder.getStore() != store) // destination is a different store. + throw new MessagingException( + move ? + "can't moveUIDMessages to a different store" : + "can't copyUIDMessages to a different store"); - /* - * Correlate source UIDs with destination UIDs. - * This won't be time or space efficient if there's - * a lot of messages. - * - * In order to make sense of the returned UIDs, we need - * the UIDs for every one of the original messages. - * We fetch them above, to make sure we have them. - * This is critical for MOVE since after the MOVE the - * messages are gone/expunged. - * - * Assume the common case is that the messages are - * in order by UID. Map the returned source - * UIDs to their corresponding Message objects. - * Step through the msgs array looking for the - * Message object in the returned source message - * list. Most commonly the source message (UID) - * for the Nth original message will be in the Nth - * position in the returned source message (UID) - * list. Thus, the destination UID is in the Nth - * position in the returned destination UID list. - * But if the source message isn't where expected, - * we have to search the entire source message - * list, starting from where we expect it and - * wrapping around until we've searched it all. - * (Gmail will often return the lists in an unexpected order.) - * - * A possible optimization: - * If the number of UIDs returned is the same as the - * number of messages being copied/moved, we could - * sort the source messages by message number, sort - * the source and destination parallel arrays by source - * UID, and the resulting message and destination UID - * arrays will correspond. - * - * If the returned UID array size is different, some - * message was expunged while we were trying to copy/move it. - * This should be rare but would mean falling back to the - * general algorithm. - */ - long[] srcuids = UIDSet.toArray(cuid.src); - long[] dstuids = UIDSet.toArray(cuid.dst); - // map source UIDs to Message objects - // XXX - could inline/optimize this - Message[] srcmsgs = getMessagesByUID(srcuids); - AppendUID[] result = new AppendUID[msgs.length]; - for (int i = 0; i < msgs.length; i++) { - int j = i; - do { - if (msgs[i] == srcmsgs[j]) { - result[i] = new AppendUID( - cuid.uidvalidity, dstuids[j]); - break; - } - j++; - if (j >= srcmsgs.length) - j = 0; - } while (j != i); + // call fetch to make sure we have all the UIDs + // necessary to interpret the COPYUID response + FetchProfile fp = new FetchProfile(); + fp.add(UIDFolder.FetchProfileItem.UID); + fetch(msgs, fp); + // XXX - could pipeline the FETCH with the COPY/MOVE below + + messageCacheLock.lock(); + try { + try { + IMAPProtocol p = getProtocol(); + // XXX - messages have to be from this Folder, who checks? + MessageSet[] ms = Utility.toMessageSet(msgs, null); + if (ms == null) + throw new MessageRemovedException( + "Messages have been removed"); + CopyUID cuid; + if (move) + cuid = p.moveuid(ms, folder.getFullName()); + else + cuid = p.copyuid(ms, folder.getFullName()); + + /* + * Correlate source UIDs with destination UIDs. + * This won't be time or space efficient if there's + * a lot of messages. + * + * In order to make sense of the returned UIDs, we need + * the UIDs for every one of the original messages. + * We fetch them above, to make sure we have them. + * This is critical for MOVE since after the MOVE the + * messages are gone/expunged. + * + * Assume the common case is that the messages are + * in order by UID. Map the returned source + * UIDs to their corresponding Message objects. + * Step through the msgs array looking for the + * Message object in the returned source message + * list. Most commonly the source message (UID) + * for the Nth original message will be in the Nth + * position in the returned source message (UID) + * list. Thus, the destination UID is in the Nth + * position in the returned destination UID list. + * But if the source message isn't where expected, + * we have to search the entire source message + * list, starting from where we expect it and + * wrapping around until we've searched it all. + * (Gmail will often return the lists in an unexpected order.) + * + * A possible optimization: + * If the number of UIDs returned is the same as the + * number of messages being copied/moved, we could + * sort the source messages by message number, sort + * the source and destination parallel arrays by source + * UID, and the resulting message and destination UID + * arrays will correspond. + * + * If the returned UID array size is different, some + * message was expunged while we were trying to copy/move it. + * This should be rare but would mean falling back to the + * general algorithm. + */ + long[] srcuids = UIDSet.toArray(cuid.src); + long[] dstuids = UIDSet.toArray(cuid.dst); + // map source UIDs to Message objects + // XXX - could inline/optimize this + Message[] srcmsgs = getMessagesByUID(srcuids); + AppendUID[] result = new AppendUID[msgs.length]; + for (int i = 0; i < msgs.length; i++) { + int j = i; + do { + if (msgs[i] == srcmsgs[j]) { + result[i] = new AppendUID( + cuid.uidvalidity, dstuids[j]); + break; + } + j++; + if (j >= srcmsgs.length) + j = 0; + } while (j != i); + } + return result; + } catch (CommandFailedException cfx) { + if (cfx.getMessage().contains("TRYCREATE")) + throw new FolderNotFoundException( + folder, + folder.getFullName() + " does not exist" + ); + else + throw new MessagingException(cfx.getMessage(), cfx); + } catch (ConnectionException cex) { + throw new FolderClosedException(this, cex.getMessage()); + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); } - return result; - } catch (CommandFailedException cfx) { - if (cfx.getMessage().contains("TRYCREATE")) - throw new FolderNotFoundException( - folder, - folder.getFullName() + " does not exist" - ); - else - throw new MessagingException(cfx.getMessage(), cfx); - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); + } finally { + messageCacheLock.unlock(); } + } finally { + lock.unlock(); } } @@ -2284,8 +2527,13 @@ private synchronized AppendUID[] copymoveUIDMessages(Message[] msgs, * Expunge all messages marked as DELETED. */ @Override - public synchronized Message[] expunge() throws MessagingException { - return expunge(null); + public Message[] expunge() throws MessagingException { + lock.lock(); + try { + return expunge(null); + } finally { + lock.unlock(); + } } /** @@ -2298,65 +2546,73 @@ public synchronized Message[] expunge() throws MessagingException { * @return the expunged messages * @exception MessagingException for failures */ - public synchronized Message[] expunge(Message[] msgs) + public Message[] expunge(Message[] msgs) throws MessagingException { - checkOpened(); + lock.lock(); + try { + checkOpened(); - if (msgs != null) { - // call fetch to make sure we have all the UIDs - FetchProfile fp = new FetchProfile(); - fp.add(UIDFolder.FetchProfileItem.UID); - fetch(msgs, fp); - } + if (msgs != null) { + // call fetch to make sure we have all the UIDs + FetchProfile fp = new FetchProfile(); + fp.add(UIDFolder.FetchProfileItem.UID); + fetch(msgs, fp); + } - IMAPMessage[] rmsgs; - synchronized (messageCacheLock) { - doExpungeNotification = false; // We do this ourselves later + IMAPMessage[] rmsgs; + messageCacheLock.lock(); try { - IMAPProtocol p = getProtocol(); + doExpungeNotification = false; // We do this ourselves later + try { + IMAPProtocol p = getProtocol(); + if (msgs != null) + p.uidexpunge(Utility.toUIDSet(msgs)); + else + p.expunge(); + } catch (CommandFailedException cfx) { + // expunge not allowed, perhaps due to a permission problem? + if (mode != READ_WRITE) + throw new IllegalStateException( + "Cannot expunge READ_ONLY folder: " + fullName); + else + throw new MessagingException(cfx.getMessage(), cfx); + } catch (ConnectionException cex) { + throw new FolderClosedException(this, cex.getMessage()); + } catch (ProtocolException pex) { + // Bad bad server .. + throw new MessagingException(pex.getMessage(), pex); + } finally { + doExpungeNotification = true; + } + + // Cleanup expunged messages and sync messageCache with reality. if (msgs != null) - p.uidexpunge(Utility.toUIDSet(msgs)); - else - p.expunge(); - } catch (CommandFailedException cfx) { - // expunge not allowed, perhaps due to a permission problem? - if (mode != READ_WRITE) - throw new IllegalStateException( - "Cannot expunge READ_ONLY folder: " + fullName); + rmsgs = messageCache.removeExpungedMessages(msgs); else - throw new MessagingException(cfx.getMessage(), cfx); - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (ProtocolException pex) { - // Bad bad server .. - throw new MessagingException(pex.getMessage(), pex); - } finally { - doExpungeNotification = true; - } - - // Cleanup expunged messages and sync messageCache with reality. - if (msgs != null) - rmsgs = messageCache.removeExpungedMessages(msgs); - else - rmsgs = messageCache.removeExpungedMessages(); - if (uidTable != null) { - for (int i = 0; i < rmsgs.length; i++) { - IMAPMessage m = rmsgs[i]; - /* remove this message from the UIDTable */ - long uid = m.getUID(); - if (uid != -1) - uidTable.remove(Long.valueOf(uid)); + rmsgs = messageCache.removeExpungedMessages(); + if (uidTable != null) { + for (int i = 0; i < rmsgs.length; i++) { + IMAPMessage m = rmsgs[i]; + /* remove this message from the UIDTable */ + long uid = m.getUID(); + if (uid != -1) + uidTable.remove(Long.valueOf(uid)); + } } + + // Update 'total' + total = messageCache.size(); + } finally { + messageCacheLock.unlock(); } - // Update 'total' - total = messageCache.size(); + // Notify listeners. This time its for real, guys. + if (rmsgs.length > 0) + notifyMessageRemovedListeners(true, rmsgs); + return rmsgs; + } finally { + lock.unlock(); } - - // Notify listeners. This time its for real, guys. - if (rmsgs.length > 0) - notifyMessageRemovedListeners(true, rmsgs); - return rmsgs; } /** @@ -2374,33 +2630,41 @@ public synchronized Message[] expunge(Message[] msgs) * @exception MessagingException for other failures */ @Override - public synchronized Message[] search(SearchTerm term) + public Message[] search(SearchTerm term) throws MessagingException { - checkOpened(); - + lock.lock(); try { - Message[] matchMsgs = null; + checkOpened(); - synchronized (messageCacheLock) { - int[] matches = getProtocol().search(term); - if (matches != null) - matchMsgs = getMessagesBySeqNumbers(matches); - } - return matchMsgs; + try { + Message[] matchMsgs = null; - } catch (CommandFailedException cfx) { - // unsupported charset or search criterion - return super.search(term); - } catch (SearchException sex) { - // too complex for IMAP - if (((IMAPStore) store).throwSearchException()) - throw sex; - return super.search(term); - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (ProtocolException pex) { - // bug in our IMAP layer ? - throw new MessagingException(pex.getMessage(), pex); + messageCacheLock.lock(); + try { + int[] matches = getProtocol().search(term); + if (matches != null) + matchMsgs = getMessagesBySeqNumbers(matches); + } finally { + messageCacheLock.unlock(); + } + return matchMsgs; + + } catch (CommandFailedException cfx) { + // unsupported charset or search criterion + return super.search(term); + } catch (SearchException sex) { + // too complex for IMAP + if (((IMAPStore) store).throwSearchException()) + throw sex; + return super.search(term); + } catch (ConnectionException cex) { + throw new FolderClosedException(this, cex.getMessage()); + } catch (ProtocolException pex) { + // bug in our IMAP layer ? + throw new MessagingException(pex.getMessage(), pex); + } + } finally { + lock.unlock(); } } @@ -2410,40 +2674,48 @@ public synchronized Message[] search(SearchTerm term) * messages are found. */ @Override - public synchronized Message[] search(SearchTerm term, Message[] msgs) + public Message[] search(SearchTerm term, Message[] msgs) throws MessagingException { - checkOpened(); + lock.lock(); + try { + checkOpened(); - if (msgs.length == 0) - // need to return an empty array (not null!) - return msgs; + if (msgs.length == 0) + // need to return an empty array (not null!) + return msgs; - try { - Message[] matchMsgs = null; + try { + Message[] matchMsgs = null; - synchronized (messageCacheLock) { - IMAPProtocol p = getProtocol(); - MessageSet[] ms = Utility.toMessageSetSorted(msgs, null); - if (ms == null) - throw new MessageRemovedException( - "Messages have been removed"); - int[] matches = p.search(ms, term); - if (matches != null) - matchMsgs = getMessagesBySeqNumbers(matches); - } - return matchMsgs; + messageCacheLock.lock(); + try { + IMAPProtocol p = getProtocol(); + MessageSet[] ms = Utility.toMessageSetSorted(msgs, null); + if (ms == null) + throw new MessageRemovedException( + "Messages have been removed"); + int[] matches = p.search(ms, term); + if (matches != null) + matchMsgs = getMessagesBySeqNumbers(matches); + } finally { + messageCacheLock.unlock(); + } + return matchMsgs; - } catch (CommandFailedException cfx) { - // unsupported charset or search criterion - return super.search(term, msgs); - } catch (SearchException sex) { - // too complex for IMAP - return super.search(term, msgs); - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (ProtocolException pex) { - // bug in our IMAP layer ? - throw new MessagingException(pex.getMessage(), pex); + } catch (CommandFailedException cfx) { + // unsupported charset or search criterion + return super.search(term, msgs); + } catch (SearchException sex) { + // too complex for IMAP + return super.search(term, msgs); + } catch (ConnectionException cex) { + throw new FolderClosedException(this, cex.getMessage()); + } catch (ProtocolException pex) { + // bug in our IMAP layer ? + throw new MessagingException(pex.getMessage(), pex); + } + } finally { + lock.unlock(); } } @@ -2460,9 +2732,14 @@ public synchronized Message[] search(SearchTerm term, Message[] msgs) * @exception MessagingException for failures * @since JavaMail 1.4.4 */ - public synchronized Message[] getSortedMessages(SortTerm[] term) + public Message[] getSortedMessages(SortTerm[] term) throws MessagingException { - return getSortedMessages(term, null); + lock.lock(); + try { + return getSortedMessages(term, null); + } finally { + lock.unlock(); + } } /** @@ -2480,31 +2757,39 @@ public synchronized Message[] getSortedMessages(SortTerm[] term) * @exception MessagingException for failures * @since JavaMail 1.4.4 */ - public synchronized Message[] getSortedMessages(SortTerm[] term, + public Message[] getSortedMessages(SortTerm[] term, SearchTerm sterm) throws MessagingException { - checkOpened(); - + lock.lock(); try { - Message[] matchMsgs = null; + checkOpened(); - synchronized (messageCacheLock) { - int[] matches = getProtocol().sort(term, sterm); - if (matches != null) - matchMsgs = getMessagesBySeqNumbers(matches); - } - return matchMsgs; + try { + Message[] matchMsgs = null; - } catch (CommandFailedException cfx) { - // unsupported charset or search criterion - throw new MessagingException(cfx.getMessage(), cfx); - } catch (SearchException sex) { - // too complex for IMAP - throw new MessagingException(sex.getMessage(), sex); - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (ProtocolException pex) { - // bug in our IMAP layer ? - throw new MessagingException(pex.getMessage(), pex); + messageCacheLock.lock(); + try { + int[] matches = getProtocol().sort(term, sterm); + if (matches != null) + matchMsgs = getMessagesBySeqNumbers(matches); + } finally { + messageCacheLock.unlock(); + } + return matchMsgs; + + } catch (CommandFailedException cfx) { + // unsupported charset or search criterion + throw new MessagingException(cfx.getMessage(), cfx); + } catch (SearchException sex) { + // too complex for IMAP + throw new MessagingException(sex.getMessage(), sex); + } catch (ConnectionException cex) { + throw new FolderClosedException(this, cex.getMessage()); + } catch (ProtocolException pex) { + // bug in our IMAP layer ? + throw new MessagingException(pex.getMessage(), pex); + } + } finally { + lock.unlock(); } } @@ -2516,9 +2801,14 @@ public synchronized Message[] getSortedMessages(SortTerm[] term, * are removed, and that's a rare case, so we don't try. */ @Override - public synchronized void addMessageCountListener(MessageCountListener l) { - super.addMessageCountListener(l); - hasMessageCountListener = true; + public void addMessageCountListener(MessageCountListener l) { + lock.lock(); + try { + super.addMessageCountListener(l); + hasMessageCountListener = true; + } finally { + lock.unlock(); + } } /*********************************************************** @@ -2529,32 +2819,37 @@ public synchronized void addMessageCountListener(MessageCountListener l) { * Returns the UIDValidity for this folder. */ @Override - public synchronized long getUIDValidity() throws MessagingException { - if (opened) // we already have this information - return uidvalidity; + public long getUIDValidity() throws MessagingException { + lock.lock(); + try { + if (opened) // we already have this information + return uidvalidity; - IMAPProtocol p = null; - Status status = null; + IMAPProtocol p = null; + Status status = null; - try { - p = getStoreProtocol(); // XXX - String[] item = {"UIDVALIDITY"}; - status = p.status(fullName, item); - } catch (BadCommandException bex) { - // Probably a RFC1730 server - throw new MessagingException("Cannot obtain UIDValidity", bex); - } catch (ConnectionException cex) { - // Oops, the store or folder died on us. - throwClosedException(cex); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); + try { + p = getStoreProtocol(); // XXX + String[] item = {"UIDVALIDITY"}; + status = p.status(fullName, item); + } catch (BadCommandException bex) { + // Probably a RFC1730 server + throw new MessagingException("Cannot obtain UIDValidity", bex); + } catch (ConnectionException cex) { + // Oops, the store or folder died on us. + throwClosedException(cex); + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); + } finally { + releaseStoreProtocol(p); + } + + if (status == null) + throw new MessagingException("Cannot obtain UIDValidity"); + return status.uidvalidity; } finally { - releaseStoreProtocol(p); + lock.unlock(); } - - if (status == null) - throw new MessagingException("Cannot obtain UIDValidity"); - return status.uidvalidity; } /** @@ -2576,32 +2871,37 @@ public synchronized long getUIDValidity() throws MessagingException { * @since JavaMail 1.3.3 */ @Override - public synchronized long getUIDNext() throws MessagingException { - if (opened) // we already have this information - return uidnext; + public long getUIDNext() throws MessagingException { + lock.lock(); + try { + if (opened) // we already have this information + return uidnext; - IMAPProtocol p = null; - Status status = null; + IMAPProtocol p = null; + Status status = null; - try { - p = getStoreProtocol(); // XXX - String[] item = {"UIDNEXT"}; - status = p.status(fullName, item); - } catch (BadCommandException bex) { - // Probably a RFC1730 server - throw new MessagingException("Cannot obtain UIDNext", bex); - } catch (ConnectionException cex) { - // Oops, the store or folder died on us. - throwClosedException(cex); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); + try { + p = getStoreProtocol(); // XXX + String[] item = {"UIDNEXT"}; + status = p.status(fullName, item); + } catch (BadCommandException bex) { + // Probably a RFC1730 server + throw new MessagingException("Cannot obtain UIDNext", bex); + } catch (ConnectionException cex) { + // Oops, the store or folder died on us. + throwClosedException(cex); + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); + } finally { + releaseStoreProtocol(p); + } + + if (status == null) + throw new MessagingException("Cannot obtain UIDNext"); + return status.uidnext; } finally { - releaseStoreProtocol(p); + lock.unlock(); } - - if (status == null) - throw new MessagingException("Cannot obtain UIDNext"); - return status.uidnext; } /** @@ -2609,42 +2909,50 @@ public synchronized long getUIDNext() throws MessagingException { * If no such message exists, null is returned. */ @Override - public synchronized Message getMessageByUID(long uid) + public Message getMessageByUID(long uid) throws MessagingException { - checkOpened(); // insure folder is open - - IMAPMessage m = null; - + lock.lock(); try { - synchronized (messageCacheLock) { - Long l = Long.valueOf(uid); + checkOpened(); // insure folder is open - if (uidTable != null) { - // Check in uidTable - m = uidTable.get(l); - if (m != null) // found it - return m; - } else - uidTable = new Hashtable<>(); + IMAPMessage m = null; + + try { + messageCacheLock.lock(); + try { + Long l = Long.valueOf(uid); + + if (uidTable != null) { + // Check in uidTable + m = uidTable.get(l); + if (m != null) // found it + return m; + } else + uidTable = new Hashtable<>(); - // Check with the server - // Issue UID FETCH command - getProtocol().fetchSequenceNumber(uid); + // Check with the server + // Issue UID FETCH command + getProtocol().fetchSequenceNumber(uid); - if (uidTable != null) { - // Check in uidTable - m = uidTable.get(l); - if (m != null) // found it - return m; + if (uidTable != null) { + // Check in uidTable + m = uidTable.get(l); + if (m != null) // found it + return m; + } + } finally { + messageCacheLock.unlock(); } + } catch (ConnectionException cex) { + throw new FolderClosedException(this, cex.getMessage()); + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); } - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); - } - return m; + return m; + } finally { + lock.unlock(); + } } /** @@ -2653,36 +2961,44 @@ public synchronized Message getMessageByUID(long uid) * Returns an empty array if no messages are found. */ @Override - public synchronized Message[] getMessagesByUID(long start, long end) + public Message[] getMessagesByUID(long start, long end) throws MessagingException { - checkOpened(); // insure that folder is open + lock.lock(); + try { + checkOpened(); // insure that folder is open - Message[] msgs; // array of messages to be returned + Message[] msgs; // array of messages to be returned - try { - synchronized (messageCacheLock) { - if (uidTable == null) - uidTable = new Hashtable<>(); + try { + messageCacheLock.lock(); + try { + if (uidTable == null) + uidTable = new Hashtable<>(); - // Issue UID FETCH for given range - long[] ua = getProtocol().fetchSequenceNumbers(start, end); + // Issue UID FETCH for given range + long[] ua = getProtocol().fetchSequenceNumbers(start, end); - List ma = new ArrayList<>(); - // NOTE: Below must be within messageCacheLock region - for (int i = 0; i < ua.length; i++) { - Message m = uidTable.get(Long.valueOf(ua[i])); - if (m != null) // found it - ma.add(m); + List ma = new ArrayList<>(); + // NOTE: Below must be within messageCacheLock region + for (int i = 0; i < ua.length; i++) { + Message m = uidTable.get(Long.valueOf(ua[i])); + if (m != null) // found it + ma.add(m); + } + msgs = ma.toArray(new Message[0]); + } finally { + messageCacheLock.unlock(); } - msgs = ma.toArray(new Message[0]); + } catch (ConnectionException cex) { + throw new FolderClosedException(this, cex.getMessage()); + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); } - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); - } - return msgs; + return msgs; + } finally { + lock.unlock(); + } } /** @@ -2693,46 +3009,54 @@ public synchronized Message[] getMessagesByUID(long start, long end) * is returned for that element. */ @Override - public synchronized Message[] getMessagesByUID(long[] uids) + public Message[] getMessagesByUID(long[] uids) throws MessagingException { - checkOpened(); // insure that folder is open - + lock.lock(); try { - synchronized (messageCacheLock) { - long[] unavailUids = uids; - if (uidTable != null) { - // to collect unavailable UIDs - List v = new ArrayList<>(); - for (long uid : uids) { - if (!uidTable.containsKey(uid)) { - // This UID has not been loaded yet. - v.add(uid); + checkOpened(); // insure that folder is open + + try { + messageCacheLock.lock(); + try { + long[] unavailUids = uids; + if (uidTable != null) { + // to collect unavailable UIDs + List v = new ArrayList<>(); + for (long uid : uids) { + if (!uidTable.containsKey(uid)) { + // This UID has not been loaded yet. + v.add(uid); + } } - } - int vsize = v.size(); - unavailUids = new long[vsize]; - for (int i = 0; i < vsize; i++) { - unavailUids[i] = v.get(i); + int vsize = v.size(); + unavailUids = new long[vsize]; + for (int i = 0; i < vsize; i++) { + unavailUids[i] = v.get(i); + } + } else + uidTable = new Hashtable<>(); + + if (unavailUids.length > 0) { + // Issue UID FETCH request for given uids + getProtocol().fetchSequenceNumbers(unavailUids); } - } else - uidTable = new Hashtable<>(); - if (unavailUids.length > 0) { - // Issue UID FETCH request for given uids - getProtocol().fetchSequenceNumbers(unavailUids); + // Return array of size = uids.length + Message[] msgs = new Message[uids.length]; + for (int i = 0; i < uids.length; i++) + msgs[i] = (Message) uidTable.get(Long.valueOf(uids[i])); + return msgs; + } finally { + messageCacheLock.unlock(); } - - // Return array of size = uids.length - Message[] msgs = new Message[uids.length]; - for (int i = 0; i < uids.length; i++) - msgs[i] = (Message) uidTable.get(Long.valueOf(uids[i])); - return msgs; + } catch (ConnectionException cex) { + throw new FolderClosedException(this, cex.getMessage()); + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); } - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); + } finally { + lock.unlock(); } } @@ -2740,23 +3064,26 @@ public synchronized Message[] getMessagesByUID(long[] uids) * Get the UID for the specified message. */ @Override - public synchronized long getUID(Message message) + public long getUID(Message message) throws MessagingException { - if (message.getFolder() != this) - throw new NoSuchElementException( - "Message does not belong to this folder"); - - checkOpened(); // insure that folder is open - - if (!(message instanceof IMAPMessage)) - throw new MessagingException("message is not an IMAPMessage"); - IMAPMessage m = (IMAPMessage) message; - // If the message already knows its UID, great .. - long uid; - if ((uid = m.getUID()) != -1) - return uid; - - synchronized (messageCacheLock) { // Acquire Lock + lock.lock(); + try { + if (message.getFolder() != this) + throw new NoSuchElementException( + "Message does not belong to this folder"); + + checkOpened(); // insure that folder is open + + if (!(message instanceof IMAPMessage)) + throw new MessagingException("message is not an IMAPMessage"); + IMAPMessage m = (IMAPMessage) message; + // If the message already knows its UID, great .. + long uid; + if ((uid = m.getUID()) != -1) + return uid; + + // Acquire Lock + messageCacheLock.lock(); try { IMAPProtocol p = getProtocol(); m.checkExpunged(); // insure that message is not expunged @@ -2775,10 +3102,14 @@ public synchronized long getUID(Message message) throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); + } finally { + messageCacheLock.unlock(); } - } - return uid; + return uid; + } finally { + lock.unlock(); + } } /** @@ -2794,9 +3125,14 @@ public synchronized long getUID(Message message) * @since JavaMail 1.6.0 * @see "RFC 4315" */ - public synchronized boolean getUIDNotSticky() throws MessagingException { - checkOpened(); - return uidNotSticky; + public boolean getUIDNotSticky() throws MessagingException { + lock.lock(); + try { + checkOpened(); + return uidNotSticky; + } finally { + lock.unlock(); + } } /** @@ -2827,34 +3163,39 @@ private Message[] createMessagesForUIDs(long[] uids) { * @since JavaMail 1.5.1 * @see "RFC 4551" */ - public synchronized long getHighestModSeq() throws MessagingException { - if (opened) // we already have this information - return highestmodseq; + public long getHighestModSeq() throws MessagingException { + lock.lock(); + try { + if (opened) // we already have this information + return highestmodseq; - IMAPProtocol p = null; - Status status = null; + IMAPProtocol p = null; + Status status = null; - try { - p = getStoreProtocol(); // XXX - if (!p.hasCapability("CONDSTORE")) - throw new BadCommandException("CONDSTORE not supported"); - String[] item = {"HIGHESTMODSEQ"}; - status = p.status(fullName, item); - } catch (BadCommandException bex) { - // Probably a RFC1730 server - throw new MessagingException("Cannot obtain HIGHESTMODSEQ", bex); - } catch (ConnectionException cex) { - // Oops, the store or folder died on us. - throwClosedException(cex); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); + try { + p = getStoreProtocol(); // XXX + if (!p.hasCapability("CONDSTORE")) + throw new BadCommandException("CONDSTORE not supported"); + String[] item = {"HIGHESTMODSEQ"}; + status = p.status(fullName, item); + } catch (BadCommandException bex) { + // Probably a RFC1730 server + throw new MessagingException("Cannot obtain HIGHESTMODSEQ", bex); + } catch (ConnectionException cex) { + // Oops, the store or folder died on us. + throwClosedException(cex); + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); + } finally { + releaseStoreProtocol(p); + } + + if (status == null) + throw new MessagingException("Cannot obtain HIGHESTMODSEQ"); + return status.highestmodseq; } finally { - releaseStoreProtocol(p); + lock.unlock(); } - - if (status == null) - throw new MessagingException("Cannot obtain HIGHESTMODSEQ"); - return status.highestmodseq; } /** @@ -2871,13 +3212,15 @@ public synchronized long getHighestModSeq() throws MessagingException { * @since JavaMail 1.5.1 * @see "RFC 4551" */ - public synchronized Message[] getMessagesByUIDChangedSince( + public Message[] getMessagesByUIDChangedSince( long start, long end, long modseq) throws MessagingException { - checkOpened(); // insure that folder is open - + lock.lock(); try { - synchronized (messageCacheLock) { + checkOpened(); // insure that folder is open + + messageCacheLock.lock(); + try { IMAPProtocol p = getProtocol(); if (!p.hasCapability("CONDSTORE")) throw new BadCommandException("CONDSTORE not supported"); @@ -2885,11 +3228,15 @@ public synchronized Message[] getMessagesByUIDChangedSince( // Issue FETCH for given range int[] nums = p.uidfetchChangedSince(start, end, modseq); return getMessagesBySeqNumbers(nums); + } catch (ConnectionException cex) { + throw new FolderClosedException(this, cex.getMessage()); + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); + } finally { + messageCacheLock.unlock(); } - } catch (ConnectionException cex) { - throw new FolderClosedException(this, cex.getMessage()); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); + } finally { + lock.unlock(); } } @@ -3087,11 +3434,16 @@ public Object doCommand(IMAPProtocol p) * @exception MessagingException for failures * @since JavaMail 1.3.3 */ - public synchronized String[] getAttributes() throws MessagingException { - checkExists(); - if (attributes == null) - exists(); // do a LIST to set the attributes - return attributes == null ? new String[0] : attributes.clone(); + public String[] getAttributes() throws MessagingException { + lock.lock(); + try { + checkExists(); + if (attributes == null) + exists(); // do a LIST to set the attributes + return attributes == null ? new String[0] : attributes.clone(); + } finally { + lock.unlock(); + } } /** @@ -3132,7 +3484,8 @@ public void idle() throws MessagingException { * @since JavaMail 1.4.3 */ public void idle(boolean once) throws MessagingException { - synchronized (this) { + lock.lock(); + try { /* * We can't support the idle method if we're using SocketChannels * because SocketChannels don't allow simultaneous read and write. @@ -3144,6 +3497,8 @@ public void idle(boolean once) throws MessagingException { if (protocol != null && protocol.getChannel() != null) throw new MessagingException( "idle method not supported with SocketChannels"); + } finally { + lock.unlock(); } if (!startIdle(null)) return; @@ -3197,9 +3552,10 @@ public void idle(boolean once) throws MessagingException { */ boolean startIdle(final IdleManager im) throws MessagingException { // ASSERT: Must NOT be called with this folder's - // synchronization lock held. - assert !Thread.holdsLock(this); - synchronized (this) { + // lock held. + assert !lock.isHeldByCurrentThread(); + lock.lock(); + try { checkOpened(); if (im != null && idleManager != null && im != idleManager) throw new MessagingException( @@ -3238,6 +3594,8 @@ public Object doCommand(IMAPProtocol p) }); logger.log(Level.FINEST, "startIdle: return {0}", started); return started.booleanValue(); + } finally { + lock.unlock(); } } @@ -3257,66 +3615,65 @@ boolean handleIdle(boolean once) throws MessagingException { Response r = null; do { r = protocol.readIdleResponse(); + messageCacheLock.lock(); try { - synchronized (messageCacheLock) { - if (r.isBYE() && r.isSynthetic() && idleState == IDLE) { - /* - * If it was a timeout and no bytes were transferred - * we ignore it and go back and read again. - * If the I/O was otherwise interrupted, and no - * bytes were transferred, we take it as a request - * to abort the IDLE. - */ - Exception ex = r.getException(); - if (ex instanceof InterruptedIOException && - ((InterruptedIOException) ex). - bytesTransferred == 0) { - if (ex instanceof SocketTimeoutException) { + if (r.isBYE() && r.isSynthetic() && idleState == IDLE) { + /* + * If it was a timeout and no bytes were transferred + * we ignore it and go back and read again. + * If the I/O was otherwise interrupted, and no + * bytes were transferred, we take it as a request + * to abort the IDLE. + */ + Exception ex = r.getException(); + if (ex instanceof InterruptedIOException && + ((InterruptedIOException) ex). + bytesTransferred == 0) { + if (ex instanceof SocketTimeoutException) { + logger.finest( + "handleIdle: ignoring socket timeout"); + r = null; // repeat do/while loop + } else { + logger.finest("handleIdle: interrupting IDLE"); + IdleManager im = idleManager; + if (im != null) { logger.finest( - "handleIdle: ignoring socket timeout"); - r = null; // repeat do/while loop + "handleIdle: request IdleManager to abort"); + im.requestAbort(this); } else { - logger.finest("handleIdle: interrupting IDLE"); - IdleManager im = idleManager; - if (im != null) { - logger.finest( - "handleIdle: request IdleManager to abort"); - im.requestAbort(this); - } else { - logger.finest("handleIdle: abort IDLE"); - protocol.idleAbort(); - idleState = ABORTING; - } - // normally will exit the do/while loop + logger.finest("handleIdle: abort IDLE"); + protocol.idleAbort(); + idleState = ABORTING; } - continue; + // normally will exit the do/while loop } + continue; } - boolean done = true; - try { - if (protocol == null || - !protocol.processIdleResponse(r)) - return false; // done - done = false; - } finally { - if (done) { - logger.finest("handleIdle: set to RUNNING"); - idleState = RUNNING; - idleManager = null; - messageCacheLock.notifyAll(); - } + } + boolean done = true; + try { + if (protocol == null || + !protocol.processIdleResponse(r)) + return false; // done + done = false; + } finally { + if (done) { + logger.finest("handleIdle: set to RUNNING"); + idleState = RUNNING; + idleManager = null; + messageCacheLock.notifyAll(); } - if (once) { - if (idleState == IDLE) { - try { - protocol.idleAbort(); - } catch (Exception ex) { - // ignore any failures, still have to abort. - // connection failures will be detected above - // in the call to readIdleResponse. - } - idleState = ABORTING; + } + if (once) { + if (idleState == IDLE) { + try { + protocol.idleAbort(); + } catch (Exception ex) { + // ignore any failures, still have to abort. + // connection failures will be detected above + // in the call to readIdleResponse. } + idleState = ABORTING; } } } catch (ConnectionException cex) { @@ -3324,6 +3681,8 @@ boolean handleIdle(boolean once) throws MessagingException { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); + } finally { + messageCacheLock.unlock(); } // keep processing responses already in our buffer } while (r == null || protocol.hasResponse()); @@ -3336,7 +3695,7 @@ boolean handleIdle(boolean once) throws MessagingException { * ASSERT: Must be called with the message cache lock held. */ void waitIfIdle() throws ProtocolException { - assert Thread.holdsLock(messageCacheLock); + assert messageCacheLock.isHeldByCurrentThread(); while (idleState != RUNNING) { if (idleState == IDLE) { IdleManager im = idleManager; @@ -3376,11 +3735,14 @@ void waitIfIdle() throws ProtocolException { * Send the DONE command that aborts the IDLE; used by IdleManager. */ void idleAbort() { - synchronized (messageCacheLock) { + messageCacheLock.lock(); + try { if (idleState == IDLE && protocol != null) { protocol.idleAbort(); idleState = ABORTING; } + } finally { + messageCacheLock.unlock(); } } @@ -3389,7 +3751,8 @@ void idleAbort() { * used by IdleManager. */ void idleAbortWait() { - synchronized (messageCacheLock) { + messageCacheLock.lock(); + try { if (idleState == IDLE && protocol != null) { protocol.idleAbort(); idleState = ABORTING; @@ -3406,6 +3769,8 @@ void idleAbortWait() { } logger.finest("IDLE aborted"); } + } finally { + messageCacheLock.unlock(); } } @@ -3456,32 +3821,37 @@ public Object doCommand(IMAPProtocol p) * @return the value of the STATUS item, or -1 * @since JavaMail 1.5.2 */ - public synchronized long getStatusItem(String item) + public long getStatusItem(String item) throws MessagingException { - if (!opened) { - checkExists(); + lock.lock(); + try { + if (!opened) { + checkExists(); - IMAPProtocol p = null; - Status status = null; - try { - p = getStoreProtocol(); // XXX - String[] items = {item}; - status = p.status(fullName, items); - return status != null ? status.getItem(item) : -1; - } catch (BadCommandException bex) { - // doesn't support STATUS, probably vanilla IMAP4 .. - // Could EXAMINE, SEARCH for UNREAD messages and - // return the count .. bah, not worth it. - return -1; - } catch (ConnectionException cex) { - throw new StoreClosedException(store, cex.getMessage()); - } catch (ProtocolException pex) { - throw new MessagingException(pex.getMessage(), pex); - } finally { - releaseStoreProtocol(p); + IMAPProtocol p = null; + Status status = null; + try { + p = getStoreProtocol(); // XXX + String[] items = {item}; + status = p.status(fullName, items); + return status != null ? status.getItem(item) : -1; + } catch (BadCommandException bex) { + // doesn't support STATUS, probably vanilla IMAP4 .. + // Could EXAMINE, SEARCH for UNREAD messages and + // return the count .. bah, not worth it. + return -1; + } catch (ConnectionException cex) { + throw new StoreClosedException(store, cex.getMessage()); + } catch (ProtocolException pex) { + throw new MessagingException(pex.getMessage(), pex); + } finally { + releaseStoreProtocol(p); + } } + return -1; + } finally { + lock.unlock(); } - return -1; } /** @@ -3493,12 +3863,12 @@ public synchronized long getStatusItem(String item) * messageCacheLock. * ASSERT: This method must *not* invoke any other method that * might grab the 'folder' lock or 'message' lock (i.e., any - * synchronized methods on IMAPFolder or IMAPMessage) + * locked methods on IMAPFolder or IMAPMessage) * since that will result in violating the locking hierarchy. */ @Override public void handleResponse(Response r) { - assert Thread.holdsLock(messageCacheLock); + assert messageCacheLock.isHeldByCurrentThread(); /* * First, delegate possible ALERT or notification to the Store. @@ -3706,15 +4076,20 @@ void handleResponses(Response[] r) { * } * * - * ASSERT: Must be called with this folder's synchronization lock held. + * ASSERT: Must be called with this folder's lock held. * * @return the IMAPProtocol for the Store's connection * @exception ProtocolException for protocol errors */ - protected synchronized IMAPProtocol getStoreProtocol() + protected IMAPProtocol getStoreProtocol() throws ProtocolException { - connectionPoolLogger.fine("getStoreProtocol() borrowing a connection"); - return ((IMAPStore) store).getFolderStoreProtocol(); + lock.lock(); + try { + connectionPoolLogger.fine("getStoreProtocol() borrowing a connection"); + return ((IMAPStore) store).getFolderStoreProtocol(); + } finally { + lock.unlock(); + } } /** @@ -3724,20 +4099,25 @@ protected synchronized IMAPProtocol getStoreProtocol() * @exception FolderClosedException if the folder is closed * @exception StoreClosedException if the store is closed */ - protected synchronized void throwClosedException(ConnectionException cex) + protected void throwClosedException(ConnectionException cex) throws FolderClosedException, StoreClosedException { - // If it's the folder's protocol object, throw a FolderClosedException; - // otherwise, throw a StoreClosedException. - // If a command has failed because the connection is closed, - // the folder will have already been forced closed by the - // time we get here and our protocol object will have been - // released, so if we no longer have a protocol object we base - // this decision on whether we *think* the folder is open. - if ((protocol != null && cex.getProtocol() == protocol) || - (protocol == null && !reallyClosed)) - throw new FolderClosedException(this, cex.getMessage()); - else - throw new StoreClosedException(store, cex.getMessage()); + lock.lock(); + try { + // If it's the folder's protocol object, throw a FolderClosedException; + // otherwise, throw a StoreClosedException. + // If a command has failed because the connection is closed, + // the folder will have already been forced closed by the + // time we get here and our protocol object will have been + // released, so if we no longer have a protocol object we base + // this decision on whether we *think* the folder is open. + if ((protocol != null && cex.getProtocol() == protocol) || + (protocol == null && !reallyClosed)) + throw new FolderClosedException(this, cex.getMessage()); + else + throw new StoreClosedException(store, cex.getMessage()); + } finally { + lock.unlock(); + } } /** @@ -3750,7 +4130,7 @@ protected synchronized void throwClosedException(ConnectionException cex) * @exception ProtocolException for protocol errors */ protected IMAPProtocol getProtocol() throws ProtocolException { - assert Thread.holdsLock(messageCacheLock); + assert messageCacheLock.isHeldByCurrentThread(); waitIfIdle(); // if we no longer have a protocol object after waiting, it probably // means the connection has been closed due to a communnication error, @@ -3908,27 +4288,35 @@ public Object doCommandIgnoreFailure(ProtocolCommand cmd) return null; } - protected synchronized Object doProtocolCommand(ProtocolCommand cmd) + protected Object doProtocolCommand(ProtocolCommand cmd) throws ProtocolException { - /* - * Check whether we have a protocol object, not whether we're - * opened, to allow use of the exsting protocol object in the - * open method before the state is changed to "opened". - */ - if (protocol != null) { - synchronized (messageCacheLock) { - return cmd.doCommand(getProtocol()); + lock.lock(); + try { + /* + * Check whether we have a protocol object, not whether we're + * opened, to allow use of the exsting protocol object in the + * open method before the state is changed to "opened". + */ + if (protocol != null) { + messageCacheLock.lock(); + try { + return cmd.doCommand(getProtocol()); + } finally { + messageCacheLock.unlock(); + } } - } - // only get here if using store's connection - IMAPProtocol p = null; + // only get here if using store's connection + IMAPProtocol p = null; - try { - p = getStoreProtocol(); - return cmd.doCommand(p); + try { + p = getStoreProtocol(); + return cmd.doCommand(p); + } finally { + releaseStoreProtocol(p); + } } finally { - releaseStoreProtocol(p); + lock.unlock(); } } @@ -3937,16 +4325,21 @@ protected synchronized Object doProtocolCommand(ProtocolCommand cmd) * object from the connection pool, give it back. If we used our * own protocol object, nothing to do. * - * ASSERT: Must be called with this folder's synchronization lock held. + * ASSERT: Must be called with this folder's lock held. * * @param p the IMAPProtocol object */ - protected synchronized void releaseStoreProtocol(IMAPProtocol p) { - if (p != protocol) - ((IMAPStore) store).releaseFolderStoreProtocol(p); - else { - // XXX - should never happen - logger.fine("releasing our protocol as store protocol?"); + protected void releaseStoreProtocol(IMAPProtocol p) { + lock.lock(); + try { + if (p != protocol) + ((IMAPStore) store).releaseFolderStoreProtocol(p); + else { + // XXX - should never happen + logger.fine("releasing our protocol as store protocol?"); + } + } finally { + lock.unlock(); } } @@ -3986,7 +4379,7 @@ protected void releaseProtocol(boolean returnToPool) { protected void keepConnectionAlive(boolean keepStoreAlive) throws ProtocolException { - assert Thread.holdsLock(messageCacheLock); + assert messageCacheLock.isHeldByCurrentThread(); if (protocol == null) // in case connection was closed return; if (System.currentTimeMillis() - protocol.getTimestamp() > 1000) { diff --git a/providers/imap/src/main/java/org/eclipse/angus/mail/imap/IMAPMessage.java b/providers/imap/src/main/java/org/eclipse/angus/mail/imap/IMAPMessage.java index d2b6fa70..502f815e 100644 --- a/providers/imap/src/main/java/org/eclipse/angus/mail/imap/IMAPMessage.java +++ b/providers/imap/src/main/java/org/eclipse/angus/mail/imap/IMAPMessage.java @@ -64,6 +64,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.locks.Lock; /** * This class implements an IMAPMessage object.

@@ -203,7 +204,7 @@ protected boolean isREV1() throws FolderClosedException { * * @return the message cache lock object */ - protected Object getMessageCacheLock() { + protected Lock getMessageCacheLock() { return ((IMAPFolder) folder).messageCacheLock; } diff --git a/providers/imap/src/main/java/org/eclipse/angus/mail/imap/IMAPNestedMessage.java b/providers/imap/src/main/java/org/eclipse/angus/mail/imap/IMAPNestedMessage.java index 55e11066..1d0b053a 100644 --- a/providers/imap/src/main/java/org/eclipse/angus/mail/imap/IMAPNestedMessage.java +++ b/providers/imap/src/main/java/org/eclipse/angus/mail/imap/IMAPNestedMessage.java @@ -16,6 +16,8 @@ package org.eclipse.angus.mail.imap; +import java.util.concurrent.locks.Lock; + import jakarta.mail.Flags; import jakarta.mail.FolderClosedException; import jakarta.mail.MessageRemovedException; @@ -73,7 +75,7 @@ protected boolean isREV1() throws FolderClosedException { * IMAPMessage.getMessageCacheLock(). */ @Override - protected Object getMessageCacheLock() { + protected Lock getMessageCacheLock() { return msg.getMessageCacheLock(); } diff --git a/providers/imap/src/main/java/org/eclipse/angus/mail/imap/protocol/ENVELOPE.java b/providers/imap/src/main/java/org/eclipse/angus/mail/imap/protocol/ENVELOPE.java index 1310f1dc..fa3794bd 100644 --- a/providers/imap/src/main/java/org/eclipse/angus/mail/imap/protocol/ENVELOPE.java +++ b/providers/imap/src/main/java/org/eclipse/angus/mail/imap/protocol/ENVELOPE.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.concurrent.locks.ReentrantLock; /** * The ENEVELOPE item of an IMAP FETCH response. @@ -54,6 +55,7 @@ public class ENVELOPE implements Item { // Used to parse dates private static final MailDateFormat mailDateFormat = new MailDateFormat(); + private static final ReentrantLock slock = new ReentrantLock(); // special debugging output to debug parsing errors private static final boolean parseDebug = @@ -71,11 +73,12 @@ public ENVELOPE(FetchResponse r) throws ParsingException { String s = r.readString(); if (s != null) { + slock.lock(); try { - synchronized (mailDateFormat) { - date = mailDateFormat.parse(s); - } + date = mailDateFormat.parse(s); } catch (ParseException pex) { + } finally { + slock.unlock(); } } if (parseDebug) diff --git a/providers/imap/src/main/java/org/eclipse/angus/mail/imap/protocol/IMAPProtocol.java b/providers/imap/src/main/java/org/eclipse/angus/mail/imap/protocol/IMAPProtocol.java index d2a55996..dd641898 100644 --- a/providers/imap/src/main/java/org/eclipse/angus/mail/imap/protocol/IMAPProtocol.java +++ b/providers/imap/src/main/java/org/eclipse/angus/mail/imap/protocol/IMAPProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -65,6 +65,7 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; /** @@ -82,6 +83,7 @@ public class IMAPProtocol extends Protocol { + private final ReentrantLock lock = new ReentrantLock(); private boolean connected = false; // did constructor succeed? private boolean rev1 = false; // REV1 server ? private boolean referralException; // throw exception for IMAP REFERRAL? @@ -546,110 +548,115 @@ public void login(String u, String p) throws ProtocolException { * @throws ProtocolException as thrown by {@link Protocol#handleResult}. * @see "RFC2060, section 6.2.1" */ - public synchronized void authlogin(String u, String p) + public void authlogin(String u, String p) throws ProtocolException { - List v = new ArrayList<>(); - String tag = null; - Response r = null; - boolean done = false; - + lock.lock(); try { - - if (noauthdebug && isTracing()) { - logger.fine("AUTHENTICATE LOGIN command trace suppressed"); - suspendTracing(); - } + List v = new ArrayList<>(); + String tag = null; + Response r = null; + boolean done = false; try { - tag = writeCommand("AUTHENTICATE LOGIN", null); - } catch (Exception ex) { - // Convert this into a BYE response - r = Response.byeResponse(ex); - done = true; - } - OutputStream os = getOutputStream(); // stream to IMAP server - - /* Wrap a BASE64Encoder around a ByteArrayOutputstream - * to craft b64 encoded username and password strings - * - * Note that the encoded bytes should be sent "as-is" to the - * server, *not* as literals or quoted-strings. - * - * Also note that unlike the B64 definition in MIME, CRLFs - * should *not* be inserted during the encoding process. So, I - * use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the bytesPerLine, - * which should be sufficiently large ! - * - * Finally, format the line in a buffer so it can be sent as - * a single packet, to avoid triggering a bug in SUN's SIMS 2.0 - * server caused by patch 105346. - */ - - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - OutputStream b64os = new BASE64EncoderStream(bos, Integer.MAX_VALUE); - boolean first = true; + if (noauthdebug && isTracing()) { + logger.fine("AUTHENTICATE LOGIN command trace suppressed"); + suspendTracing(); + } - while (!done) { // loop till we are done try { - r = readResponse(); - if (r.isContinuation()) { - // Server challenge .. - String s; - if (first) { // Send encoded username - s = u; - first = false; - } else // Send encoded password - s = p; - - // obtain b64 encoded bytes - b64os.write(s.getBytes(StandardCharsets.UTF_8)); - b64os.flush(); // complete the encoding - - bos.write(CRLF); // CRLF termination - os.write(bos.toByteArray()); // write out line - os.flush(); // flush the stream - bos.reset(); // reset buffer - } else if (r.isTagged() && r.getTag().equals(tag)) - // Ah, our tagged response - done = true; - else if (r.isBYE()) // outta here - done = true; - // hmm .. unsolicited response here ?! - } catch (Exception ioex) { - // convert this into a BYE response - r = Response.byeResponse(ioex); + tag = writeCommand("AUTHENTICATE LOGIN", null); + } catch (Exception ex) { + // Convert this into a BYE response + r = Response.byeResponse(ex); done = true; } - v.add(r); - } - } finally { - resumeTracing(); - } + OutputStream os = getOutputStream(); // stream to IMAP server + + /* Wrap a BASE64Encoder around a ByteArrayOutputstream + * to craft b64 encoded username and password strings + * + * Note that the encoded bytes should be sent "as-is" to the + * server, *not* as literals or quoted-strings. + * + * Also note that unlike the B64 definition in MIME, CRLFs + * should *not* be inserted during the encoding process. So, I + * use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the bytesPerLine, + * which should be sufficiently large ! + * + * Finally, format the line in a buffer so it can be sent as + * a single packet, to avoid triggering a bug in SUN's SIMS 2.0 + * server caused by patch 105346. + */ - Response[] responses = v.toArray(new Response[0]); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + OutputStream b64os = new BASE64EncoderStream(bos, Integer.MAX_VALUE); + boolean first = true; + + while (!done) { // loop till we are done + try { + r = readResponse(); + if (r.isContinuation()) { + // Server challenge .. + String s; + if (first) { // Send encoded username + s = u; + first = false; + } else // Send encoded password + s = p; + + // obtain b64 encoded bytes + b64os.write(s.getBytes(StandardCharsets.UTF_8)); + b64os.flush(); // complete the encoding + + bos.write(CRLF); // CRLF termination + os.write(bos.toByteArray()); // write out line + os.flush(); // flush the stream + bos.reset(); // reset buffer + } else if (r.isTagged() && r.getTag().equals(tag)) + // Ah, our tagged response + done = true; + else if (r.isBYE()) // outta here + done = true; + // hmm .. unsolicited response here ?! + } catch (Exception ioex) { + // convert this into a BYE response + r = Response.byeResponse(ioex); + done = true; + } + v.add(r); + } - // handle an illegal but not uncommon untagged CAPABILTY response - handleCapabilityResponse(responses); + } finally { + resumeTracing(); + } - /* - * Dispatch untagged responses. - * NOTE: in our current upper level IMAP classes, we add the - * responseHandler to the Protocol object only *after* the - * connection has been authenticated. So, for now, the below - * code really ends up being just a no-op. - */ - notifyResponseHandlers(responses); + Response[] responses = v.toArray(new Response[0]); - // Handle the final OK, NO, BAD or BYE response - if (noauthdebug && isTracing()) - logger.fine("AUTHENTICATE LOGIN command result: " + r); - handleLoginResult(r); - // If the response includes a CAPABILITY response code, process it - setCapabilities(r); - // if we get this far without an exception, we're authenticated - authenticated = true; + // handle an illegal but not uncommon untagged CAPABILTY response + handleCapabilityResponse(responses); + + /* + * Dispatch untagged responses. + * NOTE: in our current upper level IMAP classes, we add the + * responseHandler to the Protocol object only *after* the + * connection has been authenticated. So, for now, the below + * code really ends up being just a no-op. + */ + notifyResponseHandlers(responses); + + // Handle the final OK, NO, BAD or BYE response + if (noauthdebug && isTracing()) + logger.fine("AUTHENTICATE LOGIN command result: " + r); + handleLoginResult(r); + // If the response includes a CAPABILITY response code, process it + setCapabilities(r); + // if we get this far without an exception, we're authenticated + authenticated = true; + } finally { + lock.unlock(); + } } @@ -665,106 +672,111 @@ else if (r.isBYE()) // outta here * @see "RFC2595, section 6" * @since JavaMail 1.3.2 */ - public synchronized void authplain(String authzid, String u, String p) + public void authplain(String authzid, String u, String p) throws ProtocolException { - List v = new ArrayList<>(); - String tag = null; - Response r = null; - boolean done = false; - + lock.lock(); try { - - if (noauthdebug && isTracing()) { - logger.fine("AUTHENTICATE PLAIN command trace suppressed"); - suspendTracing(); - } + List v = new ArrayList<>(); + String tag = null; + Response r = null; + boolean done = false; try { - tag = writeCommand("AUTHENTICATE PLAIN", null); - } catch (Exception ex) { - // Convert this into a BYE response - r = Response.byeResponse(ex); - done = true; - } - - OutputStream os = getOutputStream(); // stream to IMAP server - - /* Wrap a BASE64Encoder around a ByteArrayOutputstream - * to craft b64 encoded username and password strings - * - * Note that the encoded bytes should be sent "as-is" to the - * server, *not* as literals or quoted-strings. - * - * Also note that unlike the B64 definition in MIME, CRLFs - * should *not* be inserted during the encoding process. So, I - * use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the bytesPerLine, - * which should be sufficiently large ! - * - * Finally, format the line in a buffer so it can be sent as - * a single packet, to avoid triggering a bug in SUN's SIMS 2.0 - * server caused by patch 105346. - */ - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - OutputStream b64os = new BASE64EncoderStream(bos, Integer.MAX_VALUE); + if (noauthdebug && isTracing()) { + logger.fine("AUTHENTICATE PLAIN command trace suppressed"); + suspendTracing(); + } - while (!done) { // loop till we are done try { - r = readResponse(); - if (r.isContinuation()) { - // Server challenge .. - final String nullByte = "\0"; - String s = (authzid == null ? "" : authzid) + - nullByte + u + nullByte + p; - - // obtain b64 encoded bytes - b64os.write(s.getBytes(StandardCharsets.UTF_8)); - b64os.flush(); // complete the encoding - - bos.write(CRLF); // CRLF termination - os.write(bos.toByteArray()); // write out line - os.flush(); // flush the stream - bos.reset(); // reset buffer - } else if (r.isTagged() && r.getTag().equals(tag)) - // Ah, our tagged response - done = true; - else if (r.isBYE()) // outta here - done = true; - // hmm .. unsolicited response here ?! - } catch (Exception ioex) { - // convert this into a BYE response - r = Response.byeResponse(ioex); + tag = writeCommand("AUTHENTICATE PLAIN", null); + } catch (Exception ex) { + // Convert this into a BYE response + r = Response.byeResponse(ex); done = true; } - v.add(r); - } - } finally { - resumeTracing(); - } + OutputStream os = getOutputStream(); // stream to IMAP server + + /* Wrap a BASE64Encoder around a ByteArrayOutputstream + * to craft b64 encoded username and password strings + * + * Note that the encoded bytes should be sent "as-is" to the + * server, *not* as literals or quoted-strings. + * + * Also note that unlike the B64 definition in MIME, CRLFs + * should *not* be inserted during the encoding process. So, I + * use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the bytesPerLine, + * which should be sufficiently large ! + * + * Finally, format the line in a buffer so it can be sent as + * a single packet, to avoid triggering a bug in SUN's SIMS 2.0 + * server caused by patch 105346. + */ - Response[] responses = v.toArray(new Response[0]); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + OutputStream b64os = new BASE64EncoderStream(bos, Integer.MAX_VALUE); + + while (!done) { // loop till we are done + try { + r = readResponse(); + if (r.isContinuation()) { + // Server challenge .. + final String nullByte = "\0"; + String s = (authzid == null ? "" : authzid) + + nullByte + u + nullByte + p; + + // obtain b64 encoded bytes + b64os.write(s.getBytes(StandardCharsets.UTF_8)); + b64os.flush(); // complete the encoding + + bos.write(CRLF); // CRLF termination + os.write(bos.toByteArray()); // write out line + os.flush(); // flush the stream + bos.reset(); // reset buffer + } else if (r.isTagged() && r.getTag().equals(tag)) + // Ah, our tagged response + done = true; + else if (r.isBYE()) // outta here + done = true; + // hmm .. unsolicited response here ?! + } catch (Exception ioex) { + // convert this into a BYE response + r = Response.byeResponse(ioex); + done = true; + } + v.add(r); + } - // handle an illegal but not uncommon untagged CAPABILTY response - handleCapabilityResponse(responses); + } finally { + resumeTracing(); + } - /* - * Dispatch untagged responses. - * NOTE: in our current upper level IMAP classes, we add the - * responseHandler to the Protocol object only *after* the - * connection has been authenticated. So, for now, the below - * code really ends up being just a no-op. - */ - notifyResponseHandlers(responses); + Response[] responses = v.toArray(new Response[0]); - // Handle the final OK, NO, BAD or BYE response - if (noauthdebug && isTracing()) - logger.fine("AUTHENTICATE PLAIN command result: " + r); - handleLoginResult(r); - // If the response includes a CAPABILITY response code, process it - setCapabilities(r); - // if we get this far without an exception, we're authenticated - authenticated = true; + // handle an illegal but not uncommon untagged CAPABILTY response + handleCapabilityResponse(responses); + + /* + * Dispatch untagged responses. + * NOTE: in our current upper level IMAP classes, we add the + * responseHandler to the Protocol object only *after* the + * connection has been authenticated. So, for now, the below + * code really ends up being just a no-op. + */ + notifyResponseHandlers(responses); + + // Handle the final OK, NO, BAD or BYE response + if (noauthdebug && isTracing()) + logger.fine("AUTHENTICATE PLAIN command result: " + r); + handleLoginResult(r); + // If the response includes a CAPABILITY response code, process it + setCapabilities(r); + // if we get this far without an exception, we're authenticated + authenticated = true; + } finally { + lock.unlock(); + } } /** @@ -779,95 +791,100 @@ else if (r.isBYE()) // outta here * @see "RFC2595, section 6" * @since JavaMail 1.4.3 */ - public synchronized void authntlm(String authzid, String u, String p) + public void authntlm(String authzid, String u, String p) throws ProtocolException { - List v = new ArrayList<>(); - String tag = null; - Response r = null; - boolean done = false; - - int flags = PropUtil.getIntProperty(props, - "mail." + name + ".auth.ntlm.flags", 0); - boolean v2 = PropUtil.getBooleanProperty(props, - "mail." + name + ".auth.ntlm.v2", true); - String domain = props.getProperty( - "mail." + name + ".auth.ntlm.domain", ""); - Ntlm ntlm = new Ntlm(domain, getLocalHost(), u, p, logger); - + lock.lock(); try { - - if (noauthdebug && isTracing()) { - logger.fine("AUTHENTICATE NTLM command trace suppressed"); - suspendTracing(); - } + List v = new ArrayList<>(); + String tag = null; + Response r = null; + boolean done = false; + + int flags = PropUtil.getIntProperty(props, + "mail." + name + ".auth.ntlm.flags", 0); + boolean v2 = PropUtil.getBooleanProperty(props, + "mail." + name + ".auth.ntlm.v2", true); + String domain = props.getProperty( + "mail." + name + ".auth.ntlm.domain", ""); + Ntlm ntlm = new Ntlm(domain, getLocalHost(), u, p, logger); try { - tag = writeCommand("AUTHENTICATE NTLM", null); - } catch (Exception ex) { - // Convert this into a BYE response - r = Response.byeResponse(ex); - done = true; - } - OutputStream os = getOutputStream(); // stream to IMAP server - boolean first = true; + if (noauthdebug && isTracing()) { + logger.fine("AUTHENTICATE NTLM command trace suppressed"); + suspendTracing(); + } - while (!done) { // loop till we are done try { - r = readResponse(); - if (r.isContinuation()) { - // Server challenge .. - String s; - if (first) { - s = ntlm.generateType1Msg(flags, v2); - first = false; - } else { - s = ntlm.generateType3Msg(r.getRest()); - } - - os.write(s.getBytes(StandardCharsets.UTF_8)); - os.write(CRLF); // CRLF termination - os.flush(); // flush the stream - } else if (r.isTagged() && r.getTag().equals(tag)) - // Ah, our tagged response - done = true; - else if (r.isBYE()) // outta here - done = true; - // hmm .. unsolicited response here ?! - } catch (Exception ioex) { - // convert this into a BYE response - r = Response.byeResponse(ioex); + tag = writeCommand("AUTHENTICATE NTLM", null); + } catch (Exception ex) { + // Convert this into a BYE response + r = Response.byeResponse(ex); done = true; } - v.add(r); - } - } finally { - resumeTracing(); - } + OutputStream os = getOutputStream(); // stream to IMAP server + boolean first = true; + + while (!done) { // loop till we are done + try { + r = readResponse(); + if (r.isContinuation()) { + // Server challenge .. + String s; + if (first) { + s = ntlm.generateType1Msg(flags, v2); + first = false; + } else { + s = ntlm.generateType3Msg(r.getRest()); + } + + os.write(s.getBytes(StandardCharsets.UTF_8)); + os.write(CRLF); // CRLF termination + os.flush(); // flush the stream + } else if (r.isTagged() && r.getTag().equals(tag)) + // Ah, our tagged response + done = true; + else if (r.isBYE()) // outta here + done = true; + // hmm .. unsolicited response here ?! + } catch (Exception ioex) { + // convert this into a BYE response + r = Response.byeResponse(ioex); + done = true; + } + v.add(r); + } - Response[] responses = v.toArray(new Response[0]); + } finally { + resumeTracing(); + } - // handle an illegal but not uncommon untagged CAPABILTY response - handleCapabilityResponse(responses); + Response[] responses = v.toArray(new Response[0]); - /* - * Dispatch untagged responses. - * NOTE: in our current upper level IMAP classes, we add the - * responseHandler to the Protocol object only *after* the - * connection has been authenticated. So, for now, the below - * code really ends up being just a no-op. - */ - notifyResponseHandlers(responses); + // handle an illegal but not uncommon untagged CAPABILTY response + handleCapabilityResponse(responses); - // Handle the final OK, NO, BAD or BYE response - if (noauthdebug && isTracing()) - logger.fine("AUTHENTICATE NTLM command result: " + r); - handleLoginResult(r); - // If the response includes a CAPABILITY response code, process it - setCapabilities(r); - // if we get this far without an exception, we're authenticated - authenticated = true; + /* + * Dispatch untagged responses. + * NOTE: in our current upper level IMAP classes, we add the + * responseHandler to the Protocol object only *after* the + * connection has been authenticated. So, for now, the below + * code really ends up being just a no-op. + */ + notifyResponseHandlers(responses); + + // Handle the final OK, NO, BAD or BYE response + if (noauthdebug && isTracing()) + logger.fine("AUTHENTICATE NTLM command result: " + r); + handleLoginResult(r); + // If the response includes a CAPABILITY response code, process it + setCapabilities(r); + // if we get this far without an exception, we're authenticated + authenticated = true; + } finally { + lock.unlock(); + } } /** @@ -881,91 +898,96 @@ else if (r.isBYE()) // outta here * @see "RFC2595, section 6" * @since JavaMail 1.5.5 */ - public synchronized void authoauth2(String u, String p) + public void authoauth2(String u, String p) throws ProtocolException { - List v = new ArrayList<>(); - String tag = null; - Response r = null; - boolean done = false; - + lock.lock(); try { - - if (noauthdebug && isTracing()) { - logger.fine("AUTHENTICATE XOAUTH2 command trace suppressed"); - suspendTracing(); - } + List v = new ArrayList<>(); + String tag = null; + Response r = null; + boolean done = false; try { - Argument args = new Argument(); - args.writeAtom("XOAUTH2"); - if (hasCapability("SASL-IR")) { - String resp = "user=" + u + "\001auth=Bearer " + p + "\001\001"; - byte[] ba = Base64.getEncoder().encode( - resp.getBytes(StandardCharsets.UTF_8)); - String irs = ASCIIUtility.toString(ba, 0, ba.length); - args.writeAtom(irs); - } - tag = writeCommand("AUTHENTICATE", args); - } catch (Exception ex) { - // Convert this into a BYE response - r = Response.byeResponse(ex); - done = true; - } - OutputStream os = getOutputStream(); // stream to IMAP server + if (noauthdebug && isTracing()) { + logger.fine("AUTHENTICATE XOAUTH2 command trace suppressed"); + suspendTracing(); + } - while (!done) { // loop till we are done try { - r = readResponse(); - if (r.isContinuation()) { - // Server challenge .. - String resp = "user=" + u + "\001auth=Bearer " + - p + "\001\001"; - byte[] b = Base64.getEncoder().encode( + Argument args = new Argument(); + args.writeAtom("XOAUTH2"); + if (hasCapability("SASL-IR")) { + String resp = "user=" + u + "\001auth=Bearer " + p + "\001\001"; + byte[] ba = Base64.getEncoder().encode( resp.getBytes(StandardCharsets.UTF_8)); - os.write(b); // write out response - os.write(CRLF); // CRLF termination - os.flush(); // flush the stream - } else if (r.isTagged() && r.getTag().equals(tag)) - // Ah, our tagged response - done = true; - else if (r.isBYE()) // outta here - done = true; - // hmm .. unsolicited response here ?! - } catch (Exception ioex) { - // convert this into a BYE response - r = Response.byeResponse(ioex); + String irs = ASCIIUtility.toString(ba, 0, ba.length); + args.writeAtom(irs); + } + tag = writeCommand("AUTHENTICATE", args); + } catch (Exception ex) { + // Convert this into a BYE response + r = Response.byeResponse(ex); done = true; } - v.add(r); - } - } finally { - resumeTracing(); - } + OutputStream os = getOutputStream(); // stream to IMAP server + + while (!done) { // loop till we are done + try { + r = readResponse(); + if (r.isContinuation()) { + // Server challenge .. + String resp = "user=" + u + "\001auth=Bearer " + + p + "\001\001"; + byte[] b = Base64.getEncoder().encode( + resp.getBytes(StandardCharsets.UTF_8)); + os.write(b); // write out response + os.write(CRLF); // CRLF termination + os.flush(); // flush the stream + } else if (r.isTagged() && r.getTag().equals(tag)) + // Ah, our tagged response + done = true; + else if (r.isBYE()) // outta here + done = true; + // hmm .. unsolicited response here ?! + } catch (Exception ioex) { + // convert this into a BYE response + r = Response.byeResponse(ioex); + done = true; + } + v.add(r); + } - Response[] responses = v.toArray(new Response[0]); + } finally { + resumeTracing(); + } - // handle an illegal but not uncommon untagged CAPABILTY response - handleCapabilityResponse(responses); + Response[] responses = v.toArray(new Response[0]); - /* - * Dispatch untagged responses. - * NOTE: in our current upper level IMAP classes, we add the - * responseHandler to the Protocol object only *after* the - * connection has been authenticated. So, for now, the below - * code really ends up being just a no-op. - */ - notifyResponseHandlers(responses); + // handle an illegal but not uncommon untagged CAPABILTY response + handleCapabilityResponse(responses); - // Handle the final OK, NO, BAD or BYE response - if (noauthdebug && isTracing()) - logger.fine("AUTHENTICATE XOAUTH2 command result: " + r); - handleLoginResult(r); - // If the response includes a CAPABILITY response code, process it - setCapabilities(r); - // if we get this far without an exception, we're authenticated - authenticated = true; + /* + * Dispatch untagged responses. + * NOTE: in our current upper level IMAP classes, we add the + * responseHandler to the Protocol object only *after* the + * connection has been authenticated. So, for now, the below + * code really ends up being just a no-op. + */ + notifyResponseHandlers(responses); + + // Handle the final OK, NO, BAD or BYE response + if (noauthdebug && isTracing()) + logger.fine("AUTHENTICATE XOAUTH2 command result: " + r); + handleLoginResult(r); + // If the response includes a CAPABILITY response code, process it + setCapabilities(r); + // if we get this far without an exception, we're authenticated + authenticated = true; + } finally { + lock.unlock(); + } } /**