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    }