001package systems.dmx.caching; 002 003import systems.dmx.time.TimeService; 004 005import systems.dmx.core.DMXObject; 006import systems.dmx.core.osgi.PluginActivator; 007import systems.dmx.core.service.Inject; 008import systems.dmx.core.service.event.ServiceRequestFilterListener; 009import systems.dmx.core.service.event.ServiceResponseFilterListener; 010import systems.dmx.core.util.JavaUtils; 011 012// ### TODO: hide Jersey internals. Move to JAX-RS 2.0. 013import com.sun.jersey.spi.container.ContainerRequest; 014import com.sun.jersey.spi.container.ContainerResponse; 015 016import javax.servlet.http.HttpServletRequest; 017 018import javax.ws.rs.Path; 019import javax.ws.rs.WebApplicationException; 020import javax.ws.rs.core.Context; 021import javax.ws.rs.core.MultivaluedMap; 022import javax.ws.rs.core.Response; 023 024import java.util.Date; 025import java.util.logging.Logger; 026import java.util.regex.Matcher; 027import java.util.regex.Pattern; 028 029 030 031@Path("/cache") 032public 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.fine("### 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 DMXObject 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 DMXObject responseObject(ContainerResponse response) { 120 Object entity = response.getEntity(); 121 return entity instanceof DMXObject ? (DMXObject) 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}