All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.io.content.ContentSourceTransformer Maven / Gradle / Ivy

There is a newer version: 12.1.0.alpha0
Show newest version
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.io.content;

import java.util.Objects;

import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.util.ExceptionUtil;
import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.util.thread.SerializedInvoker;

/**
 * 

This abstract {@link Content.Source} wraps another {@link Content.Source} and implementers need only * to implement the {@link #transform(Content.Chunk)} method, which is used to transform {@link Content.Chunk} * read from the wrapped source.

*

The {@link #demand(Runnable)} conversation is passed directly to the wrapped {@link Content.Source}, * which means that transformations that may fully consume bytes read can result in a null return from * {@link Content.Source#read()} even after a callback to the demand {@link Runnable} (as per spurious * invocation in {@link Content.Source#demand(Runnable)}.

*/ public abstract class ContentSourceTransformer implements Content.Source { private final SerializedInvoker invoker; private final Content.Source rawSource; private Content.Chunk rawChunk; private Content.Chunk transformedChunk; private volatile boolean needsRawRead; private volatile Runnable demandCallback; protected ContentSourceTransformer(Content.Source rawSource) { this(rawSource, new SerializedInvoker(ContentSourceTransformer.class)); } protected ContentSourceTransformer(Content.Source rawSource, SerializedInvoker invoker) { this.rawSource = rawSource; this.invoker = invoker; } protected Content.Source getContentSource() { return rawSource; } @Override public Content.Chunk read() { while (true) { if (needsRawRead) { rawChunk = rawSource.read(); needsRawRead = rawChunk == null; if (rawChunk == null) return null; } if (Content.Chunk.isFailure(rawChunk)) { Content.Chunk failure = rawChunk; rawChunk = Content.Chunk.next(rawChunk); needsRawRead = rawChunk == null; return failure; } if (Content.Chunk.isFailure(transformedChunk)) return transformedChunk; transformedChunk = process(rawChunk); if (rawChunk != null && rawChunk != transformedChunk) rawChunk.release(); rawChunk = null; if (transformedChunk != null) { Content.Chunk result = transformedChunk; transformedChunk = Content.Chunk.next(result); return result; } needsRawRead = true; } } @Override public void demand(Runnable demandCallback) { this.demandCallback = Objects.requireNonNull(demandCallback); if (needsRawRead) // Inner class used instead of lambda for clarity in stack traces. rawSource.demand(new DemandTask(Invocable.getInvocationType(demandCallback), this::invokeDemandCallback)); else invoker.run(this::invokeDemandCallback); } @Override public void fail(Throwable failure) { rawSource.fail(failure); } private void invokeDemandCallback() { Runnable demandCallback = this.demandCallback; this.demandCallback = null; if (demandCallback != null) ExceptionUtil.run(demandCallback, this::fail); } private Content.Chunk process(Content.Chunk rawChunk) { try { return transform(rawChunk); } catch (Throwable x) { fail(x); return Content.Chunk.from(x); } } /** *

Transforms the input chunk parameter into an output chunk.

*

When this method produces a non-{@code null}, non-last chunk, * it is subsequently invoked with a {@code null} input chunk to try to * produce more output chunks from the previous input chunk. * For example, a single compressed input chunk may be transformed into * multiple uncompressed output chunks.

*

The input chunk is released as soon as this method returns, so * implementations that must hold onto the input chunk must arrange to call * {@link Content.Chunk#retain()} and its correspondent {@link Content.Chunk#release()}.

*

Implementations should return an {@link Content.Chunk} with non-null * {@link Content.Chunk#getFailure()} in case * of transformation errors.

*

Exceptions thrown by this method are equivalent to returning an error chunk.

*

Implementations of this method may return:

*
    *
  • {@code null}, if more input chunks are necessary to produce an output chunk
  • *
  • the {@code inputChunk} itself, typically in case of non-null {@link Content.Chunk#getFailure()}, * or when no transformation is required
  • *
  • a new {@link Content.Chunk} derived from {@code inputChunk}.
  • *
* * @param inputChunk a chunk read from the wrapped {@link Content.Source} * @return a transformed chunk or {@code null} */ protected abstract Content.Chunk transform(Content.Chunk inputChunk); private class DemandTask extends Invocable.Task.Abstract { private final Runnable invokeDemandCallback; private DemandTask(InvocationType invocationType, Runnable invokeDemandCallback) { super(invocationType); this.invokeDemandCallback = invokeDemandCallback; } @Override public void run() { invoker.run(invokeDemandCallback); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy