PartBuilder.java

/*
 * Copyright 2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.bremersee.apiclient.webflux.contract.spring.multipart;

import static org.springframework.util.ObjectUtils.isEmpty;

import java.nio.file.Path;
import java.util.Arrays;
import java.util.function.Consumer;
import org.springframework.core.io.Resource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.FormFieldPart;
import org.springframework.http.codec.multipart.Part;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

/**
 * The part builder.
 *
 * @author Christian Bremer
 */
public class PartBuilder {

  /**
   * Instantiates a new part builder.
   */
  public PartBuilder() {
  }

  /**
   * Form field part builder.
   *
   * @param name the name
   * @param value the value
   * @return the form field part builder
   */
  public FormFieldPartBuilder part(String name, String value) {
    return new FormFieldPartBuilder(name, value);
  }

  /**
   * File part builder.
   *
   * @param name the name
   * @param file the file
   * @return the file part builder
   */
  public FilePartBuilder part(String name, Path file) {
    return new FilePartBuilder(name, file);
  }

  /**
   * Resource part builder.
   *
   * @param name the name
   * @param resource the resource
   * @return the resource part builder
   */
  public ResourcePartBuilder part(String name, Resource resource) {
    return new ResourcePartBuilder(name, resource);
  }

  /**
   * Data buffer part builder.
   *
   * @param name the name
   * @param content the content
   * @return the data buffer part builder
   */
  public DataBufferPartBuilder part(String name, Flux<DataBuffer> content) {
    return new DataBufferPartBuilder(name, content);
  }

  /**
   * Data buffer part builder.
   *
   * @param name the name
   * @param filename the filename
   * @param content the content
   * @return the data buffer part builder
   */
  public DataBufferPartBuilder part(String name, String filename, Flux<DataBuffer> content) {
    return new DataBufferPartBuilder(name, filename, content);
  }

  /**
   * The abstract part builder.
   *
   * @param <T> the type parameter
   */
  public abstract static class AbstractPartBuilder<T extends Part> {

    private DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();

    private int bufferSize = 1024;

    private final HttpHeaders headers = new HttpHeaders();

    /**
     * Instantiates a new abstract part builder.
     */
    AbstractPartBuilder() {
    }

    /**
     * Gets data buffer factory.
     *
     * @return the data buffer factory
     */
    protected DataBufferFactory getDataBufferFactory() {
      return dataBufferFactory;
    }

    /**
     * Gets buffer size.
     *
     * @return the buffer size
     */
    protected int getBufferSize() {
      return bufferSize;
    }

    /**
     * Gets headers.
     *
     * @return the headers
     */
    protected HttpHeaders getHeaders() {
      return headers;
    }

    /**
     * With data buffer factory.
     *
     * @param dataBufferFactory the data buffer factory
     * @return the abstract part builder
     */
    public AbstractPartBuilder<T> withDataBufferFactory(DataBufferFactory dataBufferFactory) {
      if (!isEmpty(dataBufferFactory)) {
        this.dataBufferFactory = dataBufferFactory;
      }
      return this;
    }

    /**
     * With buffer size.
     *
     * @param bufferSize the buffer size
     * @return the abstract part builder
     */
    public AbstractPartBuilder<T> withBufferSize(int bufferSize) {
      if (bufferSize > 0) {
        this.bufferSize = bufferSize;
      }
      return this;
    }

    /**
     * Content type.
     *
     * @param contentType the content type
     * @return the abstract part builder
     */
    public AbstractPartBuilder<T> contentType(MediaType contentType) {
      if (!isEmpty(contentType)) {
        headers.setContentType(contentType);
      }
      return this;
    }

    /**
     * Header.
     *
     * @param headerName the header name
     * @param headerValues the header values
     * @return the abstract part builder
     */
    public AbstractPartBuilder<T> header(String headerName, String... headerValues) {
      if (!isEmpty(headerName) && !isEmpty(headerValues)) {
        headers.addAll(headerName, Arrays.asList(headerValues));
      }
      return this;
    }

    /**
     * Headers.
     *
     * @param headersConsumer the headers consumer
     * @return the abstract part builder
     */
    public AbstractPartBuilder<T> headers(Consumer<HttpHeaders> headersConsumer) {
      if (!isEmpty(headersConsumer)) {
        headersConsumer.accept(headers);
      }
      return this;
    }

    /**
     * Build part.
     *
     * @return the part
     */
    public abstract T build();
  }

  /**
   * The form field part builder.
   */
  public static class FormFieldPartBuilder extends AbstractPartBuilder<FormFieldPart> {

    private final String value;

    /**
     * Instantiates a new form field part builder.
     *
     * @param name the name
     * @param value the value
     */
    FormFieldPartBuilder(String name, String value) {
      Assert.hasText(name, "Name must be present.");
      getHeaders().setContentDispositionFormData(name, null);
      this.value = value;
    }

    @Override
    public FormFieldPart build() {
      return DefaultParts.formFieldPart(getHeaders(), value);
    }
  }

  /**
   * The abstract file part builder.
   */
  public abstract static class AbstractFilePartBuilder extends AbstractPartBuilder<Part> {

    /**
     * Instantiates a new abstract file part builder.
     */
    AbstractFilePartBuilder() {
    }

    /**
     * Filename abstract file part builder.
     *
     * @param filename the filename
     * @return the abstract file part builder
     */
    public AbstractFilePartBuilder filename(String filename) {
      if (!isEmpty(filename) && !isEmpty(getHeaders().getContentDisposition())) {
        String name = getHeaders().getContentDisposition().getName();
        if (!isEmpty(name)) {
          getHeaders().setContentDispositionFormData(name, filename);
        }
      }
      return this;
    }
  }

  /**
   * The file part builder.
   */
  public static class FilePartBuilder extends AbstractFilePartBuilder {

    private Scheduler blockingOperationScheduler = Schedulers.boundedElastic();

    private final Path file;

    /**
     * Instantiates a new file part builder.
     *
     * @param name the name
     * @param file the file
     */
    FilePartBuilder(String name, Path file) {
      Assert.hasText(name, "Name must be present.");
      Assert.notNull(file, "File must be present.");
      this.file = file;
      getHeaders().setContentDispositionFormData(name, String.valueOf(file.getFileName()));
    }

    /**
     * With scheduler.
     *
     * @param scheduler the scheduler
     * @return the file part builder
     */
    public FilePartBuilder withScheduler(Scheduler scheduler) {
      if (!isEmpty(scheduler)) {
        this.blockingOperationScheduler = scheduler;
      }
      return this;
    }

    @Override
    public Part build() {
      return DefaultParts.part(getHeaders(), file, blockingOperationScheduler);
    }
  }

  /**
   * The resource part builder.
   */
  public static class ResourcePartBuilder extends AbstractFilePartBuilder {

    private final Resource resource;

    /**
     * Instantiates a new resource part builder.
     *
     * @param name the name
     * @param resource the resource
     */
    ResourcePartBuilder(String name, Resource resource) {
      Assert.hasText(name, "Name must be present.");
      Assert.notNull(resource, "Resource must be present.");
      this.resource = resource;
      getHeaders().setContentDispositionFormData(name, resource.getFilename());
    }

    @Override
    public Part build() {
      return DefaultParts.part(getHeaders(),
          DataBufferUtils.read(resource, getDataBufferFactory(), getBufferSize()));
    }
  }

  /**
   * The data buffer part builder.
   */
  public static class DataBufferPartBuilder extends AbstractFilePartBuilder {

    private final Flux<DataBuffer> content;

    /**
     * Instantiates a new data buffer part builder.
     *
     * @param name the name
     * @param content the content
     */
    DataBufferPartBuilder(String name, Flux<DataBuffer> content) {
      this(name, null, content);
    }

    /**
     * Instantiates a new data buffer part builder.
     *
     * @param name the name
     * @param filename the filename
     * @param content the content
     */
    DataBufferPartBuilder(String name, String filename, Flux<DataBuffer> content) {
      Assert.hasText(name, "Name must be present.");
      Assert.notNull(content, "Content must be present.");
      this.content = content;
      getHeaders().setContentDispositionFormData(name, filename);
    }

    @Override
    public Part build() {
      return DefaultParts.part(getHeaders(), content);
    }
  }

}