package io.fathom.cloud.storage.api.os.resources;
import io.fathom.cloud.CloudException;
import io.fathom.cloud.protobuf.CloudCommons.Attributes;
import io.fathom.cloud.protobuf.CloudCommons.KeyValueData;
import io.fathom.cloud.protobuf.FileModel.BucketAttributes;
import io.fathom.cloud.protobuf.FileModel.BucketData;
import io.fathom.cloud.server.model.Project;
import io.fathom.cloud.server.model.User;
import io.fathom.cloud.storage.FileServiceInternal;
import io.fathom.cloud.storage.FsBucket;
import io.fathom.cloud.storage.FsFile;
import io.fathom.cloud.storage.api.os.models.StorageAcl;
import io.fathom.cloud.storage.api.os.models.StorageAcl.AclType;
import io.fathom.cloud.storage.state.FileStore;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Maps;
import com.google.inject.persist.Transactional;
@Path("/openstack/storage/{project}/{bucket}")
@Transactional
public class BucketResource extends ObjectstoreResourceBase {
private static final Logger log = LoggerFactory.getLogger(BucketResource.class);
private static final String CONTAINER_META_PREFIX = "X-Container-Meta-";
private static final String HEADER_ACL_READ = "X-Container-Read";
@Inject
FileStore fileStore;
@PathParam("bucket")
String bucketName;
@QueryParam("marker")
String marker;
@QueryParam("prefix")
String prefix;
@QueryParam("delimiter")
String delimiter;
@Inject
FileServiceInternal fs;
@POST
public Response createBucket() throws Exception {
Project project = getProject();
Map<String, String> userAttributes = Maps.newHashMap();
Enumeration<String> headerNames = httpRequest.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
// Header names are case-insensitive
String normalized = headerName.toLowerCase();
if (normalized.startsWith("x-container-meta-")) {
String key = headerName.substring(17);
String value = httpRequest.getHeader(headerName);
userAttributes.put(key, value);
}
}
BucketAttributes.Builder bucketAttributes = BucketAttributes.newBuilder();
{
String header = httpRequest.getHeader(HEADER_ACL_READ);
if (header != null) {
StorageAcl acl = StorageAcl.parse(AclType.Read, header);
bucketAttributes.setAclRead(acl.toString());
}
}
Status status = fs.putBucket(project, bucketName, bucketAttributes.build(), userAttributes);
ResponseBuilder response = Response.status(status);
return response.build();
}
@PUT
public Response createBucketPut() throws Exception {
return createBucket();
}
@DELETE
public Response deleteBucket() throws Exception {
Project project = getProject();
fs.deleteBucket(project, bucketName);
ResponseBuilder response = Response.status(Status.NO_CONTENT);
return response.build();
}
// TODO: Support HEAD directly (efficiently)
@GET
public Response listObjects(@QueryParam("format") String formatParam) throws CloudException {
if (!isAuthenticated()) {
return doAnonymousRead();
}
Project project = getProject();
User user = getAuth().getUser();
FsBucket bucket = fs.findBucket(user, project, bucketName);
if (bucket == null) {
throw new WebApplicationException(Status.NOT_FOUND);
}
List<DirectoryListEntry> entries = fs.listFiles(project, bucket, prefix, delimiter, marker);
ResponseBuilder response = Response.ok();
// X-Container-Object-Count: 7
// X-Container-Bytes-Used: 413
setHeaders(bucket.getData(), response);
if (formatParam == null) {
response.entity(new ObjectListTextWriter(entries));
} else if ("json".equals(formatParam)) {
response.entity(new ObjectListJsonWriter(entries));
} else {
throw new UnsupportedOperationException();
}
return response.build();
}
private Response doAnonymousRead() throws CloudException {
FsFile found = null;
FsBucket bucket;
User user = null;
// Check for a public file
String projectName = getProjectName();
Project project = new Project(Long.valueOf(projectName));
bucket = fs.findBucket(user, project, bucketName);
if (bucket != null) {
// X-Container-Meta-Web-Index
String indexPage = bucket.getMetaWebIndex();
if (indexPage != null) {
found = fs.findFileInfo(bucket, indexPage);
}
}
return ObjectResource.buildReadResponse(httpRequest, fs, found);
}
private void setHeaders(BucketData found, ResponseBuilder response) {
// if (found.hasLastModified()) {
// response.lastModified(new Date(found.getLastModified()));
// }
//
// if (found.hasHash()) {
// response.header(HttpHeaders.ETAG,
// Hex.toHex(found.getHash().toByteArray()));
// }
// if (found.hasContentType()) {
// response.header(HttpHeaders.CONTENT_TYPE, found.getContentType());
// }
if (found.hasBucketAttributes()) {
BucketAttributes bucketAttributes = found.getBucketAttributes();
if (bucketAttributes.hasAclRead()) {
response.header(HEADER_ACL_READ, bucketAttributes.getAclRead());
}
}
// X-Container-Meta-Web-Error
// X-Container-Meta-Web-Index
// X-Container-Meta-Web-Listings
// X-Container-Meta-Web-Listings-CSS
// X-Container-Meta-InspectedBy: JackWolf
if (found.hasAttributes()) {
Attributes attributes = found.getAttributes();
for (KeyValueData entry : attributes.getUserAttributesList()) {
response.header(CONTAINER_META_PREFIX + entry.getKey(), entry.getValue());
}
}
// TODO: How should we set the length... Maybe only on a head?
// if (found.hasLength()) {
// response.header(HttpHeaders.CONTENT_LENGTH, found.getLength());
// }
}
}