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}