All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.ldap.sdk.FewestConnectionsServerSet Maven / Gradle / Ivy

Go to download

The UnboundID LDAP SDK for Java is a fast, comprehensive, and easy-to-use Java API for communicating with LDAP directory servers and performing related tasks like reading and writing LDIF, encoding and decoding data using base64 and ASN.1 BER, and performing secure communication. This package contains the Standard Edition of the LDAP SDK, which is a complete, general-purpose library for communicating with LDAPv3 directory servers.

There is a newer version: 7.0.1
Show newest version
/*
 * Copyright 2013-2023 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2013-2023 Ping Identity Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * Copyright (C) 2013-2023 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */
package com.unboundid.ldap.sdk;



import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicLong;
import javax.net.SocketFactory;

import com.unboundid.util.Debug;
import com.unboundid.util.NotMutable;
import com.unboundid.util.NotNull;
import com.unboundid.util.Nullable;
import com.unboundid.util.ObjectPair;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.Validator;



/**
 * This class provides a server set implementation that will establish a
 * connection to the server with the fewest established connections previously
 * created by the same server set instance.  If there are multiple servers that
 * share the fewest number of established connections, the first one in the list
 * will be chosen.  If a server is unavailable when an attempt is made to
 * establish a connection to it, then the connection will be established to the
 * available server with the next fewest number of established connections.
 * 

* This server set implementation has the ability to maintain a temporary * blacklist of servers that have been recently found to be unavailable or * unsuitable for use. If an attempt to establish or authenticate a * connection fails, if post-connect processing fails for that connection, or if * health checking indicates that the connection is not suitable, then that * server may be placed on the blacklist so that it will only be tried as a last * resort after all non-blacklisted servers have been attempted. The blacklist * will be checked at regular intervals to determine whether a server should be * re-instated to availability. *

* Note that this server set implementation is primarily intended for use with * connection pools, but is also suitable for cases in which standalone * connections are created as long as there will not be any attempt to close the * connections when they are re-established. It is not suitable for use in * connections that may be re-established one or more times after being closed. *

*

Example

* The following example demonstrates the process for creating a fewest * connections server set that may be used to establish connections to either of * two servers. *
 * // Create arrays with the addresses and ports of the directory server
 * // instances.
 * String[] addresses =
 * {
 *   server1Address,
 *   server2Address
 * };
 * int[] ports =
 * {
 *   server1Port,
 *   server2Port
 * };
 *
 * // Create the server set using the address and port arrays.
 * FewestConnectionsServerSet fewestConnectionsSet =
 *      new FewestConnectionsServerSet(addresses, ports);
 *
 * // Verify that we can establish a single connection using the server set.
 * LDAPConnection connection = fewestConnectionsSet.getConnection();
 * RootDSE rootDSEFromConnection = connection.getRootDSE();
 * connection.close();
 *
 * // Verify that we can establish a connection pool using the server set.
 * SimpleBindRequest bindRequest =
 *      new SimpleBindRequest("uid=pool.user,dc=example,dc=com", "password");
 * LDAPConnectionPool pool =
 *      new LDAPConnectionPool(fewestConnectionsSet, bindRequest, 10);
 * RootDSE rootDSEFromPool = pool.getRootDSE();
 * pool.close();
 * 
*/ @NotMutable() @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) public final class FewestConnectionsServerSet extends ServerSet { /** * The name of a system property that can be used to override the default * blacklist check interval, in milliseconds. */ @NotNull static final String PROPERTY_DEFAULT_BLACKLIST_CHECK_INTERVAL_MILLIS = FewestConnectionsServerSet.class.getName() + ".defaultBlacklistCheckIntervalMillis"; // The bind request to use to authenticate connections created by this // server set. @Nullable private final BindRequest bindRequest; // The set of connection options to use for new connections. @NotNull private final LDAPConnectionOptions connectionOptions; // A map with the number of connections currently established for each server. @NotNull private final Map,AtomicLong> connectionCountsByServer; // The post-connect processor to invoke against connections created by this // server set. @Nullable private final PostConnectProcessor postConnectProcessor; // The blacklist manager for this server set. @Nullable private final ServerSetBlacklistManager blacklistManager; // The socket factory to use to establish connections. @NotNull private final SocketFactory socketFactory; /** * Creates a new fewest connections server set with the specified set of * directory server addresses and port numbers. It will use the default * socket factory provided by the JVM to create the underlying sockets. * * @param addresses The addresses of the directory servers to which the * connections should be established. It must not be * {@code null} or empty. * @param ports The ports of the directory servers to which the * connections should be established. It must not be * {@code null}, and it must have the same number of * elements as the {@code addresses} array. The order of * elements in the {@code addresses} array must correspond * to the order of elements in the {@code ports} array. */ public FewestConnectionsServerSet(@NotNull final String[] addresses, @NotNull final int[] ports) { this(addresses, ports, null, null); } /** * Creates a new fewest connections server set with the specified set of * directory server addresses and port numbers. It will use the default * socket factory provided by the JVM to create the underlying sockets. * * @param addresses The addresses of the directory servers to which * the connections should be established. It must * not be {@code null} or empty. * @param ports The ports of the directory servers to which the * connections should be established. It must not * be {@code null}, and it must have the same * number of elements as the {@code addresses} * array. The order of elements in the * {@code addresses} array must correspond to the * order of elements in the {@code ports} array. * @param connectionOptions The set of connection options to use for the * underlying connections. */ public FewestConnectionsServerSet(@NotNull final String[] addresses, @NotNull final int[] ports, @Nullable final LDAPConnectionOptions connectionOptions) { this(addresses, ports, null, connectionOptions); } /** * Creates a new fewest connections server set with the specified set of * directory server addresses and port numbers. It will use the provided * socket factory to create the underlying sockets. * * @param addresses The addresses of the directory servers to which the * connections should be established. It must not be * {@code null} or empty. * @param ports The ports of the directory servers to which the * connections should be established. It must not be * {@code null}, and it must have the same number of * elements as the {@code addresses} array. The order * of elements in the {@code addresses} array must * correspond to the order of elements in the * {@code ports} array. * @param socketFactory The socket factory to use to create the underlying * connections. */ public FewestConnectionsServerSet(@NotNull final String[] addresses, @NotNull final int[] ports, @Nullable final SocketFactory socketFactory) { this(addresses, ports, socketFactory, null); } /** * Creates a new fewest connections server set with the specified set of * directory server addresses and port numbers. It will use the provided * socket factory to create the underlying sockets. * * @param addresses The addresses of the directory servers to which * the connections should be established. It must * not be {@code null} or empty. * @param ports The ports of the directory servers to which the * connections should be established. It must not * be {@code null}, and it must have the same * number of elements as the {@code addresses} * array. The order of elements in the * {@code addresses} array must correspond to the * order of elements in the {@code ports} array. * @param socketFactory The socket factory to use to create the * underlying connections. * @param connectionOptions The set of connection options to use for the * underlying connections. */ public FewestConnectionsServerSet(@NotNull final String[] addresses, @NotNull final int[] ports, @Nullable final SocketFactory socketFactory, @Nullable final LDAPConnectionOptions connectionOptions) { this(addresses, ports, socketFactory, connectionOptions, null, null); } /** * Creates a new fewest connections server set with the specified set of * directory server addresses and port numbers. It will use the provided * socket factory to create the underlying sockets. * * @param addresses The addresses of the directory servers to * which the connections should be established. * It must not be {@code null} or empty. * @param ports The ports of the directory servers to which * the connections should be established. It * must not be {@code null}, and it must have * the same number of elements as the * {@code addresses} array. The order of * elements in the {@code addresses} array must * correspond to the order of elements in the * {@code ports} array. * @param socketFactory The socket factory to use to create the * underlying connections. * @param connectionOptions The set of connection options to use for the * underlying connections. * @param bindRequest The bind request that should be used to * authenticate newly established connections. * It may be {@code null} if this server set * should not perform any authentication. * @param postConnectProcessor The post-connect processor that should be * invoked on newly established connections. It * may be {@code null} if this server set should * not perform any post-connect processing. */ public FewestConnectionsServerSet(@NotNull final String[] addresses, @NotNull final int[] ports, @Nullable final SocketFactory socketFactory, @Nullable final LDAPConnectionOptions connectionOptions, @Nullable final BindRequest bindRequest, @Nullable final PostConnectProcessor postConnectProcessor) { this(addresses, ports, socketFactory, connectionOptions, bindRequest, postConnectProcessor, getDefaultBlacklistCheckIntervalMillis()); } /** * Creates a new fewest connections server set with the specified set of * directory server addresses and port numbers. It will use the provided * socket factory to create the underlying sockets. * * @param addresses The addresses of the directory * servers to which the connections * should be established. It must not * be {@code null} or empty. * @param ports The ports of the directory servers to * which the connections should be * established. It must not be * {@code null}, and it must have the * same number of elements as the * {@code addresses} array. The order * of elements in the {@code addresses} * array must correspond to the order of * elements in the {@code ports} array. * @param socketFactory The socket factory to use to create * the underlying connections. * @param connectionOptions The set of connection options to use * for the underlying connections. * @param bindRequest The bind request that should be used * to authenticate newly established * connections. It may be {@code null} * if this server set should not perform * any authentication. * @param postConnectProcessor The post-connect processor that * should be invoked on newly * established connections. It may be * {@code null} if this server set * should not perform any post-connect * processing. * @param blacklistCheckIntervalMillis The length of time in milliseconds * between checks of servers on the * blacklist to determine whether they * are once again suitable for use. A * value that is less than or equal to * zero indicates that no blacklist * should be maintained. */ public FewestConnectionsServerSet(@NotNull final String[] addresses, @NotNull final int[] ports, @Nullable final SocketFactory socketFactory, @Nullable final LDAPConnectionOptions connectionOptions, @Nullable final BindRequest bindRequest, @Nullable final PostConnectProcessor postConnectProcessor, final long blacklistCheckIntervalMillis) { Validator.ensureNotNull(addresses, ports); Validator.ensureTrue(addresses.length > 0, "FewestConnectionsServerSet.addresses must not be empty."); Validator.ensureTrue(addresses.length == ports.length, "FewestConnectionsServerSet addresses and ports arrays must be " + "the same size."); final LinkedHashMap,AtomicLong> m = new LinkedHashMap<>(StaticUtils.computeMapCapacity(ports.length)); for (int i=0; i < addresses.length; i++) { m.put(new ObjectPair<>(addresses[i], ports[i]), new AtomicLong(0L)); } connectionCountsByServer = Collections.unmodifiableMap(m); this.bindRequest = bindRequest; this.postConnectProcessor = postConnectProcessor; if (socketFactory == null) { this.socketFactory = SocketFactory.getDefault(); } else { this.socketFactory = socketFactory; } if (connectionOptions == null) { this.connectionOptions = new LDAPConnectionOptions(); } else { this.connectionOptions = connectionOptions; } if (blacklistCheckIntervalMillis > 0L) { blacklistManager = new ServerSetBlacklistManager(this, socketFactory, connectionOptions, bindRequest, postConnectProcessor, blacklistCheckIntervalMillis); } else { blacklistManager = null; } } /** * Retrieves the default blacklist check interval (in milliseconds that should * be used if it is not specified. * * @return The default blacklist check interval (in milliseconds that should * be used if it is not specified. */ private static long getDefaultBlacklistCheckIntervalMillis() { final String propertyValue = StaticUtils.getSystemProperty( PROPERTY_DEFAULT_BLACKLIST_CHECK_INTERVAL_MILLIS); if (propertyValue != null) { try { return Long.parseLong(propertyValue); } catch (final Exception e) { Debug.debugException(e); } } return 30_000L; } /** * Retrieves the addresses of the directory servers to which the connections * should be established. * * @return The addresses of the directory servers to which the connections * should be established. */ @NotNull() public String[] getAddresses() { int i = 0; final String[] addresses = new String[connectionCountsByServer.size()]; for (final ObjectPair hostPort : connectionCountsByServer.keySet()) { addresses[i++] = hostPort.getFirst(); } return addresses; } /** * Retrieves the ports of the directory servers to which the connections * should be established. * * @return The ports of the directory servers to which the connections should * be established. */ @NotNull() public int[] getPorts() { int i = 0; final int[] ports = new int[connectionCountsByServer.size()]; for (final ObjectPair hostPort : connectionCountsByServer.keySet()) { ports[i++] = hostPort.getSecond(); } return ports; } /** * Retrieves the socket factory that will be used to establish connections. * * @return The socket factory that will be used to establish connections. */ @NotNull() public SocketFactory getSocketFactory() { return socketFactory; } /** * Retrieves the set of connection options that will be used for underlying * connections. * * @return The set of connection options that will be used for underlying * connections. */ @NotNull() public LDAPConnectionOptions getConnectionOptions() { return connectionOptions; } /** * {@inheritDoc} */ @Override() public boolean includesAuthentication() { return (bindRequest != null); } /** * {@inheritDoc} */ @Override() public boolean includesPostConnectProcessing() { return (postConnectProcessor != null); } /** * {@inheritDoc} */ @Override() @NotNull() public LDAPConnection getConnection() throws LDAPException { return getConnection(null); } /** * {@inheritDoc} */ @Override() @NotNull() public LDAPConnection getConnection( @Nullable final LDAPConnectionPoolHealthCheck healthCheck) throws LDAPException { // Organize the servers int lists by increasing numbers of connections. final TreeMap>> serversByCount = new TreeMap<>(); for (final Map.Entry,AtomicLong> e : connectionCountsByServer.entrySet()) { final ObjectPair hostPort = e.getKey(); final long count = e.getValue().get(); List> l = serversByCount.get(count); if (l == null) { l = new ArrayList<>(connectionCountsByServer.size()); serversByCount.put(count, l); } l.add(hostPort); } // Try the servers in order of fewest connections to most. If there are // multiple servers with the same number of connections, then randomize the // order of servers in that list to better spread the load across all of // the servers. LDAPException lastException = null; List> blacklistedServers = null; for (final List> l : serversByCount.values()) { if (l.size() > 1) { Collections.shuffle(l); } for (final ObjectPair hostPort : l) { if ((blacklistManager != null) && blacklistManager.isBlacklisted(hostPort)) { if (blacklistedServers == null) { blacklistedServers = new ArrayList<>(connectionCountsByServer.size()); } blacklistedServers.add(hostPort); continue; } try { final LDAPConnection conn = new LDAPConnection(socketFactory, connectionOptions, hostPort.getFirst(), hostPort.getSecond()); doBindPostConnectAndHealthCheckProcessing(conn, bindRequest, postConnectProcessor, healthCheck); connectionCountsByServer.get(hostPort).incrementAndGet(); associateConnectionWithThisServerSet(conn); return conn; } catch (final LDAPException le) { Debug.debugException(le); lastException = le; if (blacklistManager != null) { blacklistManager.addToBlacklist(hostPort, healthCheck); } } } } // If we've gotten here, then we couldn't get a connection from a // non-blacklisted server. If there were any blacklisted servers, then try // them as a last resort. if (blacklistedServers != null) { for (final ObjectPair hostPort : blacklistedServers) { try { final LDAPConnection c = new LDAPConnection(socketFactory, connectionOptions, hostPort.getFirst(), hostPort.getSecond()); doBindPostConnectAndHealthCheckProcessing(c, bindRequest, postConnectProcessor, healthCheck); associateConnectionWithThisServerSet(c); blacklistManager.removeFromBlacklist(hostPort); return c; } catch (final LDAPException e) { Debug.debugException(e); lastException = e; } } } // If we've gotten here, then we've tried all servers without any success, // so throw the last exception that was encountered. throw lastException; } /** * {@inheritDoc} */ @Override() protected void handleConnectionClosed( @NotNull final LDAPConnection connection, @NotNull final String host, final int port, @NotNull final DisconnectType disconnectType, @Nullable final String message, @Nullable final Throwable cause) { final ObjectPair hostPort = new ObjectPair<>(host, port); final AtomicLong counter = connectionCountsByServer.get(hostPort); if (counter != null) { final long remainingCount = counter.decrementAndGet(); if (remainingCount < 0L) { // This shouldn't happen. If it does, reset it back to zero. counter.compareAndSet(remainingCount, 0L); } } } /** * Retrieves the blacklist manager for this server set. * * @return The blacklist manager for this server set, or {@code null} if no * blacklist will be maintained. */ @Nullable() public ServerSetBlacklistManager getBlacklistManager() { return blacklistManager; } /** * {@inheritDoc} */ @Override() public void shutDown() { if (blacklistManager != null) { blacklistManager.shutDown(); } } /** * {@inheritDoc} */ @Override() public void toString(@NotNull final StringBuilder buffer) { buffer.append("FewestConnectionsServerSet(servers={"); final Iterator,AtomicLong>> cbsIterator = connectionCountsByServer.entrySet().iterator(); while (cbsIterator.hasNext()) { final Map.Entry,AtomicLong> e = cbsIterator.next(); final ObjectPair hostPort = e.getKey(); final long count = e.getValue().get(); buffer.append('\''); buffer.append(hostPort.getFirst()); buffer.append(':'); buffer.append(hostPort.getSecond()); buffer.append("':"); buffer.append(count); if (cbsIterator.hasNext()) { buffer.append(", "); } } buffer.append("}, includesAuthentication="); buffer.append(bindRequest != null); buffer.append(", includesPostConnectProcessing="); buffer.append(postConnectProcessor != null); buffer.append(')'); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy