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-2014 ForgeRock AS 025 */ 026package org.opends.server.protocols.http; 027 028import java.util.LinkedHashSet; 029import java.util.concurrent.atomic.AtomicInteger; 030 031import javax.servlet.http.HttpServletResponse; 032 033import org.forgerock.i18n.slf4j.LocalizedLogger; 034import org.forgerock.opendj.ldap.AbstractAsynchronousConnection; 035import org.forgerock.opendj.ldap.ByteString; 036import org.forgerock.opendj.ldap.ConnectionEventListener; 037import org.forgerock.opendj.ldap.LdapPromise; 038import org.forgerock.opendj.ldap.spi.LdapPromiseImpl; 039import org.forgerock.opendj.ldap.IntermediateResponseHandler; 040import org.forgerock.opendj.ldap.ResultCode; 041import org.forgerock.opendj.ldap.SearchResultHandler; 042import org.forgerock.opendj.ldap.requests.AbandonRequest; 043import org.forgerock.opendj.ldap.requests.AddRequest; 044import org.forgerock.opendj.ldap.requests.BindRequest; 045import org.forgerock.opendj.ldap.requests.CompareRequest; 046import org.forgerock.opendj.ldap.requests.DeleteRequest; 047import org.forgerock.opendj.ldap.requests.ExtendedRequest; 048import org.forgerock.opendj.ldap.requests.ModifyDNRequest; 049import org.forgerock.opendj.ldap.requests.ModifyRequest; 050import org.forgerock.opendj.ldap.requests.SearchRequest; 051import org.forgerock.opendj.ldap.requests.SimpleBindRequest; 052import org.forgerock.opendj.ldap.requests.UnbindRequest; 053import org.forgerock.opendj.ldap.responses.BindResult; 054import org.forgerock.opendj.ldap.responses.CompareResult; 055import org.forgerock.opendj.ldap.responses.ExtendedResult; 056import org.forgerock.opendj.ldap.responses.Result; 057import org.opends.server.core.AbandonOperation; 058import org.opends.server.core.AbandonOperationBasis; 059import org.opends.server.core.AddOperation; 060import org.opends.server.core.AddOperationBasis; 061import org.opends.server.core.BindOperation; 062import org.opends.server.core.BindOperationBasis; 063import org.opends.server.core.BoundedWorkQueueStrategy; 064import org.opends.server.core.CompareOperation; 065import org.opends.server.core.CompareOperationBasis; 066import org.opends.server.core.DeleteOperation; 067import org.opends.server.core.DeleteOperationBasis; 068import org.opends.server.core.ExtendedOperation; 069import org.opends.server.core.ExtendedOperationBasis; 070import org.opends.server.core.ModifyDNOperation; 071import org.opends.server.core.ModifyDNOperationBasis; 072import org.opends.server.core.ModifyOperation; 073import org.opends.server.core.ModifyOperationBasis; 074import org.opends.server.core.QueueingStrategy; 075import org.opends.server.core.SearchOperation; 076import org.opends.server.core.SearchOperationBasis; 077import org.opends.server.core.UnbindOperation; 078import org.opends.server.core.UnbindOperationBasis; 079import org.opends.server.protocols.ldap.AbandonRequestProtocolOp; 080import org.opends.server.protocols.ldap.AddRequestProtocolOp; 081import org.opends.server.protocols.ldap.BindRequestProtocolOp; 082import org.opends.server.protocols.ldap.CompareRequestProtocolOp; 083import org.opends.server.protocols.ldap.DeleteRequestProtocolOp; 084import org.opends.server.protocols.ldap.ExtendedRequestProtocolOp; 085import org.opends.server.protocols.ldap.LDAPMessage; 086import org.opends.server.protocols.ldap.ModifyDNRequestProtocolOp; 087import org.opends.server.protocols.ldap.ModifyRequestProtocolOp; 088import org.opends.server.protocols.ldap.ProtocolOp; 089import org.opends.server.protocols.ldap.SearchRequestProtocolOp; 090import org.opends.server.protocols.ldap.UnbindRequestProtocolOp; 091import org.opends.server.types.AuthenticationInfo; 092import org.opends.server.types.DisconnectReason; 093import org.opends.server.types.Operation; 094 095import static org.forgerock.opendj.adapter.server3x.Converters.*; 096import static org.forgerock.opendj.ldap.ByteString.*; 097import static org.forgerock.opendj.ldap.LdapException.*; 098import static org.forgerock.opendj.ldap.spi.LdapPromiseImpl.*; 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 /** {@inheritDoc} */ 243 @Override 244 public LdapPromise<Void> abandonAsync(AbandonRequest request) 245 { 246 final int messageID = nextMessageID.getAndIncrement(); 247 return enqueueOperation(new AbandonOperationBasis(clientConnection, messageID, messageID, 248 to(request.getControls()), request.getRequestID())); 249 } 250 251 /** {@inheritDoc} */ 252 @Override 253 public LdapPromise<Result> addAsync(AddRequest request, IntermediateResponseHandler intermediateResponseHandler) 254 { 255 final int messageID = nextMessageID.getAndIncrement(); 256 return enqueueOperation(new AddOperationBasis(clientConnection, messageID, messageID, to(request.getControls()), 257 valueOf(request.getName()), to(request.getAllAttributes()))); 258 } 259 260 /** {@inheritDoc} */ 261 @Override 262 public void addConnectionEventListener(ConnectionEventListener listener) 263 { 264 // not useful so far 265 } 266 267 /** {@inheritDoc} */ 268 @Override 269 public LdapPromise<BindResult> bindAsync(BindRequest request, 270 IntermediateResponseHandler intermediateResponseHandler) 271 { 272 final int messageID = nextMessageID.getAndIncrement(); 273 String userName = request.getName(); 274 byte[] password = ((SimpleBindRequest) request).getPassword(); 275 return enqueueOperation(new BindOperationBasis(clientConnection, messageID, messageID, to(request.getControls()), 276 "3", ByteString.valueOf(userName), ByteString.wrap(password))); 277 } 278 279 /** {@inheritDoc} */ 280 @Override 281 public void close(UnbindRequest request, String reason) 282 { 283 AuthenticationInfo authInfo = this.clientConnection.getAuthenticationInfo(); 284 if (authInfo != null && authInfo.isAuthenticated()) 285 { 286 final int messageID = nextMessageID.getAndIncrement(); 287 final UnbindOperationBasis operation = 288 new UnbindOperationBasis(clientConnection, messageID, messageID, 289 to(request.getControls())); 290 operation.setInnerOperation(this.clientConnection.isInnerConnection()); 291 292 // run synchronous 293 operation.run(); 294 } 295 else 296 { 297 this.clientConnection.disconnect(DisconnectReason.UNBIND, false, null); 298 } 299 300 // At this point, we try to log the request with OK status code. 301 // If it was already logged, it will be a no op. 302 this.clientConnection.log(HttpServletResponse.SC_OK); 303 304 isClosed = true; 305 } 306 307 /** {@inheritDoc} */ 308 @Override 309 public LdapPromise<CompareResult> compareAsync(CompareRequest request, 310 IntermediateResponseHandler intermediateResponseHandler) 311 { 312 final int messageID = nextMessageID.getAndIncrement(); 313 return enqueueOperation(new CompareOperationBasis(clientConnection, messageID, messageID, 314 to(request.getControls()), valueOf(request.getName()), 315 request.getAttributeDescription().getAttributeType().getOID(), 316 request.getAssertionValue())); 317 } 318 319 /** {@inheritDoc} */ 320 @Override 321 public LdapPromise<Result> deleteAsync(DeleteRequest request, 322 IntermediateResponseHandler intermediateResponseHandler) 323 { 324 final int messageID = nextMessageID.getAndIncrement(); 325 return enqueueOperation(new DeleteOperationBasis(clientConnection, messageID, messageID, 326 to(request.getControls()), valueOf(request.getName()))); 327 } 328 329 /** {@inheritDoc} */ 330 @Override 331 public <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync(ExtendedRequest<R> request, 332 IntermediateResponseHandler intermediateResponseHandler) 333 { 334 final int messageID = nextMessageID.getAndIncrement(); 335 return enqueueOperation(new ExtendedOperationBasis(this.clientConnection, messageID, messageID, 336 to(request.getControls()), request.getOID(), 337 request.getValue())); 338 } 339 340 /** 341 * Return the queueing strategy used by this connection. 342 * 343 * @return The queueing strategy used by this connection 344 */ 345 public QueueingStrategy getQueueingStrategy() 346 { 347 return queueingStrategy; 348 } 349 350 /** {@inheritDoc} */ 351 @Override 352 public boolean isClosed() 353 { 354 return isClosed; 355 } 356 357 /** {@inheritDoc} */ 358 @Override 359 public boolean isValid() 360 { 361 return this.clientConnection.isConnectionValid(); 362 } 363 364 /** {@inheritDoc} */ 365 @Override 366 public LdapPromise<Result> modifyAsync(ModifyRequest request, 367 IntermediateResponseHandler intermediateResponseHandler) 368 { 369 final int messageID = nextMessageID.getAndIncrement(); 370 return enqueueOperation(new ModifyOperationBasis(clientConnection, messageID, messageID, 371 to(request.getControls()), to(request.getName()), 372 toModifications(request.getModifications()))); 373 } 374 375 /** {@inheritDoc} */ 376 @Override 377 public LdapPromise<Result> modifyDNAsync(ModifyDNRequest request, 378 IntermediateResponseHandler intermediateResponseHandler) 379 { 380 final int messageID = nextMessageID.getAndIncrement(); 381 return enqueueOperation(new ModifyDNOperationBasis(clientConnection, messageID, messageID, 382 to(request.getControls()), to(request.getName()), to(request 383 .getNewRDN()), request.isDeleteOldRDN(), to(request 384 .getNewSuperior()))); 385 } 386 387 /** {@inheritDoc} */ 388 @Override 389 public void removeConnectionEventListener(ConnectionEventListener listener) 390 { 391 // not useful so far 392 } 393 394 /** {@inheritDoc} */ 395 @Override 396 public LdapPromise<Result> searchAsync(final SearchRequest request, 397 final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler) 398 { 399 final int messageID = nextMessageID.getAndIncrement(); 400 return enqueueOperation(new SearchOperationBasis(clientConnection, messageID, messageID, 401 to(request.getControls()), to(request.getName()), 402 request.getScope(), request.getDereferenceAliasesPolicy(), 403 request.getSizeLimit(), request.getTimeLimit(), 404 request.isTypesOnly(), toSearchFilter(request.getFilter()), 405 new LinkedHashSet<String>(request.getAttributes())), entryHandler); 406 } 407 408 /** {@inheritDoc} */ 409 @Override 410 public String toString() 411 { 412 return this.clientConnection.toString(); 413 } 414}