001package com.avaje.ebean;
002
003import com.avaje.ebean.bean.EntityBean;
004import com.avaje.ebean.util.ClassUtil;
005import org.jetbrains.annotations.Nullable;
006
007import javax.persistence.MappedSuperclass;
008import java.util.List;
009import java.util.Map;
010import java.util.Set;
011import java.util.UUID;
012
013/**
014 * A MappedSuperclass base class that provides convenience methods for inserting, updating and
015 * deleting beans.
016 * 
017 * <p>
018 * By having your entity beans extend this it provides a 'Active Record' style programming model for
019 * Ebean users.
020 * 
021 * <p>
022 * Note that there is a avaje-ebeanorm-mocker project that enables you to use Mockito or similar
023 * tools to still mock out the underlying 'default EbeanServer' for testing purposes.
024 * 
025 * <p>
026 * You may choose not use this Model mapped superclass if you don't like the 'Active Record' style
027 * or if you believe it 'pollutes' your entity beans.
028 *
029 * <p>
030 * You can use Dependency Injection like Guice or Spring to construct and wire a EbeanServer instance
031 * and have that same instance used with this Model and Finder. The way that works is that when the
032 * DI container creates the EbeanServer instance it can be registered with the Ebean singleton. In this
033 * way the EbeanServer instance can be injected as per normal Guice / Spring dependency injection and
034 * that same instance also used to support the Model and Finder active record style.
035 *
036 * <p>
037 * If you choose to use the Model mapped superclass you will probably also chose to additionally add
038 * a {@link Find} as a public static field to complete the active record pattern and provide a
039 * relatively nice clean way to write queries.
040 *
041 * <h3>Typical common @MappedSuperclass</h3>
042 * <pre>{@code
043 *
044 *     // Typically there is a common base model that has some
045 *     // common properties like the ones below
046 *
047 *     @MappedSuperclass
048 *     public class BaseModel extends Model {
049 *
050 *       @Id Long id;
051 *
052 *       @Version Long version;
053 *
054 *       @CreatedTimestamp Timestamp whenCreated;
055 *
056 *       @UpdatedTimestamp Timestamp whenUpdated;
057 *
058 *       ...
059 *
060 * }</pre>
061 *
062 * <h3>Extend the Model</h3>
063 * <pre>{@code
064 *
065 *     // Extend the mappedSuperclass
066 *
067 *     @Entity @Table(name="oto_account")
068 *     public class Customer extends BaseModel {
069 *
070 *       // Add a static Find
071 *       // ... with Long being the type of our @Id property.
072 *       // ... Note the {} at the end as Find is an abstract class.
073 *
074 *       public static final Find<Long,Account> find = new Find<Long,Account>(){};
075 *
076 *       String name;
077 *       ...
078 *     }
079 *
080 * }</pre>
081 *
082 * <h3>Modal: save()</h3>
083 * <pre>{@code
084 *
085 *     // Active record style ... save(), delete() etc
086 *     Customer customer = new Customer();
087 *     customer.setName("AC234");
088 *
089 *     // save() method inherited from Model
090 *     customer.save();
091 *
092 * }</pre>
093 *
094 * <h3>Find byId</h3>
095 * <pre>{@code
096 *
097 *     // find byId
098 *     Customer customer = Customer.find.byId(42);
099 *
100 * }</pre>
101 *
102 * <h3>Find where</h3>
103 * <pre>{@code
104 *
105 *     // find where ...
106 *     List<Customer> customers =
107 *         Customer.find
108 *         .where().gt("startDate", lastMonth)
109 *         .findList();
110 *
111 * }</pre>
112 */
113@MappedSuperclass
114public abstract class Model {
115
116  /**
117   * Return the underlying 'default' EbeanServer.
118   * 
119   * <p>
120   * This provides full access to the API such as explicit transaction demarcation etc.
121   * 
122   * <p>
123   * Example:
124   * <pre>{@code
125   *
126   * Transaction transaction = Customer.db().beginTransaction();
127   * try {
128   * 
129   *   // turn off cascade persist for this transaction
130   *   transaction.setPersistCascade(false);
131   * 
132   *   // extra control over jdbc batching for this transaction
133   *   transaction.setBatchGetGeneratedKeys(false);
134   *   transaction.setBatchMode(true);
135   *   transaction.setBatchSize(20);
136   * 
137   *   Customer customer = new Customer();
138   *   customer.setName(&quot;Roberto&quot;);
139   *   customer.save();
140   * 
141   *   Customer otherCustomer = new Customer();
142   *   otherCustomer.setName("Franko");
143   *   otherCustomer.save();
144   * 
145   *   transaction.commit();
146   * 
147   * } finally {
148   *   transaction.end();
149   * }
150   * 
151   * }</pre>
152   */
153  public static EbeanServer db() {
154    return Ebean.getDefaultServer();
155  }
156
157  /**
158   * Return a named EbeanServer that is typically different to the default server.
159   * 
160   * <p>
161   * If you are using multiple databases then each database has a name and maps to a single
162   * EbeanServer. You can use this method to get an EbeanServer for another database.
163   * 
164   * @param server
165   *          The name of the EbeanServer. If this is null then the default EbeanServer is returned.
166   */
167  public static EbeanServer db(String server) {
168    return Ebean.getServer(server);
169  }
170
171  /**
172   * Marks the entity bean as dirty.
173   * <p>
174   * This is used so that when a bean that is otherwise unmodified is updated the version
175   * property is updated.
176   * <p>
177   * An unmodified bean that is saved or updated is normally skipped and this marks the bean as
178   * dirty so that it is not skipped.
179   * 
180   * <pre>{@code
181   * 
182   * Customer customer = Customer.find.byId(id);
183   * 
184   * // mark the bean as dirty so that a save() or update() will
185   * // increment the version property
186   * customer.markAsDirty();
187   * customer.save();
188   * 
189   * }</pre>
190   *
191   * @see EbeanServer#markAsDirty(Object)
192   */
193  public void markAsDirty() {
194    db().markAsDirty(this);
195  }
196
197  /**
198   * Mark the property as unset or 'not loaded'.
199   * <p>
200   *   This would be used to specify a property that we did not wish to include in a stateless update.
201   * </p>
202   * <pre>{@code
203   *
204   *   // populate an entity bean from JSON or whatever
205   *   User user = ...;
206   *
207   *   // mark the email property as 'unset' so that it is not
208   *   // included in a 'stateless update'
209   *   user.markPropertyUnset("email");
210   *
211   *   user.update();
212   *
213   * }</pre>
214   *
215   * @param propertyName the name of the property on the bean to be marked as 'unset'
216   */
217  public void markPropertyUnset(String propertyName) {
218    ((EntityBean)this)._ebean_getIntercept().setPropertyLoaded(propertyName, false);
219  }
220
221  /**
222   * Insert or update this entity depending on its state.
223   * 
224   * <p>
225   * Ebean will detect if this is a new bean or a previously fetched bean and perform either an
226   * insert or an update based on that.
227   *
228   * @see EbeanServer#save(Object)
229   */
230  public void save() {
231    db().save(this);
232  }
233
234  /**
235   * Update this entity.
236   *
237   * @see EbeanServer#update(Object)
238   */
239  public void update() {
240    db().update(this);
241  }
242
243  /**
244   * Insert this entity.
245   *
246   * @see EbeanServer#insert(Object)
247   */
248  public void insert() {
249    db().insert(this);
250  }
251
252  /**
253   * Delete this bean.
254   * <p>
255   * This will return true if the bean was deleted successfully or JDBC batch is being used.
256   * </p>
257   * <p>
258   * If there is no current transaction one will be created and committed for
259   * you automatically.
260   * </p>
261   * <p>
262   * If the Bean does not have a version property (or loaded version property) and
263   * the bean does not exist then this returns false indicating that nothing was
264   * deleted. Note that, if JDBC batch mode is used then this always returns true.
265   * </p>
266   *
267   * @see EbeanServer#delete(Object)
268   */
269  public boolean delete() {
270    return db().delete(this);
271  }
272
273  /**
274   * Delete a bean permanently without soft delete.
275   * <p>
276   * This is used when the bean contains a <code>@SoftDelete</code> property and we
277   * want to perform a hard/permanent delete.
278   * </p>
279   *
280   * @see EbeanServer#deletePermanent(Object)
281   */
282  public boolean deletePermanent() {
283    return db().deletePermanent(this);
284  }
285
286  /**
287   * Perform an update using this entity against the specified server.
288   */
289  public void update(String server) {
290    db(server).update(this);
291  }
292
293  /**
294   * Perform an insert using this entity against the specified server.
295   */
296  public void insert(String server) {
297    db(server).insert(this);
298  }
299
300  /**
301   * Perform a delete using this entity against the specified server.
302   */
303  public boolean delete(String server) {
304    return db(server).delete(this);
305  }
306
307  /**
308   * Refreshes this entity from the database.
309   *
310   * @see EbeanServer#refresh(Object)
311   */
312  public void refresh() {
313    db().refresh(this);
314  }
315
316  /**
317   * A concrete implementation of Find.
318   * <p>
319   * It should be preferred to use {@link Find} instead of Finder as that can use reflection to determine the class
320   * literal type of the entity bean.
321   * </p>
322   * @param <I> type of the Id property
323   * @param <T> type of the entity bean
324   */
325  public static class Finder<I, T> extends Find<I, T> {
326
327    /**
328     * Create with the type of the entity bean.
329     *
330     * <pre>{@code
331     *
332     * @Entity
333     * public class Customer extends BaseModel {
334     *
335     *   public static final Finder<Long,Customer> find = new Finder<Long,Customer>(Customer.class);
336     *   ...
337     *
338     * }</pre>
339     *
340     * <p/>
341     * The preferred approach is to instead use <code>Find</code> as below. This approach is more DRY in that it does
342     * not require the class literal Customer.class to be passed into the constructor.
343     *
344     * <pre>{@code
345     *
346     * @Entity
347     * public class Customer extends BaseModel {
348     *
349     *   public static final Find<Long,Customer> find = new Find<Long,Customer>(){};
350     *   ...
351     *
352     * }</pre>
353     */
354    public Finder(Class<T> type) {
355      super(null, type);
356    }
357
358    /**
359     * Create with the type of the entity bean and specific server name.
360     */
361    public Finder(String serverName, Class<T> type) {
362      super(serverName, type);
363    }
364
365  }
366
367  /**
368   * Helper object for performing queries.
369   * 
370   * <p>
371   * Typically a Find instance is defined as a public static field on an entity bean class to provide a
372   * nice way to write queries.
373   *
374   * <h3>Example use:</h3>
375   *
376   * <pre>{@code
377   *
378   * @Entity
379   * public class Customer extends BaseModel {
380   *
381   *   public static final Find<Long,Customer> find = new Find<Long,Customer>(){};
382   *
383   *   ...
384   *
385   * }</pre>
386   * <p/>
387   * This enables you to write code like:
388   * <pre>{@code
389   *
390   * Customer customer = Customer.find.byId(42L);
391   *
392   * List<Customer> customers =
393   *     Customer.find
394   *         .select("name, dateOfBirth")
395   *         .findList();
396   *
397   * }</pre>
398   *
399   * <h3>Kotlin</h3>
400   * In Kotlin you would typically create Find as a companion object.
401   * <pre>{@code
402   *
403   *   // kotlin
404   *   companion object : Model.Find<Long, Product>() {}
405   *
406   * }</pre>
407   * @param <I>
408   *          The Id type. This is most often a {@link Long} but is also often a {@link UUID} or
409   *          {@link String}.
410   *
411   * @param <T>
412   *          The entity bean type
413   */
414  public static abstract class Find<I, T> {
415
416    /**
417     * The entity bean type.
418     */
419    private final Class<T> type;
420
421    /**
422     * The name of the EbeanServer, null for the default server.
423     */
424    private final String serverName;
425
426    /**
427     * Creates a finder for entity of type <code>T</code> with ID of type <code>I</code>.
428     * <p/>
429     * Typically you create Find as a public static field on each entity bean as the example below.
430     *
431     * <p/>
432     * Note that Find is an abstract class and hence <code>{}</code> is required. This is done so
433     * that the type (class literal) of the entity bean can be derived from the generics parameter.
434     *
435     * <pre>{@code
436     *
437     * @Entity
438     * public class Customer extends BaseModel {
439     *
440     *   // Note the trailing {} as Find is an abstract class.
441     *   // We do this so that we can derive the type literal Customer.class
442     *   // via reflection
443     *   public static final Find<Long,Customer> find = new Find<Long,Customer>(){};
444     *   ...
445     *
446     * }</pre>
447     * <p/>
448     * This enables you to write code like:
449     * <pre>{@code
450     *
451     * Customer customer = Customer.find.byId(42L);
452     *
453     * List<Customer> customers =
454     *     Customer.find
455     *        .select("name, email, dateOfBirth")
456     *        .findList();
457     *
458     * }</pre>
459     *
460     * <h3>Kotlin</h3>
461     * In Kotlin you would typically create it as a companion object.
462     *
463     * <pre>{@code
464     *
465     *   // kotlin
466     *   companion object : Model.Find<Long, Product>() {}
467     *
468     * }</pre>
469     */
470    @SuppressWarnings("unchecked")
471    public Find() {
472      this.serverName = null;
473      this.type = (Class<T>)ClassUtil.getSecondArgumentType(getClass());
474    }
475
476    /**
477     * Construct passing the class literal type of the entity type.
478     */
479    protected Find(String serverName, Class<T> type) {
480      this.serverName = serverName;
481      this.type = type;
482    }
483
484    /**
485     * Return the underlying 'default' EbeanServer.
486     * 
487     * <p>
488     * This provides full access to the API such as explicit transaction demarcation etc.
489     * 
490     */
491    public EbeanServer db() {
492      return Ebean.getServer(serverName);
493    }
494
495    /**
496     * Return typically a different EbeanServer to the default.
497     * <p>
498     * This is equivilent to {@link Ebean#getServer(String)}
499     * 
500     * @param server
501     *          The name of the EbeanServer. If this is null then the default EbeanServer is
502     *          returned.
503     */
504    public EbeanServer db(String server) {
505      return Ebean.getServer(server);
506    }
507
508    /**
509     * Creates a Finder for the named EbeanServer.
510     *
511     * <p>
512     * Create and return a new Finder for a different server.
513     */
514    public Finder<I, T> on(String server) {
515      return new Finder<I, T>(server, type);
516    }
517
518    /**
519     * Delete a bean by Id.
520     * <p>
521     * Equivalent to {@link EbeanServer#delete(Class, Object)}
522     */
523    public void deleteById(I id) {
524      db().delete(type, id);
525    }
526
527    /**
528     * Retrieves all entities of the given type.
529     * 
530     * <p>
531     * This is the same as (synonym for) {@link #findList()}
532     */
533    public List<T> all() {
534      return findList();
535    }
536
537    /**
538     * Retrieves an entity by ID.
539     * 
540     * <p>
541     * Equivalent to {@link EbeanServer#find(Class, Object)}
542     */
543    @Nullable
544    public T byId(I id) {
545      return db().find(type, id);
546    }
547
548    /**
549     * Creates an entity reference for this ID.
550     * 
551     * <p>
552     * Equivalent to {@link EbeanServer#getReference(Class, Object)}
553     */
554    public T ref(I id) {
555      return db().getReference(type, id);
556    }
557
558    /**
559     * Creates a filter for sorting and filtering lists of entities locally without going back to
560     * the database.
561     * <p>
562     * Equivalent to {@link EbeanServer#filter(Class)}
563     */
564    public Filter<T> filter() {
565      return db().filter(type);
566    }
567
568    /**
569     * Creates a query.
570     * <p>
571     * Equivalent to {@link EbeanServer#find(Class)}
572     */
573    public Query<T> query() {
574      return db().find(type);
575    }
576
577    /**
578     * Creates a query applying the path properties to set the select and fetch clauses.
579     * <p>
580     * Equivalent to {@link Query#apply(FetchPath)}
581     */
582    public Query<T> apply(FetchPath fetchPath) {
583      return db().find(type).apply(fetchPath);
584    }
585
586    /**
587     * Returns the next identity value.
588     * 
589     * @see EbeanServer#nextId(Class)
590     */
591    @SuppressWarnings("unchecked")
592    public I nextId() {
593      return (I) db().nextId(type);
594    }
595
596    /**
597     * Executes a query and returns the results as a list of IDs.
598     * <p>
599     * Equivalent to {@link Query#findIds()}
600     */
601    public List<Object> findIds() {
602      return query().findIds();
603    }
604
605    /**
606     * Execute the query consuming each bean one at a time.
607     * <p>
608     * This is generally used to process large queries where unlike findList
609     * you do not want to hold all the results in memory at once but instead
610     * process them one at a time (requiring far less memory).
611     * </p>
612     * Equivalent to {@link Query#findEach(QueryEachConsumer)}
613     */
614    public void findEach(QueryEachConsumer<T> consumer) {
615      query().findEach(consumer);
616    }
617
618    /**
619     * Execute the query consuming each bean one at a time.
620     * <p>
621     * Equivalent to {@link Query#findEachWhile(QueryEachWhileConsumer)}
622     * <p>
623     * This is similar to #findEach except that you return boolean
624     * true to continue processing beans and return false to stop
625     * processing early.
626     * </p>
627     * <p>
628     * This is generally used to process large queries where unlike findList
629     * you do not want to hold all the results in memory at once but instead
630     * process them one at a time (requiring far less memory).
631     * </p>
632     * Equivalent to {@link Query#findEachWhile(QueryEachWhileConsumer)}
633     */
634    public void findEachWhile(QueryEachWhileConsumer<T> consumer) {
635      query().findEachWhile(consumer);
636    }
637
638    /**
639     * Retrieves all entities of the given type.
640     * <p>
641     * The same as {@link #all()}
642     * <p>
643     * Equivalent to {@link Query#findList()}
644     */
645    public List<T> findList() {
646      return query().findList();
647    }
648
649    /**
650     * Returns all the entities of the given type as a set.
651     * <p>
652     * Equivalent to {@link Query#findSet()}
653     */
654    public Set<T> findSet() {
655      return query().findSet();
656    }
657
658    /**
659     * Retrieves all entities of the given type as a map of objects.
660     * <p>
661     * Equivalent to {@link Query#findMap()}
662     */
663    public Map<?, T> findMap() {
664      return query().findMap();
665    }
666
667    /**
668     * Executes the query and returns the results as a map of the objects specifying the map key
669     * property.
670     * <p>
671     * Equivalent to {@link Query#findMap(String, Class)}
672     */
673    public <K> Map<K, T> findMap(String keyProperty, Class<K> keyType) {
674      return query().findMap(keyProperty, keyType);
675    }
676
677    /**
678     * Executes a find row count query in a background thread.
679     * <p>
680     * Equivalent to {@link Query#findFutureRowCount()}
681     */
682    public FutureRowCount<T> findFutureRowCount() {
683      return query().findFutureRowCount();
684    }
685
686    /**
687     * Returns the total number of entities for this type. *
688     * <p>
689     * Equivalent to {@link Query#findRowCount()}
690     */
691    public int findRowCount() {
692      return query().findRowCount();
693    }
694
695    /**
696     * Returns the <code>ExpressionFactory</code> used by this query.
697     */
698    public ExpressionFactory getExpressionFactory() {
699      return query().getExpressionFactory();
700    }
701
702    /**
703     * Explicitly sets a comma delimited list of the properties to fetch on the 'main' entity bean,
704     * to load a partial object.
705     * <p>
706     * Equivalent to {@link Query#select(String)}
707     */
708    public Query<T> select(String fetchProperties) {
709      return query().select(fetchProperties);
710    }
711
712    /**
713     * Specifies a path to load including all its properties.
714     * <p>
715     * Equivalent to {@link Query#fetch(String)}
716     */
717    public Query<T> fetch(String path) {
718      return query().fetch(path);
719    }
720
721    /**
722     * Additionally specifies a <code>FetchConfig</code> to specify a 'query join' and/or define the
723     * lazy loading query.
724     * <p>
725     * Equivalent to {@link Query#fetch(String, FetchConfig)}
726     */
727    public Query<T> fetch(String path, FetchConfig joinConfig) {
728      return query().fetch(path, joinConfig);
729    }
730
731    /**
732     * Specifies a path to fetch with a specific list properties to include, to load a partial
733     * object.
734     * <p>
735     * Equivalent to {@link Query#fetch(String, String)}
736     */
737    public Query<T> fetch(String path, String fetchProperties) {
738      return query().fetch(path, fetchProperties);
739    }
740
741    /**
742     * Additionally specifies a <code>FetchConfig</code> to use a separate query or lazy loading to
743     * load this path.
744     * <p>
745     * Equivalent to {@link Query#fetch(String, String, FetchConfig)}
746     */
747    public Query<T> fetch(String assocProperty, String fetchProperties, FetchConfig fetchConfig) {
748      return query().fetch(assocProperty, fetchProperties, fetchConfig);
749    }
750
751    /**
752     * Adds expressions to the <code>where</code> clause with the ability to chain on the
753     * <code>ExpressionList</code>.
754     * <p>
755     * Equivalent to {@link Query#where()}
756     */
757    public ExpressionList<T> where() {
758      return query().where();
759    }
760
761    /**
762     * Returns the <code>order by</code> clause so that you can append an ascending or descending
763     * property to the <code>order by</code> clause.
764     * <p>
765     * This is exactly the same as {@link #orderBy}.
766     * <p>
767     * Equivalent to {@link Query#order()}
768     */
769    public OrderBy<T> order() {
770      return query().order();
771    }
772
773    /**
774     * Sets the <code>order by</code> clause, replacing the existing <code>order by</code> clause if
775     * there is one.
776     * <p>
777     * This is exactly the same as {@link #orderBy(String)}.
778     */
779    public Query<T> order(String orderByClause) {
780      return query().order(orderByClause);
781    }
782
783    /**
784     * Returns the <code>order by</code> clause so that you can append an ascending or descending
785     * property to the <code>order by</code> clause.
786     * <p>
787     * This is exactly the same as {@link #order}.
788     * <p>
789     * Equivalent to {@link Query#orderBy()}
790     */
791    public OrderBy<T> orderBy() {
792      return query().orderBy();
793    }
794
795    /**
796     * Set the <code>order by</code> clause replacing the existing <code>order by</code> clause if
797     * there is one.
798     * <p>
799     * This is exactly the same as {@link #order(String)}.
800     */
801    public Query<T> orderBy(String orderByClause) {
802      return query().orderBy(orderByClause);
803    }
804
805    /**
806     * Sets the first row to return for this query.
807     * <p>
808     * Equivalent to {@link Query#setFirstRow(int)}
809     */
810    public Query<T> setFirstRow(int firstRow) {
811      return query().setFirstRow(firstRow);
812    }
813
814    /**
815     * Sets the maximum number of rows to return in the query.
816     * <p>
817     * Equivalent to {@link Query#setMaxRows(int)}
818     */
819    public Query<T> setMaxRows(int maxRows) {
820      return query().setMaxRows(maxRows);
821    }
822
823    /**
824     * Sets the ID value to query.
825     * 
826     * <p>
827     * Use this to perform a find byId query but with additional control over the query such as
828     * using select and fetch to control what parts of the object graph are returned.
829     * <p>
830     * Equivalent to {@link Query#setId(Object)}
831     */
832    public Query<T> setId(Object id) {
833      return query().setId(id);
834    }
835
836    /**
837     * Create and return a new query based on the <code>RawSql</code>.
838     * <p>
839     * Equivalent to {@link Query#setRawSql(RawSql)}
840     */
841    public Query<T> setRawSql(RawSql rawSql) {
842      return query().setRawSql(rawSql);
843    }
844
845    /**
846     * Create a query with explicit 'AutoTune' use.
847     */
848    public Query<T> setAutoTune(boolean autoTune) {
849      return query().setAutoTune(autoTune);
850    }
851
852    /**
853     * Create a query with the select with "for update" specified.
854     * 
855     * <p>
856     * This will typically create row level database locks on the selected rows.
857     */
858    public Query<T> setForUpdate(boolean forUpdate) {
859      return query().setForUpdate(forUpdate);
860    }
861
862    /**
863     * Create a query specifying whether the returned beans will be read-only.
864     */
865    public Query<T> setReadOnly(boolean readOnly) {
866      return query().setReadOnly(readOnly);
867    }
868
869    /**
870     * Create a query specifying if the beans should be loaded into the L2 cache.
871     */
872    public Query<T> setLoadBeanCache(boolean loadBeanCache) {
873      return query().setLoadBeanCache(loadBeanCache);
874    }
875
876    /**
877     * Create a query specifying if the L2 bean cache should be used.
878     */
879    public Query<T> setUseCache(boolean useBeanCache) {
880      return query().setUseCache(useBeanCache);
881    }
882
883    /**
884     * Create a query specifying if the L2 query cache should be used.
885     */
886    public Query<T> setUseQueryCache(boolean useQueryCache) {
887      return query().setUseQueryCache(useQueryCache);
888    }
889
890  }
891}