1 /**
2  *  Copyright Terracotta, Inc.
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License");
5  *  you may not use this file except in compliance with the License.
6  *  You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *  Unless required by applicable law or agreed to in writing, software
11  *  distributed under the License is distributed on an "AS IS" BASIS,
12  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *  See the License for the specific language governing permissions and
14  *  limitations under the License.
15  */

16
17 package net.sf.ehcache.config;
18
19 import net.sf.ehcache.Cache;
20 import net.sf.ehcache.writer.CacheWriterManager;
21 import net.sf.ehcache.writer.writebehind.WriteBehindManager;
22 import net.sf.ehcache.writer.writethrough.WriteThroughManager;
23
24 import java.util.Collection;
25
26 /**
27  * Class to hold the CacheWriterManager configuration
28  *
29  * @author Geert Bevin
30  * @version $Id: CacheWriterConfiguration.java 6485 2012-10-27 04:33:18Z cschanck $
31  */

32 public class CacheWriterConfiguration implements Cloneable {
33     /**
34      * Default writeMode
35      */

36     public static final WriteMode DEFAULT_WRITE_MODE = WriteMode.WRITE_THROUGH;
37     /**
38      * Default notifyListenersOnException behavior
39      */

40     public static final boolean DEFAULT_NOTIFY_LISTENERS_ON_EXCEPTION = false;
41     /**
42      * Default minimum write delay
43      */

44     public static final int DEFAULT_MIN_WRITE_DELAY = 1;
45     /**
46      * Default maximum write delay
47      */

48     public static final int DEFAULT_MAX_WRITE_DELAY = 1;
49     /**
50      * Default rate limit per second
51      */

52     public static final int DEFAULT_RATE_LIMIT_PER_SECOND = 0;
53     /**
54      * Default write coalescing behavior
55      */

56     public static final boolean DEFAULT_WRITE_COALESCING = false;
57     /**
58      * Default writeBatching behavior
59      */

60     public static final boolean DEFAULT_WRITE_BATCHING = false;
61     /**
62      * Default write batch size
63      */

64     public static final int DEFAULT_WRITE_BATCH_SIZE = 1;
65     /**
66      * Default retry attempts
67      */

68     public static final int DEFAULT_RETRY_ATTEMPTS = 0;
69     /**
70      * Default retry attempt delay
71      */

72     public static final int DEFAULT_RETRY_ATTEMPT_DELAY_SECONDS = 1;
73
74     /**
75      * Default concurrency level for write behind
76      */

77     public static final int DEFAULT_WRITE_BEHIND_CONCURRENCY = 1;
78
79     /**
80      * Default max queue size for write behind
81      */

82     public static final int DEFAULT_WRITE_BEHIND_MAX_QUEUE_SIZE = 0;
83
84     /**
85      * Represents how elements are written to the {@link net.sf.ehcache.writer.CacheWriter}
86      */

87     public static enum WriteMode {
88         /**
89          * Write mode enum constant that can be used to configure a cache writer to use write through
90          */

91         WRITE_THROUGH {
92             /**
93              * {@inheritDoc}
94              */

95             @Override
96             public CacheWriterManager createWriterManager(Cache cache) {
97                 return new WriteThroughManager();
98             }
99         },
100
101         /**
102          * Write mode enum constant that can be used to configure a cache writer to use write behind
103          */

104         WRITE_BEHIND {
105             /**
106              * {@inheritDoc}
107              */

108             @Override
109             public CacheWriterManager createWriterManager(Cache cache) {
110                 return new WriteBehindManager(cache);
111             }
112         };
113
114         /**
115          * Create a new {@code WriterManager} for a particular cache instance
116          *
117          * @param cache the cache instance for which the {@code WriterManager} should be created
118          * @return the newly created {@code WriterManager}
119          */

120         public abstract CacheWriterManager createWriterManager(Cache cache);
121     }
122
123     private WriteMode writeMode = DEFAULT_WRITE_MODE;
124     private boolean notifyListenersOnException = DEFAULT_NOTIFY_LISTENERS_ON_EXCEPTION;
125     private int minWriteDelay = DEFAULT_MIN_WRITE_DELAY;
126     private int maxWriteDelay = DEFAULT_MAX_WRITE_DELAY;
127     private int rateLimitPerSecond = DEFAULT_RATE_LIMIT_PER_SECOND;
128     private boolean writeCoalescing = DEFAULT_WRITE_COALESCING;
129     private boolean writeBatching = DEFAULT_WRITE_BATCHING;
130     private int writeBatchSize = DEFAULT_WRITE_BATCH_SIZE;
131     private int retryAttempts = DEFAULT_RETRY_ATTEMPTS;
132     private int retryAttemptDelaySeconds = DEFAULT_RETRY_ATTEMPT_DELAY_SECONDS;
133     private int writeBehindConcurrency = DEFAULT_WRITE_BEHIND_CONCURRENCY;
134     private int writeBehindMaxQueueSize = DEFAULT_WRITE_BEHIND_MAX_QUEUE_SIZE;
135     private CacheWriterFactoryConfiguration cacheWriterFactoryConfiguration;
136
137     /**
138      * Clones this object, following the usual contract.
139      *
140      * @return a copy, which independent other than configurations than cannot change.
141      */

142     @Override
143     public CacheWriterConfiguration clone() {
144         CacheWriterConfiguration config;
145         try {
146             config = (CacheWriterConfiguration) super.clone();
147         } catch (CloneNotSupportedException e) {
148             throw new RuntimeException(e);
149         }
150
151         if (cacheWriterFactoryConfiguration != null) {
152             config.cacheWriterFactoryConfiguration = cacheWriterFactoryConfiguration.clone();
153         }
154
155         return config;
156     }
157
158     /**
159      * Converts the {@code valueMode} string argument to uppercase and looks up enum constant in WriteMode.
160      */

161     public void setWriteMode(String writeMode) {
162         if (writeMode == null) {
163             throw new IllegalArgumentException("WriteMode can't be null");
164         }
165         this.writeMode = WriteMode.valueOf(WriteMode.class, writeMode.replace('-', '_').toUpperCase());
166     }
167
168     /**
169      * @return this configuration instance
170      * @see #setWriteMode(String)
171      */

172     public CacheWriterConfiguration writeMode(String writeMode) {
173         setWriteMode(writeMode);
174         return this;
175     }
176
177     /**
178      * @return this configuration instance
179      * @see #setWriteMode(String)
180      */

181     public CacheWriterConfiguration writeMode(WriteMode writeMode) {
182         if (null == writeMode) {
183             throw new IllegalArgumentException("WriteMode can't be null");
184         }
185         this.writeMode = writeMode;
186         return this;
187     }
188
189     /**
190      * Get the write mode in terms of the mode enum
191      */

192     public WriteMode getWriteMode() {
193         return this.writeMode;
194     }
195
196     /**
197      * Sets whether to notify listeners when an exception occurs on a writer operation.
198      * <p/>
199      * This is only applicable to write through mode.
200      * <p/>
201      * Defaults to {@value #DEFAULT_NOTIFY_LISTENERS_ON_EXCEPTION}.
202      *
203      * @param notifyListenersOnException {@code trueif listeners should be notified when an exception occurs on a writer operation; {@code false} otherwise
204      */

205     public void setNotifyListenersOnException(boolean notifyListenersOnException) {
206         this.notifyListenersOnException = notifyListenersOnException;
207     }
208
209     /**
210      * @return this configuration instance
211      * @see #setNotifyListenersOnException(boolean)
212      */

213     public CacheWriterConfiguration notifyListenersOnException(boolean notifyListenersOnException) {
214         setNotifyListenersOnException(notifyListenersOnException);
215         return this;
216     }
217
218     /**
219      * Check whether listeners should be notified when an exception occurs on a writer operation
220      */

221     public boolean getNotifyListenersOnException() {
222         return this.notifyListenersOnException;
223     }
224
225     /**
226      * Set the minimum number of seconds to wait before writing behind. If set to a value greater than 0, it permits
227      * operations to build up in the queue. This is different from the maximum write delay in that by waiting a minimum
228      * amount of time, work is always being built up. If the minimum write delay is set to zero and the {@code CacheWriter}
229      * performs its work very quickly, the overhead of processing the write behind queue items becomes very noticeable
230      * in a cluster since all the operations might be done for individual items instead of for a collection of them.
231      * <p/>
232      * This is only applicable to write behind mode.
233      * <p/>
234      * Defaults to {@value #DEFAULT_MIN_WRITE_DELAY}).
235      *
236      * @param minWriteDelay the minimum number of seconds to wait before writing behind
237      */

238     public void setMinWriteDelay(int minWriteDelay) {
239         if (minWriteDelay < 0) {
240             this.minWriteDelay = 0;
241         } else {
242             this.minWriteDelay = minWriteDelay;
243         }
244     }
245
246     /**
247      * @return this configuration instance
248      * @see #setMinWriteDelay(int)
249      */

250     public CacheWriterConfiguration minWriteDelay(int minWriteDelay) {
251         setMinWriteDelay(minWriteDelay);
252         return this;
253     }
254
255     /**
256      * Get the minimum number of seconds to wait before writing behind
257      */

258     public int getMinWriteDelay() {
259         return this.minWriteDelay;
260     }
261
262     /**
263      * Set the maximum number of seconds to wait before writing behind. If set to a value greater than 0, it permits
264      * operations to build up in the queue to enable effective coalescing and batching optimisations.
265      * <p/>
266      * This is only applicable to write behind mode.
267      * <p/>
268      * Defaults to {@value #DEFAULT_MAX_WRITE_DELAY}).
269      *
270      * @param maxWriteDelay the maximum number of seconds to wait before writing behind
271      */

272     public void setMaxWriteDelay(int maxWriteDelay) {
273         if (maxWriteDelay < 0) {
274             this.maxWriteDelay = 0;
275         } else {
276             this.maxWriteDelay = maxWriteDelay;
277         }
278     }
279
280     /**
281      * @return this configuration instance
282      * @see #setMaxWriteDelay(int)
283      */

284     public CacheWriterConfiguration maxWriteDelay(int maxWriteDelay) {
285         setMaxWriteDelay(maxWriteDelay);
286         return this;
287     }
288
289     /**
290      * Get the maximum number of seconds to wait before writing behind
291      */

292     public int getMaxWriteDelay() {
293         return this.maxWriteDelay;
294     }
295
296     /**
297      * Sets the maximum number of write operations to allow per second when {@link #writeBatching} is enabled.
298      * <p/>
299      * This is only applicable to write behind mode.
300      * <p/>
301      * Defaults to {@value #DEFAULT_RATE_LIMIT_PER_SECOND}.
302      *
303      * @param rateLimitPerSecond the number of write operations to allow; use a number {@code &lt;=0} to disable rate limiting.
304      */

305     public void setRateLimitPerSecond(int rateLimitPerSecond) {
306         if (rateLimitPerSecond < 0) {
307             this.rateLimitPerSecond = 0;
308         } else {
309             this.rateLimitPerSecond = rateLimitPerSecond;
310         }
311     }
312
313     /**
314      * @return this configuration instance
315      * @see #setRateLimitPerSecond(int rateLimitPerSecond)
316      */

317     public CacheWriterConfiguration rateLimitPerSecond(int rateLimitPerSecond) {
318         setRateLimitPerSecond(rateLimitPerSecond);
319         return this;
320     }
321
322     /**
323      * Get the maximum number of write operations to allow per second.
324      */

325     public int getRateLimitPerSecond() {
326         return rateLimitPerSecond;
327     }
328
329     /**
330      * Sets whether to use write coalescing. If set to {@code true} and multiple operations on the same key are present
331      * in the write-behind queue, only the latest write is done, as the others are redundant. This can dramatically
332      * reduce load on the underlying resource.
333      * <p/>
334      * This is only applicable to write behind mode.
335      * <p/>
336      * Defaults to {@value #DEFAULT_WRITE_COALESCING}.
337      *
338      * @param writeCoalescing {@code true} to enable write coalescing; or {@code false} to disable it
339      */

340     public void setWriteCoalescing(boolean writeCoalescing) {
341         this.writeCoalescing = writeCoalescing;
342     }
343
344     /**
345      * @return this configuration instance
346      * @see #setWriteCoalescing(boolean)
347      */

348     public CacheWriterConfiguration writeCoalescing(boolean writeCoalescing) {
349         setWriteCoalescing(writeCoalescing);
350         return this;
351     }
352
353     /**
354      * @return this configuration instance
355      * @see #setWriteCoalescing(boolean)
356      */

357     public boolean getWriteCoalescing() {
358         return writeCoalescing;
359     }
360
361     /**
362      * Sets whether to batch write operations. If set to {@code true}, {@link net.sf.ehcache.writer.CacheWriter#writeAll} and {@code CacheWriter#deleteAll}
363      * will be called rather than {@link net.sf.ehcache.writer.CacheWriter#write} and {@link net.sf.ehcache.writer.CacheWriter#delete} being called for each key. Resources such
364      * as databases can perform more efficiently if updates are batched, thus reducing load.
365      * <p/>
366      * This is only applicable to write behind mode.
367      * <p/>
368      * Defaults to {@value #DEFAULT_WRITE_BATCHING}.
369      *
370      * @param writeBatching {@code trueif write operations should be batched; {@code false} otherwise
371      */

372     public void setWriteBatching(boolean writeBatching) {
373         this.writeBatching = writeBatching;
374     }
375
376     /**
377      * @return this configuration instance
378      * @see #setWriteBatching(boolean)
379      */

380     public CacheWriterConfiguration writeBatching(boolean writeBatching) {
381         setWriteBatching(writeBatching);
382         return this;
383     }
384
385     /**
386      * Check whether write operations should be batched
387      */

388     public boolean getWriteBatching() {
389         return this.writeBatching;
390     }
391
392     /**
393      * Sets the number of operations to include in each batch when {@link #writeBatching} is enabled. If there are less
394      * entries in the write-behind queue than the batch size, the queue length size is used.
395      * <p/>
396      * This is only applicable to write behind mode.
397      * <p/>
398      * Defaults to {@value #DEFAULT_WRITE_BATCH_SIZE}.
399      *
400      * @param writeBatchSize the number of operations to include in each batch; numbers smaller than {@code 1} will cause
401      *                       the default batch size to be used
402      */

403     public void setWriteBatchSize(int writeBatchSize) {
404         if (writeBatchSize < 1) {
405             this.writeBatchSize = DEFAULT_WRITE_BATCH_SIZE;
406         } else {
407             this.writeBatchSize = writeBatchSize;
408         }
409     }
410
411     /**
412      * @return this configuration instance
413      * @see #setWriteBatchSize(int)
414      */

415     public CacheWriterConfiguration writeBatchSize(int writeBatchSize) {
416         setWriteBatchSize(writeBatchSize);
417         return this;
418     }
419
420     /**
421      * Retrieves the size of the batch operation.
422      */

423     public int getWriteBatchSize() {
424         return writeBatchSize;
425     }
426
427     /**
428      * Sets the number of times the operation is retried in the {@code CacheWriter}, this happens after the
429      * original operation.
430      * <p/>
431      * This is only applicable to write behind mode.
432      * <p/>
433      * Defaults to {@value #DEFAULT_RETRY_ATTEMPTS}.
434      *
435      * @param retryAttempts the number of retries for a particular element
436      */

437     public void setRetryAttempts(int retryAttempts) {
438         if (retryAttempts < 0) {
439             this.retryAttempts = 0;
440         } else {
441             this.retryAttempts = retryAttempts;
442         }
443     }
444
445     /**
446      * @return this configuration instance
447      * @see #setRetryAttempts(int)
448      */

449     public CacheWriterConfiguration retryAttempts(int retryAttempts) {
450         setRetryAttempts(retryAttempts);
451         return this;
452     }
453
454     /**
455      * Retrieves the number of times the write of element is retried.
456      */

457     public int getRetryAttempts() {
458         return retryAttempts;
459     }
460
461     /**
462      * Sets the number of seconds to wait before retrying an failed operation.
463      * <p/>
464      * This is only applicable to write behind mode.
465      * <p/>
466      * Defaults to {@value #DEFAULT_RETRY_ATTEMPT_DELAY_SECONDS}.
467      *
468      * @param retryAttemptDelaySeconds the number of seconds to wait before retrying an operation
469      */

470     public void setRetryAttemptDelaySeconds(int retryAttemptDelaySeconds) {
471         if (retryAttemptDelaySeconds < 0) {
472             this.retryAttemptDelaySeconds = 0;
473         } else {
474             this.retryAttemptDelaySeconds = retryAttemptDelaySeconds;
475         }
476     }
477
478     /**
479      * @return this configuration instance
480      * @see #setRetryAttemptDelaySeconds(int)
481      */

482     public CacheWriterConfiguration retryAttemptDelaySeconds(int retryAttemptDelaySeconds) {
483         setRetryAttemptDelaySeconds(retryAttemptDelaySeconds);
484         return this;
485     }
486
487     /**
488      * Retrieves the number of seconds to wait before retrying an failed operation.
489      */

490     public int getRetryAttemptDelaySeconds() {
491         return retryAttemptDelaySeconds;
492     }
493
494     /**
495      * Configuration for the CacheWriterFactoryConfiguration.
496      */

497     public static final class CacheWriterFactoryConfiguration extends FactoryConfiguration<CacheWriterFactoryConfiguration> {
498         
499         /**
500          * Overrided hashCode()
501          */

502         @Override
503         public int hashCode() {
504             return super.hashCode();
505         }
506
507         /**
508          * Overrided equals
509          */

510         @Override
511         public boolean equals(Object obj) {
512             return super.equals(obj);
513         }
514     }
515
516     /**
517      * Allows BeanHandler to add the CacheWriterFactory to the configuration.
518      */

519     public final void addCacheWriterFactory(CacheWriterFactoryConfiguration cacheWriterFactoryConfiguration) {
520         this.cacheWriterFactoryConfiguration = cacheWriterFactoryConfiguration;
521     }
522
523     /**
524      * @return this configuration instance
525      * @see #addCacheWriterFactory(CacheWriterFactoryConfiguration)
526      */

527     public CacheWriterConfiguration cacheWriterFactory(CacheWriterFactoryConfiguration cacheWriterFactory) {
528         addCacheWriterFactory(cacheWriterFactory);
529         return this;
530     }
531
532     /**
533      * Accessor
534      *
535      * @return the configuration
536      */

537     public CacheWriterFactoryConfiguration getCacheWriterFactoryConfiguration() {
538         return cacheWriterFactoryConfiguration;
539     }
540
541     /**
542      * Configures the amount of thread/bucket pairs WriteBehind should use
543      * @param concurrency Amount of thread/bucket pairs, has to be at least 1
544      */

545     public void setWriteBehindConcurrency(int concurrency) {
546         if (concurrency < 1) {
547             this.writeBehindConcurrency = 1;
548         } else {
549             this.writeBehindConcurrency = concurrency;
550         }
551     }
552
553     /**
554      *
555      * @param concurrency Amount of thread/bucket pairs, has to be at least 1
556      * @return this configuration instance
557      * @see #setWriteBehindConcurrency(int)
558      */

559     public CacheWriterConfiguration writeBehindConcurrency(int concurrency) {
560         this.setWriteBehindConcurrency(concurrency);
561         return this;
562     }
563
564     /**
565      * Accessor
566      * @return the amount of bucket/thread pairs configured for this cache's write behind
567      */

568     public int getWriteBehindConcurrency() {
569         return writeBehindConcurrency;
570     }
571
572     /**
573      * Configures the maximum amount of operations to be on the waiting queue, before it blocks
574      * @param writeBehindMaxQueueSize maximum amount of operations allowed on the waiting queue
575      */

576     public void setWriteBehindMaxQueueSize(final int writeBehindMaxQueueSize) {
577         if (writeBehindMaxQueueSize < 0) {
578             this.writeBehindMaxQueueSize = DEFAULT_WRITE_BEHIND_MAX_QUEUE_SIZE;
579         } else {
580             this.writeBehindMaxQueueSize = writeBehindMaxQueueSize;
581         }
582     }
583
584     /**
585      * Accessor
586      * @return the maximum amount of operations allowed on the write behind queue
587      */

588     public int getWriteBehindMaxQueueSize() {
589         return writeBehindMaxQueueSize;
590     }
591
592     /**
593      * @param writeBehindMaxQueueSize maximum amount of operations allowed on the waiting queue
594      * @return this configuration instance
595      * @see #setWriteBehindMaxQueueSize(int)
596      */

597     public CacheWriterConfiguration writeBehindMaxQueueSize(int writeBehindMaxQueueSize) {
598         this.setWriteBehindMaxQueueSize(writeBehindMaxQueueSize);
599         return this;
600     }
601
602     /**
603      * Overrided hashCode()
604      */

605     @Override
606     public int hashCode() {
607         final int prime = 31;
608         final int primeTwo = 1231;
609         final int primeThree = 1237;
610         int result = 1;
611         result = prime * result + ((cacheWriterFactoryConfiguration == null) ? 0 : cacheWriterFactoryConfiguration.hashCode());
612         result = prime * result + maxWriteDelay;
613         result = prime * result + minWriteDelay;
614         result = prime * result + (notifyListenersOnException ? primeTwo : primeThree);
615         result = prime * result + rateLimitPerSecond;
616         result = prime * result + retryAttemptDelaySeconds;
617         result = prime * result + retryAttempts;
618         result = prime * result + writeBatchSize;
619         result = prime * result + (writeBatching ? primeTwo : primeThree);
620         result = prime * result + (writeCoalescing ? primeTwo : primeThree);
621         result = prime * result + ((writeMode == null) ? 0 : writeMode.hashCode());
622         result = prime * result + writeBehindConcurrency;
623         return result;
624     }
625
626     /**
627      * Overrided equals()
628      */

629     @Override
630     public boolean equals(Object obj) {
631         if (this == obj) {
632             return true;
633         }
634         if (obj == null) {
635             return false;
636         }
637         if (getClass() != obj.getClass()) {
638             return false;
639         }
640         CacheWriterConfiguration other = (CacheWriterConfiguration) obj;
641         if (cacheWriterFactoryConfiguration == null) {
642             if (other.cacheWriterFactoryConfiguration != null) {
643                 return false;
644             }
645         } else if (!cacheWriterFactoryConfiguration.equals(other.cacheWriterFactoryConfiguration)) {
646             return false;
647         }
648         if (maxWriteDelay != other.maxWriteDelay) {
649             return false;
650         }
651         if (minWriteDelay != other.minWriteDelay) {
652             return false;
653         }
654         if (notifyListenersOnException != other.notifyListenersOnException) {
655             return false;
656         }
657         if (rateLimitPerSecond != other.rateLimitPerSecond) {
658             return false;
659         }
660         if (retryAttemptDelaySeconds != other.retryAttemptDelaySeconds) {
661             return false;
662         }
663         if (retryAttempts != other.retryAttempts) {
664             return false;
665         }
666         if (writeBatchSize != other.writeBatchSize) {
667             return false;
668         }
669         if (writeBatching != other.writeBatching) {
670             return false;
671         }
672         if (writeCoalescing != other.writeCoalescing) {
673             return false;
674         }
675         if (writeBehindConcurrency != other.writeBehindConcurrency) {
676             return false;
677         }
678         if (writeMode == null) {
679             if (other.writeMode != null) {
680                 return false;
681             }
682         } else if (!writeMode.equals(other.writeMode)) {
683             return false;
684         }
685         return true;
686     }
687
688
689     /**
690      * Check for errors/inconsistencies in this configuration. Add any erros  found as
691      * {@link ConfigError} in the errors collection.
692      * @param errors collection to add errors to.
693      */

694     public void validate(Collection<ConfigError> errors) {
695         if (writeMode.equals(WriteMode.WRITE_BEHIND)) {
696             if (!getWriteBatching() && getWriteBatchSize() != 1) {
697                 errors.add(new ConfigError("Write Batch Size !=1 with Write Batching turned off."));
698             }
699         }
700     }
701 }
702