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}