/*
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
* (the "License"). You may not use this work except in compliance with the License, which is
* available at www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied, as more fully set forth in the License.
*
* See the NOTICE file distributed with this work for information regarding copyright ownership.
*/
package alluxio.web;
import alluxio.AlluxioURI;
import alluxio.Configuration;
import alluxio.Constants;
import alluxio.PropertyKey;
import alluxio.client.ReadType;
import alluxio.client.file.FileInStream;
import alluxio.client.file.FileSystem;
import alluxio.client.file.URIStatus;
import alluxio.client.file.options.OpenFileOptions;
import alluxio.exception.AccessControlException;
import alluxio.exception.AlluxioException;
import alluxio.exception.FileDoesNotExistException;
import alluxio.exception.InvalidPathException;
import alluxio.master.MasterProcess;
import alluxio.master.block.BlockMaster;
import alluxio.master.file.FileSystemMaster;
import alluxio.master.file.options.ListStatusOptions;
import alluxio.security.LoginUser;
import alluxio.security.authentication.AuthenticatedClientUser;
import alluxio.util.SecurityUtils;
import alluxio.util.io.PathUtils;
import alluxio.wire.BlockLocation;
import alluxio.wire.FileBlockInfo;
import alluxio.wire.FileInfo;
import alluxio.wire.LoadMetadataType;
import alluxio.wire.WorkerNetAddress;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.concurrent.ThreadSafe;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet that provides data for browsing the file system.
*/
@ThreadSafe
public final class WebInterfaceBrowseServlet extends HttpServlet {
private static final long serialVersionUID = 6121623049981468871L;
private final transient MasterProcess mMasterProcess;
/**
* Creates a new instance of {@link WebInterfaceBrowseServlet}.
*
* @param masterProcess the Alluxio master process
*/
public WebInterfaceBrowseServlet(MasterProcess masterProcess) {
mMasterProcess = masterProcess;
}
/**
* This function displays 5KB of a file from a specific offset if it is in ASCII format.
*
* @param path the path of the file to display
* @param request the {@link HttpServletRequest} object
* @param offset where the file starts to display
* @throws FileDoesNotExistException if the file does not exist
* @throws InvalidPathException if an invalid path is encountered
*/
private void displayFile(AlluxioURI path, HttpServletRequest request, long offset)
throws FileDoesNotExistException, InvalidPathException, IOException, AlluxioException {
FileSystem fs = FileSystem.Factory.get();
String fileData;
URIStatus status = fs.getStatus(path);
if (status.isCompleted()) {
OpenFileOptions options = OpenFileOptions.defaults().setReadType(ReadType.NO_CACHE);
try (FileInStream is = fs.openFile(path, options)) {
int len = (int) Math.min(5 * Constants.KB, status.getLength() - offset);
byte[] data = new byte[len];
long skipped = is.skip(offset);
if (skipped < 0) {
// nothing was skipped
fileData = "Unable to traverse to offset; is file empty?";
} else if (skipped < offset) {
// couldn't skip all the way to offset
fileData = "Unable to traverse to offset; is offset larger than the file?";
} else {
// read may not read up to len, so only convert what was read
int read = is.read(data, 0, len);
if (read < 0) {
// stream couldn't read anything, skip went to EOF?
fileData = "Unable to read file";
} else {
fileData = WebUtils.convertByteArrayToStringWithoutEscape(data, 0, read);
}
}
}
} else {
fileData = "The requested file is not complete yet.";
}
List<UIFileBlockInfo> uiBlockInfo = new ArrayList<>();
for (FileBlockInfo fileBlockInfo : mMasterProcess.getMaster(FileSystemMaster.class)
.getFileBlockInfoList(path)) {
uiBlockInfo.add(new UIFileBlockInfo(fileBlockInfo));
}
request.setAttribute("fileBlocks", uiBlockInfo);
request.setAttribute("fileData", fileData);
request.setAttribute("highestTierAlias",
mMasterProcess.getMaster(BlockMaster.class).getGlobalStorageTierAssoc().getAlias(0));
}
/**
* Populates attribute fields with data from the MasterInfo associated with this servlet. Errors
* will be displayed in an error field. Debugging can be enabled to display additional data. Will
* eventually redirect the request to a jsp.
*
* @param request the {@link HttpServletRequest} object
* @param response the {@link HttpServletResponse} object
* @throws ServletException if the target resource throws this exception
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (SecurityUtils.isSecurityEnabled() && AuthenticatedClientUser.get() == null) {
AuthenticatedClientUser.set(LoginUser.get().getName());
}
request.setAttribute("debug", Configuration.getBoolean(PropertyKey.DEBUG));
request.setAttribute("showPermissions",
Configuration.getBoolean(PropertyKey.SECURITY_AUTHORIZATION_PERMISSION_ENABLED));
request.setAttribute("masterNodeAddress", mMasterProcess.getRpcAddress().toString());
request.setAttribute("invalidPathError", "");
List<FileInfo> filesInfo;
String requestPath = request.getParameter("path");
if (requestPath == null || requestPath.isEmpty()) {
requestPath = AlluxioURI.SEPARATOR;
}
AlluxioURI currentPath = new AlluxioURI(requestPath);
request.setAttribute("currentPath", currentPath.toString());
request.setAttribute("viewingOffset", 0);
FileSystemMaster fileSystemMaster = mMasterProcess.getMaster(FileSystemMaster.class);
try {
long fileId = fileSystemMaster.getFileId(currentPath);
FileInfo fileInfo = fileSystemMaster.getFileInfo(fileId);
UIFileInfo currentFileInfo = new UIFileInfo(fileInfo);
if (currentFileInfo.getAbsolutePath() == null) {
throw new FileDoesNotExistException(currentPath.toString());
}
request.setAttribute("currentDirectory", currentFileInfo);
request.setAttribute("blockSizeBytes", currentFileInfo.getBlockSizeBytes());
if (!currentFileInfo.getIsDirectory()) {
String offsetParam = request.getParameter("offset");
long relativeOffset = 0;
long offset;
try {
if (offsetParam != null) {
relativeOffset = Long.parseLong(offsetParam);
}
} catch (NumberFormatException e) {
relativeOffset = 0;
}
String endParam = request.getParameter("end");
// If no param "end" presents, the offset is relative to the beginning; otherwise, it is
// relative to the end of the file.
if (endParam == null) {
offset = relativeOffset;
} else {
offset = fileInfo.getLength() - relativeOffset;
}
if (offset < 0) {
offset = 0;
} else if (offset > fileInfo.getLength()) {
offset = fileInfo.getLength();
}
try {
displayFile(new AlluxioURI(currentFileInfo.getAbsolutePath()), request, offset);
} catch (AlluxioException e) {
throw new IOException(e);
}
request.setAttribute("viewingOffset", offset);
getServletContext().getRequestDispatcher("/viewFile.jsp").forward(request, response);
return;
}
setPathDirectories(currentPath, request);
filesInfo = fileSystemMaster.listStatus(currentPath,
ListStatusOptions.defaults().setLoadMetadataType(LoadMetadataType.Always));
} catch (FileDoesNotExistException e) {
request.setAttribute("invalidPathError", "Error: Invalid Path " + e.getMessage());
getServletContext().getRequestDispatcher("/browse.jsp").forward(request, response);
return;
} catch (InvalidPathException e) {
request.setAttribute("invalidPathError", "Error: Invalid Path " + e.getLocalizedMessage());
getServletContext().getRequestDispatcher("/browse.jsp").forward(request, response);
return;
} catch (IOException e) {
request.setAttribute("invalidPathError",
"Error: File " + currentPath + " is not available " + e.getMessage());
getServletContext().getRequestDispatcher("/browse.jsp").forward(request, response);
return;
} catch (AccessControlException e) {
request.setAttribute("invalidPathError",
"Error: File " + currentPath + " cannot be accessed " + e.getMessage());
getServletContext().getRequestDispatcher("/browse.jsp").forward(request, response);
return;
}
List<UIFileInfo> fileInfos = new ArrayList<>(filesInfo.size());
for (FileInfo fileInfo : filesInfo) {
UIFileInfo toAdd = new UIFileInfo(fileInfo);
try {
if (!toAdd.getIsDirectory() && fileInfo.getLength() > 0) {
FileBlockInfo blockInfo =
fileSystemMaster.getFileBlockInfoList(new AlluxioURI(toAdd.getAbsolutePath())).get(0);
List<String> locations = new ArrayList<>();
// add the in-memory block locations
for (BlockLocation location : blockInfo.getBlockInfo().getLocations()) {
WorkerNetAddress address = location.getWorkerAddress();
locations.add(address.getHost() + ":" + address.getDataPort());
}
// add underFS locations
locations.addAll(blockInfo.getUfsLocations());
toAdd.setFileLocations(locations);
}
} catch (FileDoesNotExistException e) {
request.setAttribute("FileDoesNotExistException",
"Error: non-existing file " + e.getMessage());
getServletContext().getRequestDispatcher("/browse.jsp").forward(request, response);
return;
} catch (InvalidPathException e) {
request.setAttribute("InvalidPathException",
"Error: invalid path " + e.getMessage());
getServletContext().getRequestDispatcher("/browse.jsp").forward(request, response);
} catch (AccessControlException e) {
request.setAttribute("AccessControlException",
"Error: File " + currentPath + " cannot be accessed " + e.getMessage());
getServletContext().getRequestDispatcher("/browse.jsp").forward(request, response);
return;
}
fileInfos.add(toAdd);
}
Collections.sort(fileInfos, UIFileInfo.PATH_STRING_COMPARE);
request.setAttribute("nTotalFile", fileInfos.size());
// URL can not determine offset and limit, let javascript in jsp determine and redirect
if (request.getParameter("offset") == null && request.getParameter("limit") == null) {
getServletContext().getRequestDispatcher("/browse.jsp").forward(request, response);
return;
}
try {
int offset = Integer.parseInt(request.getParameter("offset"));
int limit = Integer.parseInt(request.getParameter("limit"));
List<UIFileInfo> sub = fileInfos.subList(offset, offset + limit);
request.setAttribute("fileInfos", sub);
} catch (NumberFormatException e) {
request.setAttribute("fatalError",
"Error: offset or limit parse error, " + e.getLocalizedMessage());
getServletContext().getRequestDispatcher("/browse.jsp").forward(request, response);
return;
} catch (IndexOutOfBoundsException e) {
request.setAttribute("fatalError",
"Error: offset or offset + limit is out of bound, " + e.getLocalizedMessage());
getServletContext().getRequestDispatcher("/browse.jsp").forward(request, response);
return;
} catch (IllegalArgumentException e) {
request.setAttribute("fatalError", e.getLocalizedMessage());
getServletContext().getRequestDispatcher("/browse.jsp").forward(request, response);
return;
}
getServletContext().getRequestDispatcher("/browse.jsp").forward(request, response);
}
/**
* This function sets the file information for directories that are in the path to the current
* directory.
*
* @param path the path of the current directory
* @param request the {@link HttpServletRequest} object
* @throws FileDoesNotExistException if the file does not exist
* @throws InvalidPathException if an invalid path is encountered
* @throws AccessControlException if permission checking fails
*/
private void setPathDirectories(AlluxioURI path, HttpServletRequest request)
throws FileDoesNotExistException, InvalidPathException, AccessControlException {
FileSystemMaster fileSystemMaster = mMasterProcess.getMaster(FileSystemMaster.class);
if (path.isRoot()) {
request.setAttribute("pathInfos", new UIFileInfo[0]);
return;
}
String[] splitPath = PathUtils.getPathComponents(path.toString());
UIFileInfo[] pathInfos = new UIFileInfo[splitPath.length - 1];
AlluxioURI currentPath = new AlluxioURI(AlluxioURI.SEPARATOR);
long fileId = fileSystemMaster.getFileId(currentPath);
pathInfos[0] = new UIFileInfo(fileSystemMaster.getFileInfo(fileId));
for (int i = 1; i < splitPath.length - 1; i++) {
currentPath = currentPath.join(splitPath[i]);
fileId = fileSystemMaster.getFileId(currentPath);
pathInfos[i] = new UIFileInfo(fileSystemMaster.getFileInfo(fileId));
}
request.setAttribute("pathInfos", pathInfos);
}
}