001    package org.hackystat.dailyprojectdata.frontsidecache;
002    
003    import java.util.HashMap;
004    import java.util.Map;
005    
006    import org.hackystat.dailyprojectdata.server.Server;
007    import org.hackystat.utilities.stacktrace.StackTrace;
008    import org.hackystat.utilities.uricache.UriCache;
009    
010    /**
011     * A cache for successfully DPD instances.  It is a "front side" cache, in the sense
012     * that it faces the clients, as opposed to the caches associated with the SensorDataClient
013     * instances, which are "back side" in that they cache sensor data instances.  While the
014     * "back side" caches avoid calls to the lower-level sensorbase, this front-side cache 
015     * avoids the overhead of DPD computation itself.
016     * <p>
017     * The front side cache is organized as follows.  Each DPD instance is associated with a 
018     * project and a project owner. The FrontSideCache is implemented as a collection of
019     * UriCaches, one for each project owner.  When a client adds data to the FrontSideCache, it
020     * must supply the project owner (which is used to figure out which UriCache to use),
021     * the URI of the DPD request (which is the key), and the string representation of the 
022     * DPD (which is the value).  
023     * <p>
024     * The FrontSideCache currently has hard-coded maxLife of 1000 hours and each UriCache has
025     * a capacity of 1M instances. We could set these via ServerProperties values if necessary. 
026     * <p>
027     * There is one important component missing from the FrontSideCache, and that is access
028     * control.  The FrontSideCache does not check to see if the client checking the cache has 
029     * the right to retrieve the cached data.  To perform access control, you should use 
030     * the SensorDataClient.inProject(owner, project) method, which checks to see if the 
031     * user associated with the SensorDataClient instance has the right to access information
032     * about the project identified by the passed owner/project pair.  To see how this 
033     * works, here is some example get code, which checks the cache but only returns the 
034     * DPD instance if the calling user is in the project:
035     * <pre>
036     * String cachedDpd = this.server.getFrontSideCache().get(uriUser, uriString);
037     * if (cachedDpd != null && client.inProject(authUser, project)) {
038     *   return super.getStringRepresentation(cachedDpd);
039     * }
040     * </pre>
041     *  
042     * @author Philip Johnson
043     *
044     */
045    public class FrontSideCache {
046      
047      /** The .hackystat subdirectory containing these cache instances. */ 
048      private String subDir = "dailyprojectdata/frontsidecache";
049      
050      /** The number of hours that a cached DPD instance stays in the cache before being deleted. */
051      private double maxLife = 1000;
052    
053      /** The total capacity of this cache. */
054      private long capacity = 1000000L;
055      
056      /** Maps user names to their associated UriCache instance. */
057      private Map<String, UriCache> user2cache = new HashMap<String, UriCache>();
058      
059      /** The server that holds this FrontSideCache. */
060      private Server server = null;
061      
062      /** 
063       * Creates a new front-side cache, which stores the DPD instances recently created.
064       * There should be only one of these created for a given DPD server.  Note that 
065       * this assumes that only one DPD service is running on a given file system.  
066       * @param server The DPD server associated with this cache. 
067       */
068      public FrontSideCache(Server server) { 
069        this.server = server;
070      }
071      
072      /**
073       * Adds a (user, dpd) pair to this front-side cache. 
074       * The associated UriCache for this user is created if it does not already exist.
075       * Does nothing if frontsidecaching is disabled. 
076       * @param user The user who is the owner of the project associated with this DPD.
077       * @param project The name of the project.
078       * @param uri The URL naming this DPD, as a string. 
079       * @param dpdRepresentation A string representing the DPD instance. 
080       */
081      public void put(String user, String project, String uri, String dpdRepresentation) {
082        if (isDisabled()) {
083          return;
084        }
085        try {
086          UriCache uriCache = getCache(user);
087          uriCache.putInGroup(uri, project, dpdRepresentation);
088        }
089        catch (Exception e) {
090          this.server.getLogger().warning("Error during DPD front-side cache add: " +
091              StackTrace.toString(e));
092        }
093      }
094      
095      /**
096       * Returns the string representation of the DPD associated with the DPD owner and the 
097       * URI, or null if not in the cache. 
098       * @param user The user who is the owner of the Project associated with this DPD.
099       * @param uri The URI naming this DPD. 
100       * @param project The project associated with this URI.
101       * @return The string representation of the DPD, or null. 
102       */
103      public String get(String user, String project, String uri) {
104        if (isDisabled()) {
105          return null;
106        }
107        UriCache uriCache = getCache(user);
108        return (String)uriCache.getFromGroup(uri, project);
109      }
110      
111      /**
112       * Clears the cache associated with user. Instantiates one if not available so that 
113       * any persistent cache that has not yet been read into memory is cleared.
114       * @param user The user whose cache is to be cleared.
115       */
116      public void clear(String user) {
117        if (isDisabled()) {
118          return;
119        }
120        try {
121          UriCache uriCache = getCache(user);
122          uriCache.clear();
123        }
124        catch (Exception e) {
125          this.server.getLogger().warning("Error during DPD front-side cache clear: " +
126              StackTrace.toString(e));
127        }
128      }
129    
130      /**
131       * Clears all of the cached DPD instances associated with this project and user. 
132       * @param user The user. 
133       * @param project The project. 
134       */
135      public void clear(String user, String project) {
136        if (isDisabled()) {
137          return;
138        }
139        try {
140          UriCache uriCache = getCache(user);
141          uriCache.clearGroup(project);
142        }
143        catch (Exception e) {
144          this.server.getLogger().warning("Error during DPD front-side cache clear: " +
145              StackTrace.toString(e));
146        }
147      }
148      
149      
150      /**
151       * Returns true if frontsidecaching is disabled.
152       * @return True if disabled.
153       */
154      private boolean isDisabled() {
155        return !this.server.getServerProperties().isFrontSideCacheEnabled();
156      }
157    
158      /**
159       * Gets the UriCache associated with this project owner from the in-memory map.
160       * Instantiates it if necessary.
161       * @param user The user email (project owner) associated with this UriCache.
162       * @return A UriCache instance for this user. 
163       */
164      private UriCache getCache(String user) {
165        UriCache uriCache = user2cache.get(user);
166        if (uriCache == null) {
167          uriCache = new UriCache(user, subDir, maxLife, capacity);
168          user2cache.put(user, uriCache);
169        }
170        return uriCache;
171      }
172    
173    }