1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.client;
20
21 import java.io.IOException;
22 import java.net.ConnectException;
23 import java.net.CookieManager;
24 import java.net.CookiePolicy;
25 import java.net.CookieStore;
26 import java.net.SocketAddress;
27 import java.net.SocketException;
28 import java.net.URI;
29 import java.nio.channels.SelectionKey;
30 import java.nio.channels.SocketChannel;
31 import java.util.ArrayList;
32 import java.util.Collection;
33 import java.util.HashSet;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.Objects;
38 import java.util.Set;
39 import java.util.concurrent.ConcurrentHashMap;
40 import java.util.concurrent.ConcurrentMap;
41 import java.util.concurrent.ExecutionException;
42 import java.util.concurrent.Executor;
43 import java.util.concurrent.TimeoutException;
44 import javax.net.ssl.SSLEngine;
45
46 import org.eclipse.jetty.client.api.AuthenticationStore;
47 import org.eclipse.jetty.client.api.Connection;
48 import org.eclipse.jetty.client.api.ContentResponse;
49 import org.eclipse.jetty.client.api.Destination;
50 import org.eclipse.jetty.client.api.ProxyConfiguration;
51 import org.eclipse.jetty.client.api.Request;
52 import org.eclipse.jetty.client.api.Response;
53 import org.eclipse.jetty.http.HttpField;
54 import org.eclipse.jetty.http.HttpHeader;
55 import org.eclipse.jetty.http.HttpMethod;
56 import org.eclipse.jetty.http.HttpScheme;
57 import org.eclipse.jetty.io.ByteBufferPool;
58 import org.eclipse.jetty.io.EndPoint;
59 import org.eclipse.jetty.io.MappedByteBufferPool;
60 import org.eclipse.jetty.io.SelectChannelEndPoint;
61 import org.eclipse.jetty.io.SelectorManager;
62 import org.eclipse.jetty.io.ssl.SslConnection;
63 import org.eclipse.jetty.util.Jetty;
64 import org.eclipse.jetty.util.Promise;
65 import org.eclipse.jetty.util.SocketAddressResolver;
66 import org.eclipse.jetty.util.URIUtil;
67 import org.eclipse.jetty.util.component.ContainerLifeCycle;
68 import org.eclipse.jetty.util.log.Log;
69 import org.eclipse.jetty.util.log.Logger;
70 import org.eclipse.jetty.util.ssl.SslContextFactory;
71 import org.eclipse.jetty.util.thread.QueuedThreadPool;
72 import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
73 import org.eclipse.jetty.util.thread.Scheduler;
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109 public class HttpClient extends ContainerLifeCycle
110 {
111 private static final Logger LOG = Log.getLogger(HttpClient.class);
112
113 private final ConcurrentMap<String, HttpDestination> destinations = new ConcurrentHashMap<>();
114 private final ConcurrentMap<Long, HttpConversation> conversations = new ConcurrentHashMap<>();
115 private final List<ProtocolHandler> handlers = new ArrayList<>();
116 private final List<Request.Listener> requestListeners = new ArrayList<>();
117 private final AuthenticationStore authenticationStore = new HttpAuthenticationStore();
118 private final Set<ContentDecoder.Factory> decoderFactories = new ContentDecoderFactorySet();
119 private final SslContextFactory sslContextFactory;
120 private volatile CookieManager cookieManager;
121 private volatile CookieStore cookieStore;
122 private volatile Executor executor;
123 private volatile ByteBufferPool byteBufferPool;
124 private volatile Scheduler scheduler;
125 private volatile SocketAddressResolver resolver;
126 private volatile SelectorManager selectorManager;
127 private volatile HttpField agentField = new HttpField(HttpHeader.USER_AGENT, "Jetty/" + Jetty.VERSION);
128 private volatile boolean followRedirects = true;
129 private volatile int maxConnectionsPerDestination = 64;
130 private volatile int maxRequestsQueuedPerDestination = 1024;
131 private volatile int requestBufferSize = 4096;
132 private volatile int responseBufferSize = 4096;
133 private volatile int maxRedirects = 8;
134 private volatile SocketAddress bindAddress;
135 private volatile long connectTimeout = 15000;
136 private volatile long addressResolutionTimeout = 15000;
137 private volatile long idleTimeout;
138 private volatile boolean tcpNoDelay = true;
139 private volatile boolean dispatchIO = true;
140 private volatile ProxyConfiguration proxyConfig;
141 private volatile HttpField encodingField;
142
143
144
145
146
147
148
149 public HttpClient()
150 {
151 this(null);
152 }
153
154
155
156
157
158
159
160
161 public HttpClient(SslContextFactory sslContextFactory)
162 {
163 this.sslContextFactory = sslContextFactory;
164 }
165
166
167
168
169
170 public SslContextFactory getSslContextFactory()
171 {
172 return sslContextFactory;
173 }
174
175 @Override
176 protected void doStart() throws Exception
177 {
178 if (sslContextFactory != null)
179 addBean(sslContextFactory);
180
181 String name = HttpClient.class.getSimpleName() + "@" + hashCode();
182
183 if (executor == null)
184 {
185 QueuedThreadPool threadPool = new QueuedThreadPool();
186 threadPool.setName(name);
187 executor = threadPool;
188 }
189 addBean(executor);
190
191 if (byteBufferPool == null)
192 byteBufferPool = new MappedByteBufferPool();
193 addBean(byteBufferPool);
194
195 if (scheduler == null)
196 scheduler = new ScheduledExecutorScheduler(name + "-scheduler", false);
197 addBean(scheduler);
198
199 resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout());
200
201 selectorManager = newSelectorManager();
202 selectorManager.setConnectTimeout(getConnectTimeout());
203 addBean(selectorManager);
204
205 handlers.add(new ContinueProtocolHandler(this));
206 handlers.add(new RedirectProtocolHandler(this));
207 handlers.add(new WWWAuthenticationProtocolHandler(this));
208 handlers.add(new ProxyAuthenticationProtocolHandler(this));
209
210 decoderFactories.add(new GZIPContentDecoder.Factory());
211
212 cookieManager = newCookieManager();
213 cookieStore = cookieManager.getCookieStore();
214
215 super.doStart();
216 }
217
218 protected SelectorManager newSelectorManager()
219 {
220 return new ClientSelectorManager(getExecutor(), getScheduler());
221 }
222
223 private CookieManager newCookieManager()
224 {
225 return new CookieManager(getCookieStore(), CookiePolicy.ACCEPT_ALL);
226 }
227
228 @Override
229 protected void doStop() throws Exception
230 {
231 cookieStore.removeAll();
232 cookieStore = null;
233 decoderFactories.clear();
234 handlers.clear();
235
236 for (HttpDestination destination : destinations.values())
237 destination.close();
238 destinations.clear();
239
240 conversations.clear();
241 requestListeners.clear();
242 authenticationStore.clearAuthentications();
243 authenticationStore.clearAuthenticationResults();
244
245 super.doStop();
246 }
247
248
249
250
251
252
253
254 public List<Request.Listener> getRequestListeners()
255 {
256 return requestListeners;
257 }
258
259
260
261
262 public CookieStore getCookieStore()
263 {
264 return cookieStore;
265 }
266
267
268
269
270 public void setCookieStore(CookieStore cookieStore)
271 {
272 this.cookieStore = Objects.requireNonNull(cookieStore);
273 this.cookieManager = newCookieManager();
274 }
275
276
277
278
279
280
281
282 CookieManager getCookieManager()
283 {
284 return cookieManager;
285 }
286
287
288
289
290 public AuthenticationStore getAuthenticationStore()
291 {
292 return authenticationStore;
293 }
294
295
296
297
298
299
300
301 public Set<ContentDecoder.Factory> getContentDecoderFactories()
302 {
303 return decoderFactories;
304 }
305
306
307
308
309
310
311
312
313 public ContentResponse GET(String uri) throws InterruptedException, ExecutionException, TimeoutException
314 {
315 return GET(URI.create(uri));
316 }
317
318
319
320
321
322
323
324
325 public ContentResponse GET(URI uri) throws InterruptedException, ExecutionException, TimeoutException
326 {
327 return newRequest(uri).send();
328 }
329
330
331
332
333
334
335
336
337 public Request POST(String uri)
338 {
339 return POST(URI.create(uri));
340 }
341
342
343
344
345
346
347
348 public Request POST(URI uri)
349 {
350 return newRequest(uri).method(HttpMethod.POST);
351 }
352
353
354
355
356
357
358
359
360 public Request newRequest(String host, int port)
361 {
362 return newRequest(address("http", host, port));
363 }
364
365
366
367
368
369
370
371 public Request newRequest(String uri)
372 {
373 return newRequest(URI.create(uri));
374 }
375
376
377
378
379
380
381
382 public Request newRequest(URI uri)
383 {
384 return new HttpRequest(this, uri);
385 }
386
387 protected Request copyRequest(Request oldRequest, URI newURI)
388 {
389 Request newRequest = new HttpRequest(this, oldRequest.getConversationID(), newURI);
390 newRequest.method(oldRequest.method())
391 .version(oldRequest.getVersion())
392 .content(oldRequest.getContent());
393 for (HttpField header : oldRequest.getHeaders())
394 {
395
396 if (HttpHeader.HOST == header.getHeader())
397 continue;
398
399
400 if (HttpHeader.EXPECT == header.getHeader())
401 continue;
402
403
404 if (HttpHeader.COOKIE == header.getHeader())
405 continue;
406
407
408 if (HttpHeader.AUTHORIZATION == header.getHeader() ||
409 HttpHeader.PROXY_AUTHORIZATION == header.getHeader())
410 continue;
411
412 newRequest.header(header.getName(), header.getValue());
413 }
414 return newRequest;
415 }
416
417 protected String address(String scheme, String host, int port)
418 {
419 StringBuilder result = new StringBuilder();
420 URIUtil.appendSchemeHostPort(result, scheme, host, port);
421 return result.toString();
422 }
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437 public Destination getDestination(String scheme, String host, int port)
438 {
439 return destinationFor(scheme, host, port);
440 }
441
442 protected HttpDestination destinationFor(String scheme, String host, int port)
443 {
444 port = normalizePort(scheme, port);
445
446 String address = address(scheme, host, port);
447 HttpDestination destination = destinations.get(address);
448 if (destination == null)
449 {
450 destination = new HttpDestination(this, scheme, host, port);
451 if (isRunning())
452 {
453 HttpDestination existing = destinations.putIfAbsent(address, destination);
454 if (existing != null)
455 destination = existing;
456 else
457 LOG.debug("Created {}", destination);
458 if (!isRunning())
459 destinations.remove(address);
460 }
461
462 }
463 return destination;
464 }
465
466
467
468
469 public List<Destination> getDestinations()
470 {
471 return new ArrayList<Destination>(destinations.values());
472 }
473
474 protected void send(final Request request, List<Response.ResponseListener> listeners)
475 {
476 String scheme = request.getScheme().toLowerCase(Locale.ENGLISH);
477 if (!HttpScheme.HTTP.is(scheme) && !HttpScheme.HTTPS.is(scheme))
478 throw new IllegalArgumentException("Invalid protocol " + scheme);
479
480 HttpDestination destination = destinationFor(scheme, request.getHost(), request.getPort());
481 destination.send(request, listeners);
482 }
483
484 protected void newConnection(final HttpDestination destination, final Promise<Connection> promise)
485 {
486 Destination.Address address = destination.getConnectAddress();
487 resolver.resolve(address.getHost(), address.getPort(), new Promise<SocketAddress>()
488 {
489 @Override
490 public void succeeded(SocketAddress socketAddress)
491 {
492 SocketChannel channel = null;
493 try
494 {
495 channel = SocketChannel.open();
496 SocketAddress bindAddress = getBindAddress();
497 if (bindAddress != null)
498 channel.bind(bindAddress);
499 configure(channel);
500 channel.configureBlocking(false);
501 channel.connect(socketAddress);
502
503 ConnectionCallback callback = new ConnectionCallback(destination, promise);
504 selectorManager.connect(channel, callback);
505 }
506
507
508 catch (Throwable x)
509 {
510 if (channel != null)
511 close(channel);
512 promise.failed(x);
513 }
514 }
515
516 @Override
517 public void failed(Throwable x)
518 {
519 promise.failed(x);
520 }
521 });
522 }
523
524 protected void configure(SocketChannel channel) throws SocketException
525 {
526 channel.socket().setTcpNoDelay(isTCPNoDelay());
527 }
528
529 private void close(SocketChannel channel)
530 {
531 try
532 {
533 channel.close();
534 }
535 catch (IOException x)
536 {
537 LOG.ignore(x);
538 }
539 }
540
541 protected HttpConversation getConversation(long id, boolean create)
542 {
543 HttpConversation conversation = conversations.get(id);
544 if (conversation == null && create)
545 {
546 conversation = new HttpConversation(this, id);
547 HttpConversation existing = conversations.putIfAbsent(id, conversation);
548 if (existing != null)
549 conversation = existing;
550 else
551 LOG.debug("{} created", conversation);
552 }
553 return conversation;
554 }
555
556 protected void removeConversation(HttpConversation conversation)
557 {
558 conversations.remove(conversation.getID());
559 LOG.debug("{} removed", conversation);
560 }
561
562 protected List<ProtocolHandler> getProtocolHandlers()
563 {
564 return handlers;
565 }
566
567 protected ProtocolHandler findProtocolHandler(Request request, Response response)
568 {
569
570 List<ProtocolHandler> protocolHandlers = getProtocolHandlers();
571 for (int i = 0; i < protocolHandlers.size(); ++i)
572 {
573 ProtocolHandler handler = protocolHandlers.get(i);
574 if (handler.accept(request, response))
575 return handler;
576 }
577 return null;
578 }
579
580
581
582
583 public ByteBufferPool getByteBufferPool()
584 {
585 return byteBufferPool;
586 }
587
588
589
590
591 public void setByteBufferPool(ByteBufferPool byteBufferPool)
592 {
593 this.byteBufferPool = byteBufferPool;
594 }
595
596
597
598
599 public long getConnectTimeout()
600 {
601 return connectTimeout;
602 }
603
604
605
606
607
608 public void setConnectTimeout(long connectTimeout)
609 {
610 this.connectTimeout = connectTimeout;
611 }
612
613
614
615
616 public long getAddressResolutionTimeout()
617 {
618 return addressResolutionTimeout;
619 }
620
621
622
623
624 public void setAddressResolutionTimeout(long addressResolutionTimeout)
625 {
626 this.addressResolutionTimeout = addressResolutionTimeout;
627 }
628
629
630
631
632 public long getIdleTimeout()
633 {
634 return idleTimeout;
635 }
636
637
638
639
640 public void setIdleTimeout(long idleTimeout)
641 {
642 this.idleTimeout = idleTimeout;
643 }
644
645
646
647
648
649 public SocketAddress getBindAddress()
650 {
651 return bindAddress;
652 }
653
654
655
656
657
658
659 public void setBindAddress(SocketAddress bindAddress)
660 {
661 this.bindAddress = bindAddress;
662 }
663
664
665
666
667 public HttpField getUserAgentField()
668 {
669 return agentField;
670 }
671
672
673
674
675 public void setUserAgentField(HttpField agent)
676 {
677 if (agent.getHeader() != HttpHeader.USER_AGENT)
678 throw new IllegalArgumentException();
679 this.agentField = agent;
680 }
681
682
683
684
685
686 public boolean isFollowRedirects()
687 {
688 return followRedirects;
689 }
690
691
692
693
694
695 public void setFollowRedirects(boolean follow)
696 {
697 this.followRedirects = follow;
698 }
699
700
701
702
703 public Executor getExecutor()
704 {
705 return executor;
706 }
707
708
709
710
711 public void setExecutor(Executor executor)
712 {
713 this.executor = executor;
714 }
715
716
717
718
719 public Scheduler getScheduler()
720 {
721 return scheduler;
722 }
723
724
725
726
727 public void setScheduler(Scheduler scheduler)
728 {
729 this.scheduler = scheduler;
730 }
731
732 protected SelectorManager getSelectorManager()
733 {
734 return selectorManager;
735 }
736
737
738
739
740 public int getMaxConnectionsPerDestination()
741 {
742 return maxConnectionsPerDestination;
743 }
744
745
746
747
748
749
750
751
752
753
754
755
756 public void setMaxConnectionsPerDestination(int maxConnectionsPerDestination)
757 {
758 this.maxConnectionsPerDestination = maxConnectionsPerDestination;
759 }
760
761
762
763
764 public int getMaxRequestsQueuedPerDestination()
765 {
766 return maxRequestsQueuedPerDestination;
767 }
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782 public void setMaxRequestsQueuedPerDestination(int maxRequestsQueuedPerDestination)
783 {
784 this.maxRequestsQueuedPerDestination = maxRequestsQueuedPerDestination;
785 }
786
787
788
789
790 public int getRequestBufferSize()
791 {
792 return requestBufferSize;
793 }
794
795
796
797
798 public void setRequestBufferSize(int requestBufferSize)
799 {
800 this.requestBufferSize = requestBufferSize;
801 }
802
803
804
805
806 public int getResponseBufferSize()
807 {
808 return responseBufferSize;
809 }
810
811
812
813
814 public void setResponseBufferSize(int responseBufferSize)
815 {
816 this.responseBufferSize = responseBufferSize;
817 }
818
819
820
821
822
823 public int getMaxRedirects()
824 {
825 return maxRedirects;
826 }
827
828
829
830
831
832 public void setMaxRedirects(int maxRedirects)
833 {
834 this.maxRedirects = maxRedirects;
835 }
836
837
838
839
840 public boolean isTCPNoDelay()
841 {
842 return tcpNoDelay;
843 }
844
845
846
847
848
849 public void setTCPNoDelay(boolean tcpNoDelay)
850 {
851 this.tcpNoDelay = tcpNoDelay;
852 }
853
854
855
856
857
858 public boolean isDispatchIO()
859 {
860 return dispatchIO;
861 }
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876 public void setDispatchIO(boolean dispatchIO)
877 {
878 this.dispatchIO = dispatchIO;
879 }
880
881
882
883
884 public ProxyConfiguration getProxyConfiguration()
885 {
886 return proxyConfig;
887 }
888
889
890
891
892 public void setProxyConfiguration(ProxyConfiguration proxyConfig)
893 {
894 this.proxyConfig = proxyConfig;
895 }
896
897 protected HttpField getAcceptEncodingField()
898 {
899 return encodingField;
900 }
901
902 protected String normalizeHost(String host)
903 {
904 if (host != null && host.matches("\\[.*\\]"))
905 return host.substring(1, host.length() - 1);
906 return host;
907 }
908
909 protected int normalizePort(String scheme, int port)
910 {
911 return port > 0 ? port : HttpScheme.HTTPS.is(scheme) ? 443 : 80;
912 }
913
914 protected boolean isDefaultPort(String scheme, int port)
915 {
916 return HttpScheme.HTTPS.is(scheme) ? port == 443 : port == 80;
917 }
918
919 protected HttpConnection newHttpConnection(HttpClient httpClient, EndPoint endPoint, HttpDestination destination)
920 {
921 return new HttpConnection(httpClient, endPoint, destination);
922 }
923
924 protected SslConnection newSslConnection(HttpClient httpClient, EndPoint endPoint, SSLEngine engine)
925 {
926 return new SslConnection(httpClient.getByteBufferPool(), httpClient.getExecutor(), endPoint, engine);
927 }
928
929 @Override
930 public void dump(Appendable out, String indent) throws IOException
931 {
932 dumpThis(out);
933 dump(out, indent, getBeans(), destinations.values());
934 }
935
936 protected Connection tunnel(Connection connection)
937 {
938 HttpConnection httpConnection = (HttpConnection)connection;
939 HttpDestination destination = httpConnection.getDestination();
940 SslConnection sslConnection = createSslConnection(destination, httpConnection.getEndPoint());
941 Connection result = (Connection)sslConnection.getDecryptedEndPoint().getConnection();
942 selectorManager.connectionClosed(httpConnection);
943 selectorManager.connectionOpened(sslConnection);
944 LOG.debug("Tunnelled {} over {}", connection, result);
945 return result;
946 }
947
948 private SslConnection createSslConnection(HttpDestination destination, EndPoint endPoint)
949 {
950 SSLEngine engine = sslContextFactory.newSSLEngine(destination.getHost(), destination.getPort());
951 engine.setUseClientMode(true);
952
953 SslConnection sslConnection = newSslConnection(HttpClient.this, endPoint, engine);
954 sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
955 endPoint.setConnection(sslConnection);
956 EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
957 HttpConnection connection = newHttpConnection(this, appEndPoint, destination);
958 appEndPoint.setConnection(connection);
959
960 return sslConnection;
961 }
962
963 protected class ClientSelectorManager extends SelectorManager
964 {
965 public ClientSelectorManager(Executor executor, Scheduler scheduler)
966 {
967 this(executor, scheduler, 1);
968 }
969
970 public ClientSelectorManager(Executor executor, Scheduler scheduler, int selectors)
971 {
972 super(executor, scheduler, selectors);
973 }
974
975 @Override
976 protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key)
977 {
978 return new SelectChannelEndPoint(channel, selector, key, getScheduler(), getIdleTimeout());
979 }
980
981 @Override
982 public org.eclipse.jetty.io.Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment) throws IOException
983 {
984 ConnectionCallback callback = (ConnectionCallback)attachment;
985 HttpDestination destination = callback.destination;
986
987 SslContextFactory sslContextFactory = getSslContextFactory();
988 if (!destination.isProxied() && HttpScheme.HTTPS.is(destination.getScheme()))
989 {
990 if (sslContextFactory == null)
991 {
992 IOException failure = new ConnectException("Missing " + SslContextFactory.class.getSimpleName() + " for " + destination.getScheme() + " requests");
993 callback.failed(failure);
994 throw failure;
995 }
996 else
997 {
998 SslConnection sslConnection = createSslConnection(destination, endPoint);
999 callback.succeeded((Connection)sslConnection.getDecryptedEndPoint().getConnection());
1000 return sslConnection;
1001 }
1002 }
1003 else
1004 {
1005 HttpConnection connection = newHttpConnection(HttpClient.this, endPoint, destination);
1006 callback.succeeded(connection);
1007 return connection;
1008 }
1009 }
1010
1011 @Override
1012 protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
1013 {
1014 ConnectionCallback callback = (ConnectionCallback)attachment;
1015 callback.failed(ex);
1016 }
1017 }
1018
1019 private class ConnectionCallback implements Promise<Connection>
1020 {
1021 private final HttpDestination destination;
1022 private final Promise<Connection> promise;
1023
1024 private ConnectionCallback(HttpDestination destination, Promise<Connection> promise)
1025 {
1026 this.destination = destination;
1027 this.promise = promise;
1028 }
1029
1030 @Override
1031 public void succeeded(Connection result)
1032 {
1033 promise.succeeded(result);
1034 }
1035
1036 @Override
1037 public void failed(Throwable x)
1038 {
1039 promise.failed(x);
1040 }
1041 }
1042
1043 private class ContentDecoderFactorySet implements Set<ContentDecoder.Factory>
1044 {
1045 private final Set<ContentDecoder.Factory> set = new HashSet<>();
1046
1047 @Override
1048 public boolean add(ContentDecoder.Factory e)
1049 {
1050 boolean result = set.add(e);
1051 invalidate();
1052 return result;
1053 }
1054
1055 @Override
1056 public boolean addAll(Collection<? extends ContentDecoder.Factory> c)
1057 {
1058 boolean result = set.addAll(c);
1059 invalidate();
1060 return result;
1061 }
1062
1063 @Override
1064 public boolean remove(Object o)
1065 {
1066 boolean result = set.remove(o);
1067 invalidate();
1068 return result;
1069 }
1070
1071 @Override
1072 public boolean removeAll(Collection<?> c)
1073 {
1074 boolean result = set.removeAll(c);
1075 invalidate();
1076 return result;
1077 }
1078
1079 @Override
1080 public boolean retainAll(Collection<?> c)
1081 {
1082 boolean result = set.retainAll(c);
1083 invalidate();
1084 return result;
1085 }
1086
1087 @Override
1088 public void clear()
1089 {
1090 set.clear();
1091 invalidate();
1092 }
1093
1094 @Override
1095 public int size()
1096 {
1097 return set.size();
1098 }
1099
1100 @Override
1101 public boolean isEmpty()
1102 {
1103 return set.isEmpty();
1104 }
1105
1106 @Override
1107 public boolean contains(Object o)
1108 {
1109 return set.contains(o);
1110 }
1111
1112 @Override
1113 public boolean containsAll(Collection<?> c)
1114 {
1115 return set.containsAll(c);
1116 }
1117
1118 @Override
1119 public Iterator<ContentDecoder.Factory> iterator()
1120 {
1121 return set.iterator();
1122 }
1123
1124 @Override
1125 public Object[] toArray()
1126 {
1127 return set.toArray();
1128 }
1129
1130 @Override
1131 public <T> T[] toArray(T[] a)
1132 {
1133 return set.toArray(a);
1134 }
1135
1136 protected void invalidate()
1137 {
1138 if (set.isEmpty())
1139 {
1140 encodingField = null;
1141 }
1142 else
1143 {
1144 StringBuilder value = new StringBuilder();
1145 for (Iterator<ContentDecoder.Factory> iterator = set.iterator(); iterator.hasNext();)
1146 {
1147 ContentDecoder.Factory decoderFactory = iterator.next();
1148 value.append(decoderFactory.getEncoding());
1149 if (iterator.hasNext())
1150 value.append(",");
1151 }
1152 encodingField = new HttpField(HttpHeader.ACCEPT_ENCODING, value.toString());
1153 }
1154 }
1155 }
1156 }