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}