WebappResourceExtractor.java

/*******************************************************************************
 * Copyright (c) 2025 Eclipse RDF4J contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *******************************************************************************/
// Some portions generated by Codex
package org.eclipse.rdf4j.tools.serverboot;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;

import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

class WebappResourceExtractor implements AutoCloseable {

	private static final String SERVER_WEBAPP_BASE = "rdf4j/server-webapp";
	private static final String WORKBENCH_WEBAPP_BASE = "rdf4j/workbench-webapp";

	private final Path serverDocBase;

	WebappResourceExtractor() {
		try {
			this.serverDocBase = Files.createTempDirectory("rdf4j-server-webapp");
			this.serverDocBase.toFile().deleteOnExit();
			copyTree(SERVER_WEBAPP_BASE, serverDocBase);

			Path workbenchTarget = serverDocBase.resolve("rdf4j-workbench");
			Files.createDirectories(workbenchTarget);
			copyTree(WORKBENCH_WEBAPP_BASE, workbenchTarget);
		} catch (IOException e) {
			throw new IllegalStateException("Failed to prepare web application resources", e);
		}
	}

	Path getServerDocBase() {
		return serverDocBase;
	}

	@Override
	public void close() throws Exception {
		if (serverDocBase == null) {
			return;
		}
		try (Stream<Path> walk = Files.walk(serverDocBase)) {
			walk.sorted((left, right) -> right.compareTo(left)).forEach(path -> {
				try {
					Files.deleteIfExists(path);
				} catch (IOException ignored) {
					// best-effort cleanup
				}
			});
		}
	}

	private static void copyTree(String resourceBase, Path destinationRoot) throws IOException {
		PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(
				Rdf4jServerWorkbenchApplication.class.getClassLoader());
		Resource[] resources = resolver.getResources("classpath*:" + resourceBase + "/**");
		Set<String> copied = new HashSet<>();
		for (Resource resource : resources) {
			if (!resource.exists() || !resource.isReadable()) {
				continue;
			}
			String url = resource.getURL().toExternalForm();
			if (url.endsWith("/")) {
				continue;
			}
			int baseIndex = url.indexOf(resourceBase);
			if (baseIndex == -1) {
				continue;
			}
			String relative = url.substring(baseIndex + resourceBase.length());
			if (relative.isEmpty() || "/".equals(relative)) {
				continue;
			}
			if (relative.startsWith("/")) {
				relative = relative.substring(1);
			}
			if (!copied.add(relative)) {
				continue;
			}
			Path target = destinationRoot.resolve(relative);
			Files.createDirectories(target.getParent());
			try (InputStream inputStream = resource.getInputStream()) {
				Files.copy(inputStream, target, StandardCopyOption.REPLACE_EXISTING);
			}
		}
	}
}