001/* 002 * Stallion Core: A Modern Web Framework 003 * 004 * Copyright (C) 2015 - 2016 Stallion Software LLC. 005 * 006 * This program is free software: you can redistribute it and/or modify it under the terms of the 007 * GNU General Public License as published by the Free Software Foundation, either version 2 of 008 * the License, or (at your option) any later version. This program is distributed in the hope that 009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 011 * License for more details. You should have received a copy of the GNU General Public License 012 * along with this program. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>. 013 * 014 * 015 * 016 */ 017 018package io.stallion.dataAccess.db; 019 020import io.stallion.Context; 021import io.stallion.requests.JobRequest; 022import io.stallion.requests.TaskRequest; 023import net.sf.ehcache.Cache; 024import net.sf.ehcache.CacheManager; 025import net.sf.ehcache.Element; 026import net.sf.ehcache.config.CacheConfiguration; 027import net.sf.ehcache.config.PersistenceConfiguration; 028 029import javax.servlet.http.Cookie; 030 031import static io.stallion.utils.Literals.*; 032 033/** 034 * SmartQueryCache is used by the DB singleton to cache query results. 035 * Smart cache uses a bunch of heuristics to ensure that a user who has 036 * just made an update never sees stale data. 037*/ 038public class SmartQueryCache { 039 private static CacheManager manager; 040 041 042 public static void load() { 043 044 manager = CacheManager.create(); 045 } 046 047 public static void initCache(String bucket) { 048 if (manager == null) { 049 load(); 050 } 051 if (manager.getCache(bucket) != null) { 052 return; 053 } 054 CacheConfiguration config = new CacheConfiguration(bucket, 10000); 055 //config.setDiskPersistent(false); 056 //config.setMaxElementsOnDisk(0); 057 //config.setMaxBytesLocalDisk(0L); 058 config.setOverflowToOffHeap(false); 059 PersistenceConfiguration persistenceConfiguration = new PersistenceConfiguration(); 060 persistenceConfiguration.strategy(PersistenceConfiguration.Strategy.NONE); 061 config.persistence(persistenceConfiguration); 062 manager.addCache(new Cache(config)); 063 } 064 065 public static void shutdown() { 066 if (manager != null) { 067 manager.shutdown(); 068 } 069 manager = null; 070 } 071 072 /** 073 * Under certain conditions, will skip the cache lookup and always return null, thus prompting a cache refresh 074 * This is to prevent getting a stale object right after a user has made a POST request. 075 * Otherwise, will hit the cache as normal. 076 * 077 * By default: 078 * 079 * * Results are cached for 15 seconds 080 * * caching is skipped when run via a task or a job 081 * * caching is skipped for non-GET requests 082 * * caching is only skipped once per request 083 * * caching is skipped if the user made a post request in the last 15 seconds. 084 * 085 * Thus altogether, if you are getting dozens of GET requests per second, due to a page 086 * getting heavy traffic, then the cache will almost always be hit. But if a user 087 * has just made some sort of update, they are guaranteed not to get stale data. 088 * 089 * @param bucket 090 * @param key 091 * @return 092 */ 093 public static Object getSmart(String bucket, String key) { 094 if (checkShouldSkip(bucket, key)) { 095 return null; 096 } 097 return get(bucket, key); 098 } 099 100 101 public static boolean checkShouldSkip(String bucket, String key) { 102 // We only skip the cache at most once per lookup, per request 103 // So we set a request context item to mark that we've done this check. 104 Boolean seen = (Boolean)Context.getRequest().getItems().get("checked-should-skip-for-key-" + bucket + "---" + key); 105 if (seen != null && seen) { 106 return false; 107 } 108 Context.getRequest().getItems().put("checked-should-skip-for-key-" + bucket + "---" + key, true); 109 110 if (Context.getRequest() instanceof TaskRequest) { 111 return true; 112 } 113 if (Context.getRequest() instanceof JobRequest) { 114 return true; 115 } 116 if (!"GET".equals(Context.getRequest().getMethod())) { 117 return true; 118 } 119 120 // The current user 121 Cookie postBackCookie = Context.getRequest().getCookie("st-recent-postback"); 122 if (postBackCookie != null && !empty(postBackCookie.getValue())) { 123 Long t = Long.parseLong(postBackCookie.getValue()); 124 if (t != null && t > (mils() - 15000)) { 125 return true; 126 } 127 } 128 return false; 129 } 130 131 public static Object get(String bucket, String key) { 132 if (manager == null) { 133 return null; 134 } 135 Element element = manager.getCache(bucket).get(key); 136 if (element != null) { 137 return element.getObjectValue(); 138 } 139 return null; 140 } 141 142 public static void set(String bucket, String key, Object value) { 143 if (manager == null) { 144 load(); 145 } 146 if (!manager.cacheExists(bucket)) { 147 initCache(bucket); 148 } 149 Element element = new Element(key, value); 150 element.setTimeToLive(15); 151 manager.getCache(bucket).put(element); 152 } 153 154 public static void clearBucket(String bucket) { 155 if (manager.cacheExists(bucket) && manager.getCache(bucket).getSize() > 0) { 156 manager.getCache(bucket).removeAll(); 157 } 158 } 159}