001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2013-2016 ForgeRock AS.
015 */
016package org.opends.server.protocols.http;
017
018import static org.forgerock.opendj.adapter.server3x.Converters.*;
019import static org.forgerock.opendj.ldap.ByteString.*;
020import static org.forgerock.opendj.ldap.LdapException.*;
021import static org.forgerock.opendj.ldap.spi.LdapPromiseImpl.*;
022
023import java.util.LinkedHashSet;
024import java.util.concurrent.atomic.AtomicInteger;
025
026import org.forgerock.i18n.slf4j.LocalizedLogger;
027import org.forgerock.opendj.ldap.AbstractAsynchronousConnection;
028import org.forgerock.opendj.ldap.ByteString;
029import org.forgerock.opendj.ldap.ConnectionEventListener;
030import org.forgerock.opendj.ldap.IntermediateResponseHandler;
031import org.forgerock.opendj.ldap.LdapPromise;
032import org.forgerock.opendj.ldap.ResultCode;
033import org.forgerock.opendj.ldap.SearchResultHandler;
034import org.forgerock.opendj.ldap.requests.AbandonRequest;
035import org.forgerock.opendj.ldap.requests.AddRequest;
036import org.forgerock.opendj.ldap.requests.BindRequest;
037import org.forgerock.opendj.ldap.requests.CompareRequest;
038import org.forgerock.opendj.ldap.requests.DeleteRequest;
039import org.forgerock.opendj.ldap.requests.ExtendedRequest;
040import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
041import org.forgerock.opendj.ldap.requests.ModifyRequest;
042import org.forgerock.opendj.ldap.requests.SearchRequest;
043import org.forgerock.opendj.ldap.requests.SimpleBindRequest;
044import org.forgerock.opendj.ldap.requests.UnbindRequest;
045import org.forgerock.opendj.ldap.responses.BindResult;
046import org.forgerock.opendj.ldap.responses.CompareResult;
047import org.forgerock.opendj.ldap.responses.ExtendedResult;
048import org.forgerock.opendj.ldap.responses.Result;
049import org.forgerock.opendj.ldap.spi.LdapPromiseImpl;
050import org.opends.server.core.AbandonOperation;
051import org.opends.server.core.AbandonOperationBasis;
052import org.opends.server.core.AddOperation;
053import org.opends.server.core.AddOperationBasis;
054import org.opends.server.core.BindOperation;
055import org.opends.server.core.BindOperationBasis;
056import org.opends.server.core.BoundedWorkQueueStrategy;
057import org.opends.server.core.CompareOperation;
058import org.opends.server.core.CompareOperationBasis;
059import org.opends.server.core.DeleteOperation;
060import org.opends.server.core.DeleteOperationBasis;
061import org.opends.server.core.ExtendedOperation;
062import org.opends.server.core.ExtendedOperationBasis;
063import org.opends.server.core.ModifyDNOperation;
064import org.opends.server.core.ModifyDNOperationBasis;
065import org.opends.server.core.ModifyOperation;
066import org.opends.server.core.ModifyOperationBasis;
067import org.opends.server.core.QueueingStrategy;
068import org.opends.server.core.SearchOperation;
069import org.opends.server.core.SearchOperationBasis;
070import org.opends.server.core.UnbindOperation;
071import org.opends.server.core.UnbindOperationBasis;
072import org.opends.server.protocols.ldap.AbandonRequestProtocolOp;
073import org.opends.server.protocols.ldap.AddRequestProtocolOp;
074import org.opends.server.protocols.ldap.BindRequestProtocolOp;
075import org.opends.server.protocols.ldap.CompareRequestProtocolOp;
076import org.opends.server.protocols.ldap.DeleteRequestProtocolOp;
077import org.opends.server.protocols.ldap.ExtendedRequestProtocolOp;
078import org.opends.server.protocols.ldap.LDAPMessage;
079import org.opends.server.protocols.ldap.ModifyDNRequestProtocolOp;
080import org.opends.server.protocols.ldap.ModifyRequestProtocolOp;
081import org.opends.server.protocols.ldap.ProtocolOp;
082import org.opends.server.protocols.ldap.SearchRequestProtocolOp;
083import org.opends.server.protocols.ldap.UnbindRequestProtocolOp;
084import org.opends.server.types.AuthenticationInfo;
085import org.opends.server.types.DisconnectReason;
086import org.opends.server.types.Operation;
087
088/**
089 * Adapter class between LDAP SDK's {@link org.forgerock.opendj.ldap.Connection}
090 * and OpenDJ server's
091 * {@link org.opends.server.protocols.http.HTTPClientConnection}.
092 */
093public class SdkConnectionAdapter extends AbstractAsynchronousConnection
094{
095
096  /** The tracer object for the debug logger. */
097  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
098
099  /** The HTTP client connection being "adapted". */
100  private final HTTPClientConnection clientConnection;
101
102  /**
103   * The next message ID (and operation ID) that should be used for this
104   * connection.
105   */
106  private final AtomicInteger nextMessageID = new AtomicInteger(0);
107
108  /** The queueing strategy used for this connection. */
109  private final QueueingStrategy queueingStrategy;
110
111  /**
112   * Whether this connection has been closed by calling {@link #close()} or
113   * {@link #close(UnbindRequest, String)}.
114   */
115  private boolean isClosed;
116
117  /**
118   * Constructor.
119   *
120   * @param clientConnection
121   *          the HTTP client connection being "adapted"
122   */
123  public SdkConnectionAdapter(HTTPClientConnection clientConnection)
124  {
125    this.clientConnection = clientConnection;
126    this.queueingStrategy =
127        new BoundedWorkQueueStrategy(clientConnection.getConnectionHandler()
128            .getCurrentConfig().getMaxConcurrentOpsPerConnection());
129  }
130
131  private <R> LdapPromise<R> enqueueOperation(Operation operation)
132  {
133    return enqueueOperation(operation, null);
134  }
135
136  @SuppressWarnings({ "rawtypes", "unchecked" })
137  private <R> LdapPromise<R> enqueueOperation(Operation operation, SearchResultHandler entryHandler)
138  {
139    final LdapPromiseImpl<R> promise = newLdapPromiseImpl(operation.getMessageID());
140
141    try
142    {
143      operation.setInnerOperation(this.clientConnection.isInnerConnection());
144
145      HTTPConnectionHandler connHandler = this.clientConnection.getConnectionHandler();
146      if (connHandler.keepStats())
147      {
148        connHandler.getStatTracker().updateMessageRead(
149            new LDAPMessage(operation.getMessageID(), toRequestProtocolOp(operation)));
150      }
151
152      // need this raw cast here to fool the compiler's generic type safety
153      // Problem here is due to the generic type R on enqueueOperation()
154      clientConnection.addOperationInProgress(operation, (LdapPromiseImpl) promise, entryHandler);
155      queueingStrategy.enqueueRequest(operation);
156    }
157    catch (Exception e)
158    {
159      logger.traceException(e);
160      clientConnection.removeOperationInProgress(operation.getMessageID());
161      // TODO JNR add error message??
162      promise.handleException(newLdapException(ResultCode.OPERATIONS_ERROR, e));
163    }
164
165    return promise;
166  }
167
168  private ProtocolOp toRequestProtocolOp(Operation operation)
169  {
170    if (operation instanceof AbandonOperation)
171    {
172      final AbandonOperation op = (AbandonOperation) operation;
173      return new AbandonRequestProtocolOp(op.getIDToAbandon());
174    }
175    else if (operation instanceof AddOperation)
176    {
177      final AddOperation op = (AddOperation) operation;
178      return new AddRequestProtocolOp(op.getRawEntryDN(),
179          op.getRawAttributes());
180    }
181    else if (operation instanceof BindOperation)
182    {
183      final BindOperation op = (BindOperation) operation;
184      return new BindRequestProtocolOp(op.getRawBindDN(),
185          op.getSASLMechanism(), op.getSASLCredentials());
186    }
187    else if (operation instanceof CompareOperation)
188    {
189      final CompareOperation op = (CompareOperation) operation;
190      return new CompareRequestProtocolOp(op.getRawEntryDN(), op
191          .getRawAttributeType(), op.getAssertionValue());
192    }
193    else if (operation instanceof DeleteOperation)
194    {
195      final DeleteOperation op = (DeleteOperation) operation;
196      return new DeleteRequestProtocolOp(op.getRawEntryDN());
197    }
198    else if (operation instanceof ExtendedOperation)
199    {
200      final ExtendedOperation op = (ExtendedOperation) operation;
201      return new ExtendedRequestProtocolOp(op.getRequestOID(), op
202          .getRequestValue());
203    }
204    else if (operation instanceof ModifyDNOperation)
205    {
206      final ModifyDNOperation op = (ModifyDNOperation) operation;
207      return new ModifyDNRequestProtocolOp(op.getRawEntryDN(), op
208          .getRawNewRDN(), op.deleteOldRDN(), op.getRawNewSuperior());
209    }
210    else if (operation instanceof ModifyOperation)
211    {
212      final ModifyOperation op = (ModifyOperation) operation;
213      return new ModifyRequestProtocolOp(op.getRawEntryDN(), op
214          .getRawModifications());
215    }
216    else if (operation instanceof SearchOperation)
217    {
218      final SearchOperation op = (SearchOperation) operation;
219      return new SearchRequestProtocolOp(op.getRawBaseDN(), op.getScope(), op
220          .getDerefPolicy(), op.getSizeLimit(), op.getTimeLimit(), op
221          .getTypesOnly(), op.getRawFilter(), op.getAttributes());
222    }
223    else if (operation instanceof UnbindOperation)
224    {
225      return new UnbindRequestProtocolOp();
226    }
227    throw new RuntimeException("Not implemented for operation " + operation);
228  }
229
230  @Override
231  public LdapPromise<Void> abandonAsync(AbandonRequest request)
232  {
233    final int messageID = nextMessageID.getAndIncrement();
234    return enqueueOperation(new AbandonOperationBasis(clientConnection, messageID, messageID,
235        to(request.getControls()), request.getRequestID()));
236  }
237
238  @Override
239  public LdapPromise<Result> addAsync(AddRequest request, IntermediateResponseHandler intermediateResponseHandler)
240  {
241    final int messageID = nextMessageID.getAndIncrement();
242    return enqueueOperation(new AddOperationBasis(clientConnection, messageID, messageID, to(request.getControls()),
243        valueOfObject(request.getName()), to(request.getAllAttributes())));
244  }
245
246  @Override
247  public void addConnectionEventListener(ConnectionEventListener listener)
248  {
249    // not useful so far
250  }
251
252  @Override
253  public LdapPromise<BindResult> bindAsync(BindRequest request,
254      IntermediateResponseHandler intermediateResponseHandler)
255  {
256    final int messageID = nextMessageID.getAndIncrement();
257    String userName = request.getName();
258    byte[] password = ((SimpleBindRequest) request).getPassword();
259    return enqueueOperation(new BindOperationBasis(clientConnection, messageID, messageID, to(request.getControls()),
260        "3", ByteString.valueOfUtf8(userName), ByteString.wrap(password)));
261  }
262
263  @Override
264  public void close(UnbindRequest request, String reason)
265  {
266    AuthenticationInfo authInfo = this.clientConnection.getAuthenticationInfo();
267    if (authInfo != null && authInfo.isAuthenticated())
268    {
269      final int messageID = nextMessageID.getAndIncrement();
270      final UnbindOperationBasis operation = new UnbindOperationBasis(
271          clientConnection, messageID, messageID, to(request.getControls()));
272      operation.setInnerOperation(this.clientConnection.isInnerConnection());
273
274      // run synchronous
275      operation.run();
276    }
277    else
278    {
279      this.clientConnection.disconnect(DisconnectReason.UNBIND, false, null);
280    }
281    isClosed = true;
282  }
283
284  @Override
285  public LdapPromise<CompareResult> compareAsync(CompareRequest request,
286      IntermediateResponseHandler intermediateResponseHandler)
287  {
288    final int messageID = nextMessageID.getAndIncrement();
289    return enqueueOperation(new CompareOperationBasis(clientConnection, messageID, messageID,
290        to(request.getControls()), valueOfObject(request.getName()),
291        request.getAttributeDescription().getAttributeType().getOID(),
292        request.getAssertionValue()));
293  }
294
295  @Override
296  public LdapPromise<Result> deleteAsync(DeleteRequest request,
297      IntermediateResponseHandler intermediateResponseHandler)
298  {
299    final int messageID = nextMessageID.getAndIncrement();
300    return enqueueOperation(new DeleteOperationBasis(clientConnection, messageID, messageID,
301        to(request.getControls()), valueOfObject(request.getName())));
302  }
303
304  @Override
305  public <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync(ExtendedRequest<R> request,
306      IntermediateResponseHandler intermediateResponseHandler)
307  {
308    final int messageID = nextMessageID.getAndIncrement();
309    ExtendedOperation op = new ExtendedOperationBasis(
310        clientConnection, messageID, messageID, to(request.getControls()), request.getOID(), request.getValue());
311    op.setAuthorizationEntry(clientConnection.getAuthenticationInfo().getAuthorizationEntry());
312    return enqueueOperation(op);
313  }
314
315  /**
316   * Return the queueing strategy used by this connection.
317   *
318   * @return The queueing strategy used by this connection
319   */
320  public QueueingStrategy getQueueingStrategy()
321  {
322    return queueingStrategy;
323  }
324
325  @Override
326  public boolean isClosed()
327  {
328    return isClosed;
329  }
330
331  @Override
332  public boolean isValid()
333  {
334    return this.clientConnection.isConnectionValid();
335  }
336
337  @Override
338  public LdapPromise<Result> modifyAsync(ModifyRequest request,
339      IntermediateResponseHandler intermediateResponseHandler)
340  {
341    final int messageID = nextMessageID.getAndIncrement();
342    return enqueueOperation(new ModifyOperationBasis(clientConnection, messageID, messageID,
343        to(request.getControls()), request.getName(),
344        toModifications(request.getModifications())));
345  }
346
347  @Override
348  public LdapPromise<Result> modifyDNAsync(ModifyDNRequest request,
349      IntermediateResponseHandler intermediateResponseHandler)
350  {
351    final int messageID = nextMessageID.getAndIncrement();
352    return enqueueOperation(new ModifyDNOperationBasis(clientConnection, messageID, messageID,
353        to(request.getControls()), request.getName(), request.getNewRDN(),
354        request.isDeleteOldRDN(), request.getNewSuperior()));
355  }
356
357  @Override
358  public void removeConnectionEventListener(ConnectionEventListener listener)
359  {
360    // not useful so far
361  }
362
363  @Override
364  public LdapPromise<Result> searchAsync(final SearchRequest request,
365      final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler)
366  {
367    final int messageID = nextMessageID.getAndIncrement();
368    return enqueueOperation(new SearchOperationBasis(clientConnection, messageID, messageID,
369        to(request.getControls()), request.getName(),
370        request.getScope(), request.getDereferenceAliasesPolicy(),
371        request.getSizeLimit(), request.getTimeLimit(),
372        request.isTypesOnly(), toSearchFilter(request.getFilter()),
373        new LinkedHashSet<String>(request.getAttributes())), entryHandler);
374  }
375
376  @Override
377  public String toString()
378  {
379    return this.clientConnection.toString();
380  }
381}