8062947: Fix exception message to correctly represent LDAP connection failure jdk8u272-b08

Wed, 09 Sep 2020 14:19:14 -0400

author
zgu
date
Wed, 09 Sep 2020 14:19:14 -0400
changeset 14206
ab2e99db6702
parent 14205
9deaed6c4a0f
child 14207
63dd5b7d50eb

8062947: Fix exception message to correctly represent LDAP connection failure
Reviewed-by: dfuchs, xyin, vtewari

src/share/classes/com/sun/jndi/ldap/Connection.java file | annotate | diff | comparison | revisions
src/share/classes/com/sun/jndi/ldap/LdapRequest.java file | annotate | diff | comparison | revisions
test/com/sun/jndi/ldap/NamingExceptionMessageTest.java file | annotate | diff | comparison | revisions
     1.1 --- a/src/share/classes/com/sun/jndi/ldap/Connection.java	Wed Sep 09 14:18:45 2020 -0400
     1.2 +++ b/src/share/classes/com/sun/jndi/ldap/Connection.java	Wed Sep 09 14:19:14 2020 -0400
     1.3 @@ -1,5 +1,5 @@
     1.4  /*
     1.5 - * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
     1.6 + * Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved.
     1.7   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     1.8   *
     1.9   * This code is free software; you can redistribute it and/or modify it
    1.10 @@ -454,22 +454,34 @@
    1.11      BerDecoder readReply(LdapRequest ldr) throws IOException, NamingException {
    1.12          BerDecoder rber;
    1.13  
    1.14 +        NamingException namingException = null;
    1.15          try {
    1.16              // if no timeout is set so we wait infinitely until
    1.17 -            // a response is received
    1.18 +            // a response is received OR until the connection is closed or cancelled
    1.19              // http://docs.oracle.com/javase/8/docs/technotes/guides/jndi/jndi-ldap.html#PROP
    1.20              rber = ldr.getReplyBer(readTimeout);
    1.21          } catch (InterruptedException ex) {
    1.22              throw new InterruptedNamingException(
    1.23                  "Interrupted during LDAP operation");
    1.24 +        } catch (CommunicationException ce) {
    1.25 +            // Re-throw
    1.26 +            throw ce;
    1.27 +        } catch (NamingException ne) {
    1.28 +            // Connection is timed out OR closed/cancelled
    1.29 +            namingException = ne;
    1.30 +            rber = null;
    1.31          }
    1.32  
    1.33          if (rber == null) {
    1.34              abandonRequest(ldr, null);
    1.35 -            throw new NamingException(
    1.36 -                    "LDAP response read timed out, timeout used:"
    1.37 -                            + readTimeout + "ms." );
    1.38 -
    1.39 +        }
    1.40 +        // namingException can be not null in the following cases:
    1.41 +        //  a) The response is timed-out
    1.42 +        //  b) LDAP request connection has been closed or cancelled
    1.43 +        // The exception message is initialized in LdapRequest::getReplyBer
    1.44 +        if (namingException != null) {
    1.45 +            // Re-throw NamingException after all cleanups are done
    1.46 +            throw namingException;
    1.47          }
    1.48          return rber;
    1.49      }
    1.50 @@ -888,15 +900,6 @@
    1.51                      inbuf = Arrays.copyOf(inbuf, offset + left.length);
    1.52                      System.arraycopy(left, 0, inbuf, offset, left.length);
    1.53                      offset += left.length;
    1.54 -/*
    1.55 -if (dump > 0) {
    1.56 -System.err.println("seqlen: " + seqlen);
    1.57 -System.err.println("bufsize: " + offset);
    1.58 -System.err.println("bytesleft: " + bytesleft);
    1.59 -System.err.println("bytesread: " + bytesread);
    1.60 -}
    1.61 -*/
    1.62 -
    1.63  
    1.64                      try {
    1.65                          retBer = new BerDecoder(inbuf, 0, offset);
    1.66 @@ -1004,36 +1007,4 @@
    1.67          }
    1.68          return buf;
    1.69      }
    1.70 -
    1.71 -    // This code must be uncommented to run the LdapAbandonTest.
    1.72 -    /*public void sendSearchReqs(String dn, int numReqs) {
    1.73 -        int i;
    1.74 -        String attrs[] = null;
    1.75 -        for(i = 1; i <= numReqs; i++) {
    1.76 -            BerEncoder ber = new BerEncoder(2048);
    1.77 -
    1.78 -            try {
    1.79 -            ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
    1.80 -                ber.encodeInt(i);
    1.81 -                ber.beginSeq(LdapClient.LDAP_REQ_SEARCH);
    1.82 -                    ber.encodeString(dn == null ? "" : dn);
    1.83 -                    ber.encodeInt(0, LdapClient.LBER_ENUMERATED);
    1.84 -                    ber.encodeInt(3, LdapClient.LBER_ENUMERATED);
    1.85 -                    ber.encodeInt(0);
    1.86 -                    ber.encodeInt(0);
    1.87 -                    ber.encodeBoolean(true);
    1.88 -                    LdapClient.encodeFilter(ber, "");
    1.89 -                    ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
    1.90 -                        ber.encodeStringArray(attrs);
    1.91 -                    ber.endSeq();
    1.92 -                ber.endSeq();
    1.93 -            ber.endSeq();
    1.94 -            writeRequest(ber, i);
    1.95 -            //System.err.println("wrote request " + i);
    1.96 -            } catch (Exception ex) {
    1.97 -            //System.err.println("ldap.search: Caught " + ex + " building req");
    1.98 -            }
    1.99 -
   1.100 -        }
   1.101 -    } */
   1.102  }
     2.1 --- a/src/share/classes/com/sun/jndi/ldap/LdapRequest.java	Wed Sep 09 14:18:45 2020 -0400
     2.2 +++ b/src/share/classes/com/sun/jndi/ldap/LdapRequest.java	Wed Sep 09 14:19:14 2020 -0400
     2.3 @@ -1,5 +1,5 @@
     2.4  /*
     2.5 - * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved.
     2.6 + * Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved.
     2.7   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     2.8   *
     2.9   * This code is free software; you can redistribute it and/or modify it
    2.10 @@ -29,11 +29,14 @@
    2.11  import java.util.concurrent.BlockingQueue;
    2.12  import java.util.concurrent.LinkedBlockingQueue;
    2.13  import javax.naming.CommunicationException;
    2.14 +import javax.naming.NamingException;
    2.15  import java.util.concurrent.TimeUnit;
    2.16  
    2.17  final class LdapRequest {
    2.18  
    2.19      private final static BerDecoder EOF = new BerDecoder(new byte[]{}, -1, 0);
    2.20 +    private final static String CLOSE_MSG = "LDAP connection has been closed";
    2.21 +    private final static String TIMEOUT_MSG_FMT = "LDAP response read timed out, timeout used: %d ms.";
    2.22  
    2.23      LdapRequest next;   // Set/read in synchronized Connection methods
    2.24      final int msgId;          // read-only
    2.25 @@ -95,14 +98,22 @@
    2.26          return pauseAfterReceipt;
    2.27      }
    2.28  
    2.29 -    BerDecoder getReplyBer(long millis) throws CommunicationException,
    2.30 +    /**
    2.31 +     * Read reply BER
    2.32 +     * @param millis timeout, infinite if the value is negative
    2.33 +     * @return BerDecoder if reply was read successfully
    2.34 +     * @throws CommunicationException request has been canceled and request does not need to be abandoned
    2.35 +     * @throws NamingException request has been closed or timed out. Request does need to be abandoned
    2.36 +     * @throws InterruptedException LDAP operation has been interrupted
    2.37 +     */
    2.38 +    BerDecoder getReplyBer(long millis) throws NamingException,
    2.39                                                 InterruptedException {
    2.40          if (cancelled) {
    2.41              throw new CommunicationException("Request: " + msgId +
    2.42                  " cancelled");
    2.43          }
    2.44          if (isClosed()) {
    2.45 -            return null;
    2.46 +            throw new NamingException(CLOSE_MSG);
    2.47          }
    2.48  
    2.49          BerDecoder result = millis > 0 ?
    2.50 @@ -113,7 +124,15 @@
    2.51                  " cancelled");
    2.52          }
    2.53  
    2.54 -        return result == EOF ? null : result;
    2.55 +        // poll from 'replies' blocking queue ended-up with timeout
    2.56 +        if (result == null) {
    2.57 +            throw new NamingException(String.format(TIMEOUT_MSG_FMT, millis));
    2.58 +        }
    2.59 +        // Unexpected EOF can be caused by connection closure or cancellation
    2.60 +        if (result == EOF) {
    2.61 +            throw new NamingException(CLOSE_MSG);
    2.62 +        }
    2.63 +        return result;
    2.64      }
    2.65  
    2.66      boolean hasSearchCompleted() {
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/test/com/sun/jndi/ldap/NamingExceptionMessageTest.java	Wed Sep 09 14:19:14 2020 -0400
     3.3 @@ -0,0 +1,165 @@
     3.4 +/*
     3.5 + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
     3.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     3.7 + *
     3.8 + * This code is free software; you can redistribute it and/or modify it
     3.9 + * under the terms of the GNU General Public License version 2 only, as
    3.10 + * published by the Free Software Foundation.
    3.11 + *
    3.12 + * This code is distributed in the hope that it will be useful, but WITHOUT
    3.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    3.14 + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    3.15 + * version 2 for more details (a copy is included in the LICENSE file that
    3.16 + * accompanied this code).
    3.17 + *
    3.18 + * You should have received a copy of the GNU General Public License version
    3.19 + * 2 along with this work; if not, write to the Free Software Foundation,
    3.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    3.21 + *
    3.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    3.23 + * or visit www.oracle.com if you need additional information or have any
    3.24 + * questions.
    3.25 + */
    3.26 +
    3.27 +/*
    3.28 + * @test
    3.29 + * @bug 8062947
    3.30 + * @summary Test that NamingException message text matches the failure reason
    3.31 + * @library lib/
    3.32 + * @library /lib/testlibrary
    3.33 + * @run testng NamingExceptionMessageTest
    3.34 + */
    3.35 +
    3.36 +import javax.naming.Context;
    3.37 +import javax.naming.NamingException;
    3.38 +import javax.naming.directory.InitialDirContext;
    3.39 +import java.io.IOException;
    3.40 +import java.io.OutputStream;
    3.41 +import java.net.InetAddress;
    3.42 +import java.net.InetSocketAddress;
    3.43 +import java.net.ServerSocket;
    3.44 +import java.net.Socket;
    3.45 +import java.util.Hashtable;
    3.46 +import java.util.concurrent.CountDownLatch;
    3.47 +import java.util.concurrent.TimeUnit;
    3.48 +
    3.49 +import org.testng.annotations.Test;
    3.50 +import org.testng.Assert;
    3.51 +import jdk.testlibrary.net.URIBuilder;
    3.52 +
    3.53 +public class NamingExceptionMessageTest {
    3.54 +
    3.55 +    @Test
    3.56 +    public void timeoutMessageTest() throws Exception {
    3.57 +        try (TestLdapServer ldapServer = TestLdapServer.newInstance(false)) {
    3.58 +            ldapServer.start();
    3.59 +            ldapServer.awaitStartup();
    3.60 +            Hashtable<Object, Object> env = ldapServer.getInitialLdapCtxEnvironment(TIMEOUT_VALUE);
    3.61 +            Exception namingException = Assert.expectThrows(NamingException.class, () -> new InitialDirContext(env));
    3.62 +            System.out.println("Got naming exception:" + namingException);
    3.63 +            Assert.assertEquals(namingException.getMessage(), EXPECTED_TIMEOUT_MESSAGE);
    3.64 +        }
    3.65 +    }
    3.66 +
    3.67 +    @Test
    3.68 +    public void connectionClosureMessageTest() throws Exception {
    3.69 +        try (TestLdapServer ldapServer = TestLdapServer.newInstance(true)) {
    3.70 +            ldapServer.start();
    3.71 +            ldapServer.awaitStartup();
    3.72 +            Hashtable<Object, Object> env = ldapServer.getInitialLdapCtxEnvironment(0);
    3.73 +            Exception namingException = Assert.expectThrows(NamingException.class, () -> new InitialDirContext(env));
    3.74 +            System.out.println("Got naming exception:" + namingException);
    3.75 +            Assert.assertEquals(namingException.getMessage(), EXPECTED_CLOSURE_MESSAGE);
    3.76 +        }
    3.77 +    }
    3.78 +
    3.79 +    // Test LDAP server
    3.80 +    private static class TestLdapServer extends BaseLdapServer {
    3.81 +
    3.82 +        private final boolean closeConnections;
    3.83 +        private final CountDownLatch startupLatch = new CountDownLatch(1);
    3.84 +
    3.85 +        public Hashtable<Object, Object> getInitialLdapCtxEnvironment(int readTimeoutValue) {
    3.86 +            // Create environment for initial LDAP context
    3.87 +            Hashtable<Object, Object> env = new Hashtable<>();
    3.88 +
    3.89 +            // Activate LDAPv3
    3.90 +            env.put("java.naming.ldap.version", "3");
    3.91 +
    3.92 +            // De-activate the ManageDsaIT control
    3.93 +            env.put(Context.REFERRAL, "follow");
    3.94 +            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
    3.95 +            env.put(Context.PROVIDER_URL, getUrlString());
    3.96 +            env.put(Context.SECURITY_AUTHENTICATION, "simple");
    3.97 +            env.put(Context.SECURITY_PRINCIPAL, "name");
    3.98 +            env.put(Context.SECURITY_CREDENTIALS, "pwd");
    3.99 +
   3.100 +            if (readTimeoutValue > 0) {
   3.101 +                env.put("com.sun.jndi.ldap.read.timeout", String.valueOf(readTimeoutValue));
   3.102 +                env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(readTimeoutValue));
   3.103 +            }
   3.104 +
   3.105 +            return env;
   3.106 +        }
   3.107 +
   3.108 +        private String getUrlString() {
   3.109 +            String url = URIBuilder.newBuilder()
   3.110 +                    .scheme("ldap")
   3.111 +                    .loopback()
   3.112 +                    .port(getPort())
   3.113 +                    .buildUnchecked()
   3.114 +                    .toString();
   3.115 +            return url;
   3.116 +        }
   3.117 +
   3.118 +        public static TestLdapServer newInstance(boolean closeConnections) throws IOException {
   3.119 +            ServerSocket srvSock = new ServerSocket();
   3.120 +            srvSock.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
   3.121 +            return new TestLdapServer(srvSock, closeConnections);
   3.122 +        }
   3.123 +
   3.124 +        void awaitStartup() throws InterruptedException {
   3.125 +            startupLatch.await();
   3.126 +        }
   3.127 +
   3.128 +        private TestLdapServer(ServerSocket serverSocket, boolean closeConnections) {
   3.129 +            super(serverSocket);
   3.130 +            this.closeConnections = closeConnections;
   3.131 +
   3.132 +        }
   3.133 +
   3.134 +        @Override
   3.135 +        protected void beforeAcceptingConnections() {
   3.136 +            startupLatch.countDown();
   3.137 +        }
   3.138 +
   3.139 +        @Override
   3.140 +        protected void handleRequest(Socket socket,
   3.141 +                                     LdapMessage msg,
   3.142 +                                     OutputStream out)
   3.143 +                throws IOException {
   3.144 +            switch (msg.getOperation()) {
   3.145 +                case BIND_REQUEST:
   3.146 +                    if (closeConnections) {
   3.147 +                        closeSilently(socket);
   3.148 +                    } else {
   3.149 +                        try {
   3.150 +                            TimeUnit.DAYS.sleep(Integer.MAX_VALUE);
   3.151 +                        } catch (InterruptedException e) {
   3.152 +                            Thread.currentThread().interrupt();
   3.153 +                        }
   3.154 +                    }
   3.155 +                default:
   3.156 +                    break;
   3.157 +            }
   3.158 +        }
   3.159 +    }
   3.160 +
   3.161 +    // Expected message for case when connection is closed on server side
   3.162 +    private static final String EXPECTED_CLOSURE_MESSAGE = "LDAP connection has been closed";
   3.163 +    // read and connect timeouts value
   3.164 +    private static final int TIMEOUT_VALUE = 129;
   3.165 +    // Expected message text when connection is timed-out
   3.166 +    private static final String EXPECTED_TIMEOUT_MESSAGE = String.format(
   3.167 +            "LDAP response read timed out, timeout used: %d ms.", TIMEOUT_VALUE);
   3.168 +}

mercurial