Java tutorial
/* Copyright (C) 2013-2014 Ian Teune <ian.teune@gmail.com> * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.tinspx.util.net; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Objects; import static com.google.common.base.Preconditions.*; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Supplier; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ListMultimap; import com.google.common.net.HttpHeaders; import com.google.common.net.InternetDomainName; import com.google.common.primitives.Ints; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableScheduledFuture; import com.google.common.util.concurrent.SettableFuture; import com.tinspx.util.base.BasicError; import com.tinspx.util.base.ContentCallback; import com.tinspx.util.base.ContentListener; import com.tinspx.util.base.Errors; import com.tinspx.util.collect.CollectUtils; import com.tinspx.util.collect.Predicated; import com.tinspx.util.concurrent.FutureUtils; import com.tinspx.util.concurrent.LimitedExecutorService; import com.tinspx.util.io.ChannelSource; import com.tinspx.util.io.StringUtils; import com.tinspx.util.io.callbacks.FileCallback; import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import lombok.NonNull; import lombok.experimental.NonFinal; import lombok.extern.slf4j.Slf4j; /** * Various {@code Request} and {@code RequestContext} utilities. * * @author Ian */ @Slf4j public class Requests { public static RequestCallback emptyCallback() { return EmptyRequestCallback.INSTANCE; } static final RedirectHandler NO_FOLLOW = new RedirectHandler() { @Override public boolean shouldRedirect(Response response) { return false; } @Override public Request apply(Request input) { return null; } @Override public String toString() { return "Requests.noFollowRedirectHandler()"; } }; /** * Does not follow any redirects. * <p> * {@link RedirectHandler#shouldRedirect(Response)} always returns false and * {@link RedirectHandler#apply(Request)} always returns null to cancel the * redirect. */ public static RedirectHandler noFollowRedirectHandler() { return NO_FOLLOW; } static final RedirectHandler DEFAULT = LimitedRedirect .wrap(ConfigRedirect.builder().redirect(301, 302, 303).with("POST").to("GET").removeBody() .convertPostToGetParams().redirect(301, 302, 303, 307, 308).build()); /** * Redirects on response codes 301, 302, 303, 307, and 308. The response * will be converted to GET on 301, 302, and 303 if the Request HTTP method * is POST (any parameters in a {@link FormEncodedBody} will be converted to * GET query parameters). Relative redirect locations are allowed. If the * Location header is missing, the Request is redirected to the same URI. * The default redirection limits of {@link LimitedRedirect} are enforced. */ public static RedirectHandler defaultRedirectHandler() { return DEFAULT; } /** * same as {@link #wrap(ContentListener, boolean) wrap(delegate, true)}. */ public static RequestCallback wrap(ContentListener<? super Response, ? super ByteBuffer> delegate) { return wrap(delegate, true); } /** * Wraps {@code delegate} as a {@code RequestCallback}. * {@code ignoreRedirects} controls whether the listener receives content * for any intermediary redirects. {@code ignoreRedirects} has no effect if * {@code delegate} is already a {@code RequestCallback}. */ public static RequestCallback wrap(ContentListener<? super Response, ? super ByteBuffer> delegate, boolean ignoreRedirects) { if (delegate instanceof RequestCallback) { return (RequestCallback) delegate; } return new ContentListenerWrapper(delegate, ignoreRedirects); } // static final Function<ContentListener<? super Response, ? super ByteBuffer>, RequestCallback> CONTENTLISTENER_FUNCTION_F = new Function<ContentListener<? super Response, ? super ByteBuffer>, RequestCallback>() { // @Override // public RequestCallback apply(ContentListener<? super Response, ? super ByteBuffer> input) { // return input != null ? wrap(input, false) : null; // } // }; // // static final Function<ContentListener<? super Response, ? super ByteBuffer>, RequestCallback> CONTENTLISTENER_FUNCTION_T = new Function<ContentListener<? super Response, ? super ByteBuffer>, RequestCallback>() { // @Override // public RequestCallback apply(ContentListener<? super Response, ? super ByteBuffer> input) { // return input != null ? wrap(input, true) : null; // } // }; //this fucks up the javadoc // static Function<ContentListener<? super Response, ? super ByteBuffer>, RequestCallback> wrapContentListener() { // return CONTENTLISTENER_FUNCTION_T; // } // // static Function<ContentListener<? super Response, ? super ByteBuffer>, RequestCallback> wrapContentListener(boolean ignoreRedirects) { // return ignoreRedirects ? CONTENTLISTENER_FUNCTION_T : CONTENTLISTENER_FUNCTION_F; // } static class ContentListenerWrapper extends EmptyRequestCallback { final ContentListener<? super Response, ? super ByteBuffer> delegate; final boolean ignoreRedirects; public ContentListenerWrapper(ContentListener<? super Response, ? super ByteBuffer> delegate, boolean ignoreRedirects) { this.delegate = checkNotNull(delegate); this.ignoreRedirects = ignoreRedirects; } @Override public boolean onContentStart(Response context) { if (delegate instanceof ContentCallback) { return ((ContentCallback<? super Response, ? super ByteBuffer>) delegate).onContentStart(context); } return true; } @Override public boolean onContent(Response context, ByteBuffer content) { return delegate.onContent(context, content); } @Override public void onContentComplete(Response context) { if (delegate instanceof ContentCallback) { ((ContentCallback<? super Response, ? super ByteBuffer>) delegate).onContentComplete(context); } } @Override public void onError(BasicError error) { if (delegate instanceof BasicError.Listener) { ((BasicError.Listener) delegate).onError(error); } } @Override public boolean ignoreRedirects() { return ignoreRedirects; } } /** * The delegate will only receive error events, regardless of whether it * actually implements other content interfaces. */ public static RequestCallback wrap(BasicError.Listener delegate) { return new ErrorListenerWrapper(delegate); } // static final Function<BasicError.Listener, RequestCallback> ERRORLISTENER_FUNCTION = new Function<BasicError.Listener, RequestCallback>() { // @Override // public RequestCallback apply(BasicError.Listener input) { // return input != null ? wrap(input) : null; // } // }; // // static Function<BasicError.Listener, RequestCallback> wrapErrorListener() { // return ERRORLISTENER_FUNCTION; // } static class ErrorListenerWrapper extends EmptyRequestCallback { final BasicError.Listener delegate; public ErrorListenerWrapper(BasicError.Listener delegate) { this.delegate = checkNotNull(delegate); } @Override public void onError(BasicError error) { delegate.onError(error); } } public static RequestContext wrap(AsyncFunction<? super Request, Response> context, Function<? super Request, ? extends Request> function) { return RequestFunction.wrap(context, function); } public static RequestFunction combine(Function<? super Request, ? extends Request>... functions) { return RequestFunction.combine(functions); } public static RequestFunction combine(Iterable<Function<? super Request, ? extends Request>> functions) { return RequestFunction.combine(functions); } static final Function<Request, URI> REQUEST_TO_URI = new Function<Request, URI>() { @Override public URI apply(Request input) { return input != null ? input.uri() : null; } }; static final Function<Response, URI> RESPONSE_TO_URI = new Function<Response, URI>() { @Override public URI apply(Response input) { return input != null ? input.uri() : null; } }; public static Function<Request, URI> requestURI() { return REQUEST_TO_URI; } public static Function<Response, URI> responseURI() { return RESPONSE_TO_URI; } static final Function<Response, Integer> RESPONSE_CODE = new Function<Response, Integer>() { @Override public Integer apply(Response input) { return input != null ? input.code() : -1; } }; /** * Returns a {@code Function} that returns the response code, or -1 if * null or unknown. */ public static Function<Response, Integer> responseCode() { return RESPONSE_CODE; } /** * The {@code RequestBody}s are considered equal if they have the same * {@link RequestBody#headers() headers} (ignoring order) and their content * is {@link ChannelSource#contentEquals(ByteSource) equal}. */ public static boolean bodyEquals(@Nullable RequestBody a, @Nullable RequestBody b) throws IOException { if (Objects.equal(a, b)) { return true; } if (a == null || b == null) { return false; } if (!CollectUtils.equalIgnoreOrder(a.headers(), b.headers())) { return false; } return a.contentEquals(b); } static final Function<Object, Object> UNWRAP = new Function<Object, Object>() { @Override public Object apply(Object input) { if (input instanceof ContentListenerWrapper) { return ((ContentListenerWrapper) input).delegate; } else if (input instanceof ErrorListenerWrapper) { return ((ErrorListenerWrapper) input).delegate; } else { return input; } } }; /** * Schedules {@code request} to be submitted to {@code context}. */ public static ListenableScheduledFuture<Response> schedule(final @NonNull Request request, final @NonNull AsyncFunction<? super Request, Response> context, @NonNull ScheduledExecutorService executor, long delay, @NonNull TimeUnit unit) { request.checkNotStarted(); request.setSubmissionMillisIfAbsent(); SettableFuture<Response> future = SettableFuture.create(); ScheduledFuture<?> sf = executor.schedule(apply(context, request, future), delay, unit); return FutureUtils.delegateListenableScheduledFuture(future, sf); } private static Runnable apply(final @NonNull AsyncFunction<? super Request, Response> context, final @NonNull Request request, final @NonNull SettableFuture<Response> future) { return new Runnable() { @Override public void run() { try { ListenableFuture<Response> result = context.apply(request); FutureUtils.linkFutures(result, future); FutureUtils.linkFailure(future, result, false); } catch (Throwable t) { future.setException(t); Throwables.propagateIfInstanceOf(t, Error.class); } } }; } /** * Returns a {@code Callable} that returns a * {@link Request#duplicate() duplicate} of {@code request}. A defensive * copy of {@code request} is made; therefore, any subsequent changes made * to {@code request} will have no effect on the supplier. */ public static Callable<Request> callable(@NonNull Request request) throws IOException { final Request r = request.duplicate(); return new Callable<Request>() { @Override public Request call() throws Exception { return r.duplicate(); } }; } /** * Returns a {@code Callable} that submits a * {@link Request#duplicate() duplicate} of {@code request} to * {@code context} every time {@code call()} is invoked. A defensive copy of * {@code request} is made; therefore, any subsequent changes made to * {@code request} will have no effect on the {@code Callable}. * * @see #apply(AsyncFunction, Supplier) */ public static Callable<ListenableFuture<Response>> apply( @NonNull AsyncFunction<? super Request, Response> context, @NonNull Request request) throws IOException { return apply(context, callable(request)); } /** * Returns a {@code Callable} that submits a {@code Request} acquired from * {@code requestCallable} every time {@code call()} is invoked. */ public static Callable<ListenableFuture<Response>> apply( final @NonNull AsyncFunction<? super Request, Response> context, final @NonNull Callable<Request> requestCallable) { return new Callable<ListenableFuture<Response>>() { @Override public ListenableFuture<Response> call() throws Exception { return context.apply(requestCallable.call()); } }; } private static void checkExpiration(long expiration, @NonNull TimeUnit unit) { checkArgument(expiration >= 0); } /** * Adds an expiration to {@code request} such that if the request is * executed more than {@code expiration} time {@code unit}s after it was * submitted to a {@code RequestContext}, it will be canceled. */ public static Request withExpiration(@NonNull Request request, final long expiration, final TimeUnit unit) { checkExpiration(expiration, unit); final long millis = Math.max(1, unit.toMillis(expiration)); request.callback(new EmptyRequestCallback() { boolean setup; @Override public void onSetup(Request request) { //should be ignoring redirects, but setup guards against onSetup //being called multiple times just in case if (setup) { return; } setup = true; long submission = asLong(request.get(Request.SUBMISSION_MILLIS)), elapsed; if (submission > 0 && (elapsed = System.currentTimeMillis() - submission) > millis) { request.cancel(); request.onError(Errors.message("request expired; expiration: %d %s, elapsed: %d %s", expiration, unit, elapsed, TimeUnit.MILLISECONDS)); } } }); return request; } /** * Returns a Function that adds the specified expiration to its input using * {@link #withExpiration(Request, long, TimeUnit)}. */ public static Function<Request, Request> withExpiration(final long expiration, final TimeUnit unit) { checkExpiration(expiration, unit); return new Function<Request, Request>() { @Override public Request apply(Request input) { return input != null ? withExpiration(input, expiration, unit) : null; } }; } /** * Wraps {@code context} and adds an expiration using * {@link #withExpiration(Request, long, TimeUnit)} to all submitted * requests. */ public static RequestContext withExpiration(@NonNull AsyncFunction<? super Request, Response> context, long expiration, TimeUnit unit) { return wrap(context, withExpiration(expiration, unit)); } static long asLong(@Nullable Object obj) { if (obj instanceof Number) { long value = ((Number) obj).longValue(); return value < 0 ? -1 : value; } else { return -1; } } static final Function<Request, Object> REQUEST_FULL_DOMAIN = Functions.compose(UriUtil.toFullDomain(), Requests.requestURI()); static final Function<Request, Object> REQUEST_TOP_PRIVATE = Functions.compose(UriUtil.toTopPrivateDomain(), Requests.requestURI()); /** * Converts the {@code Request} {@code URI} {@link URI#getHost() host} into * either an {@link InternetDomainName}, * {@link java.net.InetAddress InetAddress}, or {@code String} using * {@link UriUtil#toFullDomain()}. * * @see #toTopPrivateDomain() * @return the {@code Request} {@code URI} as an {@code InternetDomainName}, * {@code InetAddress}, or {@code String}. */ public static Function<Request, Object> toFullDomain() { return REQUEST_FULL_DOMAIN; } /** * Converts the {@code Request} {@code URI} {@link URI#getHost() host} into * either an {@link InternetDomainName}, * {@link java.net.InetAddress InetAddress}, or {@code String} using * {@link UriUtil#toTopPrivateDomain()}. * * @see #toFullDomain() * @return the {@code Request} {@code URI} as an * {@code InternetDomainName}, {@code InetAddress}, or {@code String}. */ public static Function<Request, Object> toTopPrivateDomain() { return REQUEST_TOP_PRIVATE; } private static final Function<Object, Object> NULL_CONSTANT = Functions.constant(null); /** * Wraps {@code delegate}, using {@code executor} to rate limit * {@code Request} execution. Note that <i>all</i> requests, regardless of * domain or {@code URI}, will be subject to the same rate limiting. Use * {@link #limit(AsyncFunction, LimitedExecutorService, Function)} in * conjunction with {@link #toTopPrivateDomain()} and * {@link #toFullDomain()} to apply rate limiting based on the domain or * provide a more specialized function. * * @param delegate the {@code RequestContext} to rate limit * @param executor the {@code LimitedExecutorService} that is used to rate * limit requests * @return a {@code RequestContext} using {@code executor} to rate limit * {@code delegate} */ public static RequestContext limit(AsyncFunction<? super Request, Response> delegate, LimitedExecutorService executor) { return limit(delegate, executor, NULL_CONSTANT); } /** * Wraps {@code delegate}, using {@code executor} to rate limit * {@code Request} execution. {@code keyFunction} is used to convert * each submitted {@code Request} into a {@code key} that is used to * {@link LimitedExecutorService#execute(Object, Runnable) execute} the * request. Requests with the same {@code key} will be subject to the same * rate limiting. Using {@link #toTopPrivateDomain()} will cause all requests * to the same top private domain to be rate limited. {@code keyFunction} * may return {@code null} to use the {@code LimitedExecutorService} default * key. * * @see #toTopPrivateDomain() * @see #toFullDomain() * @param delegate the {@code RequestContext} to rate limit * @param executor the {@code LimitedExecutorService} that is used to rate * limit requests * @param keyFunction converts each {@code Request} into the key passed to * {@code executor} to enforce the rate limiting * @return a {@code RequestContext} using {@code executor} to rate limit * {@code delegate} */ public static RequestContext limit(final @NonFinal AsyncFunction<? super Request, Response> delegate, final @NonFinal LimitedExecutorService executor, final @NonFinal Function<? super Request, ? extends Object> keyFunction) { return new RequestContext() { @Override public ListenableFuture<Response> apply(Request request) throws Exception { request.checkNotStarted(); if (request.isCancelled()) { request.onCancel(this, true); return Futures.immediateCancelledFuture(); } request.setSubmissionMillisIfAbsent(); SettableFuture<Response> future = SettableFuture.create(); Runnable task = Requests.apply(delegate, request, future); Object key = keyFunction.apply(request); if (key != null) { executor.execute(key, task, future); } else { executor.execute(task, future); } return future; } }; } /** * see {@link #chain(Iterable)}. */ public static RedirectHandler chain(RedirectHandler... handlers) { return chain(ImmutableList.copyOf(handlers)); } /** * Combines the supplied redirect handlers. The first handler to indicate * a redirect is necessary will be used to apply the redirect. Only one * of the handlers will be used for any given redirect. */ public static RedirectHandler chain(Iterable<? extends RedirectHandler> handlers) { final ImmutableList<RedirectHandler> redirectHandlers = ImmutableList .copyOf(CollectUtils.checkAllNotNull(handlers)); checkArgument(!redirectHandlers.isEmpty(), "no handler provided"); return new RedirectHandler() { @Override public boolean shouldRedirect(Response response) { if (response == null) { return false; } for (RedirectHandler handler : redirectHandlers) { if (handler.shouldRedirect(response)) { return true; } } return false; } @Override public Request apply(Request request) { if (request == null) { return null; } final Response cause = request.cause().get(); for (RedirectHandler handler : redirectHandlers) { if (handler.shouldRedirect(cause)) { return handler.apply(request); } } return null; } }; } /** * redirect handler that always redirects, used for testing. */ static RedirectHandler FOLLOW = new RedirectHandler() { @Override public boolean shouldRedirect(Response response) { return true; } @Override public Request apply(Request request) { if (request.cause().get().headers().contains("location")) { request.uri(URI.create(request.cause().get().headers().last("location"))); } return request; } }; /** * Wraps {@code delegate} in a {@code RequestContext} that will convert * successful responses that satisfy {@code failPredicate} to an error. Note * that converting the response to a failure only affects the status of the * response {@code ListenableFuture}, and has <i>no</i> effect on the * {@link RequestCallback} callback methods. */ public static RequestContext toFailure(final @NonNull AsyncFunction<? super Request, Response> delegate, final @NonNull Predicate<? super Response> failPredicate) { return new RequestContext() { @Override public ListenableFuture<Response> apply(Request request) throws Exception { ListenableFuture<Response> delegateFuture = delegate.apply(request); final SettableFuture<Response> future = SettableFuture.create(); Futures.addCallback(delegateFuture, new FutureCallback<Response>() { @Override public void onSuccess(Response result) { try { if (failPredicate.apply(result)) { result.onError(Errors.message("predicate failure: %s", failPredicate)); future.setException(new RequestException(result)); } else { future.set(result); } } catch (Throwable t) { //have to ensure the returned future always completes //even if the predicate or onError throws future.setException(new RequestException(result, t)); //guava Futures should log this throw Throwables.propagate(t); } } @Override public void onFailure(Throwable t) { future.setException(t); } }); FutureUtils.linkFailure(future, delegateFuture, false); return future; } }; } /** * Returns a predicates that returns true if the response is not null and * the response code is in {@code codes}. */ public static Predicate<Response> isCode(int... codes) { checkArgument(codes.length > 0, "at least one code required"); final int[] array = codes.clone(); return new Predicate<Response>() { @Override public boolean apply(Response input) { if (input == null) { return false; } return Ints.contains(array, input.code()); } }; } /** * same as * {@link #processIf(ContentListener, Predicate) processIf(listener, isCode(codes))}. */ public static RequestCallback processIf(@NonNull ContentListener<? super Response, ? super ByteBuffer> listener, int... codes) { return processIf(listener, isCode(codes)); } /** * Wraps {@code listener} in a {@code RequestCallback} that forwards * {@link ContentCallback} methods to {@code listener} only if the response * satisfies {@code predicate}. If {@code listener} is already a * {@code RequestCallback}, it is treated as only a {@code ContentCallback} * and will not receive other {@code RequestCallback} methods. * <p> * This is useful listeners (such as {@link FileCallback}) that should only * process the response content when the response was successful. For * example, to only save the first MB of content from a range request, use: * {@code Requests.processIf(new RandomAccessFileCallback("filename", "rw", Range.openClosed(0, 1024*1024)), 206)}. */ public static RequestCallback processIf( final @NonNull ContentListener<? super Response, ? super ByteBuffer> listener, final @NonNull Predicate<? super Response> predicate) { return new EmptyRequestCallback() { boolean forward; @Override public boolean onContentStart(Response context) { forward = predicate.apply(context); if (forward && listener instanceof ContentCallback) { forward = ((ContentCallback<? super Response, ?>) listener).onContentStart(context); } return forward; } @Override public boolean onContent(Response context, ByteBuffer content) { if (forward) { forward = listener.onContent(context, content); } return forward; } @Override public void onContentComplete(Response context) { if (forward && listener instanceof ContentCallback) { ((ContentCallback<? super Response, ?>) listener).onContentComplete(context); } } @Override public void onError(BasicError error) { if (listener instanceof BasicError.Listener) { ((BasicError.Listener) listener).onError(error); } } }; } /** * see {@link #removeHeaders(RedirectHandler, Iterable)}. */ public static Function<Request, Request> removeHeaders(String... headers) { return removeHeaders(ImmutableList.copyOf(headers)); } /** * Returns a Function that removes all headers mapped to the specified * header names. */ public static Function<Request, Request> removeHeaders(@NonNull Iterable<String> headers) { final List<String> names = ImmutableList.copyOf(CollectUtils.checkAllNotNull(headers)); checkArgument(!names.isEmpty()); return new Function<Request, Request>() { @Override public Request apply(Request input) { if (input != null) { for (String name : names) { input.headers().removeAll(name); } } return input; } }; } public static RedirectHandler before(RedirectHandler delegate, final @NonNull Function<? super Request, ? extends Request> function) { return new ForwardingRedirectHandler(delegate) { @Override public Request apply(Request request) { return super.apply(function.apply(request)); } }; } public static RedirectHandler after(RedirectHandler delegate, final @NonNull Function<? super Request, ? extends Request> function) { return new ForwardingRedirectHandler(delegate) { @Override public Request apply(Request request) { return function.apply(super.apply(request)); } }; } public static Predicate<Response> asPredicate(@NonNull final RedirectHandler handler) { return new Predicate<Response>() { @Override public boolean apply(Response input) { return handler.shouldRedirect(input); } }; } public static RedirectHandler asRedirectHandler(final @NonNull Predicate<? super Response> predicate, final @NonNull Function<? super Request, ? extends Request> function) { return new RedirectHandler() { @Override public boolean shouldRedirect(Response response) { return predicate.apply(response); } @Override public Request apply(Request request) { if (request == null) { return null; } if (!predicate.apply(request.cause().get())) { return null; } return function.apply(request); } }; } static final Function<String, String> notNullTrimUpper = StringUtils.stringFunction().notNull().trim() .uppercaseAscii().get(); static ListMultimap<String, String> notNull(ListMultimap<String, String> delegate) { return Predicated.listMultimap(delegate, Predicates.notNull(), Predicates.notNull()); } /** * not null and not content-type or content-length */ static final Predicate<String> HKEY_NO_TYPE_LEN = new Predicate<String>() { @Override public boolean apply(String input) { if (input == null) { return false; } input = Headers.normalize(input); return !HttpHeaders.CONTENT_TYPE.equals(input) && !HttpHeaders.CONTENT_LENGTH.equals(input); } }; /** * not null and not content-length */ static final Predicate<String> HKEY_NO_LEN = new Predicate<String>() { @Override public boolean apply(String input) { if (input == null) { return false; } input = Headers.normalize(input); return !HttpHeaders.CONTENT_LENGTH.equals(input); } }; //this works, but much less clear than below // static final Predicate<Map.Entry<String, String>> NOT_CONTENT_LENGTH = // Predicates.compose( // Predicates.compose( // Predicates.and( // Predicates.notNull(), // Predicates.not(Predicates.equalTo(HttpHeaders.CONTENT_LENGTH))), // Headers.normalize()), // MapUtils.<String, String>key()); static final Predicate<Map.Entry<String, String>> NOT_CONTENT_LENGTH = new Predicate<Map.Entry<String, String>>() { @Override public boolean apply(final Map.Entry<String, String> input) { if (input == null) { return false; } final String name = input.getKey(); if (name == null) { return false; } return !HttpHeaders.CONTENT_LENGTH.equals(Headers.normalize(name)); } }; }