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.dataAccess.*; 022import io.stallion.dataAccess.filtering.FilterCache; 023import io.stallion.dataAccess.filtering.FilterChain; 024import io.stallion.dataAccess.filtering.MySqlFilterChain; 025import io.stallion.requests.IRequest; 026import io.stallion.requests.JobRequest; 027import io.stallion.requests.TaskRequest; 028import io.stallion.utils.DateUtils; 029 030import javax.servlet.http.Cookie; 031import java.time.Instant; 032import java.time.ZoneId; 033import java.time.ZonedDateTime; 034import java.util.List; 035import java.util.Map; 036 037import static io.stallion.utils.Literals.*; 038 039 040public class DbPersister<T extends Model> extends BasePersister<T> { 041 private long lastSyncAt = 0; 042 private String tableName = ""; 043 private String sortField = "id"; 044 private String sortDirection = "ASC"; 045 046 @Override 047 public void init(DataAccessRegistration registration, ModelController<T> controller, Stash<T> stash) { 048 super.init(registration, controller, stash); 049 this.tableName = or(registration.getTableName(), getBucket()); 050 DefaultSort defaultSort = getModelClass().getAnnotation(DefaultSort.class); 051 if (defaultSort != null) { 052 sortField = defaultSort.field(); 053 sortDirection = defaultSort.direction(); 054 } 055 } 056 057 058 059 @Override 060 public List<T> fetchAll() { 061 lastSyncAt = DateUtils.mils(); 062 List<T> things = DB.instance().fetchAllSorted(getModelClass(), sortField, sortDirection); 063 for (T thing: things) { 064 thing.setBucket(getBucket()); 065 } 066 return things; 067 } 068 069 @Override 070 public T fetchOne(Long id) { 071 T o = DB.instance().fetchOne(getModelClass(), id); 072 if (o != null) { 073 handleFetchOne(o); 074 } 075 return o; 076 } 077 078 @Override 079 public T fetchOne(T obj) { 080 return fetchOne(obj.getId()); 081 } 082 083 @Override 084 public void watchEventCallback(String relativePath) { 085 086 } 087 088 @Override 089 public void persist(T obj) { 090 DB.instance().save(obj); 091 } 092 093 @Override 094 public void update(T obj, Map<String, Object> values) { 095 DB.instance().update(obj, values); 096 } 097 098 @Override 099 public void hardDelete(T obj) { 100 DB.instance().delete(obj); 101 } 102 103 @Override 104 public void attachWatcher() { 105 106 } 107 108 @Override 109 public boolean isDbBacked() { 110 return true; 111 } 112 113 @Override 114 public void onPreRead() { 115 // Check to see if we have read this bucket yet, for this request 116 if (Context.getRequest() == null || Context.getRequest().getItems() == null) { 117 return; 118 } 119 Boolean bucketSynced = (Boolean)Context.getRequest().getItems().get(getBucketSyncedKey()); 120 if (bucketSynced != null && bucketSynced) { 121 return; 122 } 123 Context.getRequest().getItems().put(getBucketSyncedKey(), true); 124 if (!checkNeedsSync()) { 125 return; 126 } 127 boolean hasChanges = syncFromDatabase(); 128 if (hasChanges) { 129 FilterCache.clearBucket(getBucket()); 130 } 131 } 132 133 134 @Override 135 public FilterChain<T> filterChain() { 136 return new MySqlFilterChain<T>(getTableName(), getBucket(), getModelClass()); 137 } 138 139 protected boolean syncFromDatabase() { 140 Long now = mils(); 141 142 ZonedDateTime dt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(lastSyncAt), ZoneId.of("UTC")); 143 //dt.format(StallionUtils.ISO_FORMAT); 144 String formatedNow = dt.format(DateUtils.SQL_FORMAT); 145 Class < T > cls = getModelClass(); 146 List<T> items = DB.instance().query(cls, "SELECT * FROM " + getTableName() + " WHERE row_updated_at >= ?", formatedNow); 147 boolean hasChanges = false; 148 for (T item: items) { 149 boolean changed = getStash().loadItem(item); 150 if (changed) { 151 hasChanges = true; 152 } 153 } 154 lastSyncAt = now; 155 return hasChanges; 156 } 157 158 159 160 protected String getBucketSyncedKey() { 161 return "bucket-synced:" + getBucket(); 162 } 163 164 protected boolean checkNeedsSync() { 165 if (Context.getRequest() instanceof TaskRequest) { 166 return true; 167 } 168 if (Context.getRequest() instanceof JobRequest) { 169 return true; 170 } 171 if (!"GET".equals(Context.getRequest().getMethod())) { 172 return true; 173 } 174 // Hasn't been synced in more than 15 seconds 175 if (lastSyncAt < (mils() - 15000)) { 176 return true; 177 } 178 // The current user 179 Cookie postBackCookie = Context.getRequest().getCookie(IRequest.RECENT_POSTBACK_COOKIE); 180 if (postBackCookie != null && !empty(postBackCookie.getValue())) { 181 Long t = Long.parseLong(postBackCookie.getValue()); 182 if (t != null && t > (mils() - 15000)) { 183 return true; 184 } 185 } 186 return false; 187 } 188 189 protected String getTableName() { 190 return this.tableName; 191 } 192 193 194}