1 package org.apache.commons.jcs3.auxiliary.disk.jdbc;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.IOException;
23 import java.sql.Connection;
24 import java.sql.DatabaseMetaData;
25 import java.sql.PreparedStatement;
26 import java.sql.ResultSet;
27 import java.sql.SQLException;
28 import java.sql.Timestamp;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Set;
33 import java.util.concurrent.atomic.AtomicInteger;
34
35 import javax.sql.DataSource;
36
37 import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
38 import org.apache.commons.jcs3.auxiliary.disk.AbstractDiskCache;
39 import org.apache.commons.jcs3.auxiliary.disk.jdbc.dsfactory.DataSourceFactory;
40 import org.apache.commons.jcs3.engine.behavior.ICache;
41 import org.apache.commons.jcs3.engine.behavior.ICacheElement;
42 import org.apache.commons.jcs3.engine.logging.behavior.ICacheEvent;
43 import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
44 import org.apache.commons.jcs3.engine.stats.StatElement;
45 import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
46 import org.apache.commons.jcs3.engine.stats.behavior.IStats;
47 import org.apache.commons.jcs3.log.Log;
48 import org.apache.commons.jcs3.log.LogManager;
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 public class JDBCDiskCache<K, V>
80 extends AbstractDiskCache<K, V>
81 {
82
83 private static final Log log = LogManager.getLog( JDBCDiskCache.class );
84
85
86 private JDBCDiskCacheAttributes jdbcDiskCacheAttributes;
87
88
89 private final AtomicInteger updateCount = new AtomicInteger(0);
90
91
92 private final AtomicInteger getCount = new AtomicInteger(0);
93
94
95 private final AtomicInteger getMatchingCount = new AtomicInteger(0);
96
97
98 private final DataSourceFactory dsFactory;
99
100
101 private TableState tableState;
102
103
104
105
106
107
108
109
110
111 public JDBCDiskCache(final JDBCDiskCacheAttributes cattr, final DataSourceFactory dsFactory, final TableState tableState)
112 {
113 super( cattr );
114
115 setTableState( tableState );
116 setJdbcDiskCacheAttributes( cattr );
117
118 log.info( "jdbcDiskCacheAttributes = {0}", this::getJdbcDiskCacheAttributes);
119
120
121 this.dsFactory = dsFactory;
122
123
124 setAlive(true);
125 }
126
127
128
129
130
131
132
133
134 @Override
135 protected void processUpdate( final ICacheElement<K, V> ce )
136 {
137 updateCount.incrementAndGet();
138
139 log.debug( "updating, ce = {0}", ce );
140
141 try (Connection con = getDataSource().getConnection())
142 {
143 log.debug( "Putting [{0}] on disk.", ce::getKey);
144
145 try
146 {
147 final byte[] element = getElementSerializer().serialize( ce );
148 insertOrUpdate( ce, con, element );
149 }
150 catch ( final IOException e )
151 {
152 log.error( "Could not serialize element", e );
153 }
154 }
155 catch ( final SQLException e )
156 {
157 log.error( "Problem getting connection.", e );
158 }
159 }
160
161
162
163
164
165
166
167
168
169
170 private void insertOrUpdate( final ICacheElement<K, V> ce, final Connection con, final byte[] element )
171 {
172 boolean exists = false;
173
174
175 if ( this.getJdbcDiskCacheAttributes().isTestBeforeInsert() )
176 {
177 exists = doesElementExist( ce, con );
178 }
179
180
181 if ( !exists )
182 {
183 exists = insertRow( ce, con, element );
184 }
185
186
187 if ( exists )
188 {
189 updateRow( ce, con, element );
190 }
191 }
192
193
194
195
196
197
198
199
200
201 private boolean insertRow( final ICacheElement<K, V> ce, final Connection con, final byte[] element )
202 {
203 boolean exists = false;
204 final String sqlI = String.format("insert into %s"
205 + " (CACHE_KEY, REGION, ELEMENT, MAX_LIFE_SECONDS, IS_ETERNAL, CREATE_TIME, UPDATE_TIME_SECONDS,"
206 + " SYSTEM_EXPIRE_TIME_SECONDS) "
207 + " values (?, ?, ?, ?, ?, ?, ?, ?)", getJdbcDiskCacheAttributes().getTableName());
208
209 try (PreparedStatement psInsert = con.prepareStatement( sqlI ))
210 {
211 psInsert.setString( 1, ce.getKey().toString() );
212 psInsert.setString( 2, this.getCacheName() );
213 psInsert.setBytes( 3, element );
214 psInsert.setLong( 4, ce.getElementAttributes().getMaxLife() );
215 psInsert.setString( 5, ce.getElementAttributes().getIsEternal() ? "T" : "F" );
216
217 final Timestamp createTime = new Timestamp( ce.getElementAttributes().getCreateTime() );
218 psInsert.setTimestamp( 6, createTime );
219
220 final long now = System.currentTimeMillis() / 1000;
221 psInsert.setLong( 7, now );
222
223 final long expireTime = now + ce.getElementAttributes().getMaxLife();
224 psInsert.setLong( 8, expireTime );
225
226 psInsert.execute();
227 }
228 catch ( final SQLException e )
229 {
230 if ("23000".equals(e.getSQLState()))
231 {
232 exists = true;
233 }
234 else
235 {
236 log.error( "Could not insert element", e );
237 }
238
239
240 if ( !exists && !this.getJdbcDiskCacheAttributes().isTestBeforeInsert() )
241 {
242 exists = doesElementExist( ce, con );
243 }
244 }
245
246 return exists;
247 }
248
249
250
251
252
253
254
255
256 private void updateRow( final ICacheElement<K, V> ce, final Connection con, final byte[] element )
257 {
258 final String sqlU = String.format("update %s"
259 + " set ELEMENT = ?, CREATE_TIME = ?, UPDATE_TIME_SECONDS = ?, " + " SYSTEM_EXPIRE_TIME_SECONDS = ? "
260 + " where CACHE_KEY = ? and REGION = ?", getJdbcDiskCacheAttributes().getTableName());
261
262 try (PreparedStatement psUpdate = con.prepareStatement( sqlU ))
263 {
264 psUpdate.setBytes( 1, element );
265
266 final Timestamp createTime = new Timestamp( ce.getElementAttributes().getCreateTime() );
267 psUpdate.setTimestamp( 2, createTime );
268
269 final long now = System.currentTimeMillis() / 1000;
270 psUpdate.setLong( 3, now );
271
272 final long expireTime = now + ce.getElementAttributes().getMaxLife();
273 psUpdate.setLong( 4, expireTime );
274
275 psUpdate.setString( 5, (String) ce.getKey() );
276 psUpdate.setString( 6, this.getCacheName() );
277 psUpdate.execute();
278
279 log.debug( "ran update {0}", sqlU );
280 }
281 catch ( final SQLException e )
282 {
283 log.error( "Error executing update sql [{0}]", sqlU, e );
284 }
285 }
286
287
288
289
290
291
292
293
294 protected boolean doesElementExist( final ICacheElement<K, V> ce, final Connection con )
295 {
296 boolean exists = false;
297
298 final String sqlS = String.format("select CACHE_KEY from %s where REGION = ? and CACHE_KEY = ?",
299 getJdbcDiskCacheAttributes().getTableName());
300
301 try (PreparedStatement psSelect = con.prepareStatement( sqlS ))
302 {
303 psSelect.setString( 1, this.getCacheName() );
304 psSelect.setString( 2, (String) ce.getKey() );
305
306 try (ResultSet rs = psSelect.executeQuery())
307 {
308 exists = rs.next();
309 }
310
311 log.debug( "[{0}] existing status is {1}", ce.getKey(), exists );
312 }
313 catch ( final SQLException e )
314 {
315 log.error( "Problem looking for item before insert.", e );
316 }
317
318 return exists;
319 }
320
321
322
323
324
325
326
327
328 @Override
329 protected ICacheElement<K, V> processGet( final K key )
330 {
331 getCount.incrementAndGet();
332
333 log.debug( "Getting [{0}] from disk", key );
334
335 if ( !isAlive() )
336 {
337 return null;
338 }
339
340 ICacheElement<K, V> obj = null;
341
342
343 final String selectString = String.format("select ELEMENT from %s where REGION = ? and CACHE_KEY = ?",
344 getJdbcDiskCacheAttributes().getTableName());
345
346 try (Connection con = getDataSource().getConnection())
347 {
348 try (PreparedStatement psSelect = con.prepareStatement( selectString ))
349 {
350 psSelect.setString( 1, this.getCacheName() );
351 psSelect.setString( 2, key.toString() );
352
353 try (ResultSet rs = psSelect.executeQuery())
354 {
355 byte[] data = null;
356
357 if ( rs.next() )
358 {
359 data = rs.getBytes( 1 );
360 }
361
362 if ( data != null )
363 {
364 try
365 {
366
367 obj = getElementSerializer().deSerialize( data, null );
368 }
369 catch ( final IOException | ClassNotFoundException e )
370 {
371 log.error( "Problem getting item for key [{0}]", key, e );
372 }
373 }
374 }
375 }
376 }
377 catch ( final SQLException sqle )
378 {
379 log.error( "Caught a SQL exception trying to get the item for key [{0}]",
380 key, sqle );
381 }
382
383 return obj;
384 }
385
386
387
388
389
390
391
392
393 @Override
394 protected Map<K, ICacheElement<K, V>> processGetMatching( final String pattern )
395 {
396 getMatchingCount.incrementAndGet();
397
398 log.debug( "Getting [{0}] from disk", pattern);
399
400 if ( !isAlive() )
401 {
402 return null;
403 }
404
405 final Map<K, ICacheElement<K, V>> results = new HashMap<>();
406
407
408 final String selectString = String.format("select ELEMENT from %s where REGION = ? and CACHE_KEY like ?",
409 getJdbcDiskCacheAttributes().getTableName());
410
411 try (Connection con = getDataSource().getConnection())
412 {
413 try (PreparedStatement psSelect = con.prepareStatement( selectString ))
414 {
415 psSelect.setString( 1, this.getCacheName() );
416 psSelect.setString( 2, constructLikeParameterFromPattern( pattern ) );
417
418 try (ResultSet rs = psSelect.executeQuery())
419 {
420 while ( rs.next() )
421 {
422 final byte[] data = rs.getBytes(1);
423 if ( data != null )
424 {
425 try
426 {
427
428 final ICacheElement<K, V> value = getElementSerializer().deSerialize( data, null );
429 results.put( value.getKey(), value );
430 }
431 catch ( final IOException | ClassNotFoundException e )
432 {
433 log.error( "Problem getting items for pattern [{0}]", pattern, e );
434 }
435 }
436 }
437 }
438 }
439 }
440 catch ( final SQLException sqle )
441 {
442 log.error( "Caught a SQL exception trying to get items for pattern [{0}]",
443 pattern, sqle );
444 }
445
446 return results;
447 }
448
449
450
451
452
453 public String constructLikeParameterFromPattern( final String pattern )
454 {
455 String likePattern = pattern.replace( ".+", "%" );
456 likePattern = likePattern.replace( ".", "_" );
457
458 log.debug( "pattern = [{0}]", likePattern );
459
460 return likePattern;
461 }
462
463
464
465
466
467
468
469
470 @Override
471 protected boolean processRemove( final K key )
472 {
473
474 final String sqlSingle = String.format("delete from %s where REGION = ? and CACHE_KEY = ?",
475 getJdbcDiskCacheAttributes().getTableName());
476
477 final String sqlPartial = String.format("delete from %s where REGION = ? and CACHE_KEY like ?",
478 getJdbcDiskCacheAttributes().getTableName());
479
480 try (Connection con = getDataSource().getConnection())
481 {
482 boolean partial = key.toString().endsWith(ICache.NAME_COMPONENT_DELIMITER);
483 String sql = partial ? sqlPartial : sqlSingle;
484
485 try (PreparedStatement psSelect = con.prepareStatement(sql))
486 {
487 psSelect.setString( 1, this.getCacheName() );
488 if ( partial )
489 {
490 psSelect.setString( 2, key.toString() + "%" );
491 }
492 else
493 {
494 psSelect.setString( 2, key.toString() );
495 }
496
497 psSelect.executeUpdate();
498
499 setAlive(true);
500 }
501 catch ( final SQLException e )
502 {
503 log.error( "Problem creating statement. sql [{0}]", sql, e );
504 setAlive(false);
505 }
506 }
507 catch ( final SQLException e )
508 {
509 log.error( "Problem updating cache.", e );
510 reset();
511 }
512 return false;
513 }
514
515
516
517
518
519 @Override
520 protected void processRemoveAll()
521 {
522
523 if ( this.jdbcDiskCacheAttributes.isAllowRemoveAll() )
524 {
525 final String sql = String.format("delete from %s where REGION = ?",
526 getJdbcDiskCacheAttributes().getTableName());
527
528 try (Connection con = getDataSource().getConnection())
529 {
530 try (PreparedStatement psDelete = con.prepareStatement( sql ))
531 {
532 psDelete.setString( 1, this.getCacheName() );
533 setAlive(true);
534 psDelete.executeUpdate();
535 }
536 catch ( final SQLException e )
537 {
538 log.error( "Problem creating statement.", e );
539 setAlive(false);
540 }
541 }
542 catch ( final SQLException e )
543 {
544 log.error( "Problem removing all.", e );
545 reset();
546 }
547 }
548 else
549 {
550 log.info( "RemoveAll was requested but the request was not fulfilled: "
551 + "allowRemoveAll is set to false." );
552 }
553 }
554
555
556
557
558
559
560 protected int deleteExpired()
561 {
562 int deleted = 0;
563
564 try (Connection con = getDataSource().getConnection())
565 {
566
567
568 final DatabaseMetaData dmd = con.getMetaData();
569 final ResultSet result = dmd.getTables(null, null,
570 getJdbcDiskCacheAttributes().getTableName(), null);
571
572 if (result.next())
573 {
574 getTableState().setState( TableState.DELETE_RUNNING );
575 final long now = System.currentTimeMillis() / 1000;
576
577 final String sql = String.format("delete from %s where IS_ETERNAL = ? and REGION = ?"
578 + " and ? > SYSTEM_EXPIRE_TIME_SECONDS", getJdbcDiskCacheAttributes().getTableName());
579
580 try (PreparedStatement psDelete = con.prepareStatement( sql ))
581 {
582 psDelete.setString( 1, "F" );
583 psDelete.setString( 2, this.getCacheName() );
584 psDelete.setLong( 3, now );
585
586 setAlive(true);
587
588 deleted = psDelete.executeUpdate();
589 }
590 catch ( final SQLException e )
591 {
592 log.error( "Problem creating statement.", e );
593 setAlive(false);
594 }
595
596 logApplicationEvent( getAuxiliaryCacheAttributes().getName(), "deleteExpired",
597 "Deleted expired elements. URL: " + getDiskLocation() );
598 }
599 else
600 {
601 log.warn( "Trying to shrink non-existing table [{0}]",
602 getJdbcDiskCacheAttributes().getTableName() );
603 }
604 }
605 catch ( final SQLException e )
606 {
607 logError( getAuxiliaryCacheAttributes().getName(), "deleteExpired",
608 e.getMessage() + " URL: " + getDiskLocation() );
609 log.error( "Problem removing expired elements from the table.", e );
610 reset();
611 }
612 finally
613 {
614 getTableState().setState( TableState.FREE );
615 }
616
617 return deleted;
618 }
619
620
621
622
623 public void reset()
624 {
625
626 }
627
628
629 @Override
630 public void processDispose()
631 {
632 final ICacheEvent<K> cacheEvent = createICacheEvent( getCacheName(), null, ICacheEventLogger.DISPOSE_EVENT );
633
634 try
635 {
636 dsFactory.close();
637 }
638 catch ( final SQLException e )
639 {
640 log.error( "Problem shutting down.", e );
641 }
642 finally
643 {
644 logICacheEvent( cacheEvent );
645 }
646 }
647
648
649
650
651
652
653 @Override
654 public int getSize()
655 {
656 int size = 0;
657
658
659 final String selectString = String.format("select count(*) from %s where REGION = ?",
660 getJdbcDiskCacheAttributes().getTableName());
661
662 try (Connection con = getDataSource().getConnection())
663 {
664 try (PreparedStatement psSelect = con.prepareStatement( selectString ))
665 {
666 psSelect.setString( 1, this.getCacheName() );
667
668 try (ResultSet rs = psSelect.executeQuery())
669 {
670 if ( rs.next() )
671 {
672 size = rs.getInt( 1 );
673 }
674 }
675 }
676 }
677 catch ( final SQLException e )
678 {
679 log.error( "Problem getting size.", e );
680 }
681
682 return size;
683 }
684
685
686
687
688
689
690 @Override
691 public Set<K> getKeySet() throws IOException
692 {
693 throw new UnsupportedOperationException( "Groups not implemented." );
694
695 }
696
697
698
699
700 protected void setJdbcDiskCacheAttributes( final JDBCDiskCacheAttributes jdbcDiskCacheAttributes )
701 {
702 this.jdbcDiskCacheAttributes = jdbcDiskCacheAttributes;
703 }
704
705
706
707
708 protected JDBCDiskCacheAttributes getJdbcDiskCacheAttributes()
709 {
710 return jdbcDiskCacheAttributes;
711 }
712
713
714
715
716 @Override
717 public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
718 {
719 return this.getJdbcDiskCacheAttributes();
720 }
721
722
723
724
725
726
727 @Override
728 public IStats getStatistics()
729 {
730 final IStats stats = super.getStatistics();
731 stats.setTypeName( "JDBC/Abstract Disk Cache" );
732
733 final List<IStatElement<?>> elems = stats.getStatElements();
734
735 elems.add(new StatElement<>( "Update Count", updateCount ) );
736 elems.add(new StatElement<>( "Get Count", getCount ) );
737 elems.add(new StatElement<>( "Get Matching Count", getMatchingCount ) );
738 elems.add(new StatElement<>( "DB URL", getJdbcDiskCacheAttributes().getUrl()) );
739
740 stats.setStatElements( elems );
741
742 return stats;
743 }
744
745
746
747
748
749
750 protected String getTableName()
751 {
752 String name = "UNDEFINED";
753 if ( this.getJdbcDiskCacheAttributes() != null )
754 {
755 name = this.getJdbcDiskCacheAttributes().getTableName();
756 }
757 return name;
758 }
759
760
761
762
763 public void setTableState( final TableState tableState )
764 {
765 this.tableState = tableState;
766 }
767
768
769
770
771 public TableState getTableState()
772 {
773 return tableState;
774 }
775
776
777
778
779
780
781 @Override
782 protected String getDiskLocation()
783 {
784 return this.jdbcDiskCacheAttributes.getUrl();
785 }
786
787
788
789
790
791
792 public DataSource getDataSource() throws SQLException
793 {
794 return dsFactory.getDataSource();
795 }
796
797
798
799
800
801
802 @Override
803 public String toString()
804 {
805 return this.getStats();
806 }
807 }