001 package de.deepamehta.plugins.caching;
002
003 import de.deepamehta.plugins.time.service.TimeService;
004
005 import de.deepamehta.core.DeepaMehtaObject;
006 import de.deepamehta.core.osgi.PluginActivator;
007 import de.deepamehta.core.service.Inject;
008 import de.deepamehta.core.service.event.ServiceRequestFilterListener;
009 import de.deepamehta.core.service.event.ServiceResponseFilterListener;
010 import de.deepamehta.core.util.JavaUtils;
011
012 // ### TODO: hide Jersey internals. Move to JAX-RS 2.0.
013 import com.sun.jersey.spi.container.ContainerRequest;
014 import com.sun.jersey.spi.container.ContainerResponse;
015
016 import javax.servlet.http.HttpServletRequest;
017
018 import javax.ws.rs.Path;
019 import javax.ws.rs.WebApplicationException;
020 import javax.ws.rs.core.Context;
021 import javax.ws.rs.core.MultivaluedMap;
022 import javax.ws.rs.core.Response;
023
024 import java.util.Date;
025 import java.util.logging.Logger;
026 import java.util.regex.Matcher;
027 import java.util.regex.Pattern;
028
029
030
031 @Path("/cache")
032 public class CachingPlugin extends PluginActivator implements ServiceRequestFilterListener,
033 ServiceResponseFilterListener {
034
035 // ------------------------------------------------------------------------------------------------------- Constants
036
037 private static String CACHABLE_PATH = "core/(topic|association)/(\\d+)";
038
039 private static String HEADER_CACHE_CONTROL = "Cache-Control";
040
041 // ---------------------------------------------------------------------------------------------- Instance Variables
042
043 @Inject
044 private TimeService timeService;
045
046 @Context
047 HttpServletRequest req;
048
049 private Pattern cachablePath = Pattern.compile(CACHABLE_PATH);
050
051 private Logger logger = Logger.getLogger(getClass().getName());
052
053 // -------------------------------------------------------------------------------------------------- Public Methods
054
055
056
057 // ********************************
058 // *** Listener Implementations ***
059 // ********************************
060
061
062
063 @Override
064 public void serviceRequestFilter(ContainerRequest request) {
065 long objectId = requestObjectId(request);
066 if (objectId != -1) {
067 if (timeService == null) {
068 throw new RuntimeException("Time service is not available");
069 }
070 //
071 long time = timeService.getModificationTime(objectId);
072 Response.ResponseBuilder builder = request.evaluatePreconditions(new Date(time));
073 if (builder != null) {
074 Response response = builder.build();
075 Response.Status status = Response.Status.fromStatusCode(response.getStatus());
076 logger.info("### Preconditions of request \"" + JavaUtils.requestInfo(req) +
077 "\" are not met -- Responding with " + JavaUtils.responseInfo(status));
078 throw new WebApplicationException(response);
079 }
080 }
081 }
082
083 @Override
084 public void serviceResponseFilter(ContainerResponse response) {
085 DeepaMehtaObject object = responseObject(response);
086 if (object != null) {
087 setCacheControlHeader(response, "max-age=0");
088 }
089 }
090
091
092
093 // ------------------------------------------------------------------------------------------------- Private Methods
094
095 private long requestObjectId(ContainerRequest request) {
096 // Example URL: "http://localhost:8080/core/topic/2695?include_childs=true"
097 // request.getBaseUri()="http://localhost:8080/"
098 // request.getPath()="core/topic/2695"
099 // request.getAbsolutePath()="http://localhost:8080/core/topic/2695"
100 // request.getRequestUri()="http://localhost:8080/core/topic/2695?include_childs=true"
101 Matcher m = cachablePath.matcher(request.getPath());
102 if (m.matches()) {
103 long objectId = Long.parseLong(m.group(2));
104 //
105 String objectType = m.group(1); // group 1 is "topic" or "association"
106 if (!objectType.equals("topic") && !objectType.equals("association")) {
107 throw new RuntimeException("Unexpected object type: \"" + objectType + "\"");
108 }
109 //
110 return objectId;
111 } else {
112 return -1;
113 }
114 }
115
116 // ---
117
118 // ### FIXME: copy in TimePlugin
119 private DeepaMehtaObject responseObject(ContainerResponse response) {
120 Object entity = response.getEntity();
121 return entity instanceof DeepaMehtaObject ? (DeepaMehtaObject) entity : null;
122 }
123
124 private void setCacheControlHeader(ContainerResponse response, String value) {
125 setHeader(response, HEADER_CACHE_CONTROL, value);
126 }
127
128 // ### FIXME: copy in TimePlugin
129 private void setHeader(ContainerResponse response, String header, String value) {
130 MultivaluedMap headers = response.getHttpHeaders();
131 //
132 if (headers.containsKey(header)) {
133 throw new RuntimeException("Response already has a \"" + header + "\" header");
134 }
135 //
136 headers.putSingle(header, value);
137 }
138 }