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 -- Generating " + 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    }