package org.geoserver.python.app;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.TreeMap;
import java.util.logging.Logger;
import org.geoserver.python.Python;
import org.geoserver.rest.RestletException;
import org.geotools.util.logging.Logging;
import org.python.core.Py;
import org.python.core.PyDictionary;
import org.python.core.PyFunction;
import org.python.core.PyIterator;
import org.python.core.PyList;
import org.python.core.PyObject;
import org.python.core.PyString;
import org.python.core.PyStringMap;
import org.python.core.PyTuple;
import org.python.util.PythonInterpreter;
import org.restlet.data.MediaType;
import org.restlet.data.Reference;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.data.Status;
import org.restlet.resource.OutputRepresentation;
import org.restlet.resource.Resource;
/**
* Python resource that provides a WSGI environments for scripts to run in.
*
* @author Justin Deoliveira, OpenGeo
*
*/
public class PythonAppResource extends Resource {
static Logger LOGGER = Logging.getLogger("org.geoserver.python");
Python python;
File appFile;
static ThreadLocal<WSGIResponse> response = new ThreadLocal<WSGIResponse>();
public PythonAppResource(Python python, File appFile, Request request, Response response) {
super(null, request, response);
this.python = python;
this.appFile = appFile;
}
/**
* Creates the environment object.
* <pre>
* REQUEST_METHOD
* The HTTP request method, such as "GET" or "POST". This cannot ever be
* an empty string, and so is always required.
* SCRIPT_NAME
* The initial portion of the request URL's "path" that corresponds to the
* application object, so that the application knows its virtual "location"
* . This may be an empty string, if the application corresponds to the
* "root" of the server.
* PATH_INFO
* The remainder of the request URL's "path", designating the virtual
* "location" of the request's target within the application. This may be
* an empty string, if the request URL targets the application root and
* does not have a trailing slash.
* QUERY_STRING
* The portion of the request URL that follows the "?", if any. May be
* empty or absent.
* CONTENT_TYPE
* The contents of any Content-Type fields in the HTTP request. May be
* empty or absent.
* CONTENT_LENGTH
* The contents of any Content-Length fields in the HTTP request. May be
* empty or absent.
* SERVER_NAME, SERVER_PORT
* When combined with SCRIPT_NAME and PATH_INFO, these variables can be
* used to complete the URL. Note, however, that HTTP_HOST, if present,
* should be used in preference to SERVER_NAME for reconstructing the
* request URL. See the URL Reconstruction section below for more detail.
* SERVER_NAME and SERVER_PORT can never be empty strings, and so are
* always required.
* SERVER_PROTOCOL
* The version of the protocol the client used to send the request.
* Typically this will be something like "HTTP/1.0" or "HTTP/1.1" and may
* be used by the application to determine how to treat any HTTP request
* headers. (This variable should probably be called REQUEST_PROTOCOL,
* since it denotes the protocol used in the request, and is not
* necessarily the protocol that will be used in the server's response.
* However, for compatibility with CGI we have to keep the existing name.)
* HTTP_ Variables
* Variables corresponding to the client-supplied HTTP request headers
* (i.e., variables whose names begin with "HTTP_"). The presence or
* absence of these variables should correspond with the presence or
* absence of the appropriate HTTP header in the request.
*
* </pre>
* @param request
* @return
*/
PyObject createEnviron(Request request) {
PyDictionary environ = new PyDictionary();
environ.put("REQUEST_METHOD", request.getMethod().toString());
Reference ref = request.getResourceRef();
environ.put("SCRIPT_NAME", ref.getLastSegment());
Reference pref = ref.getParentRef();
environ.put("PATH_INFO", pref.toString().substring(
request.getRootRef().toString().length(), pref.toString().length()-1 ));
environ.put("QUERY_STRING", request.getResourceRef().getQuery());
//TODO: fill in rest of parameters
return environ;
}
public static Object start_response(PyObject[] objs, String[] values) {
PyString status = (PyString) objs[0];
int space = status.toString().indexOf(' ');
WSGIResponse r = response.get();
if (space != -1) {
r.statusCode = status.toString().substring(0, space);
r.statusMessage = status.toString().substring(space+1);
}
else {
r.statusCode = status.toString();
}
if (objs.length > 1) {
PyList headers = (PyList) objs[1];
for (Iterator i = headers.iterator(); i.hasNext();) {
PyTuple tup = (PyTuple) i.next();
r.headers.put(tup.get(0).toString(), tup.get(1).toString());
}
}
return null;
}
PyFunction createStartResponse() {
return new PyFunction(new PyStringMap(), new PyObject[]{}, Py.newJavaCode(getClass(), "start_response"));
}
@Override
public void handleGet() {
PythonInterpreter pi = python.interpreter();
pi.execfile(appFile.getAbsolutePath());
PyObject app = pi.get("app");
if (app == null) {
throw new RestletException("'app' function not found", Status.SERVER_ERROR_INTERNAL);
}
if (!(app instanceof PyFunction)) {
throw new RestletException("'app' must be a function", Status.SERVER_ERROR_INTERNAL);
}
PyFunction appf = (PyFunction) app;
PyFunction start_response = createStartResponse();
WSGIResponse wr = new WSGIResponse();
response.set(wr);
PyObject ret = appf.__call__(new PyObject[]{createEnviron(getRequest()), start_response });
if (ret != null) {
String contentType = wr.headers.get("Content-type");
if (contentType == null) {
contentType = "text/plain";
}
MediaType mediaType = new MediaType(contentType);
if (ret instanceof PyString) {
getResponse().setEntity(ret.toString(), mediaType);
}
else if (ret instanceof PyList) {
final PyList list = (PyList) ret;
getResponse().setEntity(new OutputRepresentation(mediaType) {
@Override
public void write(OutputStream outputStream) throws IOException {
for (Iterator i = list.iterator(); i.hasNext();) {
outputStream.write(i.next().toString().getBytes());
outputStream.write('\n');
}
}
});
}
else if (ret instanceof PyIterator) {
final PyIterator iter = (PyIterator) ret;
getResponse().setEntity(new OutputRepresentation(mediaType) {
@Override
public void write(OutputStream outputStream) throws IOException {
for (Iterator i = iter.iterator(); i.hasNext();) {
outputStream.write(i.next().toString().getBytes());
outputStream.write('\n');
}
}
});
}
else {
LOGGER.warning( "Unsure how to handle " + ret + ". Resorting to outputing string " +
"representation.");
getResponse().setEntity(ret.toString(), mediaType);
}
}
response.remove();
}
static class WSGIResponse {
String statusCode;
String statusMessage;
TreeMap<String,String> headers = new TreeMap<String, String>();
}
}