package org.geoserver.rest.upload;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.activation.MimetypesFileTypeMap;
import javax.imageio.ImageIO;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.restlet.Restlet;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Reference;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.data.Status;
import org.restlet.resource.FileRepresentation;
import org.restlet.resource.Representation;
import org.restlet.resource.OutputRepresentation;
import org.restlet.ext.fileupload.RestletFileUpload;
import org.vfny.geoserver.global.GeoserverDataDirectory;
import org.geoserver.data.util.IOUtils;
import org.geoserver.rest.format.DataFormat;
import org.geoserver.rest.format.MapJSONFormat;
import org.geoserver.rest.RestletException;
/**
* Restlet to allow uploading files and serving them back, possibly with some server-side filter
* applied. For example, one might want to allow uploading images and then provide a thumbnail
* version.
*
* @author David Winslow <dwinslow@opengeo.org>
*/
public class UploadRestlet extends Restlet {
/**
* The the directory in which files will be stored.
*/
private File rootPath = GeoserverDataDirectory.getGeoserverDataDirectory();
/**
* A map of mimetype->file extension to use when generating filenames for stored files.
*/
private Map mimeTypes;
private static Logger LOG =
org.geotools.util.logging.Logging.getLogger("org.geoserver.rest.upload");
private UniqueIDGenerator myIDGenerator = new UniqueIDGenerator();
private FileStorage fileStorage;
private UploadFilter uploadFilter;
public void setTypeMap(Map m){
mimeTypes = m;
}
public Map getTypeMap(){
return mimeTypes;
}
public UploadFilter getUploadFilter() {
return this.uploadFilter;
}
public void setUploadFilter(UploadFilter filter) {
this.uploadFilter = filter;
}
public void setFileStorage(FileStorage fs) {
this.fileStorage = fs;
}
public FileStorage getFileStorage() {
return this.fileStorage;
}
public void setRootPath(String path) throws IOException {
this.rootPath = new File(GeoserverDataDirectory.getGeoserverDataDirectory(), path);
myIDGenerator.setWatchedFolder(this.rootPath);
}
public String getRootPath() {
return this.rootPath.toString();
}
public void handle(Request request, Response response){
try {
if (request.getMethod().equals(Method.GET)) {
doGet(request, response);
} else if (request.getMethod().equals(Method.POST)){
doPost(request, response);
} else if (request.getMethod().equals(Method.PUT)){
doPut(request, response);
} else if (request.getMethod().equals(Method.DELETE)) {
doDelete(request, response);
} else {
response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
}
} catch (Exception ioe) {
response.setStatus(Status.SERVER_ERROR_INTERNAL);
}
}
/**
* Handle a GET request by simply serving a static file.
*
* @param req the Request object being handled
* @param resp the Response to which the file should be written
*/
protected void doGet(Request req, Response resp)
throws Exception {
String file = (String)req.getAttributes().get("file");
if (file == null) {
listFiles(req, resp);
return;
}
File f = getSecuredFile(file);
if (!f.exists()){
resp.setStatus(Status.CLIENT_ERROR_NOT_FOUND);
return;
}
MediaType mimetype = new MediaType(new MimetypesFileTypeMap().getContentType(f));
resp.setEntity(new FileRepresentation(f, mimetype, 10));
}
/**
* Handle a POST request by reading in the file and possibly applying some filter.
*
* @param req the Request object being handled
* @param resp the Response to which the result should be written
*/
protected void doPost(Request req, Response resp) throws IOException {
if (req.getEntity().getMediaType() != null &&
req.getEntity().getMediaType().equals(MediaType.MULTIPART_FORM_DATA, true)) {
try {
FileItemFactory factory = new DiskFileItemFactory();
RestletFileUpload upload = new RestletFileUpload(factory);
List items = upload.parseRequest(req);
FileItem file = null;
Iterator it = items.iterator();
while (it.hasNext()) {
FileItem temp = (FileItem)it.next();
if (temp.getFieldName().equals("file") && !temp.isFormField()){
file = temp;
}
}
if (file == null){
resp.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
return;
}
File temp = File.createTempFile("gs_upload", null);
file.write(temp);
if (uploadFilter.filter(file.getContentType(), temp)) {
doUpload(file.getContentType(), temp, req, resp);
if (!temp.delete()) {
LOG.log(Level.WARNING, "Failure to delete temp file in upload module");
}
} else {
resp.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
}
} catch (Exception e) {
resp.setStatus(Status.SERVER_ERROR_INTERNAL);
}
} else {
File temp = File.createTempFile("gs_upload", null);
IOUtils.copy(req.getEntity().getStream(), temp);
String contentType = "";
if (req.getEntity().getMediaType() != null) {
contentType = req.getEntity().getMediaType().toString();
}
if (uploadFilter.filter(contentType, temp)) {
doUpload(contentType, temp, req, resp);
if (!temp.delete()) {
LOG.log(Level.WARNING, "Unable to delete temporary file from upload");
}
} else {
resp.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
}
}
}
/**
* Handle a PUT request by ensuring that the requested file exists before overwriting it.
*
* @param req the Request being handled
* @param resp the Response to use for reporting status
*/
protected void doPut(Request req, Response resp) throws IOException {
String file = (String)req.getAttributes().get("file");
if (file == null) {
resp.setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
return;
}
File f = getSecuredFile(file);
if (!f.exists()){
resp.setStatus(Status.CLIENT_ERROR_NOT_FOUND);
return;
}
File temp = File.createTempFile("gs_upload", null);
IOUtils.copy(req.getEntity().getStream(), temp);
String contentType = "";
if (req.getEntity().getMediaType() != null) {
contentType = req.getEntity().getMediaType().toString();
}
if (uploadFilter.filter(contentType, temp)) {
if (!temp.renameTo(f)) {
throw new RestletException(
"Unable to rename temp file to persisted file in upload restlet",
Status.SERVER_ERROR_INTERNAL);
}
} else {
resp.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
return;
}
}
private void doUpload(String contentType, File content, Request req, Response resp)
throws IOException {
if (!rootPath.exists() && !rootPath.mkdirs()) {
throw new RestletException(
"Unable to create storage directory for uploaded files",
Status.SERVER_ERROR_INTERNAL);
}
List<String> l = fileStorage.handleUpload(contentType, content, myIDGenerator, rootPath);
Map<String, List<String>> m = new HashMap<String, List<String>>();
m.put("files", l);
DataFormat outputFormat = new MapJSONFormat();
resp.setEntity(outputFormat.toRepresentation(m));
resp.setStatus(Status.SUCCESS_CREATED);
if (l.size() == 1) {
resp.setRedirectRef(new Reference(req.getResourceRef(), l.get(0)));
}
}
private void listFiles(Request req, Response resp) throws Exception {
resp.setEntity(new OutputRepresentation(MediaType.APPLICATION_JSON) {
public InputStream getStream() {
throw new UnsupportedOperationException();
}
public void write(OutputStream out) throws IOException {
PrintWriter writer =
new PrintWriter(new BufferedWriter(new OutputStreamWriter(out)));
writer.print("{maps: [");
File[] children = rootPath.listFiles();
for (int i = 0; i < children.length; i++) {
File child = children[i];
writer.print("{id:\"");
writer.print(child.getName().replace("\"", "\\\""));
writer.print("\",config:");
BufferedReader reader = new BufferedReader(new FileReader(child));
String line = null;
while ((line = reader.readLine()) != null) {
writer.println(line);
}
writer.print("}");
if (i != children.length -1) {
writer.print(",");
};
reader.close();
}
writer.print("]}");
writer.flush();
writer.close();
}
});
}
/**
* Handle a DELETE request by simply deleting the file on disk.
*
* @param req the Request object being handled
* @param resp the Response to which the result should be written
*/
protected void doDelete(Request req, Response resp)
throws Exception {
String file = (String)req.getAttributes().get("file");
File f = getSecuredFile(file);
if (!f.exists()){
resp.setStatus(Status.CLIENT_ERROR_NOT_FOUND);
return;
}
if (f.delete()) {
resp.setStatus(Status.SUCCESS_OK);
} else {
throw new RestletException("Unable to delete file on server",
Status.SERVER_ERROR_INTERNAL);
}
}
/**
* Ensure that files written and saved by this restlet are children of the target directory.
*
* @param path the name of the file within the target directory
* @return the corresponding File object, or null if that File would not be a child of the
* target dir
* @throws IOException if errors occur while creating the File object
*/
private File getSecuredFile(String path) throws IOException{
File f = new File(rootPath, path);
if (f.getCanonicalPath().startsWith(rootPath.getCanonicalPath())){
return f;
}
return null;
}
}