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

fi.evolver.ai.spring.util.MultiPartBodyPublisher Maven / Gradle / Ivy

package fi.evolver.ai.spring.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.net.http.HttpRequest;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Supplier;

import fi.evolver.ai.spring.file.AiFile;

public class MultiPartBodyPublisher {
	private final String boundary;

	private final List parts = new ArrayList<>();


	public MultiPartBodyPublisher(String boundary) {
		this.boundary = boundary;
	}


	public MultiPartBodyPublisher() {
		this(UUID.randomUUID().toString());
	}


	public HttpRequest.BodyPublisher build() {
		if (parts.isEmpty())
			throw new IllegalStateException("At least one part required");
		return HttpRequest.BodyPublishers.ofByteArrays(BodyBytesIterator::new);
	}


	Iterator iterator() {
		return new BodyBytesIterator();
	}


	public String getBoundary() {
		return boundary;
	}

	public MultiPartBodyPublisher addPart(String name, String value) {
		parts.add(new Part(
				name,
				Optional.empty(),
				() -> value.getBytes(StandardCharsets.UTF_8),
				Map.of()));
		return this;
	}

	public MultiPartBodyPublisher addPart(String name, Supplier value, String filename, String contentType) {
		parts.add(new Part(
				name,
				Optional.ofNullable(filename),
				value,
				Map.of("Content-Type", contentType)));
		return this;
	}

	public MultiPartBodyPublisher addPart(String name, AiFile file) {
		parts.add(new Part(
				name,
				Optional.ofNullable(file.fileName()),
				file::data,
				Map.of("Content-Type", file.mimeType())));
		return this;
	}


	private static record Part(
			String name,
			Optional filename,
			Supplier dataSource,
			Map headers) {

	}


	private class BodyBytesIterator implements Iterator {
		private static final String LINE_CHANGE = "\r\n";

		private int index = 0;
		private boolean atBoundary = true;


		@Override
		public boolean hasNext() {
			return atBoundary || hasMoreParts();
		}


		private boolean hasMoreParts() {
			return index < parts.size();
		}


		@Override
		public byte[] next() {
			byte[] result;
			if (atBoundary) {
				ByteArrayOutputStream bout = new ByteArrayOutputStream();
				try (Writer writer = new OutputStreamWriter(bout, StandardCharsets.UTF_8)) {
					if (index > 0)
						writer.append(LINE_CHANGE);
					writer.append("--").append(boundary);
					if (!hasMoreParts())
						writer.append("--");
					writer.append(LINE_CHANGE);

					if (hasMoreParts()) {
						Part part = parts.get(index);
						writer.append("Content-Disposition: form-data; name=\"").append(part.name().replaceAll("\"", "%22")).append('"');
						if (part.filename.isPresent())
							writer.append("; filename=\"").append(part.filename().get().replaceAll("\"", "%22")).append('"');
						writer.append(LINE_CHANGE);
						for (var entry: part.headers().entrySet())
							writer.append(entry.getKey()).append(": ").append(entry.getValue()).append(LINE_CHANGE);
						writer.append(LINE_CHANGE);
					}
				}
				catch (IOException e) {
					throw new UncheckedIOException(e);
				}

				result = bout.toByteArray();
				atBoundary = false;
			}
			else {
				result = parts.get(index).dataSource().get();
				atBoundary = true;
				++index;
			}
			return result;
		}

	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy