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