001 /*
002 * Copyright 2015-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2015-2016 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 */
021 package com.unboundid.ldap.listener;
022
023
024
025 import java.io.File;
026 import java.io.FileOutputStream;
027 import java.io.IOException;
028 import java.io.OutputStream;
029 import java.io.PrintStream;
030 import java.util.ArrayList;
031 import java.util.Date;
032 import java.util.List;
033 import java.util.concurrent.atomic.AtomicBoolean;
034
035 import com.unboundid.ldap.protocol.AbandonRequestProtocolOp;
036 import com.unboundid.ldap.protocol.AddRequestProtocolOp;
037 import com.unboundid.ldap.protocol.BindRequestProtocolOp;
038 import com.unboundid.ldap.protocol.CompareRequestProtocolOp;
039 import com.unboundid.ldap.protocol.DeleteRequestProtocolOp;
040 import com.unboundid.ldap.protocol.ExtendedRequestProtocolOp;
041 import com.unboundid.ldap.protocol.LDAPMessage;
042 import com.unboundid.ldap.protocol.ModifyRequestProtocolOp;
043 import com.unboundid.ldap.protocol.ModifyDNRequestProtocolOp;
044 import com.unboundid.ldap.protocol.SearchRequestProtocolOp;
045 import com.unboundid.ldap.protocol.UnbindRequestProtocolOp;
046 import com.unboundid.ldap.sdk.AddRequest;
047 import com.unboundid.ldap.sdk.BindRequest;
048 import com.unboundid.ldap.sdk.CompareRequest;
049 import com.unboundid.ldap.sdk.Control;
050 import com.unboundid.ldap.sdk.DeleteRequest;
051 import com.unboundid.ldap.sdk.ExtendedRequest;
052 import com.unboundid.ldap.sdk.LDAPException;
053 import com.unboundid.ldap.sdk.ModifyRequest;
054 import com.unboundid.ldap.sdk.ModifyDNRequest;
055 import com.unboundid.ldap.sdk.SearchRequest;
056 import com.unboundid.ldap.sdk.ToCodeArgHelper;
057 import com.unboundid.ldap.sdk.ToCodeHelper;
058 import com.unboundid.util.NotMutable;
059 import com.unboundid.util.StaticUtils;
060 import com.unboundid.util.ThreadSafety;
061 import 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)
073 public 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 }