001/* 002 * Copyright 2015-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2015-2020 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2015-2020 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.listener; 037 038 039 040import java.io.File; 041import java.io.FileOutputStream; 042import java.io.IOException; 043import java.io.OutputStream; 044import java.io.PrintStream; 045import java.util.ArrayList; 046import java.util.Date; 047import java.util.List; 048import java.util.concurrent.atomic.AtomicBoolean; 049 050import com.unboundid.ldap.protocol.AbandonRequestProtocolOp; 051import com.unboundid.ldap.protocol.AddRequestProtocolOp; 052import com.unboundid.ldap.protocol.BindRequestProtocolOp; 053import com.unboundid.ldap.protocol.CompareRequestProtocolOp; 054import com.unboundid.ldap.protocol.DeleteRequestProtocolOp; 055import com.unboundid.ldap.protocol.ExtendedRequestProtocolOp; 056import com.unboundid.ldap.protocol.LDAPMessage; 057import com.unboundid.ldap.protocol.ModifyRequestProtocolOp; 058import com.unboundid.ldap.protocol.ModifyDNRequestProtocolOp; 059import com.unboundid.ldap.protocol.SearchRequestProtocolOp; 060import com.unboundid.ldap.protocol.UnbindRequestProtocolOp; 061import com.unboundid.ldap.sdk.AddRequest; 062import com.unboundid.ldap.sdk.BindRequest; 063import com.unboundid.ldap.sdk.CompareRequest; 064import com.unboundid.ldap.sdk.Control; 065import com.unboundid.ldap.sdk.DeleteRequest; 066import com.unboundid.ldap.sdk.ExtendedRequest; 067import com.unboundid.ldap.sdk.LDAPException; 068import com.unboundid.ldap.sdk.ModifyRequest; 069import com.unboundid.ldap.sdk.ModifyDNRequest; 070import com.unboundid.ldap.sdk.SearchRequest; 071import com.unboundid.ldap.sdk.ToCodeArgHelper; 072import com.unboundid.ldap.sdk.ToCodeHelper; 073import com.unboundid.util.NotMutable; 074import com.unboundid.util.StaticUtils; 075import com.unboundid.util.ThreadSafety; 076import com.unboundid.util.ThreadSafetyLevel; 077 078 079 080/** 081 * This class provides a request handler that may be used to create a log file 082 * with code that may be used to generate the requests received from clients. 083 * It will be also be associated with another request handler that will actually 084 * be used to handle the request. 085 */ 086@NotMutable() 087@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 088public final class ToCodeRequestHandler 089 extends LDAPListenerRequestHandler 090{ 091 // Indicates whether any messages have been written to the log so far. 092 private final AtomicBoolean firstMessage; 093 094 // Indicates whether the output should include code that may be used to 095 // process the request and handle the response. 096 private final boolean includeProcessing; 097 098 // The client connection with which this request handler is associated. 099 private final LDAPListenerClientConnection clientConnection; 100 101 // The request handler that actually will be used to process any requests 102 // received. 103 private final LDAPListenerRequestHandler requestHandler; 104 105 // The stream to which the generated code will be written. 106 private final PrintStream logStream; 107 108 // Thread-local lists used to hold the generated code. 109 private final ThreadLocal<List<String>> lineLists; 110 111 112 113 /** 114 * Creates a new LDAP listener request handler that will write a log file with 115 * LDAP SDK code that corresponds to requests received from clients. The 116 * requests will be forwarded on to another request handler for further 117 * processing. 118 * 119 * @param outputFilePath The path to the output file to be which the 120 * generated code should be written. It must not 121 * be {@code null}, and the parent directory must 122 * exist. If a file already exists with the 123 * specified path, then new generated code will be 124 * appended to it. 125 * @param includeProcessing Indicates whether the output should include 126 * sample code for processing the request and 127 * handling the response. 128 * @param requestHandler The request handler that will actually be used 129 * to process any requests received. It must not 130 * be {@code null}. 131 * 132 * @throws IOException If a problem is encountered while opening the 133 * output file for writing. 134 */ 135 public ToCodeRequestHandler(final String outputFilePath, 136 final boolean includeProcessing, 137 final LDAPListenerRequestHandler requestHandler) 138 throws IOException 139 { 140 this(new File(outputFilePath), includeProcessing, requestHandler); 141 } 142 143 144 145 /** 146 * Creates a new LDAP listener request handler that will write a log file with 147 * LDAP SDK code that corresponds to requests received from clients. The 148 * requests will be forwarded on to another request handler for further 149 * processing. 150 * 151 * @param outputFile The output file to be which the generated code 152 * should be written. It must not be {@code null}, 153 * and the parent directory must exist. If the 154 * file already exists, then new generated code 155 * will be appended to it. 156 * @param includeProcessing Indicates whether the output should include 157 * sample code for processing the request and 158 * handling the response. 159 * @param requestHandler The request handler that will actually be used 160 * to process any requests received. It must not 161 * be {@code null}. 162 * 163 * @throws IOException If a problem is encountered while opening the 164 * output file for writing. 165 */ 166 public ToCodeRequestHandler(final File outputFile, 167 final boolean includeProcessing, 168 final LDAPListenerRequestHandler requestHandler) 169 throws IOException 170 { 171 this(new FileOutputStream(outputFile, true), includeProcessing, 172 requestHandler); 173 } 174 175 176 177 /** 178 * Creates a new LDAP listener request handler that will write a log file with 179 * LDAP SDK code that corresponds to requests received from clients. The 180 * requests will be forwarded on to another request handler for further 181 * processing. 182 * 183 * @param outputStream The output stream to which the generated code 184 * will be written. It must not be {@code null}. 185 * @param includeProcessing Indicates whether the output should include 186 * sample code for processing the request and 187 * handling the response. 188 * @param requestHandler The request handler that will actually be used 189 * to process any requests received. It must not 190 * be {@code null}. 191 */ 192 public ToCodeRequestHandler(final OutputStream outputStream, 193 final boolean includeProcessing, 194 final LDAPListenerRequestHandler requestHandler) 195 { 196 logStream = new PrintStream(outputStream, true); 197 198 this.includeProcessing = includeProcessing; 199 this.requestHandler = requestHandler; 200 201 firstMessage = new AtomicBoolean(true); 202 lineLists = new ThreadLocal<>(); 203 clientConnection = null; 204 } 205 206 207 208 /** 209 * Creates a new to code request handler instance for the provided client 210 * connection. 211 * 212 * @param parentHandler The parent handler with which this instance will be 213 * associated. 214 * @param connection The client connection for this instance. 215 * 216 * @throws LDAPException If a problem is encountered while creating a new 217 * instance of the downstream request handler. 218 */ 219 private ToCodeRequestHandler(final ToCodeRequestHandler parentHandler, 220 final LDAPListenerClientConnection connection) 221 throws LDAPException 222 { 223 logStream = parentHandler.logStream; 224 includeProcessing = parentHandler.includeProcessing; 225 requestHandler = parentHandler.requestHandler.newInstance(connection); 226 firstMessage = parentHandler.firstMessage; 227 clientConnection = connection; 228 lineLists = parentHandler.lineLists; 229 } 230 231 232 233 /** 234 * {@inheritDoc} 235 */ 236 @Override() 237 public ToCodeRequestHandler newInstance( 238 final LDAPListenerClientConnection connection) 239 throws LDAPException 240 { 241 return new ToCodeRequestHandler(this, connection); 242 } 243 244 245 246 /** 247 * {@inheritDoc} 248 */ 249 @Override() 250 public void closeInstance() 251 { 252 // We'll always close the downstream request handler instance. 253 requestHandler.closeInstance(); 254 255 256 // We only want to close the log stream if this is the parent instance that 257 // is not associated with any specific connection. 258 if (clientConnection == null) 259 { 260 synchronized (logStream) 261 { 262 logStream.close(); 263 } 264 } 265 } 266 267 268 269 /** 270 * {@inheritDoc} 271 */ 272 @Override() 273 public void processAbandonRequest(final int messageID, 274 final AbandonRequestProtocolOp request, 275 final List<Control> controls) 276 { 277 // The LDAP SDK doesn't provide an AbandonRequest object. In order to 278 // process abandon operations, the LDAP SDK requires the client to have 279 // invoked an asynchronous operation in order to get an AsyncRequestID. 280 // Since this uses LDAPConnection.abandon, then that falls under the 281 // "processing" umbrella. So we'll only log something if we should include 282 // processing details. 283 if (includeProcessing) 284 { 285 final List<String> lineList = getLineList(messageID); 286 287 final ArrayList<ToCodeArgHelper> args = new ArrayList<>(2); 288 args.add(ToCodeArgHelper.createRaw( 289 "asyncRequestID" + request.getIDToAbandon(), "Async Request ID")); 290 if (! controls.isEmpty()) 291 { 292 final Control[] controlArray = new Control[controls.size()]; 293 controls.toArray(controlArray); 294 args.add(ToCodeArgHelper.createControlArray(controlArray, 295 "Request Controls")); 296 } 297 298 ToCodeHelper.generateMethodCall(lineList, 0, null, null, 299 "connection.abandon", args); 300 301 writeLines(lineList); 302 } 303 304 requestHandler.processAbandonRequest(messageID, request, controls); 305 } 306 307 308 309 /** 310 * {@inheritDoc} 311 */ 312 @Override() 313 public LDAPMessage processAddRequest(final int messageID, 314 final AddRequestProtocolOp request, 315 final List<Control> controls) 316 { 317 final List<String> lineList = getLineList(messageID); 318 319 final String requestID = "conn" + clientConnection.getConnectionID() + 320 "Msg" + messageID + "Add"; 321 final AddRequest addRequest = 322 request.toAddRequest(getControlArray(controls)); 323 addRequest.toCode(lineList, requestID, 0, includeProcessing); 324 writeLines(lineList); 325 326 return requestHandler.processAddRequest(messageID, request, controls); 327 } 328 329 330 331 /** 332 * {@inheritDoc} 333 */ 334 @Override() 335 public LDAPMessage processBindRequest(final int messageID, 336 final BindRequestProtocolOp request, 337 final List<Control> controls) 338 { 339 final List<String> lineList = getLineList(messageID); 340 341 final String requestID = "conn" + clientConnection.getConnectionID() + 342 "Msg" + messageID + "Bind"; 343 final BindRequest bindRequest = 344 request.toBindRequest(getControlArray(controls)); 345 bindRequest.toCode(lineList, requestID, 0, includeProcessing); 346 writeLines(lineList); 347 348 return requestHandler.processBindRequest(messageID, request, controls); 349 } 350 351 352 353 /** 354 * {@inheritDoc} 355 */ 356 @Override() 357 public LDAPMessage processCompareRequest(final int messageID, 358 final CompareRequestProtocolOp request, 359 final List<Control> controls) 360 { 361 final List<String> lineList = getLineList(messageID); 362 363 final String requestID = "conn" + clientConnection.getConnectionID() + 364 "Msg" + messageID + "Compare"; 365 final CompareRequest compareRequest = 366 request.toCompareRequest(getControlArray(controls)); 367 compareRequest.toCode(lineList, requestID, 0, includeProcessing); 368 writeLines(lineList); 369 370 return requestHandler.processCompareRequest(messageID, request, controls); 371 } 372 373 374 375 /** 376 * {@inheritDoc} 377 */ 378 @Override() 379 public LDAPMessage processDeleteRequest(final int messageID, 380 final DeleteRequestProtocolOp request, 381 final List<Control> controls) 382 { 383 final List<String> lineList = getLineList(messageID); 384 385 final String requestID = "conn" + clientConnection.getConnectionID() + 386 "Msg" + messageID + "Delete"; 387 final DeleteRequest deleteRequest = 388 request.toDeleteRequest(getControlArray(controls)); 389 deleteRequest.toCode(lineList, requestID, 0, includeProcessing); 390 writeLines(lineList); 391 392 return requestHandler.processDeleteRequest(messageID, request, controls); 393 } 394 395 396 397 /** 398 * {@inheritDoc} 399 */ 400 @Override() 401 public LDAPMessage processExtendedRequest(final int messageID, 402 final ExtendedRequestProtocolOp request, 403 final List<Control> controls) 404 { 405 final List<String> lineList = getLineList(messageID); 406 407 final String requestID = "conn" + clientConnection.getConnectionID() + 408 "Msg" + messageID + "Extended"; 409 final ExtendedRequest extendedRequest = 410 request.toExtendedRequest(getControlArray(controls)); 411 extendedRequest.toCode(lineList, requestID, 0, includeProcessing); 412 writeLines(lineList); 413 414 return requestHandler.processExtendedRequest(messageID, request, controls); 415 } 416 417 418 419 /** 420 * {@inheritDoc} 421 */ 422 @Override() 423 public LDAPMessage processModifyRequest(final int messageID, 424 final ModifyRequestProtocolOp request, 425 final List<Control> controls) 426 { 427 final List<String> lineList = getLineList(messageID); 428 429 final String requestID = "conn" + clientConnection.getConnectionID() + 430 "Msg" + messageID + "Modify"; 431 final ModifyRequest modifyRequest = 432 request.toModifyRequest(getControlArray(controls)); 433 modifyRequest.toCode(lineList, requestID, 0, includeProcessing); 434 writeLines(lineList); 435 436 return requestHandler.processModifyRequest(messageID, request, controls); 437 } 438 439 440 441 /** 442 * {@inheritDoc} 443 */ 444 @Override() 445 public LDAPMessage processModifyDNRequest(final int messageID, 446 final ModifyDNRequestProtocolOp request, 447 final List<Control> controls) 448 { 449 final List<String> lineList = getLineList(messageID); 450 451 final String requestID = "conn" + clientConnection.getConnectionID() + 452 "Msg" + messageID + "ModifyDN"; 453 final ModifyDNRequest modifyDNRequest = 454 request.toModifyDNRequest(getControlArray(controls)); 455 modifyDNRequest.toCode(lineList, requestID, 0, includeProcessing); 456 writeLines(lineList); 457 458 return requestHandler.processModifyDNRequest(messageID, request, controls); 459 } 460 461 462 463 /** 464 * {@inheritDoc} 465 */ 466 @Override() 467 public LDAPMessage processSearchRequest(final int messageID, 468 final SearchRequestProtocolOp request, 469 final List<Control> controls) 470 { 471 final List<String> lineList = getLineList(messageID); 472 473 final String requestID = "conn" + clientConnection.getConnectionID() + 474 "Msg" + messageID + "Search"; 475 final SearchRequest searchRequest = 476 request.toSearchRequest(getControlArray(controls)); 477 searchRequest.toCode(lineList, requestID, 0, includeProcessing); 478 writeLines(lineList); 479 480 return requestHandler.processSearchRequest(messageID, request, controls); 481 } 482 483 484 485 /** 486 * {@inheritDoc} 487 */ 488 @Override() 489 public void processUnbindRequest(final int messageID, 490 final UnbindRequestProtocolOp request, 491 final List<Control> controls) 492 { 493 // The LDAP SDK doesn't provide an UnbindRequest object, because it is not 494 // possible to separate an unbind request from a connection closure, which 495 // is done by using LDAPConnection.close method. That falls under the 496 // "processing" umbrella, so we'll only log something if we should include 497 // processing details. 498 if (includeProcessing) 499 { 500 final List<String> lineList = getLineList(messageID); 501 502 final ArrayList<ToCodeArgHelper> args = new ArrayList<>(1); 503 if (! controls.isEmpty()) 504 { 505 final Control[] controlArray = new Control[controls.size()]; 506 controls.toArray(controlArray); 507 args.add(ToCodeArgHelper.createControlArray(controlArray, 508 "Request Controls")); 509 } 510 511 ToCodeHelper.generateMethodCall(lineList, 0, null, null, 512 "connection.close", args); 513 514 writeLines(lineList); 515 } 516 517 requestHandler.processUnbindRequest(messageID, request, controls); 518 } 519 520 521 522 /** 523 * Retrieves a list to use to hold the lines of output. It will include 524 * comments with information about the client that submitted the request. 525 * 526 * @param messageID The message ID for the associated request. 527 * 528 * @return A list to use to hold the lines of output. 529 */ 530 private List<String> getLineList(final int messageID) 531 { 532 // Get a thread-local string list, creating it if necessary. 533 List<String> lineList = lineLists.get(); 534 if (lineList == null) 535 { 536 lineList = new ArrayList<>(20); 537 lineLists.set(lineList); 538 } 539 else 540 { 541 lineList.clear(); 542 } 543 544 545 // Add the appropriate header content to the list. 546 lineList.add("// Time: " + new Date()); 547 lineList.add("// Client Address: " + 548 clientConnection.getSocket().getInetAddress().getHostAddress() + ':' + 549 clientConnection.getSocket().getPort()); 550 lineList.add("// Server Address: " + 551 clientConnection.getSocket().getLocalAddress().getHostAddress() + ':' + 552 clientConnection.getSocket().getLocalPort()); 553 lineList.add("// Connection ID: " + clientConnection.getConnectionID()); 554 lineList.add("// Message ID: " + messageID); 555 556 return lineList; 557 } 558 559 560 561 /** 562 * Writes the lines contained in the provided list to the output stream. 563 * 564 * @param lineList The list containing the lines to be written. 565 */ 566 private void writeLines(final List<String> lineList) 567 { 568 synchronized (logStream) 569 { 570 if (! firstMessage.compareAndSet(true, false)) 571 { 572 logStream.println(); 573 logStream.println(); 574 } 575 576 for (final String s : lineList) 577 { 578 logStream.println(s); 579 } 580 } 581 } 582 583 584 585 /** 586 * Converts the provided list of controls into an array of controls. 587 * 588 * @param controls The list of controls to convert to an array. 589 * 590 * @return An array of controls that corresponds to the provided list. 591 */ 592 private static Control[] getControlArray(final List<Control> controls) 593 { 594 if ((controls == null) || controls.isEmpty()) 595 { 596 return StaticUtils.NO_CONTROLS; 597 } 598 599 final Control[] controlArray = new Control[controls.size()]; 600 return controls.toArray(controlArray); 601 } 602}