View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.http.nio.pool;
28  
29  import java.io.IOException;
30  import java.net.SocketAddress;
31  import java.util.HashMap;
32  import java.util.HashSet;
33  import java.util.Iterator;
34  import java.util.LinkedList;
35  import java.util.ListIterator;
36  import java.util.Map;
37  import java.util.Set;
38  import java.util.concurrent.ConcurrentLinkedQueue;
39  import java.util.concurrent.Future;
40  import java.util.concurrent.TimeUnit;
41  import java.util.concurrent.TimeoutException;
42  import java.util.concurrent.atomic.AtomicBoolean;
43  import java.util.concurrent.locks.Lock;
44  import java.util.concurrent.locks.ReentrantLock;
45  
46  import org.apache.http.annotation.ThreadSafe;
47  import org.apache.http.concurrent.BasicFuture;
48  import org.apache.http.concurrent.FutureCallback;
49  import org.apache.http.nio.reactor.ConnectingIOReactor;
50  import org.apache.http.nio.reactor.IOReactorStatus;
51  import org.apache.http.nio.reactor.IOSession;
52  import org.apache.http.nio.reactor.SessionRequest;
53  import org.apache.http.nio.reactor.SessionRequestCallback;
54  import org.apache.http.pool.ConnPool;
55  import org.apache.http.pool.ConnPoolControl;
56  import org.apache.http.pool.PoolEntry;
57  import org.apache.http.pool.PoolEntryCallback;
58  import org.apache.http.pool.PoolStats;
59  import org.apache.http.util.Args;
60  import org.apache.http.util.Asserts;
61  
62  /**
63   * Abstract non-blocking connection pool.
64   *
65   * @param <T> route
66   * @param <C> connection object
67   * @param <E> pool entry
68   *
69   * @since 4.2
70   */
71  @ThreadSafe
72  public abstract class AbstractNIOConnPool<T, C, E extends PoolEntry<T, C>>
73                                                    implements ConnPool<T, E>, ConnPoolControl<T> {
74  
75      private final ConnectingIOReactor ioreactor;
76      private final NIOConnFactory<T, C> connFactory;
77      private final SocketAddressResolver<T> addressResolver;
78      private final SessionRequestCallback sessionRequestCallback;
79      private final Map<T, RouteSpecificPool<T, C, E>> routeToPool;
80      private final LinkedList<LeaseRequest<T, C, E>> leasingRequests;
81      private final Set<SessionRequest> pending;
82      private final Set<E> leased;
83      private final LinkedList<E> available;
84      private final ConcurrentLinkedQueue<LeaseRequest<T, C, E>> completedRequests;
85      private final Map<T, Integer> maxPerRoute;
86      private final Lock lock;
87      private final AtomicBoolean isShutDown;
88  
89      private volatile int defaultMaxPerRoute;
90      private volatile int maxTotal;
91  
92      /**
93       * @deprecated use {@link AbstractNIOConnPool#AbstractNIOConnPool(ConnectingIOReactor,
94       *   NIOConnFactory, SocketAddressResolver, int, int)}
95       */
96      @Deprecated
97      public AbstractNIOConnPool(
98              final ConnectingIOReactor ioreactor,
99              final NIOConnFactory<T, C> connFactory,
100             final int defaultMaxPerRoute,
101             final int maxTotal) {
102         super();
103         Args.notNull(ioreactor, "I/O reactor");
104         Args.notNull(connFactory, "Connection factory");
105         Args.positive(defaultMaxPerRoute, "Max per route value");
106         Args.positive(maxTotal, "Max total value");
107         this.ioreactor = ioreactor;
108         this.connFactory = connFactory;
109         this.addressResolver = new SocketAddressResolver<T>() {
110 
111             public SocketAddress resolveLocalAddress(final T route) throws IOException {
112                 return AbstractNIOConnPool.this.resolveLocalAddress(route);
113             }
114 
115             public SocketAddress resolveRemoteAddress(final T route) throws IOException {
116                 return AbstractNIOConnPool.this.resolveRemoteAddress(route);
117             }
118 
119         };
120         this.sessionRequestCallback = new InternalSessionRequestCallback();
121         this.routeToPool = new HashMap<T, RouteSpecificPool<T, C, E>>();
122         this.leasingRequests = new LinkedList<LeaseRequest<T, C, E>>();
123         this.pending = new HashSet<SessionRequest>();
124         this.leased = new HashSet<E>();
125         this.available = new LinkedList<E>();
126         this.maxPerRoute = new HashMap<T, Integer>();
127         this.completedRequests = new ConcurrentLinkedQueue<LeaseRequest<T, C, E>>();
128         this.lock = new ReentrantLock();
129         this.isShutDown = new AtomicBoolean(false);
130         this.defaultMaxPerRoute = defaultMaxPerRoute;
131         this.maxTotal = maxTotal;
132     }
133 
134     /**
135      * @since 4.3
136      */
137     public AbstractNIOConnPool(
138             final ConnectingIOReactor ioreactor,
139             final NIOConnFactory<T, C> connFactory,
140             final SocketAddressResolver<T> addressResolver,
141             final int defaultMaxPerRoute,
142             final int maxTotal) {
143         super();
144         Args.notNull(ioreactor, "I/O reactor");
145         Args.notNull(connFactory, "Connection factory");
146         Args.notNull(addressResolver, "Address resolver");
147         Args.positive(defaultMaxPerRoute, "Max per route value");
148         Args.positive(maxTotal, "Max total value");
149         this.ioreactor = ioreactor;
150         this.connFactory = connFactory;
151         this.addressResolver = addressResolver;
152         this.sessionRequestCallback = new InternalSessionRequestCallback();
153         this.routeToPool = new HashMap<T, RouteSpecificPool<T, C, E>>();
154         this.leasingRequests = new LinkedList<LeaseRequest<T, C, E>>();
155         this.pending = new HashSet<SessionRequest>();
156         this.leased = new HashSet<E>();
157         this.available = new LinkedList<E>();
158         this.completedRequests = new ConcurrentLinkedQueue<LeaseRequest<T, C, E>>();
159         this.maxPerRoute = new HashMap<T, Integer>();
160         this.lock = new ReentrantLock();
161         this.isShutDown = new AtomicBoolean(false);
162         this.defaultMaxPerRoute = defaultMaxPerRoute;
163         this.maxTotal = maxTotal;
164     }
165 
166     /**
167      * @deprecated (4.3) use {@link SocketAddressResolver}
168      */
169     @Deprecated
170     protected SocketAddress resolveRemoteAddress(final T route) {
171         return null;
172     }
173 
174     /**
175      * @deprecated (4.3) use {@link SocketAddressResolver}
176      */
177     @Deprecated
178     protected SocketAddress resolveLocalAddress(final T route) {
179         return null;
180     }
181 
182     protected abstract E createEntry(T route, C conn);
183 
184     /**
185      * @since 4.3
186      */
187     protected void onLease(final E entry) {
188     }
189 
190     /**
191      * @since 4.3
192      */
193     protected void onRelease(final E entry) {
194     }
195 
196     public boolean isShutdown() {
197         return this.isShutDown.get();
198     }
199 
200     public void shutdown(final long waitMs) throws IOException {
201         if (this.isShutDown.compareAndSet(false, true)) {
202             fireCallbacks();
203             this.lock.lock();
204             try {
205                 for (final SessionRequest sessionRequest: this.pending) {
206                     sessionRequest.cancel();
207                 }
208                 for (final E entry: this.available) {
209                     entry.close();
210                 }
211                 for (final E entry: this.leased) {
212                     entry.close();
213                 }
214                 for (final RouteSpecificPool<T, C, E> pool: this.routeToPool.values()) {
215                     pool.shutdown();
216                 }
217                 this.routeToPool.clear();
218                 this.leased.clear();
219                 this.pending.clear();
220                 this.available.clear();
221                 this.leasingRequests.clear();
222                 this.ioreactor.shutdown(waitMs);
223             } finally {
224                 this.lock.unlock();
225             }
226         }
227     }
228 
229     private RouteSpecificPool<T, C, E> getPool(final T route) {
230         RouteSpecificPool<T, C, E> pool = this.routeToPool.get(route);
231         if (pool == null) {
232             pool = new RouteSpecificPool<T, C, E>(route) {
233 
234                 @Override
235                 protected E createEntry(final T route, final C conn) {
236                     return AbstractNIOConnPool.this.createEntry(route, conn);
237                 }
238 
239             };
240             this.routeToPool.put(route, pool);
241         }
242         return pool;
243     }
244 
245     public Future<E> lease(
246             final T route, final Object state,
247             final long connectTimeout, final TimeUnit tunit,
248             final FutureCallback<E> callback) {
249         return this.lease(route, state, connectTimeout, connectTimeout, tunit, callback);
250     }
251 
252     /**
253      * @since 4.3
254      */
255     public Future<E> lease(
256             final T route, final Object state,
257             final long connectTimeout, final long leaseTimeout, final TimeUnit tunit,
258             final FutureCallback<E> callback) {
259         Args.notNull(route, "Route");
260         Args.notNull(tunit, "Time unit");
261         Asserts.check(!this.isShutDown.get(), "Connection pool shut down");
262         final BasicFuture<E> future = new BasicFuture<E>(callback);
263         this.lock.lock();
264         try {
265             final long timeout = connectTimeout > 0 ? tunit.toMillis(connectTimeout) : 0;
266             final LeaseRequest<T, C, E> request = new LeaseRequest<T, C, E>(route, state, timeout, leaseTimeout, future);
267             final boolean completed = processPendingRequest(request);
268             if (!request.isDone() && !completed) {
269                 this.leasingRequests.add(request);
270             }
271             if (request.isDone()) {
272                 this.completedRequests.add(request);
273             }
274         } finally {
275             this.lock.unlock();
276         }
277         fireCallbacks();
278         return future;
279     }
280 
281     public Future<E> lease(final T route, final Object state, final FutureCallback<E> callback) {
282         return lease(route, state, -1, TimeUnit.MICROSECONDS, callback);
283     }
284 
285     public Future<E> lease(final T route, final Object state) {
286         return lease(route, state, -1, TimeUnit.MICROSECONDS, null);
287     }
288 
289     public void release(final E entry, final boolean reusable) {
290         if (entry == null) {
291             return;
292         }
293         if (this.isShutDown.get()) {
294             return;
295         }
296         this.lock.lock();
297         try {
298             if (this.leased.remove(entry)) {
299                 final RouteSpecificPool<T, C, E> pool = getPool(entry.getRoute());
300                 pool.free(entry, reusable);
301                 if (reusable) {
302                     this.available.addFirst(entry);
303                     onRelease(entry);
304                 } else {
305                     entry.close();
306                 }
307                 processNextPendingRequest();
308             }
309         } finally {
310             this.lock.unlock();
311         }
312         fireCallbacks();
313     }
314 
315     private void processPendingRequests() {
316         final ListIterator<LeaseRequest<T, C, E>> it = this.leasingRequests.listIterator();
317         while (it.hasNext()) {
318             final LeaseRequest<T, C, E> request = it.next();
319             final boolean completed = processPendingRequest(request);
320             if (request.isDone() || completed) {
321                 it.remove();
322             }
323             if (request.isDone()) {
324                 this.completedRequests.add(request);
325             }
326         }
327     }
328 
329     private void processNextPendingRequest() {
330         final ListIterator<LeaseRequest<T, C, E>> it = this.leasingRequests.listIterator();
331         while (it.hasNext()) {
332             final LeaseRequest<T, C, E> request = it.next();
333             final boolean completed = processPendingRequest(request);
334             if (request.isDone() || completed) {
335                 it.remove();
336             }
337             if (request.isDone()) {
338                 this.completedRequests.add(request);
339             }
340             if (completed) {
341                 return;
342             }
343         }
344     }
345 
346     private boolean processPendingRequest(final LeaseRequest<T, C, E> request) {
347         final T route = request.getRoute();
348         final Object state = request.getState();
349         final long deadline = request.getDeadline();
350 
351         final long now = System.currentTimeMillis();
352         if (now > deadline) {
353             request.failed(new TimeoutException());
354             return false;
355         }
356 
357         final RouteSpecificPool<T, C, E> pool = getPool(route);
358         E entry;
359         for (;;) {
360             entry = pool.getFree(state);
361             if (entry == null) {
362                 break;
363             }
364             if (entry.isClosed() || entry.isExpired(System.currentTimeMillis())) {
365                 entry.close();
366                 this.available.remove(entry);
367                 pool.free(entry, false);
368             } else {
369                 break;
370             }
371         }
372         if (entry != null) {
373             this.available.remove(entry);
374             this.leased.add(entry);
375             request.completed(entry);
376             onLease(entry);
377             return true;
378         }
379 
380         // New connection is needed
381         final int maxPerRoute = getMax(route);
382         // Shrink the pool prior to allocating a new connection
383         final int excess = Math.max(0, pool.getAllocatedCount() + 1 - maxPerRoute);
384         if (excess > 0) {
385             for (int i = 0; i < excess; i++) {
386                 final E lastUsed = pool.getLastUsed();
387                 if (lastUsed == null) {
388                     break;
389                 }
390                 lastUsed.close();
391                 this.available.remove(lastUsed);
392                 pool.remove(lastUsed);
393             }
394         }
395 
396         if (pool.getAllocatedCount() < maxPerRoute) {
397             final int totalUsed = this.pending.size() + this.leased.size();
398             final int freeCapacity = Math.max(this.maxTotal - totalUsed, 0);
399             if (freeCapacity == 0) {
400                 return false;
401             }
402             final int totalAvailable = this.available.size();
403             if (totalAvailable > freeCapacity - 1) {
404                 if (!this.available.isEmpty()) {
405                     final E lastUsed = this.available.removeLast();
406                     lastUsed.close();
407                     final RouteSpecificPool<T, C, E> otherpool = getPool(lastUsed.getRoute());
408                     otherpool.remove(lastUsed);
409                 }
410             }
411 
412             final SocketAddress localAddress;
413             final SocketAddress remoteAddress;
414             try {
415                 remoteAddress = this.addressResolver.resolveRemoteAddress(route);
416                 localAddress = this.addressResolver.resolveLocalAddress(route);
417             } catch (final IOException ex) {
418                 request.failed(ex);
419                 return false;
420             }
421 
422             final SessionRequest sessionRequest = this.ioreactor.connect(
423                     remoteAddress, localAddress, route, this.sessionRequestCallback);
424             final int timout = request.getConnectTimeout() < Integer.MAX_VALUE ?
425                     (int) request.getConnectTimeout() : Integer.MAX_VALUE;
426             sessionRequest.setConnectTimeout(timout);
427             this.pending.add(sessionRequest);
428             pool.addPending(sessionRequest, request.getFuture());
429             return true;
430         } else {
431             return false;
432         }
433     }
434 
435     private void fireCallbacks() {
436         LeaseRequest<T, C, E> request;
437         while ((request = this.completedRequests.poll()) != null) {
438             final BasicFuture<E> future = request.getFuture();
439             final Exception ex = request.getException();
440             final E result = request.getResult();
441             if (ex != null) {
442                 future.failed(ex);
443             } else if (result != null) {
444                 future.completed(result);
445             } else {
446                 future.cancel();
447             }
448         }
449     }
450 
451     public void validatePendingRequests() {
452         this.lock.lock();
453         try {
454             final long now = System.currentTimeMillis();
455             final ListIterator<LeaseRequest<T, C, E>> it = this.leasingRequests.listIterator();
456             while (it.hasNext()) {
457                 final LeaseRequest<T, C, E> request = it.next();
458                 final long deadline = request.getDeadline();
459                 if (now > deadline) {
460                     it.remove();
461                     request.failed(new TimeoutException());
462                     this.completedRequests.add(request);
463                 }
464             }
465         } finally {
466             this.lock.unlock();
467         }
468         fireCallbacks();
469     }
470 
471     protected void requestCompleted(final SessionRequest request) {
472         if (this.isShutDown.get()) {
473             return;
474         }
475         @SuppressWarnings("unchecked")
476         final
477         T route = (T) request.getAttachment();
478         this.lock.lock();
479         try {
480             this.pending.remove(request);
481             final RouteSpecificPool<T, C, E> pool = getPool(route);
482             final IOSession session = request.getSession();
483             try {
484                 final C conn = this.connFactory.create(route, session);
485                 final E entry = pool.createEntry(request, conn);
486                 this.leased.add(entry);
487                 pool.completed(request, entry);
488                 onLease(entry);
489             } catch (final IOException ex) {
490                 pool.failed(request, ex);
491             }
492         } finally {
493             this.lock.unlock();
494         }
495         fireCallbacks();
496     }
497 
498     protected void requestCancelled(final SessionRequest request) {
499         if (this.isShutDown.get()) {
500             return;
501         }
502         @SuppressWarnings("unchecked")
503         final
504         T route = (T) request.getAttachment();
505         this.lock.lock();
506         try {
507             this.pending.remove(request);
508             final RouteSpecificPool<T, C, E> pool = getPool(route);
509             pool.cancelled(request);
510             if (this.ioreactor.getStatus().compareTo(IOReactorStatus.ACTIVE) <= 0) {
511                 processNextPendingRequest();
512             }
513         } finally {
514             this.lock.unlock();
515         }
516         fireCallbacks();
517     }
518 
519     protected void requestFailed(final SessionRequest request) {
520         if (this.isShutDown.get()) {
521             return;
522         }
523         @SuppressWarnings("unchecked")
524         final
525         T route = (T) request.getAttachment();
526         this.lock.lock();
527         try {
528             this.pending.remove(request);
529             final RouteSpecificPool<T, C, E> pool = getPool(route);
530             pool.failed(request, request.getException());
531             processNextPendingRequest();
532         } finally {
533             this.lock.unlock();
534         }
535         fireCallbacks();
536     }
537 
538     protected void requestTimeout(final SessionRequest request) {
539         if (this.isShutDown.get()) {
540             return;
541         }
542         @SuppressWarnings("unchecked")
543         final
544         T route = (T) request.getAttachment();
545         this.lock.lock();
546         try {
547             this.pending.remove(request);
548             final RouteSpecificPool<T, C, E> pool = getPool(route);
549             pool.timeout(request);
550             processNextPendingRequest();
551         } finally {
552             this.lock.unlock();
553         }
554         fireCallbacks();
555     }
556 
557     private int getMax(final T route) {
558         final Integer v = this.maxPerRoute.get(route);
559         if (v != null) {
560             return v.intValue();
561         } else {
562             return this.defaultMaxPerRoute;
563         }
564     }
565 
566     public void setMaxTotal(final int max) {
567         Args.positive(max, "Max value");
568         this.lock.lock();
569         try {
570             this.maxTotal = max;
571         } finally {
572             this.lock.unlock();
573         }
574     }
575 
576     public int getMaxTotal() {
577         this.lock.lock();
578         try {
579             return this.maxTotal;
580         } finally {
581             this.lock.unlock();
582         }
583     }
584 
585     public void setDefaultMaxPerRoute(final int max) {
586         Args.positive(max, "Max value");
587         this.lock.lock();
588         try {
589             this.defaultMaxPerRoute = max;
590         } finally {
591             this.lock.unlock();
592         }
593     }
594 
595     public int getDefaultMaxPerRoute() {
596         this.lock.lock();
597         try {
598             return this.defaultMaxPerRoute;
599         } finally {
600             this.lock.unlock();
601         }
602     }
603 
604     public void setMaxPerRoute(final T route, final int max) {
605         Args.notNull(route, "Route");
606         Args.positive(max, "Max value");
607         this.lock.lock();
608         try {
609             this.maxPerRoute.put(route, Integer.valueOf(max));
610         } finally {
611             this.lock.unlock();
612         }
613     }
614 
615     public int getMaxPerRoute(final T route) {
616         Args.notNull(route, "Route");
617         this.lock.lock();
618         try {
619             return getMax(route);
620         } finally {
621             this.lock.unlock();
622         }
623     }
624 
625     public PoolStats getTotalStats() {
626         this.lock.lock();
627         try {
628             return new PoolStats(
629                     this.leased.size(),
630                     this.pending.size(),
631                     this.available.size(),
632                     this.maxTotal);
633         } finally {
634             this.lock.unlock();
635         }
636     }
637 
638     public PoolStats getStats(final T route) {
639         Args.notNull(route, "Route");
640         this.lock.lock();
641         try {
642             final RouteSpecificPool<T, C, E> pool = getPool(route);
643             return new PoolStats(
644                     pool.getLeasedCount(),
645                     pool.getPendingCount(),
646                     pool.getAvailableCount(),
647                     getMax(route));
648         } finally {
649             this.lock.unlock();
650         }
651     }
652 
653     /**
654      * Enumerates all available connections.
655      *
656      * @since 4.3
657      */
658     protected void enumAvailable(final PoolEntryCallback<T, C> callback) {
659         this.lock.lock();
660         try {
661             final Iterator<E> it = this.available.iterator();
662             while (it.hasNext()) {
663                 final E entry = it.next();
664                 callback.process(entry);
665                 if (entry.isClosed()) {
666                     final RouteSpecificPool<T, C, E> pool = getPool(entry.getRoute());
667                     pool.remove(entry);
668                     it.remove();
669                 }
670             }
671             processPendingRequests();
672             purgePoolMap();
673         } finally {
674             this.lock.unlock();
675         }
676     }
677 
678     /**
679      * Enumerates all leased connections.
680      *
681      * @since 4.3
682      */
683     protected void enumLeased(final PoolEntryCallback<T, C> callback) {
684         this.lock.lock();
685         try {
686             final Iterator<E> it = this.leased.iterator();
687             while (it.hasNext()) {
688                 final E entry = it.next();
689                 callback.process(entry);
690             }
691             processPendingRequests();
692         } finally {
693             this.lock.unlock();
694         }
695     }
696 
697     /**
698      * Use {@link #enumLeased(org.apache.http.pool.PoolEntryCallback)}
699      *  or {@link #enumAvailable(org.apache.http.pool.PoolEntryCallback)} instead.
700      *
701      * @deprecated (4.3.2)
702      */
703     @Deprecated
704     protected void enumEntries(final Iterator<E> it, final PoolEntryCallback<T, C> callback) {
705         while (it.hasNext()) {
706             final E entry = it.next();
707             callback.process(entry);
708         }
709         processPendingRequests();
710     }
711 
712     private void purgePoolMap() {
713         final Iterator<Map.Entry<T, RouteSpecificPool<T, C, E>>> it = this.routeToPool.entrySet().iterator();
714         while (it.hasNext()) {
715             final Map.Entry<T, RouteSpecificPool<T, C, E>> entry = it.next();
716             final RouteSpecificPool<T, C, E> pool = entry.getValue();
717             if (pool.getAllocatedCount() == 0) {
718                 it.remove();
719             }
720         }
721     }
722 
723     public void closeIdle(final long idletime, final TimeUnit tunit) {
724         Args.notNull(tunit, "Time unit");
725         long time = tunit.toMillis(idletime);
726         if (time < 0) {
727             time = 0;
728         }
729         final long deadline = System.currentTimeMillis() - time;
730         enumAvailable(new PoolEntryCallback<T, C>() {
731 
732             public void process(final PoolEntry<T, C> entry) {
733                 if (entry.getUpdated() <= deadline) {
734                     entry.close();
735                 }
736             }
737 
738         });
739     }
740 
741     public void closeExpired() {
742         final long now = System.currentTimeMillis();
743         enumAvailable(new PoolEntryCallback<T, C>() {
744 
745             public void process(final PoolEntry<T, C> entry) {
746                 if (entry.isExpired(now)) {
747                     entry.close();
748                 }
749             }
750 
751         });
752     }
753 
754     @Override
755     public String toString() {
756         final StringBuilder buffer = new StringBuilder();
757         buffer.append("[leased: ");
758         buffer.append(this.leased);
759         buffer.append("][available: ");
760         buffer.append(this.available);
761         buffer.append("][pending: ");
762         buffer.append(this.pending);
763         buffer.append("]");
764         return buffer.toString();
765     }
766 
767     class InternalSessionRequestCallback implements SessionRequestCallback {
768 
769         public void completed(final SessionRequest request) {
770             requestCompleted(request);
771         }
772 
773         public void cancelled(final SessionRequest request) {
774             requestCancelled(request);
775         }
776 
777         public void failed(final SessionRequest request) {
778             requestFailed(request);
779         }
780 
781         public void timeout(final SessionRequest request) {
782             requestTimeout(request);
783         }
784 
785     }
786 
787 }