summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Morgan <jesse@jesterpm.net>2014-03-31 22:35:43 -0700
committerJesse Morgan <jesse@jesterpm.net>2014-03-31 22:35:43 -0700
commit38c12cf70ef4714a7fc508f7fbaf44487ea971b7 (patch)
tree59d29b82f65952653f789615db1003b431be607a
parentcfb2c5ef6582e51ae9cfdfff35e12b5b7fdc24fb (diff)
Locking down restlet library version.
While trying to fix this issue, I also moved FMFacade into this package and fixed a couple bugs that snuck into the last commit.
-rw-r--r--ivy.xml12
-rw-r--r--src/com/p4square/fmfacade/FMFacade.java98
-rw-r--r--src/com/p4square/fmfacade/FreeMarkerPageResource.java97
-rw-r--r--src/com/p4square/fmfacade/ftl/GetMethod.java94
-rw-r--r--src/com/p4square/fmfacade/json/ClientException.java20
-rw-r--r--src/com/p4square/fmfacade/json/JsonRequestClient.java109
-rw-r--r--src/com/p4square/fmfacade/json/JsonResponse.java87
-rw-r--r--src/com/p4square/grow/GrowProcessComponent.java7
-rw-r--r--src/com/p4square/grow/frontend/SurveyPageResource.java5
-rw-r--r--src/com/p4square/session/Session.java59
-rw-r--r--src/com/p4square/session/SessionAuthenticator.java36
-rw-r--r--src/com/p4square/session/SessionCheckingAuthenticator.java39
-rw-r--r--src/com/p4square/session/SessionCookieAuthenticator.java59
-rw-r--r--src/com/p4square/session/SessionCreatingAuthenticator.java46
-rw-r--r--src/com/p4square/session/Sessions.java155
-rw-r--r--src/templates/utils/dump.ftl98
-rw-r--r--web/WEB-INF/web.xml10
17 files changed, 1010 insertions, 21 deletions
diff --git a/ivy.xml b/ivy.xml
index 62d73cf..00cdbc6 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -16,13 +16,17 @@
</publications>
<dependencies defaultconfmapping="*->default">
+ <dependency org="org.restlet.jee" name="org.restlet" rev="[2.2-M5]" conf="default" />
+ <dependency org="org.restlet.jee" name="org.restlet.ext.servlet" rev="[2.2-M5]" conf="default" />
+ <dependency org="org.restlet.jee" name="org.restlet.ext.jackson" rev="[2.2-M5]" conf="default" />
+ <dependency org="org.restlet.jee" name="org.restlet.ext.freemarker" rev="[2.2-M5]" conf="default" />
+ <dependency org="org.restlet.jee" name="org.restlet.ext.httpclient" rev="[2.2-M5]" conf="default" />
+
+ <dependency org="log4j" name="log4j" rev="[1.2,)" conf="default" />
+
<dependency org="junit" name="junit" rev="4.7" conf="test" />
<dependency org="net.sourceforge.cobertura" name="cobertura" rev="1.9rc1" conf="test" />
- <dependency org="net.jesterpm" name="fmfacade" rev="[1.0-SNAPSHOT,)" conf="default" />
- <dependency org="org.restlet.jee" name="org.restlet.ext.httpclient" rev="[2.1,)" conf="default" />
- <dependency org="org.restlet.jee" name="org.restlet.ext.wadl" rev="[2.1,)" conf="default" />
-
<!-- Backend Dependencies -->
<dependency org="com.netflix.astyanax" name="astyanax-core" rev="[1.0,)" conf="default" />
<dependency org="com.netflix.astyanax" name="astyanax-thrift" rev="[1.0,)" conf="default" />
diff --git a/src/com/p4square/fmfacade/FMFacade.java b/src/com/p4square/fmfacade/FMFacade.java
new file mode 100644
index 0000000..54e4098
--- /dev/null
+++ b/src/com/p4square/fmfacade/FMFacade.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2013 Jesse Morgan <jesse@jesterpm.net>
+ */
+
+package com.p4square.fmfacade;
+
+import java.io.IOException;
+
+import org.restlet.Application;
+import org.restlet.Component;
+import org.restlet.data.Protocol;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.Template;
+
+import org.apache.log4j.Logger;
+
+/**
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class FMFacade extends Application {
+ private static final Logger cLog = Logger.getLogger(FMFacade.class);
+ private final Configuration mFMConfig;
+
+ public FMFacade() {
+ mFMConfig = new Configuration();
+ mFMConfig.setClassForTemplateLoading(getClass(), "/templates");
+ mFMConfig.setObjectWrapper(new DefaultObjectWrapper());
+ }
+
+ @Override
+ public synchronized Restlet createInboundRoot() {
+ return createRouter();
+ }
+
+ /**
+ * Retrieve a template.
+ *
+ * @param name The template name.
+ * @return A FreeMarker template or null on error.
+ */
+ public Template getTemplate(String name) {
+ try {
+ return mFMConfig.getTemplate(name);
+
+ } catch (IOException e) {
+ cLog.error("Could not load template \"" + name + "\"", e);
+ return null;
+ }
+ }
+
+ /**
+ * Create the router to be used by this application. This can be overriden
+ * by sub-classes to add additional routes.
+ *
+ * @return The router.
+ */
+ protected Router createRouter() {
+ Router router = new Router(getContext());
+ router.attachDefault(FreeMarkerPageResource.class);
+
+ return router;
+ }
+
+ /**
+ * Stand-alone main for testing.
+ */
+ public static void main(String[] args) {
+ // Start the HTTP Server
+ final Component component = new Component();
+ component.getServers().add(Protocol.HTTP, 8085);
+ component.getClients().add(Protocol.HTTP);
+ component.getDefaultHost().attach(new FMFacade());
+
+ // Setup shutdown hook
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ public void run() {
+ try {
+ component.stop();
+ } catch (Exception e) {
+ cLog.error("Exception during cleanup", e);
+ }
+ }
+ });
+
+ cLog.info("Starting server...");
+
+ try {
+ component.start();
+ } catch (Exception e) {
+ cLog.fatal("Could not start: " + e.getMessage(), e);
+ }
+ }
+}
diff --git a/src/com/p4square/fmfacade/FreeMarkerPageResource.java b/src/com/p4square/fmfacade/FreeMarkerPageResource.java
new file mode 100644
index 0000000..46b0ec5
--- /dev/null
+++ b/src/com/p4square/fmfacade/FreeMarkerPageResource.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.fmfacade;
+
+import java.util.Map;
+import java.util.HashMap;
+
+import freemarker.template.Template;
+
+import org.restlet.Context;
+import org.restlet.data.MediaType;
+import org.restlet.data.Status;
+import org.restlet.ext.freemarker.TemplateRepresentation;
+import org.restlet.representation.Representation;
+import org.restlet.resource.ServerResource;
+import org.restlet.security.User;
+
+import org.apache.log4j.Logger;
+
+import com.p4square.fmfacade.ftl.GetMethod;
+
+import com.p4square.session.Session;
+import com.p4square.session.Sessions;
+
+/**
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class FreeMarkerPageResource extends ServerResource {
+ private static Logger cLog = Logger.getLogger(FreeMarkerPageResource.class);
+
+ public static Map<String, Object> baseRootObject(Context context) {
+ Map<String, Object> root = new HashMap<String, Object>();
+
+ root.put("get", new GetMethod(context.getClientDispatcher()));
+
+ return root;
+ }
+
+ private FMFacade mFMF;
+ private String mCurrentPage;
+
+ @Override
+ public void doInit() {
+ mFMF = (FMFacade) getApplication();
+ mCurrentPage = getReference().getRemainingPart(false, false);
+ }
+
+ protected Representation get() {
+ try {
+ Template t = mFMF.getTemplate("pages" + mCurrentPage + ".ftl");
+
+ if (t == null) {
+ setStatus(Status.CLIENT_ERROR_NOT_FOUND);
+ return null;
+ }
+
+ return new TemplateRepresentation(t, getRootObject(),
+ MediaType.TEXT_HTML);
+
+ } catch (Exception e) {
+ cLog.fatal("Could not render page: " + e.getMessage(), e);
+ setStatus(Status.SERVER_ERROR_INTERNAL);
+ return null;
+ }
+ }
+
+ /**
+ * Build and return the root object to pass to the FTL Template.
+ * @return A map of objects and methods for the template to access.
+ */
+ protected Map<String, Object> getRootObject() {
+ Map<String, Object> root = baseRootObject(getContext());
+
+ root.put("attributes", getRequestAttributes());
+ root.put("query", getQuery().getValuesMap());
+
+ if (getClientInfo().isAuthenticated()) {
+ final User user = getClientInfo().getUser();
+ final Map<String, String> userMap = new HashMap<String, String>();
+ userMap.put("id", user.getIdentifier());
+ userMap.put("firstName", user.getFirstName());
+ userMap.put("lastName", user.getLastName());
+ userMap.put("email", user.getEmail());
+ root.put("user", userMap);
+ }
+
+ Session s = Sessions.getInstance().get(getRequest());
+ if (s != null) {
+ root.put("session", s.getMap());
+ }
+
+ return root;
+ }
+}
diff --git a/src/com/p4square/fmfacade/ftl/GetMethod.java b/src/com/p4square/fmfacade/ftl/GetMethod.java
new file mode 100644
index 0000000..a47c4b0
--- /dev/null
+++ b/src/com/p4square/fmfacade/ftl/GetMethod.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.fmfacade.ftl;
+
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+
+import java.io.IOException;
+
+import freemarker.core.Environment;
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateMethodModel;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+
+import org.apache.log4j.Logger;
+
+import org.restlet.data.Status;
+import org.restlet.data.Method;
+import org.restlet.representation.Representation;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.Restlet;
+
+import org.restlet.ext.jackson.JacksonRepresentation;
+
+/**
+ * This method allows templates to make GET requests.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class GetMethod implements TemplateMethodModel {
+ private static final Logger cLog = Logger.getLogger(GetMethod.class);
+
+ private final Restlet mDispatcher;
+
+ public GetMethod(Restlet dispatcher) {
+ mDispatcher = dispatcher;
+ }
+
+ /**
+ * @param args List with exactly two arguments:
+ * * The variable in which to put the result.
+ * * The URI to GET.
+ */
+ public TemplateModel exec(List args) throws TemplateModelException {
+ final Environment env = Environment.getCurrentEnvironment();
+
+ if (args.size() != 2) {
+ throw new TemplateModelException(
+ "Expecting exactly one argument containing the URI");
+ }
+
+ Request request = new Request(Method.GET, (String) args.get(1));
+ Response response = mDispatcher.handle(request);
+ Status status = response.getStatus();
+ Representation representation = response.getEntity();
+
+ try {
+ if (response.getStatus().isSuccess()) {
+ JacksonRepresentation<Map> mapRepresentation;
+ if (representation instanceof JacksonRepresentation) {
+ mapRepresentation = (JacksonRepresentation<Map>) representation;
+ } else {
+ mapRepresentation = new JacksonRepresentation<Map>(
+ representation, Map.class);
+ }
+ try {
+ TemplateModel mapModel = env.getObjectWrapper().wrap(mapRepresentation.getObject());
+
+ env.setVariable((String) args.get(0), mapModel);
+
+ } catch (IOException e) {
+ cLog.warn("Exception occurred when calling getObject(): "
+ + e.getMessage(), e);
+ status = Status.SERVER_ERROR_INTERNAL;
+ }
+ }
+
+ Map statusMap = new HashMap();
+ statusMap.put("code", status.getCode());
+ statusMap.put("reason", status.getReasonPhrase());
+ statusMap.put("succeeded", status.isSuccess());
+ return env.getObjectWrapper().wrap(statusMap);
+ } finally {
+ if (representation != null) {
+ representation.release();
+ }
+ }
+ }
+}
diff --git a/src/com/p4square/fmfacade/json/ClientException.java b/src/com/p4square/fmfacade/json/ClientException.java
new file mode 100644
index 0000000..c233193
--- /dev/null
+++ b/src/com/p4square/fmfacade/json/ClientException.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.fmfacade.json;
+
+/**
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class ClientException extends Exception {
+
+ public ClientException(final String msg) {
+ super(msg);
+ }
+
+ public ClientException(final String msg, final Exception cause) {
+ super(msg, cause);
+ }
+}
diff --git a/src/com/p4square/fmfacade/json/JsonRequestClient.java b/src/com/p4square/fmfacade/json/JsonRequestClient.java
new file mode 100644
index 0000000..19a394f
--- /dev/null
+++ b/src/com/p4square/fmfacade/json/JsonRequestClient.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.fmfacade.json;
+
+import java.util.Map;
+
+import java.io.IOException;
+
+import org.apache.log4j.Logger;
+
+import org.restlet.data.Status;
+import org.restlet.data.Method;
+import org.restlet.representation.Representation;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.Restlet;
+
+import org.restlet.ext.jackson.JacksonRepresentation;
+
+/**
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class JsonRequestClient {
+ private final Restlet mDispatcher;
+
+ public JsonRequestClient(Restlet dispatcher) {
+ mDispatcher = dispatcher;
+ }
+
+ /**
+ * Perform a GET request for the given URI and parse the response as a
+ * JSON map.
+ *
+ * @return A JsonResponse object which can be used to retrieve the
+ * response as a JSON map.
+ */
+ public JsonResponse get(final String uri) {
+ final Request request = new Request(Method.GET, uri);
+ final Response response = mDispatcher.handle(request);
+
+ return new JsonResponse(response);
+ }
+
+ /**
+ * Perform a PUT request for the given URI and parse the response as a
+ * JSON map.
+ *
+ * @return A JsonResponse object which can be used to retrieve the
+ * response as a JSON map.
+ */
+ public JsonResponse put(final String uri, Representation entity) {
+ final Request request = new Request(Method.PUT, uri);
+ request.setEntity(entity);
+
+ final Response response = mDispatcher.handle(request);
+ return new JsonResponse(response);
+ }
+
+ /**
+ * Perform a PUT request for the given URI and parse the response as a
+ * JSON map.
+ *
+ * @return A JsonResponse object which can be used to retrieve the
+ * response as a JSON map.
+ */
+ public JsonResponse put(final String uri, Map map) {
+ return put(uri, new JacksonRepresentation<Map>(map));
+ }
+
+ /**
+ * Perform a POST request for the given URI and parse the response as a
+ * JSON map.
+ *
+ * @return A JsonResponse object which can be used to retrieve the
+ * response as a JSON map.
+ */
+ public JsonResponse post(final String uri, Representation entity) {
+ final Request request = new Request(Method.POST, uri);
+ request.setEntity(entity);
+
+ final Response response = mDispatcher.handle(request);
+ return new JsonResponse(response);
+ }
+
+ /**
+ * Perform a POST request for the given URI and parse the response as a
+ * JSON map.
+ *
+ * @return A JsonResponse object which can be used to retrieve the
+ * response as a JSON map.
+ */
+ public JsonResponse post(final String uri, Map map) {
+ return post(uri, new JacksonRepresentation<Map>(map));
+ }
+
+ /**
+ * Perform a DELETE request for the given URI.
+ *
+ * @return A JsonResponse object with the status of the request.
+ */
+ public JsonResponse delete(final String uri) {
+ final Request request = new Request(Method.DELETE, uri);
+ final Response response = mDispatcher.handle(request);
+ return new JsonResponse(response);
+ }
+}
diff --git a/src/com/p4square/fmfacade/json/JsonResponse.java b/src/com/p4square/fmfacade/json/JsonResponse.java
new file mode 100644
index 0000000..b9cb587
--- /dev/null
+++ b/src/com/p4square/fmfacade/json/JsonResponse.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.fmfacade.json;
+
+import java.util.Map;
+
+import java.io.IOException;
+
+import org.restlet.data.Status;
+import org.restlet.data.Reference;
+import org.restlet.representation.Representation;
+import org.restlet.Response;
+
+import org.restlet.ext.jackson.JacksonRepresentation;
+
+/**
+ * JsonResponse wraps a Restlet Response object and parses the entity, if any,
+ * as a JSON map.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class JsonResponse {
+ private final Response mResponse;
+ private final Representation mRepresentation;
+
+ private Map<String, Object> mMap;
+
+ JsonResponse(Response response) {
+ mResponse = response;
+ mRepresentation = response.getEntity();
+ mMap = null;
+
+ if (!response.getStatus().isSuccess()) {
+ if (mRepresentation != null) {
+ mRepresentation.release();
+ }
+ }
+ }
+
+ /**
+ * @return the Status info from the response.
+ */
+ public Status getStatus() {
+ return mResponse.getStatus();
+ }
+
+ /**
+ * @return the Reference for a redirect.
+ */
+ public Reference getRedirectLocation() {
+ return mResponse.getLocationRef();
+ }
+
+ /**
+ * Return the parsed json map from the response.
+ */
+ public Map<String, Object> getMap() throws ClientException {
+ if (mMap == null) {
+ Representation representation = mRepresentation;
+
+ // Parse response
+ if (representation == null) {
+ return null;
+ }
+
+ JacksonRepresentation<Map> mapRepresentation;
+ if (representation instanceof JacksonRepresentation) {
+ mapRepresentation = (JacksonRepresentation<Map>) representation;
+ } else {
+ mapRepresentation = new JacksonRepresentation<Map>(
+ representation, Map.class);
+ }
+
+ try {
+ mMap = (Map<String, Object>) mapRepresentation.getObject();
+
+ } catch (IOException e) {
+ throw new ClientException("Failed to parse response: " + e.getMessage(), e);
+ }
+ }
+
+ return mMap;
+ }
+
+}
diff --git a/src/com/p4square/grow/GrowProcessComponent.java b/src/com/p4square/grow/GrowProcessComponent.java
index eb92840..29da766 100644
--- a/src/com/p4square/grow/GrowProcessComponent.java
+++ b/src/com/p4square/grow/GrowProcessComponent.java
@@ -56,8 +56,8 @@ public class GrowProcessComponent extends Component {
// Authenticated access to the backend
BackendVerifier verifier = new BackendVerifier(backend.getUserRecordProvider());
- ChallengeAuthenticator auth = new ChallengeAuthenticator(getContext(), false,
- ChallengeScheme.HTTP_BASIC, BACKEND_REALM, verifier);
+ ChallengeAuthenticator auth = new ChallengeAuthenticator(getContext().createChildContext(),
+ false, ChallengeScheme.HTTP_BASIC, BACKEND_REALM, verifier);
auth.setNext(backend);
getDefaultHost().attach("/backend", auth);
}
@@ -87,9 +87,6 @@ public class GrowProcessComponent extends Component {
// Start the HTTP Server
final GrowProcessComponent component = new GrowProcessComponent();
component.getServers().add(Protocol.HTTP, 8085);
- component.getClients().add(Protocol.HTTP);
- component.getClients().add(Protocol.HTTPS);
- component.getClients().add(Protocol.FILE);
//component.getClients().add(new Client(null, Arrays.asList(Protocol.HTTPS), "org.restlet.ext.httpclient.HttpClientHelper"));
// Static content
diff --git a/src/com/p4square/grow/frontend/SurveyPageResource.java b/src/com/p4square/grow/frontend/SurveyPageResource.java
index 1f9c56c..4bb132a 100644
--- a/src/com/p4square/grow/frontend/SurveyPageResource.java
+++ b/src/com/p4square/grow/frontend/SurveyPageResource.java
@@ -33,7 +33,6 @@ import com.p4square.grow.model.UserRecord;
import com.p4square.grow.provider.DelegateProvider;
import com.p4square.grow.provider.JsonEncodedProvider;
import com.p4square.grow.provider.Provider;
-import com.p4square.grow.provider.QuestionProvider;
/**
* SurveyPageResource handles rendering the survey and processing user's answers.
@@ -71,7 +70,9 @@ public class SurveyPageResource extends FreeMarkerPageResource {
}
mJsonClient = new JsonRequestClient(getContext().getClientDispatcher());
- mQuestionProvider = new QuestionProvider<String>(new JsonRequestProvider<Question>(getContext().getClientDispatcher(), Question.class)) {
+ mQuestionProvider = new DelegateProvider<String, String, Question>(
+ new JsonRequestProvider<Question>(getContext().getClientDispatcher(),
+ Question.class)) {
@Override
public String makeKey(String questionId) {
return getBackendEndpoint() + "/assessment/question/" + questionId;
diff --git a/src/com/p4square/session/Session.java b/src/com/p4square/session/Session.java
new file mode 100644
index 0000000..1bb65f5
--- /dev/null
+++ b/src/com/p4square/session/Session.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.session;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import org.restlet.security.User;
+
+/**
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class Session {
+ static final long LIFETIME = 86400000;
+
+ private final String mSessionId;
+ private final User mUser;
+ private final Map<String, String> mData;
+ private long mExpires;
+
+ Session(User user) {
+ mUser = user;
+ mSessionId = UUID.randomUUID().toString();
+ mExpires = System.currentTimeMillis() + LIFETIME;
+ mData = new HashMap<String, String>();
+ }
+
+ void touch() {
+ mExpires = System.currentTimeMillis() + LIFETIME;
+ }
+
+ boolean isExpired() {
+ return System.currentTimeMillis() > mExpires;
+ }
+
+ public String getId() {
+ return mSessionId;
+ }
+
+ public Object get(String key) {
+ return mData.get(key);
+ }
+
+ public void put(String key, String value) {
+ mData.put(key, value);
+ }
+
+ public User getUser() {
+ return mUser;
+ }
+
+ public Map<String, String> getMap() {
+ return mData;
+ }
+}
diff --git a/src/com/p4square/session/SessionAuthenticator.java b/src/com/p4square/session/SessionAuthenticator.java
new file mode 100644
index 0000000..794e1a8
--- /dev/null
+++ b/src/com/p4square/session/SessionAuthenticator.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.session;
+
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.security.Authenticator;
+import org.restlet.security.User;
+
+/**
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class SessionAuthenticator /*extends Authenticator*/ {
+ /*
+ @Override
+ protected boolean authenticate(Request request, Response response) {
+ // Check for authentication cookie
+ final String cookie = request.getCookies().getFirstValue(COOKIE_NAME);
+ if (cookie != null) {
+ cLog.debug("Got cookie: " + cookie);
+ // TODO Decrypt user info
+ User user = new User(cookie);
+ request.getClientInfo().setUser(user);
+ return true;
+ }
+
+ // Challenge the user if not authenticated
+ response.redirectSeeOther(mLoginPage);
+ return false;
+ }
+ */
+}
diff --git a/src/com/p4square/session/SessionCheckingAuthenticator.java b/src/com/p4square/session/SessionCheckingAuthenticator.java
new file mode 100644
index 0000000..489d6a0
--- /dev/null
+++ b/src/com/p4square/session/SessionCheckingAuthenticator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.session;
+
+import org.apache.log4j.Logger;
+
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.security.Authenticator;
+
+/**
+ * Authenticator which succeeds if a valid Session exists.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class SessionCheckingAuthenticator extends Authenticator {
+ private static final Logger LOG = Logger.getLogger(SessionCheckingAuthenticator.class);
+
+ public SessionCheckingAuthenticator(Context context, boolean optional) {
+ super(context, optional);
+ }
+
+ protected boolean authenticate(Request request, Response response) {
+ Session s = Sessions.getInstance().get(request);
+
+ if (s != null) {
+ LOG.debug("Found session for user " + s.getUser());
+ request.getClientInfo().setUser(s.getUser());
+ return true;
+
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/src/com/p4square/session/SessionCookieAuthenticator.java b/src/com/p4square/session/SessionCookieAuthenticator.java
new file mode 100644
index 0000000..0074b77
--- /dev/null
+++ b/src/com/p4square/session/SessionCookieAuthenticator.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.session;
+
+import org.apache.log4j.Logger;
+
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.security.Authenticator;
+
+/**
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class SessionCookieAuthenticator extends Authenticator {
+ private static final Logger LOG = Logger.getLogger(SessionCookieAuthenticator.class);
+
+ private static final String COOKIE_NAME = "S";
+
+ private final Sessions mSessions;
+
+ public SessionCookieAuthenticator(Context context, boolean optional, Sessions sessions) {
+ super(context, optional);
+
+ mSessions = sessions;
+ }
+
+ protected boolean authenticate(Request request, Response response) {
+ final String cookie = request.getCookies().getFirstValue(COOKIE_NAME);
+
+ if (request.getClientInfo().isAuthenticated()) {
+ // Request is already authenticated... create session if it doesn't exist.
+ if (cookie == null) {
+ Session s = mSessions.create(request.getClientInfo().getUser());
+ response.getCookieSettings().add(COOKIE_NAME, s.getId());
+ }
+
+ return true;
+
+ } else {
+ // Check for authentication cookie
+ if (cookie != null) {
+ LOG.debug("Got cookie: " + cookie);
+
+ Session s = mSessions.get(cookie);
+ if (s != null) {
+ request.getClientInfo().setUser(s.getUser());
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+}
diff --git a/src/com/p4square/session/SessionCreatingAuthenticator.java b/src/com/p4square/session/SessionCreatingAuthenticator.java
new file mode 100644
index 0000000..3ec14b4
--- /dev/null
+++ b/src/com/p4square/session/SessionCreatingAuthenticator.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.session;
+
+import org.apache.log4j.Logger;
+
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.security.Authenticator;
+import org.restlet.security.User;
+
+/**
+ * Authenticator which creates a Session for the request and adds a cookie
+ * to the response.
+ *
+ * The Request MUST be Authenticated and MUST have a User object associated.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class SessionCreatingAuthenticator extends Authenticator {
+ private static final Logger LOG = Logger.getLogger(SessionCreatingAuthenticator.class);
+
+ public SessionCreatingAuthenticator(Context context) {
+ super(context, true);
+ }
+
+ protected boolean authenticate(Request request, Response response) {
+ if (Sessions.getInstance().get(request) != null) {
+ return true;
+ }
+
+ User user = request.getClientInfo().getUser();
+
+ if (request.getClientInfo().isAuthenticated() && user != null) {
+ Sessions.getInstance().create(request, response);
+ LOG.debug(response);
+ return true;
+ }
+
+ return false;
+ }
+
+}
diff --git a/src/com/p4square/session/Sessions.java b/src/com/p4square/session/Sessions.java
new file mode 100644
index 0000000..9f9dda0
--- /dev/null
+++ b/src/com/p4square/session/Sessions.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.session;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.restlet.Response;
+import org.restlet.Request;
+import org.restlet.data.CookieSetting;
+import org.restlet.security.User;
+
+/**
+ * Singleton Session Manager.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class Sessions {
+ private static final String COOKIE_NAME = "S";
+ private static final int DELETE = 0;
+
+ private static final Sessions THE = new Sessions();
+ public static Sessions getInstance() {
+ return THE;
+ }
+
+ private final Map<String, Session> mSessions;
+ private final Timer mCleanupTimer;
+
+ private Sessions() {
+ mSessions = new ConcurrentHashMap<String, Session>();
+
+ mCleanupTimer = new Timer("sessionCleaner", true);
+ mCleanupTimer.scheduleAtFixedRate(new TimerTask() {
+ @Override
+ public void run() {
+ for (Session s : mSessions.values()) {
+ if (s.isExpired()) {
+ mSessions.remove(s.getId());
+ }
+ }
+ }
+ }, Session.LIFETIME, Session.LIFETIME);
+ }
+
+ /**
+ * Get a session by ID.
+ *
+ * @param sessionid
+ * The Session id
+ * @return The Session if found and not expired, null otherwise.
+ */
+ public Session get(String sessionid) {
+ Session s = mSessions.get(sessionid);
+
+ if (s != null && !s.isExpired()) {
+ s.touch();
+ return s;
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the Session associated with the Request.
+ *
+ * @param request
+ * The request to fetch a session for.
+ * @return A session or null if no session is found.
+ */
+ public Session get(Request request) {
+ final String cookie = request.getCookies().getFirstValue(COOKIE_NAME);
+
+ if (cookie != null) {
+ return get(cookie);
+ }
+
+ return null;
+ }
+
+ /**
+ * Create a new Session for the given User object.
+ *
+ * @param user
+ * The User to associate with the Session.
+ * @return The new Session object.
+ */
+ public Session create(User user) {
+ if (user == null) {
+ throw new IllegalArgumentException("Can not create session for null user.");
+ }
+
+ Session s = new Session(user);
+ mSessions.put(s.getId(), s);
+
+ return s;
+ }
+
+ /**
+ * Delete a Session.
+ *
+ * @param sessionid
+ * The id of the Session to remove.
+ */
+ public void delete(String sessionid) {
+ mSessions.remove(sessionid);
+ }
+
+ /**
+ * Create a new Session and add the Session cookie to the response.
+ *
+ * @param request
+ * The request to create the Session for.
+ * @param response
+ * The response to add the session cookie to.
+ * @return The new Session.
+ */
+ public Session create(Request request, Response response) {
+ Session s = create(request.getClientInfo().getUser());
+
+ CookieSetting cookie = new CookieSetting(COOKIE_NAME, s.getId());
+ cookie.setPath("/");
+
+ request.getCookies().add(cookie);
+ response.getCookieSettings().add(cookie);
+
+ return s;
+ }
+
+ /**
+ * Remove a Session and delete the cookies.
+ *
+ * @param request
+ * The request with the session cookie to remove
+ * @param response
+ * The response to remove the session cookie from.
+ */
+ public void delete(Request request, Response response) {
+ final String sessionid = request.getCookies().getFirstValue(COOKIE_NAME);
+
+ delete(sessionid);
+
+ CookieSetting cookie = new CookieSetting(COOKIE_NAME, "");
+ cookie.setPath("/");
+ cookie.setMaxAge(DELETE);
+
+ request.getCookies().add(cookie);
+ response.getCookieSettings().add(cookie);
+ }
+
+}
diff --git a/src/templates/utils/dump.ftl b/src/templates/utils/dump.ftl
new file mode 100644
index 0000000..6491a25
--- /dev/null
+++ b/src/templates/utils/dump.ftl
@@ -0,0 +1,98 @@
+<#-- dump.ftl
+ --
+ -- Generates tree representations of data model items.
+ --
+ -- Usage:
+ -- <#import "dump.ftl" as dumper>
+ --
+ -- <#assign foo = something.in["your"].data[0].model />
+ --
+ -- <@dumper.dump foo />
+ --
+ -- When used within html pages you've to use <pre>-tags to get the wanted
+ -- result:
+ -- <pre>
+ -- <@dumper.dump foo />
+ -- <pre>
+ -->
+
+<#-- The black_list contains bad hash keys. Any hash key which matches a
+ -- black_list entry is prevented from being displayed.
+ -->
+<#assign black_list = ["class"] />
+
+
+<#--
+ -- The main macro.
+ -->
+
+<#macro dump data>
+(root)
+<#if data?is_enumerable>
+<@printList data,[] />
+<#elseif data?is_hash_ex>
+<@printHashEx data,[] />
+</#if>
+</#macro>
+
+<#-- private helper macros. it's not recommended to use these macros from
+ -- outside the macro library.
+ -->
+
+<#macro printList list has_next_array>
+<#local counter=0 />
+<#list list as item>
+<#list has_next_array+[true] as has_next><#if !has_next> <#else> | </#if></#list>
+<#list has_next_array as has_next><#if !has_next> <#else> | </#if></#list><#t>
+<#t><@printItem item?if_exists,has_next_array+[item_has_next], counter />
+<#local counter = counter + 1/>
+</#list>
+</#macro>
+
+<#macro printHashEx hash has_next_array>
+<#list hash?keys as key>
+<#list has_next_array+[true] as has_next><#if !has_next> <#else> | </#if></#list>
+<#list has_next_array as has_next><#if !has_next> <#else> | </#if></#list><#t>
+<#t><@printItem hash[key]?if_exists,has_next_array+[key_has_next], key />
+</#list>
+</#macro>
+
+<#macro printItem item has_next_array key>
+<#if item?is_method>
+ +- ${key} = ?? (method)
+<#elseif item?is_enumerable>
+ +- ${key}
+ <@printList item, has_next_array /><#t>
+<#elseif item?is_hash_ex && omit(key?string)><#-- omit bean-wrapped java.lang.Class objects -->
+ +- ${key} (omitted)
+<#elseif item?is_hash_ex>
+ +- ${key}
+ <@printHashEx item, has_next_array /><#t>
+<#elseif item?is_number>
+ +- ${key} = ${item}
+<#elseif item?is_string>
+ +- ${key} = "${item}"
+<#elseif item?is_boolean>
+ +- ${key} = ${item?string}
+<#elseif item?is_date>
+ +- ${key} = ${item?string("yyyy-MM-dd HH:mm:ss zzzz")}
+<#elseif item?is_transform>
+ +- ${key} = ?? (transform)
+<#elseif item?is_macro>
+ +- ${key} = ?? (macro)
+<#elseif item?is_hash>
+ +- ${key} = ?? (hash)
+<#elseif item?is_node>
+ +- ${key} = ?? (node)
+</#if>
+</#macro>
+
+<#function omit key>
+ <#local what = key?lower_case>
+ <#list black_list as item>
+ <#if what?index_of(item) gte 0>
+ <#return true>
+ </#if>
+ </#list>
+ <#return false>
+</#function> \ No newline at end of file
diff --git a/web/WEB-INF/web.xml b/web/WEB-INF/web.xml
index a06031c..4b85bde 100644
--- a/web/WEB-INF/web.xml
+++ b/web/WEB-INF/web.xml
@@ -41,14 +41,4 @@
<servlet-name>RestletServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
-
- <error-page>
- <error-code>404</error-code>
- <location>/notfound.html</location>
- </error-page>
-
- <error-page>
- <error-code>500</error-code>
- <location>/error.html</location>
- </error-page>
</web-app>