/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.ldap.client.api;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.naming.InvalidNameException;
import javax.net.ssl.SSLContext;
import org.apache.directory.ldap.client.api.exception.InvalidConnectionException;
import org.apache.directory.ldap.client.api.exception.LdapException;
import org.apache.directory.ldap.client.api.future.AddFuture;
import org.apache.directory.ldap.client.api.future.BindFuture;
import org.apache.directory.ldap.client.api.future.CompareFuture;
import org.apache.directory.ldap.client.api.future.DeleteFuture;
import org.apache.directory.ldap.client.api.future.ExtendedFuture;
import org.apache.directory.ldap.client.api.future.ModifyDnFuture;
import org.apache.directory.ldap.client.api.future.ModifyFuture;
import org.apache.directory.ldap.client.api.future.ResponseFuture;
import org.apache.directory.ldap.client.api.future.SearchFuture;
import org.apache.directory.ldap.client.api.listener.DeleteListener;
import org.apache.directory.ldap.client.api.message.AbandonRequest;
import org.apache.directory.ldap.client.api.message.AbstractMessage;
import org.apache.directory.ldap.client.api.message.AddRequest;
import org.apache.directory.ldap.client.api.message.AddResponse;
import org.apache.directory.ldap.client.api.message.BindRequest;
import org.apache.directory.ldap.client.api.message.BindResponse;
import org.apache.directory.ldap.client.api.message.CompareRequest;
import org.apache.directory.ldap.client.api.message.CompareResponse;
import org.apache.directory.ldap.client.api.message.DeleteRequest;
import org.apache.directory.ldap.client.api.message.DeleteResponse;
import org.apache.directory.ldap.client.api.message.ExtendedIntermediateResponse;
import org.apache.directory.ldap.client.api.message.ExtendedRequest;
import org.apache.directory.ldap.client.api.message.ExtendedResponse;
import org.apache.directory.ldap.client.api.message.IntermediateResponse;
import org.apache.directory.ldap.client.api.message.LdapResult;
import org.apache.directory.ldap.client.api.message.ModifyDnRequest;
import org.apache.directory.ldap.client.api.message.ModifyDnResponse;
import org.apache.directory.ldap.client.api.message.ModifyRequest;
import org.apache.directory.ldap.client.api.message.ModifyResponse;
import org.apache.directory.ldap.client.api.message.Referral;
import org.apache.directory.ldap.client.api.message.Response;
import org.apache.directory.ldap.client.api.message.SearchIntermediateResponse;
import org.apache.directory.ldap.client.api.message.SearchRequest;
import org.apache.directory.ldap.client.api.message.SearchResponse;
import org.apache.directory.ldap.client.api.message.SearchResultDone;
import org.apache.directory.ldap.client.api.message.SearchResultEntry;
import org.apache.directory.ldap.client.api.message.SearchResultReference;
import org.apache.directory.ldap.client.api.protocol.LdapProtocolCodecFactory;
import org.apache.directory.shared.asn1.ber.IAsn1Container;
import org.apache.directory.shared.asn1.codec.DecoderException;
import org.apache.directory.shared.asn1.primitives.OID;
import org.apache.directory.shared.ldap.codec.LdapMessageCodec;
import org.apache.directory.shared.ldap.codec.LdapMessageContainer;
import org.apache.directory.shared.ldap.codec.LdapResultCodec;
import org.apache.directory.shared.ldap.codec.LdapTransformer;
import org.apache.directory.shared.ldap.codec.abandon.AbandonRequestCodec;
import org.apache.directory.shared.ldap.codec.add.AddRequestCodec;
import org.apache.directory.shared.ldap.codec.add.AddResponseCodec;
import org.apache.directory.shared.ldap.codec.bind.BindRequestCodec;
import org.apache.directory.shared.ldap.codec.bind.BindResponseCodec;
import org.apache.directory.shared.ldap.codec.bind.LdapAuthentication;
import org.apache.directory.shared.ldap.codec.bind.SaslCredentials;
import org.apache.directory.shared.ldap.codec.bind.SimpleAuthentication;
import org.apache.directory.shared.ldap.codec.compare.CompareRequestCodec;
import org.apache.directory.shared.ldap.codec.compare.CompareResponseCodec;
import org.apache.directory.shared.ldap.codec.controls.ControlImpl;
import org.apache.directory.shared.ldap.codec.del.DelRequestCodec;
import org.apache.directory.shared.ldap.codec.del.DelResponseCodec;
import org.apache.directory.shared.ldap.codec.extended.ExtendedRequestCodec;
import org.apache.directory.shared.ldap.codec.extended.ExtendedResponseCodec;
import org.apache.directory.shared.ldap.codec.intermediate.IntermediateResponseCodec;
import org.apache.directory.shared.ldap.codec.modify.ModifyRequestCodec;
import org.apache.directory.shared.ldap.codec.modify.ModifyResponseCodec;
import org.apache.directory.shared.ldap.codec.modifyDn.ModifyDNRequestCodec;
import org.apache.directory.shared.ldap.codec.modifyDn.ModifyDNResponseCodec;
import org.apache.directory.shared.ldap.codec.search.Filter;
import org.apache.directory.shared.ldap.codec.search.SearchRequestCodec;
import org.apache.directory.shared.ldap.codec.search.SearchResultDoneCodec;
import org.apache.directory.shared.ldap.codec.search.SearchResultEntryCodec;
import org.apache.directory.shared.ldap.codec.search.SearchResultReferenceCodec;
import org.apache.directory.shared.ldap.codec.unbind.UnBindRequestCodec;
import org.apache.directory.shared.ldap.constants.SchemaConstants;
import org.apache.directory.shared.ldap.cursor.Cursor;
import org.apache.directory.shared.ldap.entry.Entry;
import org.apache.directory.shared.ldap.entry.EntryAttribute;
import org.apache.directory.shared.ldap.entry.ModificationOperation;
import org.apache.directory.shared.ldap.entry.Value;
import org.apache.directory.shared.ldap.filter.ExprNode;
import org.apache.directory.shared.ldap.filter.FilterParser;
import org.apache.directory.shared.ldap.filter.SearchScope;
import org.apache.directory.shared.ldap.message.AliasDerefMode;
import org.apache.directory.shared.ldap.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.message.control.Control;
import org.apache.directory.shared.ldap.name.DN;
import org.apache.directory.shared.ldap.name.RDN;
import org.apache.directory.shared.ldap.schema.SchemaManager;
import org.apache.directory.shared.ldap.schema.loader.ldif.JarLdifSchemaLoader;
import org.apache.directory.shared.ldap.schema.manager.impl.DefaultSchemaManager;
import org.apache.directory.shared.ldap.util.LdapURL;
import org.apache.directory.shared.ldap.util.StringTools;
import org.apache.mina.core.filterchain.IoFilter;
import org.apache.mina.core.future.CloseFuture;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.ssl.SslFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is the base for every operations sent or received to and
* from a LDAP server.
*
* A connection instance is necessary to send requests to the server. The connection
* is valid until either the client closes it, the server closes it or the
* client does an unbind.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$, $Date$
*/
public class LdapConnection extends IoHandlerAdapter
{
/** logger for reporting errors that might not be handled properly upstream */
private static final Logger LOG = LoggerFactory.getLogger( LdapConnection.class );
private static final String LDAP_RESPONSE = "LdapReponse";
/** The timeout used for response we are waiting for */
private long timeOut = LdapConnectionConfig.DEFAULT_TIMEOUT;
/** configuration object for the connection */
private LdapConnectionConfig config = new LdapConnectionConfig();
/** The connector open with the remote server */
private IoConnector connector;
/** A flag set to true when we used a local connector */
private boolean localConnector;
/** The Ldap codec */
private IoFilter ldapProtocolFilter = new ProtocolCodecFilter( new LdapProtocolCodecFactory() );
/**
* The created session, created when we open a connection with
* the Ldap server.
*/
private IoSession ldapSession;
/** A Message ID which is incremented for each operation */
private AtomicInteger messageId;
/** a map to hold the ResponseFutures for all operations */
private Map<Integer, ResponseFuture<? extends Response>> futureMap = new ConcurrentHashMap<Integer, ResponseFuture<? extends Response>>();
/** list of controls supported by the server */
private List<String> supportedControls;
/** The ROOT DSE entry */
private Entry rootDSE;
/** A flag indicating that the BindRequest has been issued and successfully authenticated the user */
private AtomicBoolean authenticated = new AtomicBoolean( false );
/** A flag indicating that the connection is connected or not */
private AtomicBoolean connected = new AtomicBoolean( false );
/** the schema manager */
private SchemaManager schemaManager;
// ~~~~~~~~~~~~~~~~~ common error messages ~~~~~~~~~~~~~~~~~~~~~~~~~~
private static final String OPERATION_CANCELLED = "Operation would have been cancelled";
static final String TIME_OUT_ERROR = "TimeOut occured";
static final String NO_RESPONSE_ERROR = "The response queue has been emptied, no response was found.";
private static final String COMPARE_FAILED = "Failed to perform compare operation";
//--------------------------- Helper methods ---------------------------//
/**
* Check if we are connected
*
* @return <code>true</code> if we are connected.
*/
public boolean isConnected()
{
return ( ldapSession != null ) && connected.get();
}
/**
* Check if we are authenticated
*
* @return <code>true</code> if we are connected.
*/
public boolean isAuthenticated()
{
return isConnected() && authenticated.get();
}
/**
* Check that a session is valid, ie we can send requests to the
* server
*
* @throws Exception If the session is not valid
*/
private void checkSession() throws InvalidConnectionException
{
if ( ldapSession == null )
{
throw new InvalidConnectionException( "Cannot connect on the server, the connection is null" );
}
if ( !connected.get() )
{
throw new InvalidConnectionException( "Cannot connect on the server, the connection is invalid" );
}
}
private void addToFutureMap( int messageId, ResponseFuture<? extends Response> future )
{
LOG.debug( "Adding <" + messageId + ", " + future.getClass().getName() + ">" );
futureMap.put( messageId, future );
}
private ResponseFuture<? extends Response> getFromFutureMap( int messageId )
{
ResponseFuture<? extends Response> future = futureMap.remove( messageId );
if ( future != null )
{
LOG.debug( "Removing <" + messageId + ", " + future.getClass().getName() + ">" );
}
return future;
}
private ResponseFuture<? extends Response> peekFromFutureMap( int messageId )
{
ResponseFuture<? extends Response> future = futureMap.get( messageId );
// future can be null if there was a abandon operation on that messageId
if( future != null )
{
LOG.debug( "Getting <" + messageId + ", " + future.getClass().getName() + ">" );
}
return future;
}
/**
* Return the response stored into the current session.
*
* @return The last request response
*/
public LdapMessageCodec getResponse()
{
return ( LdapMessageCodec ) ldapSession.getAttribute( LDAP_RESPONSE );
}
/**
* Inject the client Controls into the message
*/
private void setControls( Map<String, Control> controls, LdapMessageCodec message )
{
// Add the controls
if ( controls != null )
{
for ( Control control : controls.values() )
{
ControlImpl ctrl = new ControlImpl( control.getOid() );
ctrl.setValue( control.getValue() );
message.addControl( ctrl );
}
}
}
/**
* Get the smallest timeout from the client timeout and the connection
* timeout.
*/
private long getTimeout( long clientTimeOut )
{
if ( clientTimeOut <= 0 )
{
return ( timeOut <= 0 ) ? Long.MAX_VALUE : timeOut;
}
else if ( timeOut <= 0 )
{
return clientTimeOut;
}
else
{
return timeOut < clientTimeOut ? timeOut : clientTimeOut;
}
}
/**
* Convert a BindResponseCodec to a BindResponse message
*/
private BindResponse convert( BindResponseCodec bindResponseCodec )
{
BindResponse bindResponse = new BindResponse();
bindResponse.setMessageId( bindResponseCodec.getMessageId() );
bindResponse.setServerSaslCreds( bindResponseCodec.getServerSaslCreds() );
bindResponse.setLdapResult( convert( bindResponseCodec.getLdapResult() ) );
return bindResponse;
}
/**
* Convert a IntermediateResponseCodec to a IntermediateResponse message based on the ResponseFuture's type
*/
private void setIResponse( IntermediateResponseCodec intermediateResponseCodec, ResponseFuture responseFuture ) throws Exception
{
IntermediateResponse intermediateResponse = null;
if( responseFuture instanceof SearchFuture )
{
intermediateResponse = new SearchIntermediateResponse();
}
else if( responseFuture instanceof ExtendedFuture )
{
intermediateResponse = new ExtendedIntermediateResponse();
}
else
{
// currently we only support IR for search and extended operations
throw new UnsupportedOperationException( "Unknown ResponseFuture type " + responseFuture.getClass().getName() );
}
intermediateResponse.setResponseName( intermediateResponseCodec.getResponseName() );
intermediateResponse.setResponseValue( intermediateResponseCodec.getResponseValue() );
responseFuture.set( intermediateResponse );
}
/**
* Convert a LdapResultCodec to a LdapResult message
*/
private LdapResult convert( LdapResultCodec ldapResultCodec )
{
LdapResult ldapResult = new LdapResult();
ldapResult.setErrorMessage( ldapResultCodec.getErrorMessage() );
ldapResult.setMatchedDn( ldapResultCodec.getMatchedDN() );
// Loop on the referrals
Referral referral = new Referral();
if ( ldapResultCodec.getReferrals() != null )
{
for ( LdapURL url : ldapResultCodec.getReferrals() )
{
referral.addLdapUrls( url );
}
}
ldapResult.setReferral( referral );
ldapResult.setResultCode( ldapResultCodec.getResultCode() );
return ldapResult;
}
/**
* Convert a SearchResultEntryCodec to a SearchResultEntry message
*/
private SearchResultEntry convert( SearchResultEntryCodec searchEntryResultCodec )
{
SearchResultEntry searchResultEntry = new SearchResultEntry();
searchResultEntry.setMessageId( searchEntryResultCodec.getMessageId() );
searchResultEntry.setEntry( searchEntryResultCodec.getEntry() );
addControls( searchEntryResultCodec, searchResultEntry );
return searchResultEntry;
}
/**
* Convert a SearchResultDoneCodec to a SearchResultDone message
*/
private SearchResultDone convert( SearchResultDoneCodec searchResultDoneCodec )
{
SearchResultDone searchResultDone = new SearchResultDone();
searchResultDone.setMessageId( searchResultDoneCodec.getMessageId() );
searchResultDone.setLdapResult( convert( searchResultDoneCodec.getLdapResult() ) );
addControls( searchResultDoneCodec, searchResultDone );
return searchResultDone;
}
/**
* Convert a SearchResultReferenceCodec to a SearchResultReference message
*/
private SearchResultReference convert( SearchResultReferenceCodec searchEntryReferenceCodec )
{
SearchResultReference searchResultReference = new SearchResultReference();
searchResultReference.setMessageId( searchEntryReferenceCodec.getMessageId() );
// Loop on the referrals
Referral referral = new Referral();
if ( searchEntryReferenceCodec.getSearchResultReferences() != null )
{
for ( LdapURL url : searchEntryReferenceCodec.getSearchResultReferences() )
{
referral.addLdapUrls( url );
}
}
searchResultReference.setReferral( referral );
addControls( searchEntryReferenceCodec, searchResultReference );
return searchResultReference;
}
//------------------------- The constructors --------------------------//
/**
* Create a new instance of a LdapConnection on localhost,
* port 389.
*/
public LdapConnection()
{
config.setUseSsl( false );
config.setLdapPort( config.getDefaultLdapPort() );
config.setLdapHost( config.getDefaultLdapHost() );
messageId = new AtomicInteger( 0 );
}
/**
*
* Creates a new instance of LdapConnection with the given connection configuration.
*
* @param config the configuration of the LdapConnection
*/
public LdapConnection( LdapConnectionConfig config )
{
this.config = config;
messageId = new AtomicInteger( 0 );
}
/**
* Create a new instance of a LdapConnection on localhost,
* port 389 if the SSL flag is off, or 636 otherwise.
*
* @param useSsl A flag to tell if it's a SSL connection or not.
*/
public LdapConnection( boolean useSsl )
{
config.setUseSsl( useSsl );
config.setLdapPort( useSsl ? config.getDefaultLdapsPort() : config.getDefaultLdapPort() );
config.setLdapHost( config.getDefaultLdapHost() );
messageId = new AtomicInteger( 0 );
}
/**
* Create a new instance of a LdapConnection on a given
* server, using the default port (389).
*
* @param server The server we want to be connected to
*/
public LdapConnection( String server )
{
config.setUseSsl( false );
config.setLdapPort( config.getDefaultLdapPort() );
config.setLdapHost( server );
messageId = new AtomicInteger( 0 );
}
/**
* Create a new instance of a LdapConnection on a given
* server, using the default port (389) if the SSL flag
* is off, or 636 otherwise.
*
* @param server The server we want to be connected to
* @param useSsl A flag to tell if it's a SSL connection or not.
*/
public LdapConnection( String server, boolean useSsl )
{
config.setUseSsl( useSsl );
config.setLdapPort( useSsl ? config.getDefaultLdapsPort() : config.getDefaultLdapPort() );
config.setLdapHost( server );
messageId = new AtomicInteger( 0 );
}
/**
* Create a new instance of a LdapConnection on a
* given server and a given port. We don't use ssl.
*
* @param server The server we want to be connected to
* @param port The port the server is listening to
*/
public LdapConnection( String server, int port )
{
this( server, port, false );
}
/**
* Create a new instance of a LdapConnection on a given
* server, and a give port. We set the SSL flag accordingly
* to the last parameter.
*
* @param server The server we want to be connected to
* @param port The port the server is listening to
* @param useSsl A flag to tell if it's a SSL connection or not.
*/
public LdapConnection( String server, int port, boolean useSsl )
{
config.setUseSsl( useSsl );
config.setLdapPort( port );
config.setLdapHost( server );
messageId = new AtomicInteger();
}
//-------------------------- The methods ---------------------------//
/**
* Connect to the remote LDAP server.
*
* @return <code>true</code> if the connection is established, false otherwise
* @throws LdapException if some error has occured
*/
public boolean connect() throws LdapException, IOException
{
if ( ( ldapSession != null ) && connected.get() )
{
// No need to connect if we already have a connected session
return true;
}
// Create the connector if needed
if ( connector == null )
{
connector = new NioSocketConnector();
localConnector = true;
// Add the codec to the chain
connector.getFilterChain().addLast( "ldapCodec", ldapProtocolFilter );
// If we use SSL, we have to add the SslFilter to the chain
if ( config.isUseSsl() )
{
try
{
SSLContext sslContext = SSLContext.getInstance( config.getSslProtocol() );
sslContext.init( config.getKeyManagers(), config.getTrustManagers(), config.getSecureRandom() );
SslFilter sslFilter = new SslFilter( sslContext );
sslFilter.setUseClientMode( true );
connector.getFilterChain().addFirst( "sslFilter", sslFilter );
}
catch ( Exception e )
{
String msg = "Failed to initialize the SSL context";
LOG.error( msg, e );
throw new LdapException( msg, e );
}
}
// Add an executor so that this connection can be used
// for handling more than one request (mainly because
// we may have to handle some abandon request)
/*connector.getFilterChain().addLast( "executor",
new ExecutorFilter( new OrderedThreadPoolExecutor( 10 ), IoEventType.MESSAGE_RECEIVED ) );*/
// Inject the protocolHandler
connector.setHandler( this );
}
// Build the connection address
SocketAddress address = new InetSocketAddress( config.getLdapHost(), config.getLdapPort() );
// And create the connection future
ConnectFuture connectionFuture = connector.connect( address );
// Wait until it's established
connectionFuture.awaitUninterruptibly();
if ( !connectionFuture.isConnected() )
{
// disposing connector if not connected
try
{
close();
}
catch ( IOException ioe )
{
// Nothing to do
}
return false;
}
// Get back the session
ldapSession = connectionFuture.getSession();
connected.set( true );
// And inject the current Ldap container into the session
IAsn1Container ldapMessageContainer = new LdapMessageContainer();
// Store the container into the session
ldapSession.setAttribute( "LDAP-Container", ldapMessageContainer );
// Initialize the MessageId
messageId.set( 0 );
// And return
return true;
}
/**
* Disconnect from the remote LDAP server
*
* @return <code>true</code> if the connection is closed, false otherwise
* @throws IOException if some I/O error occurs
*/
public boolean close() throws IOException
{
// Close the session
if ( ( ldapSession != null ) && connected.get() )
{
ldapSession.close( true );
connected.set( false );
}
// And close the connector if it has been created locally
if ( localConnector )
{
// Release the connector
connector.dispose();
connector = null;
}
// Reset the messageId
messageId.set( 0 );
return true;
}
//------------------------ The LDAP operations ------------------------//
// Add operations //
//---------------------------------------------------------------------//
/**
* Add an entry to the server. This is a blocking add : the user has
* to wait for the response until the AddResponse is returned.
*
* @param entry The entry to add
* @result the add operation's response
*/
public AddResponse add( Entry entry ) throws LdapException
{
if ( entry == null )
{
String msg = "Cannot add empty entry";
LOG.debug( msg );
throw new NullPointerException( msg );
}
return add( new AddRequest( entry ) );
}
/**
* Add an entry to the server asynchronously. This is a non blocking add :
* the user has to get for the response from the returned Future.
*
* @param entry The entry to add
* @result the add operation's Future
*/
public AddFuture addAsync( Entry entry ) throws LdapException
{
if ( entry == null )
{
String msg = "Cannot add null entry";
LOG.debug( msg );
throw new NullPointerException( msg );
}
return addAsync( new AddRequest( entry ) );
}
/**
* Add an entry present in the AddRequest to the server.
*
* @param addRequest the request object containing an entry and controls(if any)
* @return the add operation's response
* @throws LdapException
*/
public AddResponse add( AddRequest addRequest ) throws LdapException
{
AddFuture addFuture = addAsync( addRequest );
// Get the result from the future
try
{
// Read the response, waiting for it if not available immediately
long timeout = getTimeout( addRequest.getTimeout() );
// Get the response, blocking
AddResponse addResponse = ( AddResponse ) addFuture.get( timeout, TimeUnit.MILLISECONDS );
if ( addResponse == null )
{
// We didn't received anything : this is an error
LOG.error( "Add failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
if ( addResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
{
// Everything is fine, return the response
LOG.debug( "Add successful : {}", addResponse );
}
else
{
// We have had an error
LOG.debug( "Add failed : {}", addResponse );
}
return addResponse;
}
catch ( TimeoutException te )
{
// Send an abandon request
if ( !addFuture.isCancelled() )
{
abandon( addRequest.getMessageId() );
}
// We didn't received anything : this is an error
LOG.error( "Add failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
catch ( Exception ie )
{
// Catch all other exceptions
LOG.error( NO_RESPONSE_ERROR, ie );
LdapException ldapException = new LdapException( NO_RESPONSE_ERROR );
ldapException.initCause( ie );
// Send an abandon request
if ( !addFuture.isCancelled() )
{
abandon( addRequest.getMessageId() );
}
throw ldapException;
}
}
/**
* Add an entry present in the AddRequest to the server.
*
* @param addRequest the request object containing an entry and controls(if any)
* @return the add operation's response
* @throws LdapException
*/
public AddFuture addAsync( AddRequest addRequest ) throws LdapException
{
checkSession();
AddRequestCodec addReqCodec = new AddRequestCodec();
int newId = messageId.incrementAndGet();
addRequest.setMessageId( newId );
addReqCodec.setMessageId( newId );
addReqCodec.setEntry( addRequest.getEntry() );
addReqCodec.setEntryDn( addRequest.getEntry().getDn() );
setControls( addRequest.getControls(), addReqCodec );
AddFuture addFuture = new AddFuture( this, newId );
addToFutureMap( newId, addFuture );
// Send the request to the server
WriteFuture writeFuture = ldapSession.write( addReqCodec );
// Wait for the message to be sent to the server
if ( !writeFuture.awaitUninterruptibly( getTimeout( 0 ) ) )
{
// We didn't received anything : this is an error
LOG.error( "Add failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
// Ok, done return the future
return addFuture;
}
/**
* converts the AddResponseCodec to AddResponse.
*/
private AddResponse convert( AddResponseCodec addRespCodec )
{
AddResponse addResponse = new AddResponse();
addResponse.setMessageId( addRespCodec.getMessageId() );
addResponse.setLdapResult( convert( addRespCodec.getLdapResult() ) );
return addResponse;
}
//------------------------ The LDAP operations ------------------------//
/**
* Abandons a request submitted to the server for performing a particular operation
*
* The abandonRequest is always non-blocking, because no response is expected
*
* @param messageId the ID of the request message sent to the server
*/
public void abandon( int messageId )
{
AbandonRequest abandonRequest = new AbandonRequest();
abandonRequest.setAbandonedMessageId( messageId );
abandonInternal( abandonRequest );
}
/**
* An abandon request essentially with the request message ID of the operation to be cancelled
* and/or potentially some controls and timeout (the controls and timeout are not mandatory).
*
* The abandonRequest is always non-blocking, because no response is expected
*
* @param abandonRequest the abandon operation's request
*/
public void abandon( AbandonRequest abandonRequest )
{
abandonInternal( abandonRequest );
}
/**
* Internal AbandonRequest handling
*/
private void abandonInternal( AbandonRequest abandonRequest )
{
// Create the inner abandonRequest
AbandonRequestCodec request = new AbandonRequestCodec();
// Todo : The Abandon messageID is always 0
int newId = messageId.incrementAndGet();
abandonRequest.setMessageId( newId );
request.setMessageId( newId );
// Inject the data into the request
request.setAbandonedMessageId( abandonRequest.getAbandonedMessageId() );
// Inject the controls
setControls( abandonRequest.getControls(), request );
LOG.debug( "-----------------------------------------------------------------" );
LOG.debug( "Sending request \n{}", request );
// Send the request to the server
ldapSession.write( request );
// remove the associated listener if any
int abandonId = abandonRequest.getAbandonedMessageId();
ResponseFuture rf = getFromFutureMap( abandonId );
// if the listener is not null, this is a async operation and no need to
// send cancel signal on future, sending so will leave a dangling poision object in the corresponding queue
// this is a sync operation send cancel signal to the corresponding ResponseFuture
if ( rf != null )
{
LOG.debug( "sending cancel signal to future" );
rf.cancel( true );
}
else
{
// this shouldn't happen
LOG
.error(
"There is no future asscoiated with operation message ID {}, perhaps the operation would have been completed",
abandonId );
}
}
/**
* Anonymous Bind on a server.
*
* @return The BindResponse LdapResponse
*/
public BindResponse bind() throws LdapException, IOException
{
LOG.debug( "Anonymous Bind request" );
// Create the BindRequest
BindRequest bindRequest = new BindRequest();
bindRequest.setName( StringTools.EMPTY );
bindRequest.setCredentials( StringTools.EMPTY_BYTES );
return bind( bindRequest );
}
/**
* Anonymous asynchronous Bind on a server.
*
* @return The BindFuture
*/
public BindFuture bindAsync() throws LdapException, IOException
{
LOG.debug( "Anonymous Bind request" );
// Create the BindRequest
BindRequest bindRequest = new BindRequest();
bindRequest.setName( StringTools.EMPTY );
bindRequest.setCredentials( StringTools.EMPTY_BYTES );
return bindAsync( bindRequest );
}
/**
* Simple Bind on a server.
*
* @param name The name we use to authenticate the user. It must be a
* valid DN
* @param credentials The password. It can't be null
* @return The BindResponse LdapResponse
*/
public BindResponse bind( String name, String credentials ) throws LdapException, IOException
{
LOG.debug( "Bind request : {}", name );
// Create the BindRequest
BindRequest bindRequest = new BindRequest();
bindRequest.setName( name );
bindRequest.setCredentials( StringTools.getBytesUtf8( credentials ) );
return bind( bindRequest );
}
/**
* Simple asynchronous Bind on a server.
*
* @param name The name we use to authenticate the user. It must be a
* valid DN
* @param credentials The password. It can't be null
* @return The BindResponse LdapResponse
*/
public BindFuture bindAsync( String name, String credentials ) throws LdapException, IOException
{
LOG.debug( "Bind request : {}", name );
// Create the BindRequest
BindRequest bindRequest = new BindRequest();
bindRequest.setName( name );
bindRequest.setCredentials( StringTools.getBytesUtf8( credentials ) );
return bindAsync( bindRequest );
}
/**
* Simple Bind on a server.
*
* @param name The name we use to authenticate the user. It must be a
* valid DN
* @param credentials The password. It can't be null
* @return The BindResponse LdapResponse
*/
public BindResponse bind( DN name, String credentials ) throws LdapException, IOException
{
LOG.debug( "Bind request : {}", name );
// Create the BindRequest
BindRequest bindRequest = new BindRequest();
bindRequest.setCredentials( StringTools.getBytesUtf8( credentials ) );
if ( name == null )
{
bindRequest.setName( StringTools.EMPTY );
}
else
{
bindRequest.setName( name.getName() );
}
return bind( bindRequest );
}
/**
* Simple asynchronous Bind on a server.
*
* @param name The name we use to authenticate the user. It must be a
* valid DN
* @param credentials The password. It can't be null
* @return The BindResponse LdapResponse
*/
public BindFuture bindAsync( DN name, String credentials ) throws LdapException, IOException
{
LOG.debug( "Bind request : {}", name );
// Create the BindRequest
BindRequest bindRequest = new BindRequest();
bindRequest.setCredentials( StringTools.getBytesUtf8( credentials ) );
if ( name == null )
{
bindRequest.setName( StringTools.EMPTY );
}
else
{
bindRequest.setName( name.getName() );
}
return bindAsync( bindRequest );
}
/**
* Bind to the server using a BindRequest object.
*
* @param bindRequest The BindRequest POJO containing all the needed
* parameters
* @return A LdapResponse containing the result
*/
public BindResponse bind( BindRequest bindRequest ) throws LdapException, IOException
{
BindFuture bindFuture = bindAsync( bindRequest );
// Get the result from the future
try
{
// Read the response, waiting for it if not available immediately
long timeout = getTimeout( bindRequest.getTimeout() );
// Get the response, blocking
BindResponse bindResponse = ( BindResponse ) bindFuture.get( timeout, TimeUnit.MILLISECONDS );
if ( bindResponse == null )
{
// We didn't received anything : this is an error
LOG.error( "Bind failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
{
authenticated.set( true );
// Everything is fine, return the response
LOG.debug( "Bind successful : {}", bindResponse );
}
else
{
// We have had an error
LOG.debug( "Bind failed : {}", bindResponse );
}
return bindResponse;
}
catch ( TimeoutException te )
{
// Send an abandon request
if ( !bindFuture.isCancelled() )
{
abandon( bindRequest.getMessageId() );
}
// We didn't received anything : this is an error
LOG.error( "Bind failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
catch ( Exception ie )
{
// Catch all other exceptions
LOG.error( NO_RESPONSE_ERROR, ie );
LdapException ldapException = new LdapException( NO_RESPONSE_ERROR );
ldapException.initCause( ie );
// Send an abandon request
if ( !bindFuture.isCancelled() )
{
abandon( bindRequest.getMessageId() );
}
throw ldapException;
}
}
/**
* Create a SearchRequestCodec ready to be sent.
*/
private SearchRequestCodec createSearchMessage( SearchRequest searchRequest ) throws LdapException
{
// Create a new codec SearchRequest object
SearchRequestCodec request = new SearchRequestCodec();
// Creates the messageID and stores it into the
// initial message and the transmitted message.
int newId = messageId.incrementAndGet();
searchRequest.setMessageId( newId );
request.setMessageId( newId );
// Set the name
try
{
DN dn = new DN( searchRequest.getBaseDn() );
request.setBaseObject( dn );
}
catch ( InvalidNameException ine )
{
String msg = "The given dn '" + searchRequest.getBaseDn() + "' is not valid";
LOG.error( msg );
LdapException ldapException = new LdapException( msg );
ldapException.initCause( ine );
throw ldapException;
}
// Set the scope
request.setScope( searchRequest.getScope() );
// Set the typesOnly flag
request.setDerefAliases( searchRequest.getDerefAliases().getValue() );
// Set the timeLimit
request.setTimeLimit( searchRequest.getTimeLimit() );
// Set the sizeLimit
request.setSizeLimit( searchRequest.getSizeLimit() );
// Set the typesOnly flag
request.setTypesOnly( searchRequest.getTypesOnly() );
// Set the filter
Filter filter = null;
try
{
ExprNode filterNode = FilterParser.parse( searchRequest.getFilter() );
filter = LdapTransformer.transformFilter( filterNode );
}
catch ( ParseException pe )
{
String msg = "The given filter '" + searchRequest.getFilter() + "' is not valid";
LOG.error( msg );
LdapException ldapException = new LdapException( msg );
ldapException.initCause( pe );
throw ldapException;
}
request.setFilter( filter );
// Set the attributes
Set<String> attributes = searchRequest.getAttributes();
if ( attributes != null )
{
for ( String attribute : attributes )
{
request.addAttribute( attribute );
}
}
// Add the controls
setControls( searchRequest.getControls(), request );
return request;
}
/**
* Create a BindRequest ready to be sent.
*/
private BindRequestCodec createBindMessage( BindRequest bindRequest ) throws LdapException
{
// Create a new codec BindRequest object
BindRequestCodec bindRequestCodec = new BindRequestCodec();
// clear the mappings if any (in case of a second call to bind() without calling unBind())
//clearMaps();
// Set the new messageId
int newId = messageId.incrementAndGet();
bindRequest.setMessageId( newId );
bindRequestCodec.setMessageId( newId );
// Set the version
bindRequestCodec.setVersion( LdapConnectionConfig.LDAP_V3 );
// Set the name
try
{
DN dn = new DN( bindRequest.getName() );
bindRequestCodec.setName( dn );
}
catch ( InvalidNameException ine )
{
String msg = "The given dn '" + bindRequest.getName() + "' is not valid";
LOG.error( msg );
LdapException ldapException = new LdapException( msg );
ldapException.initCause( ine );
throw ldapException;
}
// Set the credentials
LdapAuthentication authentication = null;
if ( bindRequest.isSimple() )
{
// Simple bind
authentication = new SimpleAuthentication();
( ( SimpleAuthentication ) authentication ).setSimple( bindRequest.getCredentials() );
}
else
{
// SASL bind
authentication = new SaslCredentials();
( ( SaslCredentials ) authentication ).setCredentials( bindRequest.getCredentials() );
( ( SaslCredentials ) authentication ).setMechanism( bindRequest.getSaslMechanism() );
}
// The authentication
bindRequestCodec.setAuthentication( authentication );
// Add the controls
setControls( bindRequest.getControls(), bindRequestCodec );
return bindRequestCodec;
}
/**
* Do an asynchronous bind, based on a BindRequest.
*
* @param bindRequest The BindRequest to send
* @return BindFuture A future
*/
public BindFuture bindAsync( BindRequest bindRequest ) throws LdapException, IOException
{
// First switch to anonymous state
authenticated.set( false );
// try to connect, if we aren't already connected.
connect();
// If the session has not been establish, or is closed, we get out immediately
checkSession();
// Create the new message and update the messageId
LdapMessageCodec bindMessage = createBindMessage( bindRequest );
int newId = bindMessage.getMessageId();
LOG.debug( "-----------------------------------------------------------------" );
LOG.debug( "Sending request \n{}", bindMessage );
// Create a future for this Bind operation
BindFuture bindFuture = new BindFuture( this, newId );
addToFutureMap( newId, bindFuture );
// Send the request to the server
WriteFuture writeFuture = ldapSession.write( bindMessage );
// Wait for the message to be sent to the server
if ( !writeFuture.awaitUninterruptibly( getTimeout( 0 ) ) )
{
// We didn't received anything : this is an error
LOG.error( "Bind failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
// Ok, done return the future
return bindFuture;
}
/**
* Do a search, on the base object, using the given filter. The
* SearchRequest parameters default to :
* Scope : ONE
* DerefAlias : ALWAYS
* SizeLimit : none
* TimeLimit : none
* TypesOnly : false
* Attributes : all the user's attributes.
* This method is blocking.
*
* @param baseDn The base for the search. It must be a valid
* DN, and can't be emtpy
* @param filterString The filter to use for this search. It can't be empty
* @param scope The sarch scope : OBJECT, ONELEVEL or SUBTREE
* @return A cursor on the result.
*/
public Cursor<SearchResponse> search( String baseDn, String filter, SearchScope scope, String... attributes )
throws LdapException
{
// Create a new SearchRequest object
SearchRequest searchRequest = new SearchRequest();
searchRequest.setBaseDn( baseDn );
searchRequest.setFilter( filter );
searchRequest.setScope( scope );
searchRequest.addAttributes( attributes );
searchRequest.setDerefAliases( AliasDerefMode.DEREF_ALWAYS );
// Process the request in blocking mode
return search( searchRequest );
}
/**
* Do an asynchronous search, on the base object, using the given filter. The
* SearchRequest parameters default to :
* Scope : ONE
* DerefAlias : ALWAYS
* SizeLimit : none
* TimeLimit : none
* TypesOnly : false
* Attributes : all the user's attributes.
* This method is blocking.
*
* @param baseDn The base for the search. It must be a valid
* DN, and can't be emtpy
* @param filterString The filter to use for this search. It can't be empty
* @param scope The sarch scope : OBJECT, ONELEVEL or SUBTREE
* @return A cursor on the result.
*/
public SearchFuture searchAsync( String baseDn, String filter, SearchScope scope, String... attributes )
throws LdapException
{
// Create a new SearchRequest object
SearchRequest searchRequest = new SearchRequest();
searchRequest.setBaseDn( baseDn );
searchRequest.setFilter( filter );
searchRequest.setScope( scope );
searchRequest.addAttributes( attributes );
searchRequest.setDerefAliases( AliasDerefMode.DEREF_ALWAYS );
// Process the request in blocking mode
return searchAsync( searchRequest );
}
/**
* Do a search, on the base object, using the given filter. The
* SearchRequest parameters default to :
* Scope : ONE
* DerefAlias : ALWAYS
* SizeLimit : none
* TimeLimit : none
* TypesOnly : false
* Attributes : all the user's attributes.
* This method is blocking.
*
* @param searchRequest The search request to send to the server
* @return A Future
*/
public SearchFuture searchAsync( SearchRequest searchRequest ) throws LdapException
{
// If the session has not been establish, or is closed, we get out immediately
checkSession();
// Create the server request
SearchRequestCodec request = createSearchMessage( searchRequest );
LOG.debug( "-----------------------------------------------------------------" );
LOG.debug( "Sending request \n{}", request );
SearchFuture searchFuture = new SearchFuture( this, request.getMessageId() );
addToFutureMap( request.getMessageId(), searchFuture );
// Send the request to the server
WriteFuture writeFuture = ldapSession.write( request );
// Wait for the message to be sent to the server
if ( !writeFuture.awaitUninterruptibly( getTimeout( 0 ) ) )
{
// We didn't received anything : this is an error
LOG.error( "Search failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
// Ok, done return the future
return searchFuture;
}
/**
* Performs search in a synchronous mode.
*
* @param searchRequest The search configuration
* @return A {@link Cursor} containing Entries and Referencs
* @throws LdapException @TODO
*/
public Cursor<SearchResponse> search( SearchRequest searchRequest ) throws LdapException
{
SearchFuture searchFuture = searchAsync( searchRequest );
long timeout = getTimeout( searchRequest.getTimeout() );
return new SearchCursor( searchFuture, timeout, TimeUnit.MILLISECONDS );
}
//------------------------ The LDAP operations ------------------------//
// Unbind operations //
//---------------------------------------------------------------------//
/**
* UnBind from a server. this is a request which expect no response.
*/
public void unBind() throws Exception
{
// If the session has not been establish, or is closed, we get out immediately
checkSession();
// Create the UnbindRequest
UnBindRequestCodec unbindRequest = new UnBindRequestCodec();
// Creates the messageID and stores it into the
// initial message and the transmitted message.
int newId = messageId.incrementAndGet();
unbindRequest.setMessageId( newId );
LOG.debug( "-----------------------------------------------------------------" );
LOG.debug( "Sending Unbind request \n{}", unbindRequest );
// Send the request to the server
WriteFuture unbindFuture = ldapSession.write( unbindRequest );
//LOG.debug( "waiting for unbindFuture" );
//unbindFuture.awaitUninterruptibly();
//LOG.debug( "unbindFuture done" );
authenticated.set( false );
// clear the mappings
clearMaps();
// We now have to close the session
if ( ( ldapSession != null ) && connected.get() )
{
CloseFuture closeFuture = ldapSession.close( true );
LOG.debug( "waiting for closeFuture" );
closeFuture.awaitUninterruptibly();
LOG.debug( "closeFuture done" );
connected.set( false );
}
// Last, not least, reset the MessageId value
messageId.set(0);
// And get out
LOG.debug( "Unbind successful" );
}
/**
* Set the connector to use.
*
* @param connector The connector to use
*/
public void setConnector( IoConnector connector )
{
this.connector = connector;
}
/**
* Set the timeOut for the responses. We wont wait longer than this
* value.
*
* @param timeOut The timeout, in milliseconds
*/
public void setTimeOut( long timeOut )
{
this.timeOut = timeOut;
}
/**
* Handle the incoming LDAP messages. This is where we feed the cursor for search
* requests, or call the listener.
*/
public void messageReceived( IoSession session, Object message ) throws Exception
{
// Feed the response and store it into the session
LdapMessageCodec response = ( LdapMessageCodec ) message;
LOG.debug( "-------> {} Message received <-------", response );
// this check is necessary to prevent adding an abandoned operation's
// result(s) to corresponding queue
ResponseFuture<? extends Response> responseFuture = peekFromFutureMap( response.getMessageId() );
if ( responseFuture == null )
{
LOG.info( "There is no future associated with the messageId {}, ignoring the message", response
.getMessageId() );
return;
}
int messageId = response.getMessageId();
switch ( response.getMessageType() )
{
case ADD_RESPONSE:
// Transform the response
AddResponseCodec addRespCodec = (AddResponseCodec)response;
addRespCodec.addControl( response.getCurrentControl() );
addRespCodec.setMessageId( messageId );
AddResponse addResponse = convert( addRespCodec );
AddFuture addFuture = (AddFuture)responseFuture;
if ( addFuture == null )
{
LOG.error( "AddFuture is null" );
throw new LdapException( "AddFuture is null" );
}
// remove the listener from the listener map
if ( LOG.isDebugEnabled() )
{
if ( addResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
{
// Everything is fine, return the response
LOG.debug( "Add successful : {}", addResponse );
}
else
{
// We have had an error
LOG.debug( "Add failed : {}", addResponse );
}
}
// Store the response into the future
addFuture.set( addResponse );
// Remove the future from the map
removeFromFutureMaps( messageId );
break;
case BIND_RESPONSE:
// Transform the response
BindResponseCodec bindResponseCodec = (BindResponseCodec)response;
bindResponseCodec.setMessageId( messageId );
bindResponseCodec.addControl( response.getCurrentControl() );
BindResponse bindResponse = convert( bindResponseCodec );
BindFuture bindFuture = (BindFuture)responseFuture;
if ( bindFuture == null )
{
LOG.error( "BindFuture is null" );
throw new LdapException( "BindFuture is null" );
}
// remove the listener from the listener map
if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
{
authenticated.set( true );
// Everything is fine, return the response
LOG.debug( "Bind successful : {}", bindResponse );
}
else
{
// We have had an error
LOG.debug( "Bind failed : {}", bindResponse );
}
// Store the response into the future
bindFuture.set( bindResponse );
// Remove the future from the map
removeFromFutureMaps( messageId );
break;
case COMPARE_RESPONSE:
// Transform the response
CompareResponseCodec compResCodec = (CompareResponseCodec)response;
compResCodec.setMessageId( messageId );
compResCodec.addControl( response.getCurrentControl() );
CompareResponse compareResponse = convert( compResCodec );
CompareFuture compareFuture = (CompareFuture)responseFuture;
if ( compareFuture == null )
{
LOG.error( "CompareFuture is null" );
throw new LdapException( "CompareFuture is null" );
}
// remove the listener from the listener map
if ( LOG.isDebugEnabled() )
{
if ( compareResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
{
// Everything is fine, return the response
LOG.debug( "Compare successful : {}", compareResponse );
}
else
{
// We have had an error
LOG.debug( "Compare failed : {}", compareResponse );
}
}
// Store the response into the future
compareFuture.set( compareResponse );
// Remove the future from the map
removeFromFutureMaps( messageId );
break;
case DEL_RESPONSE:
// Transform the response
DelResponseCodec delRespCodec = (DelResponseCodec)response;
delRespCodec.addControl( response.getCurrentControl() );
delRespCodec.setMessageId( messageId );
DeleteResponse deleteResponse = convert( delRespCodec );
DeleteFuture deleteFuture = (DeleteFuture)responseFuture;
if ( deleteFuture == null )
{
LOG.error( "DeleteFuture is null" );
throw new LdapException( "DeleteFuture is null" );
}
if ( LOG.isDebugEnabled() )
{
if ( deleteResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
{
// Everything is fine, return the response
LOG.debug( "Delete successful : {}", deleteResponse );
}
else
{
// We have had an error
LOG.debug( "Delete failed : {}", deleteResponse );
}
}
// Store the response into the future
deleteFuture.set( deleteResponse );
// Remove the future from the map
removeFromFutureMaps( messageId );
break;
case EXTENDED_RESPONSE:
// Transform the response
ExtendedResponseCodec extResCodec = (ExtendedResponseCodec)response;
extResCodec.setMessageId( messageId );
extResCodec.addControl( response.getCurrentControl() );
ExtendedResponse extendedResponse = convert( extResCodec );
ExtendedFuture extendedFuture = (ExtendedFuture)responseFuture;
if ( extendedFuture == null )
{
LOG.error( "ExtendedFuture is null" );
throw new LdapException( "extendedFuture is null" );
}
// remove the listener from the listener map
if ( LOG.isDebugEnabled() )
{
if ( extendedResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
{
// Everything is fine, return the response
LOG.debug( "Extended successful : {}", extendedResponse );
}
else
{
// We have had an error
LOG.debug( "Extended failed : {}", extendedResponse );
}
}
// Store the response into the future
extendedFuture.set( extendedResponse );
// Remove the future from the map
removeFromFutureMaps( messageId );
break;
case INTERMEDIATE_RESPONSE:
IntermediateResponseCodec intermediateResponseCodec = (IntermediateResponseCodec)response;
intermediateResponseCodec.setMessageId( messageId );
intermediateResponseCodec.addControl( response.getCurrentControl() );
setIResponse( intermediateResponseCodec, responseFuture );
break;
case MODIFY_RESPONSE:
// Transform the response
ModifyResponseCodec modRespCodec = (ModifyResponseCodec)response;
modRespCodec.setMessageId( messageId );
modRespCodec.addControl( response.getCurrentControl() );
ModifyResponse modifyResp = convert( modRespCodec );
ModifyFuture modifyFuture = (ModifyFuture)responseFuture;
if ( modifyFuture == null )
{
LOG.error( "ModifyFuture is null" );
throw new LdapException( "ModifyFuture is null" );
}
if ( LOG.isDebugEnabled() )
{
if ( modifyResp.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
{
// Everything is fine, return the response
LOG.debug( "ModifyFuture successful : {}", modifyResp );
}
else
{
// We have had an error
LOG.debug( "ModifyFuture failed : {}", modifyResp );
}
}
// Store the response into the future
modifyFuture.set( modifyResp );
// Remove the future from the map
removeFromFutureMaps( messageId );
break;
case MODIFYDN_RESPONSE:
// Transform the response
ModifyDNResponseCodec modDnRespCodec = (ModifyDNResponseCodec)response;
modDnRespCodec.setMessageId( messageId );
modDnRespCodec.addControl( response.getCurrentControl() );
ModifyDnResponse modifyDnResp = convert( modDnRespCodec );
ModifyDnFuture modifyDnFuture = (ModifyDnFuture)responseFuture;
if ( modifyDnFuture == null )
{
LOG.error( "ModifyDNFuture is null" );
throw new LdapException( "ModifyDNFuture is null" );
}
if ( LOG.isDebugEnabled() )
{
if ( modifyDnResp.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
{
// Everything is fine, return the response
LOG.debug( "ModifyDN successful : {}", modifyDnResp );
}
else
{
// We have had an error
LOG.debug( "ModifyDN failed : {}", modifyDnResp );
}
}
// Store the response into the future
modifyDnFuture.set( modifyDnResp );
// Remove the future from the map
removeFromFutureMaps( messageId );
break;
case SEARCH_RESULT_DONE:
// Store the response into the responseQueue
SearchResultDoneCodec searchResultDoneCodec = (SearchResultDoneCodec)response;
searchResultDoneCodec.setMessageId( messageId );
searchResultDoneCodec.addControl( response.getCurrentControl() );
SearchResultDone searchResultDone = convert( searchResultDoneCodec );
SearchFuture searchFuture = (SearchFuture)responseFuture;
if ( searchFuture == null )
{
LOG.error( "SearchFuture is null" );
throw new LdapException( "SearchFuture is null" );
}
if ( LOG.isDebugEnabled() )
{
if ( searchResultDone.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
{
// Everything is fine, return the response
LOG.debug( "Search successful : {}", searchResultDone );
}
else
{
// We have had an error
LOG.debug( "Search failed : {}", searchResultDone );
}
}
// Store the response into the future
searchFuture.set( searchResultDone );
// Remove the future from the map
removeFromFutureMaps( messageId );
break;
case SEARCH_RESULT_ENTRY:
// Store the response into the responseQueue
SearchResultEntryCodec searchResultEntryCodec = (SearchResultEntryCodec)response;
searchResultEntryCodec.setMessageId( messageId );
searchResultEntryCodec.addControl( response.getCurrentControl() );
SearchResultEntry srchEntry = convert( searchResultEntryCodec );
searchFuture = (SearchFuture)responseFuture;
if ( searchFuture == null )
{
LOG.error( "SearchFuture is null" );
throw new LdapException( "SearchFuture is null" );
}
if ( LOG.isDebugEnabled() )
{
LOG.debug( "Search entry found : {}", srchEntry );
}
// Store the response into the future
searchFuture.set( srchEntry );
break;
case SEARCH_RESULT_REFERENCE:
// Store the response into the responseQueue
SearchResultReferenceCodec searchResultReferenceCodec = (SearchResultReferenceCodec)response;
searchResultReferenceCodec.setMessageId( messageId );
searchResultReferenceCodec.addControl( response.getCurrentControl() );
SearchResultReference searchResultReference = convert( searchResultReferenceCodec );
searchFuture = (SearchFuture)responseFuture;
if ( searchFuture == null )
{
LOG.error( "SearchFuture is null" );
throw new LdapException( "SearchFuture is null" );
}
if ( LOG.isDebugEnabled() )
{
LOG.debug( "Search reference found : {}", searchResultReference );
}
// Store the response into the future
searchFuture.set( searchResultReference );
break;
default:
LOG.error( "~~~~~~~~~~~~~~~~~~~~~ Unknown message type {} ~~~~~~~~~~~~~~~~~~~~~", response
.getMessageTypeName() );
}
}
/**
*
* modifies all the attributes present in the entry by applying the same operation.
*
* @param entry the entry whise attributes to be modified
* @param modOp the operation to be applied on all the attributes of the above entry
* @return the modify operation's response
* @throws LdapException in case of modify operation failure or timeout happens
*/
public ModifyResponse modify( Entry entry, ModificationOperation modOp ) throws LdapException
{
if ( entry == null )
{
LOG.debug( "received a null entry for modification" );
throw new NullPointerException( "Entry to be modified cannot be null" );
}
ModifyRequest modReq = new ModifyRequest( entry.getDn() );
Iterator<EntryAttribute> itr = entry.iterator();
while ( itr.hasNext() )
{
modReq.addModification( itr.next(), modOp );
}
return modify( modReq );
}
/**
* Performs an modify operation based on the modifications present in
* the ModifyRequest.
*
* @param modRequest the request for modify operation
* @return the modify operation's r"esponse
* @throws LdapException in case of modify operation failure or timeout happens
*/
public ModifyResponse modify( ModifyRequest modRequest ) throws LdapException
{
ModifyFuture modifyFuture = modifyAsync( modRequest );
// Get the result from the future
try
{
// Read the response, waiting for it if not available immediately
long timeout = getTimeout( modRequest.getTimeout() );
// Get the response, blocking
ModifyResponse modifyResponse = ( ModifyResponse ) modifyFuture.get( timeout, TimeUnit.MILLISECONDS );
if ( modifyResponse == null )
{
// We didn't received anything : this is an error
LOG.error( "Modify failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
if ( modifyResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
{
// Everything is fine, return the response
LOG.debug( "Modify successful : {}", modifyResponse );
}
else
{
// We have had an error
LOG.debug( "Modify failed : {}", modifyResponse );
}
return modifyResponse;
}
catch ( TimeoutException te )
{
// Send an abandon request
if ( !modifyFuture.isCancelled() )
{
abandon( modRequest.getMessageId() );
}
// We didn't received anything : this is an error
LOG.error( "Modify failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
catch ( Exception ie )
{
// Catch all other exceptions
LOG.error( NO_RESPONSE_ERROR, ie );
LdapException ldapException = new LdapException( NO_RESPONSE_ERROR );
ldapException.initCause( ie );
// Send an abandon request
if ( !modifyFuture.isCancelled() )
{
abandon( modRequest.getMessageId() );
}
throw ldapException;
}
}
/**
* Performs an asynchronous modify operation based on the modifications present in
* the ModifyRequest.
*
* @param modRequest the request for modify operation
* @return the modify operation's future
* @throws LdapException in case of modify operation failure or timeout happens
*/
public ModifyFuture modifyAsync( ModifyRequest modRequest ) throws LdapException
{
checkSession();
ModifyRequestCodec modReqCodec = new ModifyRequestCodec();
int newId = messageId.incrementAndGet();
modRequest.setMessageId( newId );
modReqCodec.setMessageId( newId );
modReqCodec.setModifications( modRequest.getMods() );
modReqCodec.setObject( modRequest.getDn() );
setControls( modRequest.getControls(), modReqCodec );
ModifyFuture modifyFuture = new ModifyFuture( this, newId );
addToFutureMap( newId, modifyFuture );
// Send the request to the server
WriteFuture writeFuture = ldapSession.write( modReqCodec );
// Wait for the message to be sent to the server
if ( !writeFuture.awaitUninterruptibly( getTimeout( 0 ) ) )
{
// We didn't received anything : this is an error
LOG.error( "Modify failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
// Ok, done return the future
return modifyFuture;
}
/**
* converts the ModifyResponseCodec to ModifyResponse.
*/
private ModifyResponse convert( ModifyResponseCodec modRespCodec )
{
ModifyResponse modResponse = new ModifyResponse();
modResponse.setMessageId( modRespCodec.getMessageId() );
modResponse.setLdapResult( convert( modRespCodec.getLdapResult() ) );
return modResponse;
}
/**
* renames the given entryDn with new Rdn and deletes the old RDN.
* @see #rename(String, String, boolean)
*/
public ModifyDnResponse rename( String entryDn, String newRdn ) throws LdapException
{
return rename( entryDn, newRdn, true );
}
/**
* renames the given entryDn with new RDN and deletes the old RDN.
* @see #rename(DN, RDN, boolean)
*/
public ModifyDnResponse rename( DN entryDn, RDN newRdn ) throws LdapException
{
return rename( entryDn, newRdn, true );
}
/**
* @see #rename(DN, RDN, boolean)
*/
public ModifyDnResponse rename( String entryDn, String newRdn, boolean deleteOldRdn ) throws LdapException
{
try
{
return rename( new DN( entryDn ), new RDN( newRdn ), deleteOldRdn );
}
catch ( InvalidNameException e )
{
LOG.error( e.getMessage(), e );
throw new LdapException( e.getMessage(), e );
}
}
/**
*
* renames the given entryDn with new RDN and deletes the old Rdn if
* deleteOldRdn is set to true.
*
* @param entryDn the target DN
* @param newRdn new Rdn for the target DN
* @param deleteOldRdn flag to indicate whether to delete the old Rdn
* @return modifyDn operations response
* @throws LdapException
*/
public ModifyDnResponse rename( DN entryDn, RDN newRdn, boolean deleteOldRdn ) throws LdapException
{
ModifyDnRequest modDnRequest = new ModifyDnRequest();
modDnRequest.setEntryDn( entryDn );
modDnRequest.setNewRdn( newRdn );
modDnRequest.setDeleteOldRdn( deleteOldRdn );
return modifyDn( modDnRequest );
}
/**
* @see #move(DN, DN)
*/
public ModifyDnResponse move( String entryDn, String newSuperiorDn ) throws LdapException
{
try
{
return move( new DN( entryDn ), new DN( newSuperiorDn ) );
}
catch ( InvalidNameException e )
{
LOG.error( e.getMessage(), e );
throw new LdapException( e.getMessage(), e );
}
}
/**
* moves the given entry DN under the new superior DN
*
* @param entryDn the DN of the target entry
* @param newSuperiorDn DN of the new parent/superior
* @return modifyDn operations response
* @throws LdapException
*/
public ModifyDnResponse move( DN entryDn, DN newSuperiorDn ) throws LdapException
{
ModifyDnRequest modDnRequest = new ModifyDnRequest();
modDnRequest.setEntryDn( entryDn );
modDnRequest.setNewSuperior( newSuperiorDn );
//TODO not setting the below value is resulting in error
modDnRequest.setNewRdn( entryDn.getRdn() );
return modifyDn( modDnRequest );
}
/**
*
* performs the modifyDn operation based on the given ModifyDnRequest.
*
* @param modDnRequest the request
* @return modifyDn operations response, null if non-null listener is provided
* @throws LdapException
*/
public ModifyDnResponse modifyDn( ModifyDnRequest modDnRequest ) throws LdapException
{
ModifyDnFuture modifyDnFuture = modifyDnAsync( modDnRequest );
// Get the result from the future
try
{
// Read the response, waiting for it if not available immediately
long timeout = getTimeout( modDnRequest.getTimeout() );
// Get the response, blocking
ModifyDnResponse modifyDnResponse = ( ModifyDnResponse ) modifyDnFuture.get( timeout, TimeUnit.MILLISECONDS );
if ( modifyDnResponse == null )
{
// We didn't received anything : this is an error
LOG.error( "ModifyDN failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
if ( modifyDnResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
{
// Everything is fine, return the response
LOG.debug( "ModifyDN successful : {}", modifyDnResponse );
}
else
{
// We have had an error
LOG.debug( "Modify failed : {}", modifyDnResponse );
}
return modifyDnResponse;
}
catch ( TimeoutException te )
{
// Send an abandon request
if ( !modifyDnFuture.isCancelled() )
{
abandon( modDnRequest.getMessageId() );
}
// We didn't received anything : this is an error
LOG.error( "Modify failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
catch ( Exception ie )
{
// Catch all other exceptions
LOG.error( NO_RESPONSE_ERROR, ie );
LdapException ldapException = new LdapException( NO_RESPONSE_ERROR );
ldapException.initCause( ie );
// Send an abandon request
if ( !modifyDnFuture.isCancelled() )
{
abandon( modDnRequest.getMessageId() );
}
throw ldapException;
}
}
/**
*
* performs the modifyDn operation based on the given ModifyDnRequest.
*
* @param modDnRequest the request
* @param listener callback listener which will be called after the operation is completed
* @return modifyDn operations response, null if non-null listener is provided
* @throws LdapException
*/
public ModifyDnFuture modifyDnAsync( ModifyDnRequest modDnRequest ) throws LdapException
{
checkSession();
ModifyDNRequestCodec modDnCodec = new ModifyDNRequestCodec();
int newId = messageId.incrementAndGet();
modDnRequest.setMessageId( newId );
modDnCodec.setMessageId( newId );
modDnCodec.setEntry( modDnRequest.getEntryDn() );
modDnCodec.setNewRDN( modDnRequest.getNewRdn() );
modDnCodec.setDeleteOldRDN( modDnRequest.isDeleteOldRdn() );
modDnCodec.setNewSuperior( modDnRequest.getNewSuperior() );
setControls( modDnRequest.getControls(), modDnCodec );
ModifyDnFuture modifyDnFuture = new ModifyDnFuture( this, newId );
addToFutureMap( newId, modifyDnFuture );
// Send the request to the server
WriteFuture writeFuture = ldapSession.write( modDnCodec );
// Wait for the message to be sent to the server
if ( !writeFuture.awaitUninterruptibly( getTimeout( 0 ) ) )
{
// We didn't received anything : this is an error
LOG.error( "Modify failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
// Ok, done return the future
return modifyDnFuture;
}
/**
* converts the ModifyDnResponseCodec to ModifyResponse.
*/
private ModifyDnResponse convert( ModifyDNResponseCodec modDnRespCodec )
{
ModifyDnResponse modDnResponse = new ModifyDnResponse();
modDnResponse.setMessageId( modDnRespCodec.getMessageId() );
modDnResponse.setLdapResult( convert( modDnRespCodec.getLdapResult() ) );
return modDnResponse;
}
/**
* deletes the entry with the given DN
*
* @param dn the target entry's DN as a String
* @throws LdapException If the DN is not valid or if the deletion failed
*/
public DeleteResponse delete( String dn ) throws LdapException
{
try
{
DeleteRequest deleteRequest = new DeleteRequest( new DN( dn ) );
return delete( deleteRequest );
}
catch ( InvalidNameException e )
{
LOG.error( e.getMessage(), e );
throw new LdapException( e.getMessage(), e );
}
}
/**
* deletes the entry with the given DN
*
* @param dn the target entry's DN
* @throws LdapException If the DN is not valid or if the deletion failed
*/
public DeleteResponse delete( DN dn ) throws LdapException
{
DeleteRequest deleteRequest = new DeleteRequest( dn );
return delete( deleteRequest );
}
/**
* deletes the entry with the given DN, and all its children
*
* @param dn the target entry's DN
* @return operation's response
* @throws LdapException If the DN is not valid or if the deletion failed
*/
public DeleteResponse deleteTree( DN dn ) throws LdapException
{
String treeDeleteOid = "1.2.840.113556.1.4.805";
if ( isControlSupported( treeDeleteOid ) )
{
DeleteRequest delRequest = new DeleteRequest( dn );
delRequest.add( new ControlImpl( treeDeleteOid ) );
return delete( delRequest );
}
else
{
String msg = "The subtreeDelete control (1.2.840.113556.1.4.805) is not supported by the server\n" +
" The deletion has been aborted";
LOG.error( msg );
throw new LdapException( msg );
}
}
/**
* deletes the entry with the given DN, and all its children
*
* @param dn the target entry's DN as a String
* @return operation's response
* @throws LdapException If the DN is not valid or if the deletion failed
*/
public DeleteResponse deleteTree( String dn ) throws LdapException
{
try
{
String treeDeleteOid = "1.2.840.113556.1.4.805";
DN newDn = new DN( dn );
if ( isControlSupported( treeDeleteOid ) )
{
DeleteRequest delRequest = new DeleteRequest( newDn );
delRequest.add( new ControlImpl( treeDeleteOid ) );
return delete( delRequest );
}
else
{
String msg = "The subtreeDelete control (1.2.840.113556.1.4.805) is not supported by the server\n" +
" The deletion has been aborted";
LOG.error( msg );
throw new LdapException( msg );
}
}
catch ( InvalidNameException e )
{
LOG.error( e.getMessage(), e );
throw new LdapException( e.getMessage(), e );
}
}
/**
* removes all child entries present under the given DN and finally the DN itself
*
* Working:
* This is a recursive function which maintains a Map<DN,Cursor>.
* The way the cascade delete works is by checking for children for a
* given DN(i.e opening a search cursor) and if the cursor is empty
* then delete the DN else for each entry's DN present in cursor call
* deleteChildren() with the DN and the reference to the map.
*
* The reason for opening a search cursor is based on an assumption
* that an entry *might* contain children, consider the below DIT fragment
*
* parent
* / \
* child1 child2
* / \
* grand21 grand22
*
* The below method works better in the case where the tree depth is >1
*
* In the case of passing a non-null DeleteListener, the return value will always be null, cause the
* operation is treated as asynchronous and response result will be sent using the listener callback
*
* //FIXME provide another method for optimizing delete operation for a tree with depth <=1
*
* @param dn the DN which will be removed after removing its children
* @param map a map to hold the Cursor related to a DN
* @param listener the delete operation response listener
* @throws LdapException If the DN is not valid or if the deletion failed
*/
private DeleteResponse deleteRecursive( DN dn, Map<DN, Cursor<SearchResponse>> cursorMap,
DeleteListener listener ) throws LdapException
{
LOG.debug( "searching for {}", dn.getName() );
DeleteResponse delResponse = null;
Cursor<SearchResponse> cursor = null;
try
{
if ( cursorMap == null )
{
cursorMap = new HashMap<DN, Cursor<SearchResponse>>();
}
cursor = cursorMap.get( dn );
if ( cursor == null )
{
cursor = search( dn.getName(), "(objectClass=*)", SearchScope.ONELEVEL, ( String[] ) null );
LOG.debug( "putting cursor for {}", dn.getName() );
cursorMap.put( dn, cursor );
}
if ( !cursor.next() ) // if this is a leaf entry's DN
{
LOG.debug( "deleting {}", dn.getName() );
cursorMap.remove( dn );
cursor.close();
delResponse = delete( new DeleteRequest( dn ) );
}
else
{
do
{
SearchResponse searchResp = cursor.get();
if ( searchResp instanceof SearchResultEntry )
{
SearchResultEntry searchResult = ( SearchResultEntry ) searchResp;
deleteRecursive( searchResult.getEntry().getDn(), cursorMap, listener );
}
}
while ( cursor.next() );
cursorMap.remove( dn );
cursor.close();
LOG.debug( "deleting {}", dn.getName() );
delResponse = delete( new DeleteRequest( dn ) );
}
}
catch ( Exception e )
{
String msg = "Failed to delete child entries under the DN " + dn.getName();
LOG.error( msg, e );
throw new LdapException( msg, e );
}
return delResponse;
}
/**
* Performs a delete operation based on the delete request object.
*
* @param deleteRequest the delete operation's request
* @return delete operation's response, null if a non-null listener value is provided
* @throws LdapException If the DN is not valid or if the deletion failed
*/
public DeleteResponse delete( DeleteRequest deleteRequest ) throws LdapException
{
DeleteFuture deleteFuture = deleteAsync( deleteRequest );
// Get the result from the future
try
{
// Read the response, waiting for it if not available immediately
long timeout = getTimeout( deleteRequest.getTimeout() );
// Get the response, blocking
DeleteResponse delResponse = ( DeleteResponse ) deleteFuture.get( timeout, TimeUnit.MILLISECONDS );
if ( delResponse == null )
{
// We didn't received anything : this is an error
LOG.error( "Delete failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
if ( delResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
{
// Everything is fine, return the response
LOG.debug( "Delete successful : {}", delResponse );
}
else
{
// We have had an error
LOG.debug( "Delete failed : {}", delResponse );
}
return delResponse;
}
catch ( TimeoutException te )
{
// Send an abandon request
if ( !deleteFuture.isCancelled() )
{
abandon( deleteRequest.getMessageId() );
}
// We didn't received anything : this is an error
LOG.error( "Del failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
catch ( Exception ie )
{
// Catch all other exceptions
LOG.error( NO_RESPONSE_ERROR, ie );
LdapException ldapException = new LdapException( NO_RESPONSE_ERROR );
ldapException.initCause( ie );
// Send an abandon request
if ( !deleteFuture.isCancelled() )
{
abandon( deleteRequest.getMessageId() );
}
throw ldapException;
}
}
/**
* Performs an asynchronous delete operation based on the delete request object.
*
* @param delRequest the delete operation's request
* @return delete operation's response, null if a non-null listener value is provided
* @throws LdapException If the DN is not valid or if the deletion failed
*/
public DeleteFuture deleteAsync( DeleteRequest delRequest ) throws LdapException
{
checkSession();
DelRequestCodec delReqCodec = new DelRequestCodec();
int newId = messageId.incrementAndGet();
delRequest.setMessageId( newId );
delReqCodec.setMessageId( newId );
delReqCodec.setEntry( delRequest.getTargetDn() );
setControls( delRequest.getControls(), delReqCodec );
DeleteFuture deleteFuture = new DeleteFuture( this, newId );
addToFutureMap( newId, deleteFuture );
// Send the request to the server
WriteFuture writeFuture = ldapSession.write( delReqCodec );
// Wait for the message to be sent to the server
if ( !writeFuture.awaitUninterruptibly( getTimeout( 0 ) ) )
{
// We didn't received anything : this is an error
LOG.error( "Delete failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
// Ok, done return the future
return deleteFuture;
}
/**
* Compares a whether a given attribute's value matches that of the
* existing value of the attribute present in the entry with the given DN
*
* @param dn the target entry's String DN
* @param attributeName the attribute's name
* @param value a String value with which the target entry's attribute value to be compared with
* @return compare operation's response
* @throws LdapException
*/
public CompareResponse compare( String dn, String attributeName, String value ) throws LdapException
{
try
{
CompareRequest compareRequest = new CompareRequest();
compareRequest.setEntryDn( new DN( dn ) );
compareRequest.setAttrName( attributeName );
compareRequest.setValue( value );
return compare( compareRequest );
}
catch ( Exception e )
{
LOG.error( COMPARE_FAILED, e );
throw new LdapException( COMPARE_FAILED, e );
}
}
/**
* Compares a whether a given attribute's value matches that of the
* existing value of the attribute present in the entry with the given DN
*
* @param dn the target entry's String DN
* @param attributeName the attribute's name
* @param value a byte[] value with which the target entry's attribute value to be compared with
* @return compare operation's response
* @throws LdapException
*/
public CompareResponse compare( String dn, String attributeName, byte[] value ) throws LdapException
{
try
{
CompareRequest compareRequest = new CompareRequest();
compareRequest.setEntryDn( new DN( dn ) );
compareRequest.setAttrName( attributeName );
compareRequest.setValue( value );
return compare( compareRequest );
}
catch ( Exception e )
{
LOG.error( COMPARE_FAILED, e );
throw new LdapException( COMPARE_FAILED, e );
}
}
/**
* Compares a whether a given attribute's value matches that of the
* existing value of the attribute present in the entry with the given DN
*
* @param dn the target entry's String DN
* @param attributeName the attribute's name
* @param value a Value<?> value with which the target entry's attribute value to be compared with
* @return compare operation's response
* @throws LdapException
*/
public CompareResponse compare( String dn, String attributeName, Value<?> value ) throws LdapException
{
try
{
CompareRequest compareRequest = new CompareRequest();
compareRequest.setEntryDn( new DN( dn ) );
compareRequest.setAttrName( attributeName );
compareRequest.setValue( value );
return compare( compareRequest );
}
catch ( Exception e )
{
LOG.error( COMPARE_FAILED, e );
throw new LdapException( COMPARE_FAILED, e );
}
}
/**
* Compares a whether a given attribute's value matches that of the
* existing value of the attribute present in the entry with the given DN
*
* @param dn the target entry's DN
* @param attributeName the attribute's name
* @param value a String value with which the target entry's attribute value to be compared with
* @return compare operation's response
* @throws LdapException
*/
public CompareResponse compare( DN dn, String attributeName, String value ) throws LdapException
{
CompareRequest compareRequest = new CompareRequest();
compareRequest.setEntryDn( dn );
compareRequest.setAttrName( attributeName );
compareRequest.setValue( value );
return compare( compareRequest );
}
/**
* Compares a whether a given attribute's value matches that of the
* existing value of the attribute present in the entry with the given DN
*
* @param dn the target entry's DN
* @param attributeName the attribute's name
* @param value a byte[] value with which the target entry's attribute value to be compared with
* @return compare operation's response
* @throws LdapException
*/
public CompareResponse compare( DN dn, String attributeName, byte[] value ) throws LdapException
{
CompareRequest compareRequest = new CompareRequest();
compareRequest.setEntryDn( dn );
compareRequest.setAttrName( attributeName );
compareRequest.setValue( value );
return compare( compareRequest );
}
/**
* Compares a whether a given attribute's value matches that of the
* existing value of the attribute present in the entry with the given DN
*
* @param dn the target entry's DN
* @param attributeName the attribute's name
* @param value a Value<?> value with which the target entry's attribute value to be compared with
* @return compare operation's response
* @throws LdapException
*/
public CompareResponse compare( DN dn, String attributeName, Value<?> value ) throws LdapException
{
CompareRequest compareRequest = new CompareRequest();
compareRequest.setEntryDn( dn );
compareRequest.setAttrName( attributeName );
compareRequest.setValue( value.get() );
return compare( compareRequest );
}
/**
* compares an entry's attribute's value with that of the given value
*
* @param compareRequest the CompareRequest which contains the target DN, attribute name and value
* @return compare operation's response
* @throws LdapException
*/
public CompareResponse compare( CompareRequest compareRequest ) throws LdapException
{
CompareFuture compareFuture = compareAsync( compareRequest );
// Get the result from the future
try
{
// Read the response, waiting for it if not available immediately
long timeout = getTimeout( compareRequest.getTimeout() );
// Get the response, blocking
CompareResponse compareResponse = ( CompareResponse ) compareFuture.get( timeout, TimeUnit.MILLISECONDS );
if ( compareResponse == null )
{
// We didn't received anything : this is an error
LOG.error( "Compare failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
if ( compareResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
{
// Everything is fine, return the response
LOG.debug( "Compare successful : {}", compareResponse );
}
else
{
// We have had an error
LOG.debug( "Compare failed : {}", compareResponse );
}
return compareResponse;
}
catch ( TimeoutException te )
{
// Send an abandon request
if ( !compareFuture.isCancelled() )
{
abandon( compareRequest.getMessageId() );
}
// We didn't received anything : this is an error
LOG.error( "Compare failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
catch ( Exception ie )
{
// Catch all other exceptions
LOG.error( NO_RESPONSE_ERROR, ie );
LdapException ldapException = new LdapException( NO_RESPONSE_ERROR );
ldapException.initCause( ie );
// Send an abandon request
if ( !compareFuture.isCancelled() )
{
abandon( compareRequest.getMessageId() );
}
throw ldapException;
}
}
/**
* Asynchronously compares an entry's attribute's value with that of the given value
*
* @param compareRequest the CompareRequest which contains the target DN, attribute name and value
* @return compare operation's future
* @throws LdapException
*/
public CompareFuture compareAsync( CompareRequest compareRequest ) throws LdapException
{
checkSession();
CompareRequestCodec compareReqCodec = new CompareRequestCodec();
int newId = messageId.incrementAndGet();
compareRequest.setMessageId( newId );
compareReqCodec.setMessageId( newId );
compareReqCodec.setEntry( compareRequest.getEntryDn() );
compareReqCodec.setAttributeDesc( compareRequest.getAttrName() );
compareReqCodec.setAssertionValue( compareRequest.getValue() );
setControls( compareRequest.getControls(), compareReqCodec );
CompareFuture compareFuture = new CompareFuture( this, newId );
addToFutureMap( newId, compareFuture );
// Send the request to the server
WriteFuture writeFuture = ldapSession.write( compareReqCodec );
// Wait for the message to be sent to the server
if ( !writeFuture.awaitUninterruptibly( getTimeout( 0 ) ) )
{
// We didn't received anything : this is an error
LOG.error( "Compare failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
// Ok, done return the future
return compareFuture;
}
/**
* converts the CompareResponseCodec to CompareResponse.
*/
private CompareResponse convert( CompareResponseCodec compareRespCodec )
{
CompareResponse compareResponse = new CompareResponse();
compareResponse.setMessageId( compareRespCodec.getMessageId() );
compareResponse.setLdapResult( convert( compareRespCodec.getLdapResult() ) );
return compareResponse;
}
/**
* converts the DeleteResponseCodec to DeleteResponse object.
*/
private DeleteResponse convert( DelResponseCodec delRespCodec )
{
DeleteResponse response = new DeleteResponse();
response.setMessageId( delRespCodec.getMessageId() );
response.setLdapResult( convert( delRespCodec.getLdapResult() ) );
return response;
}
/**
* @see #extended(OID, byte[])
*/
public ExtendedResponse extended( String oid ) throws LdapException
{
return extended( oid, null );
}
/**
* @see #extended(OID, byte[])
*/
public ExtendedResponse extended( String oid, byte[] value ) throws LdapException
{
try
{
return extended( new OID( oid ), value );
}
catch ( DecoderException e )
{
String msg = "Failed to decode the OID " + oid;
LOG.error( msg );
throw new LdapException( msg, e );
}
}
/**
* @see #extended(OID, byte[])
*/
public ExtendedResponse extended( OID oid ) throws LdapException
{
return extended( oid, null );
}
/**
* sends a extended operation request to the server with the given OID and value
*
* @param oid the object identifier of the extended operation
* @param value value to be used by the extended operation, can be a null value
* @return extended operation's response
* @throws LdapException
*/
public ExtendedResponse extended( OID oid, byte[] value ) throws LdapException
{
ExtendedRequest extRequest = new ExtendedRequest( oid );
extRequest.setValue( value );
return extended( extRequest );
}
/**
* Performs an extended operation based on the Extended request object.
*
* @param extendedRequest the extended operation's request
* @return Extended operation's response
* @throws LdapException If the DN is not valid or if the extended operation failed
*/
public ExtendedResponse extended( ExtendedRequest extendedRequest ) throws LdapException
{
ExtendedFuture extendedFuture = extendedAsync( extendedRequest );
// Get the result from the future
try
{
// Read the response, waiting for it if not available immediately
long timeout = getTimeout( extendedRequest.getTimeout() );
// Get the response, blocking
ExtendedResponse extendedResponse = ( ExtendedResponse ) extendedFuture.get( timeout, TimeUnit.MILLISECONDS );
if ( extendedResponse == null )
{
// We didn't received anything : this is an error
LOG.error( "Extended failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
if ( extendedResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
{
// Everything is fine, return the response
LOG.debug( "Extended successful : {}", extendedResponse );
}
else
{
// We have had an error
LOG.debug( "Extended failed : {}", extendedResponse );
}
return extendedResponse;
}
catch ( TimeoutException te )
{
// Send an abandon request
if ( !extendedFuture.isCancelled() )
{
abandon( extendedRequest.getMessageId() );
}
// We didn't received anything : this is an error
LOG.error( "Extended failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
catch ( Exception ie )
{
// Catch all other exceptions
LOG.error( NO_RESPONSE_ERROR, ie );
LdapException ldapException = new LdapException( NO_RESPONSE_ERROR );
ldapException.initCause( ie );
// Send an abandon request
if ( !extendedFuture.isCancelled() )
{
abandon( extendedRequest.getMessageId() );
}
throw ldapException;
}
}
/**
* Asynchronously requests the server to perform an extended operation based on the given request.
*
* @param extendedRequest the object containing the details of the extended operation to be performed
* @return extended operation's Future
* @throws LdapException
*/
public ExtendedFuture extendedAsync( ExtendedRequest extendedRequest ) throws LdapException
{
checkSession();
ExtendedRequestCodec extReqCodec = new ExtendedRequestCodec();
int newId = messageId.incrementAndGet();
extReqCodec.setMessageId( newId );
extReqCodec.setMessageId( newId );
extReqCodec.setRequestName( extendedRequest.getOid() );
extReqCodec.setRequestValue( extendedRequest.getValue() );
setControls( extendedRequest.getControls(), extReqCodec );
ExtendedFuture extendedFuture = new ExtendedFuture( this, newId );
addToFutureMap( newId, extendedFuture );
// Send the request to the server
WriteFuture writeFuture = ldapSession.write( extReqCodec );
// Wait for the message to be sent to the server
if ( !writeFuture.awaitUninterruptibly( getTimeout( 0 ) ) )
{
// We didn't received anything : this is an error
LOG.error( "Extended failed : timeout occured" );
throw new LdapException( TIME_OUT_ERROR );
}
// Ok, done return the future
return extendedFuture;
}
/**
* @see #lookup(String, String...)
*/
public SearchResponse lookup( String dn ) throws LdapException
{
return lookup( dn, SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
}
/**
* searches for an entry having the given DN
*
* @param dn the DN of the entry to be fetched
* @param attributes the attributes to be returned along with entry
* @return the Entry with the given DN or null if no entry exists with that DN
* @throws LdapException in case of any problems while searching for the DN
*/
public SearchResponse lookup( String dn, String... attributes ) throws LdapException
{
SearchResponse resp = null;
try
{
Cursor<SearchResponse> cursor = search( dn, "(objectClass=*)", SearchScope.OBJECT, attributes );
if( cursor.next() )
{
resp = cursor.get();
}
cursor.close();
}
catch( Exception e )
{
throw new LdapException( e );
}
return resp;
}
/**
* converts the ExtendedResponseCodec to ExtendedResponse.
*/
private ExtendedResponse convert( ExtendedResponseCodec extRespCodec )
{
ExtendedResponse extResponse = new ExtendedResponse();
OID oid = null;
try
{
if ( extRespCodec.getResponseName() != null )
{
oid = new OID( extRespCodec.getResponseName() );
}
}
catch ( DecoderException e )
{
// can happen in case of a PROTOCOL_ERROR result, ignore
//LOG.error( "invalid response name {}", extRespCodec.getResponseName() );
}
extResponse.setOid( oid );
extResponse.setValue( extRespCodec.getResponse() );
extResponse.setMessageId( extRespCodec.getMessageId() );
extResponse.setLdapResult( convert( extRespCodec.getLdapResult() ) );
return extResponse;
}
/**
* checks if a control with the given OID is supported
*
* @param controlOID the OID of the control
* @return true if the control is supported, false otherwise
*/
public boolean isControlSupported( String controlOID ) throws LdapException
{
return getSupportedControls().contains( controlOID );
}
/**
* get the Controls supported by server.
*
* @return a list of control OIDs supported by server
* @throws LdapException
*/
public List<String> getSupportedControls() throws LdapException
{
if ( supportedControls != null )
{
return supportedControls;
}
if ( rootDSE == null )
{
fetchRootDSE();
}
supportedControls = new ArrayList<String>();
EntryAttribute attr = rootDSE.get( SchemaConstants.SUPPORTED_CONTROL_AT );
for (Value<?> value:attr)
{
supportedControls.add( value.getString() );
}
return supportedControls;
}
/**
* loads the default schema that is bundled in the shared-ldap-schema.jar
*
* @throws LdapException in case of problems while loading the schema
*/
public void loadSchema() throws LdapException
{
try
{
JarLdifSchemaLoader jarSchemaLoader = new JarLdifSchemaLoader();
schemaManager = new DefaultSchemaManager( jarSchemaLoader );
schemaManager.loadAllEnabled();
if( ! schemaManager.getErrors().isEmpty() )
{
String msg = "there are errors while loading the schema";
LOG.error( msg + " {}", schemaManager.getErrors() );
throw new LdapException( msg );
}
}
catch( LdapException le )
{
throw le;
}
catch( Exception e )
{
LOG.error( "failed to load the schema", e );
throw new LdapException( e );
}
}
public SchemaManager getSchemaManager()
{
return schemaManager;
}
/**
* fetches the rootDSE from the server
* @throws LdapException
*/
private void fetchRootDSE() throws LdapException
{
Cursor<SearchResponse> cursor = null;
try
{
cursor = search( "", "(objectClass=*)", SearchScope.OBJECT, "*", "+" );
cursor.next();
SearchResultEntry searchRes = ( SearchResultEntry ) cursor.get();
rootDSE = searchRes.getEntry();
}
catch ( Exception e )
{
String msg = "Failed to fetch the RootDSE";
LOG.error( msg );
throw new LdapException( msg, e );
}
finally
{
if ( cursor != null )
{
try
{
cursor.close();
}
catch ( Exception e )
{
LOG.error( "Failed to close open cursor", e );
}
}
}
}
/**
* gives the configuration information of the connection
*
* @return the configuration of the connection
*/
public LdapConnectionConfig getConfig()
{
return config;
}
private void addControls( LdapMessageCodec codec, AbstractMessage message )
{
List<Control> ccList = codec.getControls();
if ( ccList != null )
{
for ( Control cc : ccList )
{
// FIXME why the cc is coming as null!?
if ( cc == null )
{
continue;
}
Control control = new ControlImpl( cc.getOid() );
control.setValue( cc.getValue() );
control.setCritical( cc.isCritical() );
message.add( control );
}
}
}
/**
* removes the Objects associated with the given message ID
* from future and response queue maps
*
* @param msgId id of the message
*/
private void removeFromFutureMaps( int msgId )
{
getFromFutureMap( msgId );
}
/**
* clears the async listener, responseQueue and future mapppings to the corresponding request IDs
*/
private void clearMaps()
{
futureMap.clear();
}
/**
* checks if there is a ResponseFuture associated with the given message id
*
* @param messageId ID of the request
* @return true if there is a non-null future exists, false otherwise
*/
public boolean doesFutureExistFor( Integer messageId )
{
return futureMap.get( messageId ) != null;
}
}