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;
019
020import io.stallion.dataAccess.filtering.FilterChain;
021import io.stallion.dataAccess.filtering.FilterOperator;
022import io.stallion.dataAccess.filtering.Or;
023import io.stallion.exceptions.NotFoundException;
024import io.stallion.exceptions.UsageException;
025import io.stallion.reflection.PropertyUtils;
026import io.stallion.services.AuditTrailController;
027
028import java.util.List;
029import java.util.Map;
030import java.util.Set;
031
032import static io.stallion.utils.Literals.*;
033
034
035public interface ModelController<T extends Model> {
036
037    /**
038     * Initialize the controller, loading all the key fields, setting defaults,
039     * initializing key variables, etc. This must be callsed before the ModelController
040     * can be used.
041     *
042     * @param registration
043     * @param persister
044     * @param stash
045     */
046    public void init(DataAccessRegistration registration, Persister<T> persister, Stash<T> stash);
047
048
049    // Associated information and classes
050
051    /**
052     * A name by which this controller gets accessed when it is accessed via a template,
053     * or via DalRegistry.instance().get("bucketName"). Usually this is the table name or the
054     * the file-system folder name (for file backed sites).
055     *
056     * @return
057     */
058    public String getBucket();
059
060    /**
061     * The instance of the Perister class
062     * @return
063     */
064    public Persister<T> getPersister();
065
066    /**
067     * The instance of the Stash class.
068     * @return
069     */
070    public Stash<T> getStash();
071
072    /**
073     * The Model class this controller manages.
074     * @return
075     */
076    public Class<T> getModelClass();
077
078    // Config and helpers
079
080    /**
081     * True if this controller can create or update objects, false if this
082     * controller is read-only.
083     *
084     * @return
085     */
086    public Boolean isWritable();
087
088    /**
089     * Gets a controller implementation that wraps this controller, while stubbing out
090     * all the write methods. This is used when passing the controller to a sandboxed plugin
091     * that may only have read-only access.
092     *
093     * @return
094     */
095    public ReadOnlyWrapper<T> getReadonlyWrapper();
096
097    // Crud
098
099    /***
100     * Gets a cloned version of an object, so that you can work with the object with out affecting
101     * the live, in memory version
102     * @param obj
103     * @return
104     */
105    T detach(T obj);
106
107
108
109    /**
110     * Saves "obj" to the persistence layer, creating it if it does not exist.
111     * If obj is detached, it synces the fields to the in-memory object, and saves
112     * the real copy
113     * @param obj
114     */
115    public void save(T obj);
116
117
118    /**
119     * Update
120     */
121    public default void update(T obj, String field, Object value) {
122        updateValues(obj, map(val(field, value)));
123    }
124
125    /**
126     * Update
127     */
128    public default void updateValues(T obj, Map<String, Object> values) {
129        if (!getPersister().isDbBacked()) {
130            for(Map.Entry<String, Object> entry: values.entrySet()) {
131                PropertyUtils.setProperty(obj, entry.getKey(), entry.getValue());
132            }
133            save(obj);
134        } else {
135            getPersister().update(obj, values);
136            T original = originalForId(obj.getId());
137            for(Map.Entry<String, Object> entry: values.entrySet()) {
138                PropertyUtils.setProperty(original, entry.getKey(), entry.getValue());
139            }
140
141        }
142    }
143
144
145    /**
146     * Calls obj.setDeleted(true) then saves the object.
147     * @param obj
148     */
149    public void softDelete(T obj);
150
151    /**
152     * Actually removes the item from the underlying data store
153     *
154     * @param obj
155     */
156    public void hardDelete(T obj);
157
158
159    // Hooks
160
161    void onPreRead();
162
163    /**
164     * Override this to perform an action every time before the object is saved.
165     *
166     * @param obj
167     */
168    public void onPreSavePrepare(T obj);
169
170    /**
171     * Override this to validate the object before it is saved.
172     *
173     * @param obj
174     */
175    public void onPreSaveValidate(T obj);
176
177    /**
178     * Override this to prepare the object with any default values before it is
179     * saved to the datastore for the first time.
180     *
181     * @param obj
182     */
183    public void onPreCreatePrepare(T obj);
184
185    /**
186     * Override this to prepare the validate the object before it is saved to the datastore
187     * for the very first time.
188     *
189     * @param obj
190     */
191    public void onPreCreateValidate(T obj);
192
193    /**
194     * Override this to perform some action after the object is saved
195     * @param obj
196     */
197    public void onPostSave(T obj);
198
199    /**
200     * Override this to save to the audit trail log after a save
201     * @param obj
202     */
203    public default void onPostSaveAuditTrailLog(T obj) {
204        AuditTrailEnabled ae = getClass().getAnnotation(AuditTrailEnabled.class);
205        if (ae != null && ae.value()) {
206            AuditTrailController.instance().logUpdate(obj);
207        }
208    }
209
210
211
212    /**
213     * Override this to perform some action after the object is created.
214     * @param obj
215     */
216    public void onPostCreate(T obj);
217
218    /**
219     * Override this to perform some action after an item is loaded from the datastore
220     * @param obj
221     */
222    public void onPostLoadItem(T obj);
223
224
225    // Fetching
226
227    /**
228     * Return a list of all objects.
229     *
230     * @return
231     */
232    public List<T> all();
233
234    /**
235     * Create a new FilterChain instance for this controller.
236     * @return
237     */
238    public FilterChain<T> filterChain();
239
240    /**
241     * Create a new FilterChain and set an initial filter whereby
242     * the field @name is equal to @value
243     *
244     * @param name
245     * @param value
246     * @return
247     */
248    public FilterChain<T> filter(String name, Object value);
249
250    /**
251     * Searches for @value in all @fields, using a case-insensitive
252     * string contains search.
253     *
254     * @param value
255     * @param value
256     * @return
257     */
258    public default FilterChain<T> search(String value, String...fields) {
259        return filterChain().search(value, fields);
260    }
261
262    /**
263     *
264     * Create a new FilterChain and initialize with an initial filter.
265     *
266     * @param name
267     * @param value
268     * @param op
269     * @return
270     */
271    public FilterChain<T> filter(String name, Object value, String op);
272
273    /**
274     *
275     * Create a new FilterChain and initialize with an initial filter.
276     *
277     * @param name
278     * @param value
279     * @param op
280     * @return
281     */
282    public FilterChain<T> filterBy(String name, Object value, FilterOperator op);
283
284    /**
285     * Short-cut for applying filter(name, value) for every key-value pair in the dictionary.
286     *
287     * Use from javascript as so:
288     *
289     * controller.find({'author': 'Mark Twain', 'type': 'short-story'});
290     *
291     * @param where - a map of key value pairs to find matching objets of
292     * @return
293     */
294    public default FilterChain<T> find(Map<String, Object> where) {
295        FilterChain<T> chain = filterChain();
296        for(Map.Entry<String, Object> entry: where.entrySet()) {
297            chain = chain.filter(entry.getKey(), entry.getValue());
298        }
299        return chain;
300    }
301
302    /**
303     * Short-cut for filterChain().andAnyOf(Or("someField", "someValue"), Or("someField", "someValue"));
304     * Finds all items that match any of the criteria.
305     */
306    public default FilterChain<T> anyOf(Or...ors) {
307        return filterChain().andAnyOf(ors);
308    }
309
310    /**
311     * Short-cut for filterChain().andAnyOf(["someField", "value"], ["otherField", "anotherValue"]);
312     * Finds all items that match any of the criteria
313     */
314    public default FilterChain<T> anyOf(List<String>...filters) {
315        return filterChain().andAnyOf(filters);
316    }
317
318
319    /**
320     * Instantiate a filter chain and start by filtering on an index/keyed field.
321     *
322     * @param keyName
323     * @param value
324     * @return
325     */
326    public FilterChain<T> filterByKey(String keyName, Object value);
327
328
329    /**
330     * Get the object by id. Will return objects that have been soft-deleted.
331     *
332     * @param id
333     * @return
334     */
335    public T forIdWithDeleted(Long id);
336
337    /**
338     * Load an object by id, without detaching it. Changes to the object
339     * will affect the object stashed in memory. Do not use this unless
340     * you know what you are doing.
341     *
342     * @param id
343     * @return
344     */
345    public T originalForId(T id);
346
347    /**
348     * Loads an object by primary id. Detaches/clones the returned object
349     * so that changes will not affect the original object. Missing or
350     * soft deleted objects will return as null.
351     *
352     * @param id
353     * @return
354     */
355    T forId(Long id);
356
357    /**
358     * Calls forId() to load an object by id, raises a NotFoundException if it does not exist
359     * @param id
360     * @return
361     */
362    default public T forIdOrNotFound(Long id) {
363        T o = forId(id);
364        if (o == null) {
365            throw new NotFoundException("The " + getBucket() + " item was not found.");
366        }
367        return o;
368    }
369
370    /**
371     * Load an object by id, without detaching it. Changes to the object
372     * will affect the object stashed in memory. Do not use this unless
373     * you know what you are doing.
374     *
375     * @param id
376     * @return
377     */
378    T originalForId(Long id);
379
380    /**
381     * Look up an object by a unique key. Only works if the @UniqueKey annotation
382     * has been added to the getter of the property.
383     *
384     * @param keyName
385     * @param value
386     * @return
387     */
388    public T forUniqueKey(String keyName, Object value);
389
390    /**
391     * Finds the item by key and value, raises a NotFoundException if it does not exist
392     * @param keyName
393     * @param value
394     * @return
395     */
396    default public T forUniqueKeyOrNotFound(String keyName, Object value) {
397        T o = forUniqueKey(keyName, value);
398        if (o == null) {
399            throw new NotFoundException("The " + getBucket() + " item was not found.");
400        }
401        return o;
402    }
403
404    /**
405     * Get all items for a indexed/keyed field. This only works if the @Key annotation
406     * has been added to the getter of the property
407     *
408     * @param keyName
409     * @param value
410     * @return
411     */
412    List<T> listForKey(String keyName, Object value);
413
414    /**
415     * Counts the items for a indexed/keyed field. This only works if the @Key annotation
416     * has been added to the getter of the property
417     * @param keyName
418     * @param value
419     * @return
420     */
421    int countForKey(String keyName, Object value);
422
423    // Keys
424
425    /**
426     * Get all model field names that were marked as indexed/keyed using the @Key
427     * annotation on the getter
428     * @return
429     */
430    public Set<String> getKeyFields();
431
432    /**
433     * Get all model field names that were marked as a unique key using the @UniqueKey
434     * annotation on the getter.
435     * @return
436     */
437    public Set<String> getUniqueFields();
438
439    /** If the datastore has been synced to memory, reset() will resync everything.
440     *
441     */
442    public void reset();
443
444
445}