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