package org.dcache.restful.resources.namespace;
import com.google.common.collect.Range;
import org.json.JSONException;
import org.json.JSONObject;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.DELETE;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.GET;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import diskCacheV111.util.CacheException;
import diskCacheV111.util.FileLocality;
import diskCacheV111.util.FileNotFoundCacheException;
import diskCacheV111.util.FsPath;
import diskCacheV111.util.PermissionDeniedCacheException;
import diskCacheV111.util.PnfsHandler;
import org.dcache.auth.Subjects;
import org.dcache.namespace.FileAttribute;
import org.dcache.namespace.FileType;
import org.dcache.poolmanager.RemotePoolMonitor;
import org.dcache.restful.providers.JsonFileAttributes;
import org.dcache.restful.util.ServletContextHandlerAttributes;
import org.dcache.util.list.DirectoryEntry;
import org.dcache.util.list.DirectoryStream;
import org.dcache.util.list.ListDirectoryHandler;
import org.dcache.vehicles.FileAttributes;
import org.dcache.restful.util.PathMapper;
import static com.google.common.base.Preconditions.checkArgument;
import static org.dcache.restful.providers.SuccessfulResponse.successfulResponse;
/**
* RestFul API to provide files/folders manipulation operations
*
* @version v1.0
*/
@Path("/namespace")
public class FileResources {
@Context
ServletContext ctx;
/*
This "request" parameter is used to get fully qualified name of the client
* or the last proxy that sent the request. Later used for quering locality of the file.
*/
@Context
HttpServletRequest request;
/**
* The method offer to list the content of a directory or return metadata of
* a specified file or directory.
*
* @param isList optional boolean parameter, set to false by default.
* When set to true (e.g. /?children=true) the file attributes(e.g. file size, locality, creation time... )
* of the children (files/directories) of
* the specified directory will be displayed.
* @param isLocality optional boolean parameter, set to false by default.
* When set to true the locality of file (ONLINE/NEARLINE) is displayed as a part of FileAttributes.
* @return JsonFileAttributes Json Object
* <p>
* <p>
* * EXAMPLES
* Return all the files in the given directory
* @method GET
* @Resources URL (default)
* http://localhost:2880/api/v1/namespace/urlPath/?children=true&locality=true
* @Request Header:
* Accept: application/json
* Content-Type: application/json
* Method:
* GET
* URL:
* http://localhost:2880/api/v1/namespace/replica/?children=true&locality=true
* @Response {
* "children":
* [
* {
* "fileName": "test000001",
* "fileLocality": "ONLINE",
* "mtime": 1459959425090,
* "fileType": "REGULAR",
* "creationTime": 1459959409825,
* "size": 378
* },
* {
* "fileName": "test1",
* "mtime": 1461000184802,
* "fileType": "DIR",
* "creationTime": 1461000167903,
* "size": 512
* }
* ],
* "mtime": 1461000173723,
* "fileType": "DIR",
* "creationTime": 1459949700167,
* "size": 512
* }
*/
@GET
@Path("{value : .*}")
@Produces(MediaType.APPLICATION_JSON)
public JsonFileAttributes getFileAttributes(@PathParam("value") String value,
@DefaultValue("false")
@QueryParam("children") boolean isList,
@DefaultValue("false")
@QueryParam("locality") boolean isLocality) throws CacheException {
JsonFileAttributes fileAttributes = new JsonFileAttributes();
Set<FileAttribute> attributes = EnumSet.allOf(FileAttribute.class);
PnfsHandler handler = ServletContextHandlerAttributes.getPnfsHandler(ctx);
PathMapper pathMapper = ServletContextHandlerAttributes.getPathMapper(ctx);
FsPath path = pathMapper.asDcachePath(request, value);
try {
FileAttributes namespaceAttrributes = handler.getFileAttributes(path, attributes);
chimeraToJsonAttributes(fileAttributes, namespaceAttrributes, isLocality);
// fill children list id it's a directory and listing is requested
if (namespaceAttrributes.getFileType() == FileType.DIR && isList) {
List<JsonFileAttributes> children = new ArrayList<>();
ListDirectoryHandler listDirectoryHandler = ServletContextHandlerAttributes.getListDirectoryHandler(ctx);
DirectoryStream stream = listDirectoryHandler.list(
ServletContextHandlerAttributes.getSubject(),
ServletContextHandlerAttributes.getRestriction(),
path,
null,
Range.<Integer>all(),
attributes);
for (DirectoryEntry entry : stream) {
String fName = entry.getName();
JsonFileAttributes childrenAttributes = new JsonFileAttributes();
chimeraToJsonAttributes(childrenAttributes, entry.getFileAttributes(), isLocality);
childrenAttributes.setFileName(fName);
children.add(childrenAttributes);
}
fileAttributes.setChildren(children);
}
} catch (FileNotFoundCacheException e) {
throw new NotFoundException(e);
} catch (PermissionDeniedCacheException e) {
if (Subjects.isNobody(ServletContextHandlerAttributes.getSubject())) {
throw new NotAuthorizedException(e);
} else {
throw new ForbiddenException(e);
}
} catch (CacheException | InterruptedException ex) {
throw new InternalServerErrorException(ex);
}
return fileAttributes;
}
@POST
@Path("{value : .*}")
@Consumes({MediaType.APPLICATION_JSON})
@Produces(MediaType.APPLICATION_JSON)
public Response cmrResources (@PathParam("value") String path, String requestPayload)
{
JSONObject reqPayload = new JSONObject(requestPayload);
String action = (String) reqPayload.get("action");
PnfsHandler handler = ServletContextHandlerAttributes.getPnfsHandler(ctx);
try {
switch (action) {
case "mkdir":
String folderName = (String) reqPayload.get("name");
checkArgument(!folderName.contains("/"), "The folderName cannot contain forward slash.");
checkArgument(!folderName.isEmpty(), "The folderName cannot be empty.");
String newPath;
if (path == null || path.isEmpty()) {
newPath = "/" + folderName;
} else {
newPath = "/" + path + "/" + folderName;
}
handler.createPnfsDirectory(newPath);
break;
case "mv":
String dest = (String) reqPayload.get("destination");
FsPath source = FsPath.ROOT.resolve(path);
FsPath target = source.parent().resolve(dest);
handler.renameEntry(source.toString(), target.toString(), true);
break;
default:
throw new IllegalArgumentException("The request body must contain the action to be perform. " +
"See the documentation for details.");
}
} catch (FileNotFoundCacheException e) {
throw new NotFoundException(e);
} catch (PermissionDeniedCacheException e) {
if (Subjects.isNobody(ServletContextHandlerAttributes.getSubject())) {
throw new NotAuthorizedException(e);
} else {
throw new ForbiddenException(e);
}
} catch (JSONException | IllegalArgumentException | CacheException e) {
throw new BadRequestException(e);
} catch (Exception e) {
throw new InternalServerErrorException(e);
}
return successfulResponse(Response.Status.CREATED);
}
@DELETE
@Path("{value : .*}")
@Produces(MediaType.APPLICATION_JSON)
public Response deleteFileEntry(@PathParam("value") String value) throws CacheException {
PnfsHandler handler = ServletContextHandlerAttributes.getPnfsHandler(ctx);
PathMapper pathMapper = ServletContextHandlerAttributes.getPathMapper(ctx);
FsPath path = pathMapper.asDcachePath(request, value);
try {
handler.deletePnfsEntry(path.toString(), EnumSet.of(FileType.REGULAR));
} catch (FileNotFoundCacheException e) {
throw new NotFoundException(e);
} catch (PermissionDeniedCacheException e) {
if (Subjects.isNobody(ServletContextHandlerAttributes.getSubject())) {
throw new NotAuthorizedException(e);
} else {
throw new ForbiddenException(e);
}
} catch (JSONException | IllegalArgumentException | CacheException e) {
throw new BadRequestException(e);
} catch (Exception e) {
throw new InternalServerErrorException(e);
}
return successfulResponse(Response.Status.OK);
}
/**
* Map returned fileAttributes to JsonFileAttributes object.
*
* @param fileAttributes will be mapped to the FileAttributes
* @param namespaceAttrributes FileAttributes returned by the request
* @param isLocality used to check weather user queried locality to the file
*/
private void chimeraToJsonAttributes(JsonFileAttributes fileAttributes,
FileAttributes namespaceAttrributes,
boolean isLocality) throws CacheException {
fileAttributes.setMtime(namespaceAttrributes.getModificationTime());
fileAttributes.setCreationTime(namespaceAttrributes.getCreationTime());
fileAttributes.setSize(namespaceAttrributes.getSize());
fileAttributes.setFileType(namespaceAttrributes.getFileType());
// when user set locality param. in the request, the locality should be returned only for directories
if (isLocality && namespaceAttrributes.getFileType() != FileType.DIR) {
String client = request.getRemoteHost();
RemotePoolMonitor remotePoolMonitor = ServletContextHandlerAttributes.getRemotePoolMonitor(ctx);
FileLocality fileLocality = remotePoolMonitor.getFileLocality(namespaceAttrributes, client);
fileAttributes.setFileLocality(fileLocality);
}
}
}