LegacyFileSystemAdapter.java
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.apache.doris.fs;
import org.apache.doris.backup.Status;
import org.apache.doris.fs.io.DorisInputFile;
import org.apache.doris.fs.io.DorisOutputFile;
import org.apache.doris.fs.io.ParsedPath;
import org.apache.doris.fs.remote.RemoteFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Bridge adapter that implements the new {@link FileSystem} interface by delegating
* to existing Status-based legacy methods.
* <p>
* Subclasses override the {@code legacy*()} methods (which have the old Status-based
* signatures) and gain the new IOException-based interface for free.
* <p>
* Migration path:
* <ol>
* <li>Existing classes that extend {@link org.apache.doris.fs.remote.RemoteFileSystem}
* (or its subclasses) should be gradually updated to extend this class instead.</li>
* <li>In Phase 3, when {@code RemoteFileSystem} is removed, all subclasses will directly
* extend this class and implement {@code legacy*()} methods.</li>
* </ol>
*/
public abstract class LegacyFileSystemAdapter implements FileSystem {
// ─────────────────────────── ABSTRACT LEGACY METHODS ───────────────────────────
/** Checks file/directory existence. Returns {@link Status.ErrCode#NOT_FOUND} if absent. */
protected abstract Status legacyExists(String remotePath);
/** Deletes a file at {@code remotePath}. */
protected abstract Status legacyDelete(String remotePath);
/** Renames (moves) a file from {@code orig} to {@code dest}. */
protected abstract Status legacyRename(String orig, String dest);
/** Creates a directory at {@code remotePath} (including parents). */
protected abstract Status legacyMakeDir(String remotePath);
/**
* Lists files under {@code remotePath}.
* Results are appended to {@code result}.
*/
protected abstract Status legacyListFiles(String remotePath, boolean recursive,
List<RemoteFile> result);
/**
* Lists immediate child directories under {@code remotePath}.
* Results are appended to {@code result}.
* Default implementation throws UnsupportedOperationException.
*/
protected Status legacyListDirectories(String remotePath, Set<String> result) {
throw new UnsupportedOperationException(
"listDirectories not supported by " + getClass().getSimpleName());
}
/**
* Creates a new output file. Subclasses must implement.
*/
protected abstract DorisOutputFile legacyNewOutputFile(ParsedPath path);
/** Creates a new input file with optional length hint (-1 = unknown). */
protected abstract DorisInputFile legacyNewInputFile(ParsedPath path, long length);
// ─────────────────────────── NEW INTERFACE IMPLEMENTATIONS ───────────────────────────
@Override
public DorisInputFile newInputFile(Location location) {
return legacyNewInputFile(new ParsedPath(location.toString()), -1L);
}
@Override
public DorisInputFile newInputFile(Location location, long length) {
return legacyNewInputFile(new ParsedPath(location.toString()), length);
}
@Override
public DorisOutputFile newOutputFile(Location location) {
return legacyNewOutputFile(new ParsedPath(location.toString()));
}
@Override
public boolean exists(Location location) throws IOException {
Status status = legacyExists(location.toString());
if (status.ok()) {
return true;
}
if (Status.ErrCode.NOT_FOUND.equals(status.getErrCode())) {
return false;
}
throw new IOException("exists(" + location + ") failed: " + status.getErrMsg());
}
@Override
public void deleteFile(Location location) throws IOException {
Status status = legacyDelete(location.toString());
if (!status.ok()) {
throw new IOException("deleteFile(" + location + ") failed: " + status.getErrMsg());
}
}
@Override
public void renameFile(Location source, Location target) throws IOException {
Status status = legacyRename(source.toString(), target.toString());
if (!status.ok()) {
throw new IOException("renameFile(" + source + " -> " + target + ") failed: "
+ status.getErrMsg());
}
}
@Override
public void deleteDirectory(Location location) throws IOException {
Status status = legacyDelete(location.toString());
if (!status.ok()) {
throw new IOException("deleteDirectory(" + location + ") failed: " + status.getErrMsg());
}
}
@Override
public void createDirectory(Location location) throws IOException {
Status status = legacyMakeDir(location.toString());
if (!status.ok()) {
throw new IOException("createDirectory(" + location + ") failed: " + status.getErrMsg());
}
}
@Override
public void renameDirectory(Location source, Location target) throws IOException {
Status status = legacyRename(source.toString(), target.toString());
if (!status.ok()) {
throw new IOException("renameDirectory(" + source + " -> " + target + ") failed: "
+ status.getErrMsg());
}
}
@Override
public FileIterator listFiles(Location location, boolean recursive) throws IOException {
List<RemoteFile> remoteFiles = new ArrayList<>();
Status status = legacyListFiles(location.toString(), recursive, remoteFiles);
if (!status.ok()) {
throw new IOException("listFiles(" + location + ") failed: " + status.getErrMsg());
}
String base = location.toString();
List<FileEntry> entries = remoteFiles.stream()
.map(rf -> convertRemoteFile(rf, base))
.collect(Collectors.toList());
return FileIterator.ofList(entries);
}
@Override
public Set<Location> listDirectories(Location location) throws IOException {
Set<String> dirs = new HashSet<>();
Status status = legacyListDirectories(location.toString(), dirs);
if (!status.ok()) {
throw new IOException("listDirectories(" + location + ") failed: " + status.getErrMsg());
}
Set<Location> result = new HashSet<>(dirs.size() * 2);
for (String dir : dirs) {
result.add(Location.of(dir));
}
return result;
}
// ─────────────────────────── HELPERS ───────────────────────────
private static FileEntry convertRemoteFile(RemoteFile rf, String baseUri) {
org.apache.hadoop.fs.Path hadoopPath = rf.getPath();
Location loc = hadoopPath != null
? Location.of(hadoopPath.toString())
: Location.of(baseUri.endsWith("/") ? baseUri + rf.getName()
: baseUri + "/" + rf.getName());
FileEntry.Builder builder = FileEntry.builder(loc)
.directory(rf.isDirectory())
.length(rf.getSize())
.blockSize(rf.getBlockSize())
.modificationTime(rf.getModificationTime());
if (rf.getBlockLocations() != null) {
List<FileEntry.BlockInfo> blocks = new ArrayList<>();
for (org.apache.hadoop.fs.BlockLocation bl : rf.getBlockLocations()) {
try {
String[] hostsArr = bl.getHosts();
List<String> hosts = hostsArr != null
? java.util.Arrays.asList(hostsArr)
: java.util.Collections.emptyList();
blocks.add(new FileEntry.BlockInfo(bl.getOffset(), bl.getLength(), hosts));
} catch (IOException e) {
blocks.add(new FileEntry.BlockInfo(bl.getOffset(), bl.getLength(),
java.util.Collections.emptyList()));
}
}
builder.blocks(blocks);
}
return builder.build();
}
}