001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2019–2020 microBean™.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
014 * implied.  See the License for the specific language governing
015 * permissions and limitations under the License.
016 */
017package org.microbean.jersey.netty;
018
019import java.lang.reflect.Type;
020
021import java.net.URI;
022
023import java.util.Arrays;
024import java.util.List;
025import java.util.Objects;
026
027import java.util.function.Supplier;
028
029import java.util.logging.Logger;
030
031import javax.ws.rs.core.Configuration;
032import javax.ws.rs.core.SecurityContext;
033
034import io.netty.buffer.ByteBuf;
035import io.netty.buffer.ByteBufAllocator;
036import io.netty.buffer.ByteBufHolder;
037
038import io.netty.channel.ChannelConfig; // for javadoc only
039import io.netty.channel.ChannelHandlerContext;
040import io.netty.channel.ChannelInboundHandlerAdapter; // for javadoc only
041import io.netty.channel.ChannelPipeline; // for javadoc only
042
043import io.netty.handler.codec.MessageToMessageDecoder;
044
045import org.glassfish.jersey.internal.PropertiesDelegate;
046
047import org.glassfish.jersey.internal.util.collection.Ref;
048
049import org.glassfish.jersey.server.ContainerRequest;
050
051import org.glassfish.jersey.server.internal.ContainerUtils;
052
053/**
054 * A {@link MessageToMessageDecoder} that decodes messages of a
055 * specific type into {@link ContainerRequest}s.
056 *
057 * <p>Instances of this class are normally followed in a {@link
058 * ChannelPipeline} by instances of the {@link
059 * AbstractContainerRequestHandlingResponseWriter} class.</p>
060 *
061 * @param <T> the common supertype of messages that can be decoded
062 *
063 * @param <H> the type of {@linkplain #isHeaders(Object) "headers" messages}
064 *
065 * @param <D> the type of {@linkplain #isData(Object) "data" messages}
066 *
067 * @author <a href="https://about.me/lairdnelson"
068 * target="_parent">Laird Nelson</a>
069 *
070 * @see #decode(ChannelHandlerContext, Object, List)
071 *
072 * @see MessageToMessageDecoder
073 *
074 * @see ContainerRequest
075 *
076 * @see AbstractContainerRequestHandlingResponseWriter
077 */
078public abstract class AbstractContainerRequestDecoder<T, H extends T, D extends T> extends MessageToMessageDecoder<T> {
079
080
081  /*
082   * Static fields.
083   */
084
085
086  private static final String cn = AbstractContainerRequestDecoder.class.getName();
087
088  private static final Logger logger = Logger.getLogger(cn);
089
090  private static final Type channelHandlerContextRefType = ChannelHandlerContextReferencingFactory.genericRefType.getType();
091
092
093  /*
094   * Instance fields.
095   */
096
097
098  private final Class<H> headersClass;
099
100  private final Type headersClassRefType;
101
102  private final Class<D> dataClass;
103
104  private final URI baseUri;
105
106  private final Supplier<? extends Configuration> configurationSupplier;
107
108  private TerminableByteBufInputStream terminableByteBufInputStream;
109
110  private ContainerRequest containerRequestUnderConstruction;
111
112
113  /*
114   * Constructors.
115   */
116
117
118  /**
119   * Creates a new {@link AbstractContainerRequestDecoder} implementation.
120   *
121   * @param baseUri the base {@link URI} against which relative
122   * request URIs will be resolved; may be {@code null} in which case
123   * the return value of {@link URI#create(String) URI.create("/")}
124   * will be used instead
125   *
126   * @param headersClass the type representing a "headers" message;
127   * must not be {@code null}
128   *
129   * @param dataClass the type representing a "data" message; must not
130   * be {@code null}
131   *
132   * @exception NullPointerException if either {@code headersClass} or
133   * {@code dataClass} is {@code null}
134   *
135   * @see #AbstractContainerRequestDecoder(URI, Configuration, Class,
136   * Class)
137   *
138   * @deprecated Please use the {@link
139   * #AbstractContainerRequestDecoder(URI, Configuration, Class,
140   * Class)} constructor instead.
141   */
142  @Deprecated
143  protected AbstractContainerRequestDecoder(final URI baseUri,
144                                            final Class<H> headersClass,
145                                            final Class<D> dataClass) {
146    this(baseUri, AbstractContainerRequestDecoder::returnNull, headersClass, dataClass);
147  }
148
149  /**
150   * Creates a new {@link AbstractContainerRequestDecoder} implementation.
151   *
152   * @param baseUri the base {@link URI} against which relative
153   * request URIs will be resolved; may be {@code null} in which case
154   * the return value of {@link URI#create(String) URI.create("/")}
155   * will be used instead
156   *
157   * @param configuration a {@link Configuration} describing how the
158   * container is configured; may be {@code null}
159   *
160   * @param headersClass the type representing a "headers" message;
161   * must not be {@code null}
162   *
163   * @param dataClass the type representing a "data" message; must not
164   * be {@code null}
165   *
166   * @exception NullPointerException if either {@code headersClass} or
167   * {@code dataClass} is {@code null}
168   */
169  protected AbstractContainerRequestDecoder(final URI baseUri,
170                                            final Configuration configuration,
171                                            final Class<H> headersClass,
172                                            final Class<D> dataClass) {
173    this(baseUri, configuration == null ? AbstractContainerRequestDecoder::returnNull : new ImmutableSupplier<>(configuration), headersClass, dataClass);
174  }
175
176  /**
177   * Creates a new {@link AbstractContainerRequestDecoder} implementation.
178   *
179   * @param baseUri the base {@link URI} against which relative
180   * request URIs will be resolved; may be {@code null} in which case
181   * the return value of {@link URI#create(String) URI.create("/")}
182   * will be used instead
183   *
184   * @param configurationSupplier a {@link Supplier} of {@link
185   * Configuration} instances describing how the container is
186   * configured; may be {@code null}
187   *
188   * @param headersClass the type representing a "headers" message;
189   * must not be {@code null}
190   *
191   * @param dataClass the type representing a "data" message; must not
192   * be {@code null}
193   *
194   * @exception NullPointerException if either {@code headersClass} or
195   * {@code dataClass} is {@code null}
196   */
197  protected AbstractContainerRequestDecoder(final URI baseUri,
198                                            final Supplier<? extends Configuration> configurationSupplier,
199                                            final Class<H> headersClass,
200                                            final Class<D> dataClass) {
201    super();
202    this.baseUri = baseUri == null ? URI.create("/") : baseUri;
203    if (configurationSupplier == null) {
204      this.configurationSupplier = AbstractContainerRequestDecoder::returnNull;
205    } else {
206      this.configurationSupplier = configurationSupplier;
207    }
208    this.headersClass = Objects.requireNonNull(headersClass);
209    this.headersClassRefType = new ParameterizedType(Ref.class, headersClass);
210    this.dataClass = Objects.requireNonNull(dataClass);
211  }
212
213
214  /*
215   * Instance methods.
216   */
217
218
219  /**
220   * Overrides the {@link
221   * ChannelInboundHandlerAdapter#channelReadComplete(ChannelHandlerContext)}
222   * method to {@linkplain ChannelHandlerContext#read() request a
223   * read} when necessary, taking {@linkplain
224   * ChannelConfig#isAutoRead() the auto-read status of the associated
225   * <code>Channel</code>} into account.
226   *
227   * @param channelHandlerContext the {@link ChannelHandlerContext} in
228   * effect; must not be {@code null}
229   *
230   * @exception NullPointerException if {@code channelHandlerContext}
231   * is {@code null}
232   *
233   * @see ChannelConfig#isAutoRead()
234   *
235   * @see
236   * ChannelInboundHandlerAdapter#channelReadComplete(ChannelHandlerContext)
237   */
238  @Override
239  public void channelReadComplete(final ChannelHandlerContext channelHandlerContext)
240    throws Exception {
241    super.channelReadComplete(channelHandlerContext);
242    if (this.containerRequestUnderConstruction != null &&
243        !channelHandlerContext.channel().config().isAutoRead()) {
244      channelHandlerContext.read();
245    }
246  }
247
248  /**
249   * Returns {@code true} if the supplied {@code message} is an
250   * instance of either the {@linkplain
251   * #AbstractContainerRequestDecoder(URI, Class, Class) headers type
252   * or data type supplied at construction time}, and {@code false} in
253   * all other cases.
254   *
255   * @param message the message to interrogate; may be {@code null} in
256   * which case {@code false} will be returned
257   *
258   * @return {@code true} if the supplied {@code message} is an
259   * instance of either the {@linkplain
260   * #AbstractContainerRequestDecoder(URI, Class, Class) headers type
261   * or data type supplied at construction time}; {@code false} in all
262   * other cases
263   *
264   * @see #AbstractContainerRequestDecoder(URI, Class, Class)
265   */
266  @Override
267  public boolean acceptInboundMessage(final Object message) {
268    return this.headersClass.isInstance(message) || this.dataClass.isInstance(message);
269  }
270
271  /**
272   * Returns {@code true} if the supplied message represents a
273   * "headers" message (as distinguished from a "data" message).
274   *
275   * @param message the message to interrogate; will not be {@code
276   * null}
277   *
278   * @return {@code true} if the supplied message represents a
279   * "headers" message; {@code false} otherwise
280   *
281   * @see #isData(Object)
282   */
283  protected boolean isHeaders(final T message) {
284    return this.headersClass.isInstance(message);
285  }
286
287  /**
288   * Extracts and returns a {@link String} representing a request URI
289   * from the supplied message, which is guaranteed to be a
290   * {@linkplain #isHeaders(Object) "headers" message}.
291   *
292   * <p>Implementations of this method may return {@code null} but
293   * normally will not.</p>
294   *
295   * @param message the message to interrogate; will not be {@code
296   * null}
297   *
298   * @return a {@link String} representing a request URI from the
299   * supplied message, or {@code null}
300   */
301  protected abstract String getRequestUriString(final H message);
302
303  /**
304   * Extracts and returns a {@link String} representing a request
305   * method from the supplied message, which is guaranteed to be a
306   * {@linkplain #isHeaders(Object) "headers" message}.
307   *
308   * <p>Implementations of this method may return {@code null} but
309   * normally will not.</p>
310   *
311   * @param message the message to interrogate; will not be {@code
312   * null}
313   *
314   * @return a {@link String} representing a request method from the
315   * supplied message, or {@code null}
316   */
317  protected abstract String getMethod(final H message);
318
319  /**
320   * Creates and returns a {@link SecurityContext} appropriate for the
321   * supplied message, which is guaranteed to be a {@linkplain
322   * #isHeaders(Object) "headers" message}.
323   *
324   * <p>Implementations of this method must not return {@code
325   * null}.</p>
326   *
327   * @param message the {@linkplain #isHeaders(Object) "headers"
328   * message} for which a new {@link SecurityContext} is to be
329   * returned; will not be {@code null}
330   *
331   * @return a new, non-{@code null} {@link SecurityContext}
332   */
333  protected SecurityContext createSecurityContext(final H message) {
334    return new SecurityContextAdapter();
335  }
336
337  /**
338   * Creates and returns a {@link PropertiesDelegate} appropriate for the
339   * supplied message, which is guaranteed to be a {@linkplain
340   * #isHeaders(Object) "headers" message}.
341   *
342   * <p>Implementations of this method must not return {@code
343   * null}.</p>
344   *
345   * @param message the {@linkplain #isHeaders(Object) "headers"
346   * message} for which a new {@link PropertiesDelegate} is to be
347   * returned; will not be {@code null}
348   *
349   * @return a new, non-{@code null} {@link PropertiesDelegate}
350   */
351  protected PropertiesDelegate createPropertiesDelegate(final H message) {
352    return new MapBackedPropertiesDelegate();
353  }
354
355  /**
356   * Installs the supplied {@code message} into the supplied {@link
357   * ContainerRequest} in some way.
358   *
359   * <p>This implementation calls {@link
360   * ContainerRequest#setProperty(String, Object)} with the
361   * {@linkplain Class#getName() fully-qualified class name} of the
362   * headers class supplied at construction time as the key, and the
363   * supplied {@code message} as the value.</p>
364   *
365   * @param channelHandlerContext the {@link ChannelHandlerContext} in
366   * effect; will not be {@code null}; supplied for convenience;
367   * overrides may (and often do) ignore this parameter
368   *
369   * @param message the message to install; will not be {@code null}
370   * and is guaranteed to be a {@linkplain #isHeaders(Object)
371   * "headers" message}
372   *
373   * @param containerRequest the just-constructed {@link
374   * ContainerRequest} into which to install the supplied {@code
375   * message}; will not be {@code null}
376   */
377  protected void installMessage(final ChannelHandlerContext channelHandlerContext,
378                                final H message,
379                                final ContainerRequest containerRequest) {
380    containerRequest.setProperty(ChannelHandlerContext.class.getName(), channelHandlerContext);
381    containerRequest.setProperty(this.headersClass.getName(), message);
382  }
383
384  /**
385   * Returns {@code true} if the supplied message represents a
386   * "data" message (as distinguished from a "headers" message).
387   *
388   * @param message the message to interrogate; will not be {@code
389   * null}
390   *
391   * @return {@code true} if the supplied message represents a
392   * "data" message; {@code false} otherwise
393   *
394   * @see #isHeaders(Object)
395   */
396  protected boolean isData(final T message) {
397    return this.dataClass.isInstance(message);
398  }
399
400  /**
401   * Extracts any content from the supplied {@linkplain
402   * #isData(Object) "data" message} as a {@link ByteBuf}, or returns
403   * {@code null} if there is no such content.
404   *
405   * @param message the {@linkplain #isData(Object) "data" message}
406   * from which a {@link ByteBuf} is to be extracted; will not be
407   * {@code null}
408   *
409   * @return a {@link ByteBuf} representing the message's content, or
410   * {@code null}
411   */
412  protected ByteBuf getContent(final D message) {
413    final ByteBuf returnValue;
414    if (message instanceof ByteBuf) {
415      returnValue = (ByteBuf)message;
416    } else if (message instanceof ByteBufHolder) {
417      returnValue = ((ByteBufHolder)message).content();
418    } else {
419      returnValue = null;
420    }
421    return returnValue;
422  }
423
424  /**
425   * Returns {@code true} if there will be no further message
426   * components in an overall larger message after the supplied one.
427   *
428   * @param message the message component to interrogate; will not be {@code
429   * null}; may be either a {@linkplain #isHeaders(Object) "headers"}
430   * or {@linkplain #isData(Object) "data"} message component
431   *
432   * @return {@code true} if and only if there will be no further
433   * message components to come
434   */
435  protected abstract boolean isLast(final T message);
436
437  /**
438   * Decodes the supplied {@code message} into a {@link
439   * ContainerRequest} and adds it to the supplied {@code out} {@link
440   * List}.
441   *
442   * @param channelHandlerContext the {@link ChannelHandlerContext} in
443   * effect; must not be {@code null}
444   *
445   * @param message the message to decode; must not be {@code null}
446   * and must be {@linkplain #acceptInboundMessage(Object) acceptable}
447   *
448   * @param out a {@link List} of {@link Object}s that result from
449   * decoding the supplied {@code message}; must not be {@code null}
450   * and must be mutable
451   *
452   * @exception NullPointerException if {@code channelHandlerContext}
453   * or {@code out} is {@code null}
454   *
455   * @exception IllegalArgumentException if {@code message} is {@code
456   * null} or otherwise unacceptable
457   *
458   * @exception IllegalStateException if there is an internal problem
459   * with state management
460   */
461  @Override
462  protected final void decode(final ChannelHandlerContext channelHandlerContext,
463                              final T message,
464                              final List<Object> out) {
465    if (isHeaders(message)) {
466      if (this.containerRequestUnderConstruction == null) {
467        if (this.terminableByteBufInputStream == null) {
468          final URI requestUri;
469          final H headersMessage = this.headersClass.cast(message);
470          final String requestUriString = this.getRequestUriString(headersMessage);
471          if (requestUriString == null) {
472            requestUri = this.baseUri;
473          } else if (requestUriString.startsWith("/") && requestUriString.length() > 1) {
474            requestUri = this.baseUri.resolve(ContainerUtils.encodeUnsafeCharacters(requestUriString.substring(1)));
475          } else {
476            requestUri = this.baseUri.resolve(ContainerUtils.encodeUnsafeCharacters(requestUriString));
477          }
478          final String method = this.getMethod(headersMessage);
479          final SecurityContext securityContext = this.createSecurityContext(headersMessage);
480          final PropertiesDelegate propertiesDelegate = this.createPropertiesDelegate(headersMessage);
481          final ContainerRequest containerRequest =
482            new ContainerRequest(this.baseUri,
483                                 requestUri,
484                                 method,
485                                 securityContext == null ? new SecurityContextAdapter() : securityContext,
486                                 propertiesDelegate == null ? new MapBackedPropertiesDelegate() : propertiesDelegate,
487                                 this.configurationSupplier.get());
488          this.installMessage(channelHandlerContext, headersMessage, containerRequest);
489          containerRequest.setRequestScopedInitializer(injectionManager -> {
490              // See JerseyChannelInitializer, where the factories of
491              // factories that produce references of the things we're
492              // interested in are installed.  Here, in request scope
493              // itself, we set the actual target of those references.
494              // This is apparently the proper way to do this sort of
495              // thing in Jersey (!) and examples of this pattern show
496              // up throughout its codebase.  With jaw somewhat agape,
497              // we follow suit.
498              final Ref<ChannelHandlerContext> channelHandlerContextRef = injectionManager.getInstance(channelHandlerContextRefType);
499              if (channelHandlerContextRef != null) {
500                channelHandlerContextRef.set(channelHandlerContext);
501              }
502              final Ref<H> headersRef = injectionManager.getInstance(this.headersClassRefType);
503              if (headersRef != null) {
504                headersRef.set(headersMessage);
505              }
506            });
507          if (this.isLast(message)) {
508            out.add(containerRequest);
509          } else {
510            this.containerRequestUnderConstruction = containerRequest;
511          }
512        } else {
513          throw new IllegalStateException("this.terminableByteBufInputStream != null: " + this.terminableByteBufInputStream);
514        }
515      } else {
516        throw new IllegalStateException("this.containerRequestUnderConstruction != null: " + this.containerRequestUnderConstruction);
517      }
518    } else if (this.isData(message)) {
519      final D dataMessage = this.dataClass.cast(message);
520      final ByteBuf content = this.getContent(dataMessage);
521      if (content == null || content.readableBytes() <= 0) {
522        if (this.isLast(message)) {
523          if (this.terminableByteBufInputStream == null) {
524            if (this.containerRequestUnderConstruction == null) {
525              // Do nothing; this is a final, zero-content message and
526              // we already dealt with the previous headers message
527              // component.
528            } else {
529              out.add(this.containerRequestUnderConstruction);
530              this.containerRequestUnderConstruction = null;
531            }
532          } else if (this.containerRequestUnderConstruction == null) {
533            throw new IllegalStateException("this.containerRequestUnderConstruction == null && this.terminableByteBufInputStream != null: " + this.terminableByteBufInputStream);
534          } else {
535            out.add(this.containerRequestUnderConstruction);
536            this.containerRequestUnderConstruction = null;
537            this.terminableByteBufInputStream.terminate();
538            this.terminableByteBufInputStream = null;
539          }
540        } else if (this.containerRequestUnderConstruction == null) {
541          throw new IllegalStateException("this.containerRequestUnderConstruction == null");
542        } else {
543          // We got an empty chunk in the middle of the stream.
544          // Ignore it.  Note that
545          // #channelReadComplete(ChannelHandlerContext) will take
546          // care of auto-read-or-not situations.
547        }
548      } else if (this.containerRequestUnderConstruction == null) {
549        throw new IllegalStateException("this.containerRequestUnderConstruction == null");
550      } else if (this.isLast(message)) {
551        final TerminableByteBufInputStream terminableByteBufInputStream;
552        if (this.terminableByteBufInputStream == null) {
553          final TerminableByteBufInputStream newlyCreatedTerminableByteBufInputStream = this.createTerminableByteBufInputStream(channelHandlerContext.alloc());
554          this.containerRequestUnderConstruction.setEntityStream(newlyCreatedTerminableByteBufInputStream);
555          out.add(this.containerRequestUnderConstruction);
556          terminableByteBufInputStream = newlyCreatedTerminableByteBufInputStream;
557        } else {
558          terminableByteBufInputStream = this.terminableByteBufInputStream;
559          this.terminableByteBufInputStream = null;
560        }
561        assert this.terminableByteBufInputStream == null;
562        assert terminableByteBufInputStream != null;
563        content.retain(); // see https://github.com/microbean/microbean-jersey-netty/issues/12
564        terminableByteBufInputStream.addByteBuf(content);
565        terminableByteBufInputStream.terminate();
566        this.containerRequestUnderConstruction = null;
567      } else {
568        if (this.terminableByteBufInputStream == null) {
569          final TerminableByteBufInputStream newlyCreatedTerminableByteBufInputStream = this.createTerminableByteBufInputStream(channelHandlerContext.alloc());
570          this.terminableByteBufInputStream = newlyCreatedTerminableByteBufInputStream;
571          this.containerRequestUnderConstruction.setEntityStream(newlyCreatedTerminableByteBufInputStream);
572          out.add(this.containerRequestUnderConstruction);
573        }
574        content.retain(); // see https://github.com/microbean/microbean-jersey-netty/issues/12
575        this.terminableByteBufInputStream.addByteBuf(content);
576      }
577    } else {
578      throw new IllegalArgumentException("Unexpected message: " + message);
579    }
580  }
581
582  /**
583   * Creates and returns a new {@link TerminableByteBufInputStream}.
584   *
585   * @param byteBufAllocator a {@link ByteBufAllocator} that may be
586   * used or ignored; will not be {@code null}
587   *
588   * @return a new, non-{@code null} {@link
589   * TerminableByteBufInputStream}
590   */
591  protected TerminableByteBufInputStream createTerminableByteBufInputStream(final ByteBufAllocator byteBufAllocator) {
592    return new TerminableByteBufInputStream(byteBufAllocator);
593  }
594
595
596  /*
597   * Static methods.
598   */
599
600
601  /**
602   * Returns {@code null} when invoked.
603   *
604   * <p>This method is used only via method reference and only in
605   * pathological cases.</p>
606   *
607   * @return {@code null} in all cases
608   */
609  private static final Configuration returnNull() {
610    return null;
611  }
612
613
614  /*
615   * Inner and nested classes.
616   */
617
618
619  private static final class ParameterizedType implements java.lang.reflect.ParameterizedType {
620
621    private final Type rawType;
622
623    private final Type[] actualTypeArguments;
624
625    private ParameterizedType(final Type rawType, final Type... actualTypeArguments) {
626      super();
627      this.rawType = rawType;
628      this.actualTypeArguments = actualTypeArguments;
629    }
630
631    @Override
632    public final Type[] getActualTypeArguments() {
633      return this.actualTypeArguments;
634    }
635
636    @Override
637    public final Type getRawType() {
638      return this.rawType;
639    }
640
641    @Override
642    public final Type getOwnerType() {
643      return null;
644    }
645
646    @Override
647    public int hashCode() {
648      final Object rawType = this.getRawType();
649      final int actualTypeArgumentsHashCode = Arrays.hashCode(this.getActualTypeArguments());
650      return rawType == null ? actualTypeArgumentsHashCode : actualTypeArgumentsHashCode ^ rawType.hashCode();
651    }
652
653    @Override
654    public final boolean equals(final Object other) {
655      if (other == this) {
656        return true;
657      } else if (other instanceof java.lang.reflect.ParameterizedType) {
658        final java.lang.reflect.ParameterizedType her = (java.lang.reflect.ParameterizedType)other;
659
660        final Object rawType = this.getRawType();
661        if (rawType == null) {
662          if (her.getRawType() != null) {
663            return false;
664          }
665        } else if (!rawType.equals(her.getRawType())) {
666          return false;
667        }
668
669        final Object[] actualTypeArguments = this.getActualTypeArguments();
670        if (actualTypeArguments == null) {
671          if (her.getActualTypeArguments() != null) {
672            return false;
673          }
674        } else if (!Arrays.equals(actualTypeArguments, her.getActualTypeArguments())) {
675          return false;
676        }
677
678        return true;
679      } else {
680        return false;
681      }
682    }
683
684  }
685
686}