/**
* Copyright (c) 2012, Thilo Planz. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package v7db.files.buckets;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.Map.Entry;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.bson.BSON;
import org.bson.BSONObject;
import org.bson.BasicBSONObject;
import org.bson.types.ObjectId;
import v7db.files.ContentStorageFacade;
import v7db.files.mongodb.BSONUtils;
import v7db.files.spi.Content;
import v7db.files.spi.InlineContent;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBRef;
public class BucketsServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private DBCollection bucketCollection;
private ContentStorageFacade storage;
private final BucketsServiceConfiguration properties;
public BucketsServlet(Properties props) {
properties = new BucketsServiceConfiguration(props);
}
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
try {
properties.init();
bucketCollection = properties.getBucketCollection();
storage = properties.getContentStorage();
} catch (Exception e) {
throw new ServletException(e);
}
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String _id = request.getPathInfo().substring(1);
BSONObject bucket = bucketCollection.findOne(_id);
if (bucket == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Bucket '"
+ _id + "' not found");
return;
}
String mode = BSONUtils.getString(bucket, "POST");
if ("FormPost".equals(mode)) {
doFormPost(request, response, bucket);
return;
}
// method not allowed
super.doPost(request, response);
}
private void doFormPost(HttpServletRequest request,
HttpServletResponse response, BSONObject bucket) throws IOException {
ObjectId uploadId = new ObjectId();
BSONObject parameters = new BasicBSONObject();
List<FileItem> files = new ArrayList<FileItem>();
if (ServletFileUpload.isMultipartContent(request)) {
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
try {
for (Object _file : upload.parseRequest(request)) {
FileItem file = (FileItem) _file;
if (file.isFormField()) {
String v = file.getString();
parameters.put(file.getFieldName(), v);
} else {
files.add(file);
}
}
} catch (FileUploadException e) {
throw new IOException(e);
}
} else {
for (Entry<String, String[]> param : request.getParameterMap()
.entrySet()) {
String[] v = param.getValue();
if (v.length == 1)
parameters.put(param.getKey(), v[0]);
else
parameters.put(param.getKey(), v);
}
}
BSONObject result = new BasicBSONObject("_id", uploadId);
BSONObject uploads = new BasicBSONObject();
for (FileItem file : files) {
DiskFileItem f = (DiskFileItem) file;
// inline until 10KB
if (f.isInMemory()) {
uploads.put(f.getFieldName(), storage
.inlineOrInsertContentsAndBackRefs(10240, f.get(),
uploadId, f.getName(), f.getContentType()));
} else {
uploads.put(f.getFieldName(), storage
.inlineOrInsertContentsAndBackRefs(10240, f
.getStoreLocation(), uploadId, f.getName(), f
.getContentType()));
}
file.delete();
}
result.put("files", uploads);
result.put("parameters", parameters);
bucketCollection.update(new BasicDBObject("_id", bucket.get("_id")),
new BasicDBObject("$push", new BasicDBObject("FormPost.data",
result)));
String redirect = BSONUtils.getString(bucket, "FormPost.redirect");
// redirect mode
if (StringUtils.isNotBlank(redirect)) {
response.sendRedirect(redirect + "?v7_formpost_id=" + uploadId);
return;
}
// echo mode
// JSON does not work, see https://jira.mongodb.org/browse/JAVA-332
// response.setContentType("application/json");
// response.getWriter().write(JSON.serialize(result));
byte[] bson = BSON.encode(result);
response.getOutputStream().write(bson);
}
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String _id = request.getPathInfo().substring(1);
byte[] sha = null;
{
String s = request.getParameter("sha");
if (StringUtils.isBlank(s)) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
"Missing content digest");
return;
}
// sha parameter
try {
sha = Hex.decodeHex(s.toCharArray());
} catch (Exception e) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
"Invalid content digest '" + s + "'");
return;
}
}
BSONObject bucket = bucketCollection.findOne(new BasicDBObject("_id",
_id));
if (bucket == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Bucket '"
+ _id + "' not found");
return;
}
String mode = BSONUtils.getString(bucket, "GET");
if ("FormPost".equals(mode)) {
doFormPostGet(request, response, bucket, sha);
return;
}
if ("EchoPut".equals(mode)) {
doEchoPutGet(request, response, bucket, sha);
return;
}
// method not allowed
super.doGet(request, response);
}
static byte[] getInlineData(BSONObject metaData) {
return (byte[]) metaData.get("in");
}
private static byte[] getSha(BSONObject metaData) {
byte[] sha = (byte[]) metaData.get("sha");
if (sha != null)
return sha;
byte[] data = getInlineData(metaData);
if (data != null) {
return DigestUtils.sha(data);
}
return null;
}
private void doFormPostGet(HttpServletRequest request,
HttpServletResponse response, BSONObject bucket, byte[] sha)
throws IOException {
BSONObject file = null;
data: for (Object o : BSONUtils.values(bucket, "FormPost.data")) {
BSONObject upload = (BSONObject) o;
for (Object f : BSONUtils.values(upload, "files")) {
BSONObject bf = (BSONObject) f;
for (String fn : bf.keySet()) {
BSONObject x = (BSONObject) bf.get(fn);
byte[] theSha = getSha(x);
if (Arrays.equals(theSha, sha)) {
file = x;
break data;
}
}
}
}
if (file == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Bucket '"
+ bucket.get("_id")
+ "' does not have a file matching digest '"
+ Hex.encodeHexString(sha) + "'");
return;
}
Content content = storage.getContent(sha);
if (content == null) {
response
.sendError(
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Bucket '"
+ bucket.get("_id")
+ "' has a file matching digest '"
+ Hex.encodeHexString(sha)
+ "', but it could not be found in the content storage");
return;
}
String customFilename = request.getParameter("filename");
if (StringUtils.isNotBlank(customFilename))
file.put("filename", customFilename);
sendFile(request, response, sha, file, content);
}
private void doEchoPutGet(HttpServletRequest request,
HttpServletResponse response, BSONObject bucket, byte[] sha)
throws IOException {
Content content = storage.getContent(sha);
if (content == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Bucket '"
+ bucket.get("_id")
+ "' does not have a file matching digest '"
+ Hex.encodeHexString(sha) + "'");
return;
}
BSONObject file = new BasicBSONObject("sha", sha);
String customFilename = request.getParameter("filename");
if (StringUtils.isNotBlank(customFilename))
file.put("filename", customFilename);
sendFile(request, response, sha, file, content);
}
private void sendFile(HttpServletRequest request,
HttpServletResponse response, byte[] sha, BSONObject file,
Content content) throws IOException {
String contentType = BSONUtils.getString(file, "contentType");
String name = BSONUtils.getString(file, "filename");
String eTag = Hex.encodeHexString(sha);
String ifNoneMatch = request.getHeader("If-None-Match");
if (eTag.equals(ifNoneMatch)) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
response.setHeader("ETag", eTag);
response.setHeader("Content-type", StringUtils.defaultString(
contentType, "application/octet-stream"));
if (StringUtils.isNotBlank(name))
response.setHeader("Content-disposition", "attachment; filename=\""
+ name + "\"");
response.setContentLength((int) content.getLength());
InputStream in = content.getInputStream();
try {
IOUtils.copy(in, response.getOutputStream());
} finally {
in.close();
}
}
@Override
protected void doPut(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String _id = request.getPathInfo().substring(1);
BSONObject bucket = bucketCollection.findOne(_id);
if (bucket == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Bucket '"
+ _id + "' not found");
return;
}
String mode = BSONUtils.getString(bucket, "PUT");
if ("EchoPut".equals(mode)) {
doEchoPut(request, response, bucket);
return;
}
// method not allowed
super.doPut(request, response);
}
private void doEchoPut(HttpServletRequest request,
HttpServletResponse response, BSONObject bucket) throws IOException {
byte[] sha;
BSONObject content = storage.insertContentsAndBackRefs(request
.getInputStream(), new DBRef(null, bucketCollection.getName(),
bucket.get("_id")), null, null);
sha = (byte[]) content.get("sha");
if (sha == null) {
sha = ((InlineContent) storage.getContentPointer(content)).getSHA();
}
response.setContentType("text/plain");
response.getWriter().write(Hex.encodeHexString(sha));
}
}