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