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}