torquebox-cache 2.0.0.beta1-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,124 @@
1
+ # Copyright 2008-2011 Red Hat, Inc, and individual contributors.
2
+ #
3
+ # This is free software; you can redistribute it and/or modify it
4
+ # under the terms of the GNU Lesser General Public License as
5
+ # published by the Free Software Foundation; either version 2.1 of
6
+ # the License, or (at your option) any later version.
7
+ #
8
+ # This software is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this software; if not, write to the Free
15
+ # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
16
+ # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
17
+
18
+ require 'active_support/cache'
19
+ require 'torquebox/kernel'
20
+ require 'cache'
21
+
22
+ module ActiveSupport
23
+ module Cache
24
+ class TorqueBoxStore < Store
25
+
26
+ SECONDS = java.util.concurrent.TimeUnit::SECONDS
27
+
28
+ def initialize(options = {})
29
+ super(options)
30
+ cache
31
+ end
32
+
33
+ def name
34
+ options[:name] || TORQUEBOX_APP_NAME
35
+ end
36
+
37
+ def clustering_mode
38
+ cache.clustering_mode
39
+ end
40
+
41
+ # Clear the entire cache. Be careful with this method since it could
42
+ # affect other processes if shared cache is being used.
43
+ def clear(options = nil)
44
+ cache.clear
45
+ end
46
+
47
+ # Delete all entries with keys matching the pattern.
48
+ def delete_matched( matcher, options = nil )
49
+ options = merged_options(options)
50
+ pattern = key_matcher( matcher, options )
51
+ keys.each { |key| delete( key, options ) if key =~ pattern }
52
+ end
53
+
54
+ # Increment an integer value in the cache; return new value
55
+ def increment(name, amount = 1, options = nil)
56
+ options = merged_options( options )
57
+
58
+ # Get the current entry
59
+ key = namespaced_key( name, options )
60
+ current = cache.get(key)
61
+ value = decode(current).value.to_i
62
+
63
+ new_entry = Entry.new( value+amount, options )
64
+ if cache.replace( key, current, encode(new_entry) )
65
+ return new_entry.value
66
+ else
67
+ raise "Concurrent modification, old value was #{value}"
68
+ end
69
+ end
70
+
71
+ # Decrement an integer value in the cache; return new value
72
+ def decrement(name, amount = 1, options = nil)
73
+ increment( name, -amount, options )
74
+ end
75
+
76
+ # Cleanup the cache by removing expired entries.
77
+ def cleanup(options = nil)
78
+ options = merged_options(options)
79
+ keys.each do |key|
80
+ entry = read_entry(key, options)
81
+ delete_entry(key, options) if entry && entry.expired?
82
+ end
83
+ end
84
+
85
+ protected
86
+
87
+ def encode value
88
+ Marshal.dump(value).to_java_bytes
89
+ end
90
+
91
+ def decode value
92
+ value && Marshal.load(String.from_java_bytes(value))
93
+ end
94
+
95
+ # Return the keys in the cache; potentially very expensive depending on configuration
96
+ def keys
97
+ cache.keys
98
+ end
99
+
100
+ # Read an entry from the cache implementation. Subclasses must implement this method.
101
+ def read_entry(key, options)
102
+ decode( cache.get( key ) )
103
+ end
104
+
105
+ # Write an entry to the cache implementation. Subclasses must implement this method.
106
+ def write_entry(key, entry, options = {})
107
+ options[:unless_exist] ? cache.put_if_absent( key, encode(entry) ) : cache.put( key, encode(entry) )
108
+ end
109
+
110
+ # Delete an entry from the cache implementation. Subclasses must implement this method.
111
+ def delete_entry(key, options) # :nodoc:
112
+ cache.remove( key ) && true
113
+ end
114
+
115
+
116
+ private
117
+
118
+ def cache
119
+ @cache ||= TorqueBox::Infinispan::Cache.new(options)
120
+ end
121
+ end
122
+ end
123
+ end
124
+
data/lib/cache.rb ADDED
@@ -0,0 +1,420 @@
1
+ # Copyright 2008-2011 Red Hat, Inc, and individual contributors.
2
+ #
3
+ # This is free software; you can redistribute it and/or modify it
4
+ # under the terms of the GNU Lesser General Public License as
5
+ # published by the Free Software Foundation; either version 2.1 of
6
+ # the License, or (at your option) any later version.
7
+ #
8
+ # This software is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this software; if not, write to the Free
15
+ # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
16
+ # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
17
+
18
+ require 'torquebox/kernel'
19
+ require 'torquebox/injectors'
20
+ require 'torquebox/transactions'
21
+
22
+ module TorqueBox
23
+ module Infinispan
24
+
25
+ class ContainerTransactionManagerLookup
26
+ include TorqueBox::Injectors
27
+ begin
28
+ include org.infinispan.transaction.lookup.TransactionManagerLookup
29
+ rescue NameError
30
+ # Not running inside TorqueBox
31
+ end
32
+
33
+ def getTransactionManager
34
+ inject('transaction-manager')
35
+ end
36
+ end
37
+
38
+ class NoOpCodec
39
+ def self.encode(object)
40
+ object
41
+ end
42
+
43
+ def self.decode(object)
44
+ object
45
+ end
46
+ end
47
+
48
+ class Sequence
49
+ include java.io.Serializable
50
+
51
+ class Codec
52
+ def self.encode(sequence)
53
+ sequence.value.to_s
54
+ end
55
+
56
+ def self.decode(sequence_bytes)
57
+ sequence_bytes && Sequence.new( sequence_bytes.to_s.to_i )
58
+ end
59
+ end
60
+
61
+ def initialize(amount = 1)
62
+ @data = amount
63
+ end
64
+
65
+ def value
66
+ @data ? @data.to_i : @data
67
+ end
68
+
69
+ def next(amount = 1)
70
+ Sequence.new( @data.to_i + amount )
71
+ end
72
+
73
+ def ==(other)
74
+ self.value == other.value
75
+ end
76
+
77
+ def to_s
78
+ "Sequence: #{self.value}"
79
+ end
80
+ end
81
+
82
+ class Cache
83
+ include TorqueBox::Injectors
84
+
85
+ SECONDS = java.util.concurrent.TimeUnit::SECONDS
86
+ begin
87
+ java_import org.infinispan.config.Configuration::CacheMode
88
+ java_import org.infinispan.transaction::TransactionMode
89
+ java_import org.infinispan.transaction::LockingMode
90
+ INFINISPAN_AVAILABLE = true
91
+ rescue NameError
92
+ INFINISPAN_AVAILABLE = false
93
+ # Not running inside TorqueBox
94
+ end
95
+
96
+ def initialize(opts = {})
97
+ @options = opts
98
+ options[:transaction_mode] = :transactional unless options.has_key?( :transaction_mode )
99
+ options[:locking_mode] ||= :optimistic if (transactional? && !options.has_key?( :locking_mode ))
100
+ cache
101
+ end
102
+
103
+ def name
104
+ options[:name] || TORQUEBOX_APP_NAME
105
+ end
106
+
107
+ def search_manager
108
+ @search_manager ||= org.infinispan.query.Search.getSearchManager(@cache)
109
+ end
110
+
111
+ def clustering_mode
112
+ replicated = [:r, :repl, :replicated, :replication].include? options[:mode]
113
+ distributed = [:d, :dist, :distributed, :distribution].include? options[:mode]
114
+ sync = !!options[:sync]
115
+ case
116
+ when replicated
117
+ sync ? CacheMode::REPL_SYNC : CacheMode::REPL_ASYNC
118
+ when distributed
119
+ sync ? CacheMode::DIST_SYNC : CacheMode::DIST_ASYNC
120
+ else
121
+ sync ? CacheMode::INVALIDATION_SYNC : CacheMode::INVALIDATION_ASYNC
122
+ end
123
+ end
124
+
125
+ def locking_mode
126
+ case options[:locking_mode]
127
+ when :optimistic then LockingMode::OPTIMISTIC
128
+ when :pessimistic then LockingMode::PESSIMISTIC
129
+ end
130
+ end
131
+
132
+ def transaction_mode
133
+ options[:transaction_mode] == :transactional ? TransactionMode::TRANSACTIONAL : TransactionMode::NON_TRANSACTIONAL
134
+ end
135
+
136
+ def transactional?
137
+ transaction_mode == TransactionMode::TRANSACTIONAL
138
+ end
139
+
140
+ # Clear the entire cache. Be careful with this method since it could
141
+ # affect other processes if shared cache is being used.
142
+ def clear
143
+ cache.clearAsync
144
+ end
145
+
146
+ # Return the keys in the cache; potentially very expensive depending on configuration
147
+ def keys
148
+ cache.key_set
149
+ end
150
+
151
+ def all
152
+ cache.key_set.collect{|k| get(k)}
153
+ end
154
+
155
+ def contains_key?( key )
156
+ cache.contains_key( key )
157
+ end
158
+
159
+ # Get an entry from the cache
160
+ def get(key)
161
+ cache.get(key)
162
+ end
163
+
164
+ # Write an entry to the cache
165
+ def put(key, value, expires = 0)
166
+ __put(key, value, expires, :put_async)
167
+ end
168
+
169
+ def put_if_absent(key, value, expires = 0)
170
+ __put(key, value, expires, :put_if_absent_async)
171
+ end
172
+
173
+ def evict( key )
174
+ cache.evict( key )
175
+ end
176
+
177
+ def replace(key, original_value, new_value, codec=NoOpCodec)
178
+ # First, grab the raw value from the cache, which is a byte[]
179
+
180
+ current = get( key )
181
+ decoded = codec.decode( current )
182
+
183
+ # great! we've got a byte[] now. Let's apply == to it, like Jim says will work always
184
+
185
+ if ( decoded == original_value )
186
+ # how does this work?
187
+ cache.replace( key, current, codec.encode( new_value ) )
188
+ end
189
+ end
190
+
191
+ # Delete an entry from the cache
192
+ def remove(key)
193
+ cache.removeAsync( key ) && true
194
+ end
195
+
196
+ def increment( sequence_name, amount = 1 )
197
+ current_entry = Sequence::Codec.decode( get( sequence_name ) )
198
+
199
+ # If we can't find the sequence in the cache, create a new one and return
200
+ put( sequence_name, Sequence::Codec.encode( Sequence.new( amount ) ) ) and return amount if current_entry.nil?
201
+
202
+ # Increment the sequence, stash it, and return
203
+ next_entry = current_entry.next( amount )
204
+
205
+ # Since replace() doesn't encode, let's encode everything to a byte[] for it, no?
206
+ if replace( sequence_name, current_entry, next_entry, Sequence::Codec )
207
+ return next_entry.value
208
+ else
209
+ raise "Concurrent modification, old value was #{current_entry.value} new value #{next_entry.value}"
210
+ end
211
+ end
212
+
213
+ # Decrement an integer value in the cache; return new value
214
+ def decrement(name, amount = 1)
215
+ increment( name, -amount )
216
+ end
217
+
218
+ def transaction(&block)
219
+ if !transactional?
220
+ yield self
221
+ elsif inject('transaction-manager').nil?
222
+ tm = cache.getAdvancedCache.getTransactionManager
223
+ begin
224
+ tm.begin if tm
225
+ yield self
226
+ tm.commit if tm
227
+ rescue Exception => e
228
+ if tm.nil?
229
+ log( "Transaction is nil", "ERROR" )
230
+ log( e.message, 'ERROR' )
231
+ log( e.backtrace, 'ERROR' )
232
+ elsif tm.status == javax.transaction.Status.STATUS_NO_TRANSACTION
233
+ log( "No transaction was started", "ERROR" )
234
+ log( e.message, 'ERROR' )
235
+ log( e.backtrace, 'ERROR' )
236
+ else
237
+ tm.rollback
238
+ log( "Rolling back.", 'ERROR' )
239
+ log( e.message, 'ERROR' )
240
+ log( e.backtrace, 'ERROR' )
241
+ end
242
+ raise e
243
+ end
244
+ else
245
+ TorqueBox.transaction do
246
+ yield self
247
+ end
248
+ end
249
+ end
250
+
251
+ def add_listener( listener )
252
+ cache.add_listener( listener )
253
+ end
254
+
255
+ def stop
256
+ cache.stop
257
+ end
258
+
259
+ def self.shutdown
260
+ Cache.local_managers.each { |m| m.stop }
261
+ end
262
+
263
+ @@local_managers = []
264
+ def self.local_managers
265
+ @@local_managers
266
+ end
267
+
268
+ def self.log( message, status = 'INFO' )
269
+ $stdout.puts( "#{status}: #{message}" )
270
+ end
271
+
272
+ def log( message, status = 'INFO' )
273
+ TorqueBox::Infinispan::Cache.log( message, status )
274
+ end
275
+
276
+ private
277
+
278
+ def options
279
+ @options ||= {}
280
+ end
281
+
282
+ def cache
283
+ if INFINISPAN_AVAILABLE
284
+ @cache ||= clustered || local || nothing
285
+ else
286
+ @cache ||= nothing
287
+ end
288
+ end
289
+
290
+ def manager
291
+ begin
292
+ @manager ||= TorqueBox::ServiceRegistry[org.jboss.msc.service.ServiceName::JBOSS.append( "infinispan", "torquebox" )]
293
+ rescue Exception => e
294
+ log( "Caught exception while looking up Infinispan service.", 'ERROR' )
295
+ log( e.message, 'ERROR' )
296
+ end
297
+ @manager
298
+ end
299
+
300
+ def reconfigure(mode=clustering_mode)
301
+ cache = manager.get_cache(name)
302
+ base_config = cache.configuration
303
+ unless base_config.cache_mode == mode
304
+ log( "Reconfiguring Infinispan cache #{name} from #{config.cache_mode} to #{mode}" )
305
+ cache.stop
306
+ base_config.cache_mode = mode
307
+ config = base_config.fluent
308
+ config.transaction.transactionMode( transaction_mode )
309
+ config.transaction.recovery.transactionManagerLookup( transaction_manager_lookup )
310
+ manager.define_configuration(name, config.build)
311
+ cache.start
312
+ end
313
+ return cache
314
+ end
315
+
316
+ def configure(mode=clustering_mode)
317
+ log( "Configuring Infinispan cache #{name} as #{mode}" )
318
+ base_config = manager.default_configuration.clone
319
+ base_config.class_loader = java.lang::Thread.current_thread.context_class_loader
320
+ base_config.cache_mode = mode
321
+
322
+ config = base_config.fluent
323
+ config.transaction.transactionMode( transaction_mode )
324
+ config.transaction.recovery.transactionManagerLookup( transaction_manager_lookup )
325
+ manager.define_configuration(name, config.build )
326
+ manager.get_cache(name)
327
+ end
328
+
329
+ def transaction_manager_lookup
330
+ @tm ||= if inject('transaction-manager')
331
+ ContainerTransactionManagerLookup.new
332
+ else
333
+ org.infinispan.transaction.lookup.GenericTransactionManagerLookup.new
334
+ end
335
+ end
336
+
337
+ def clustered
338
+ (manager.running?(name) ? reconfigure : configure) if manager
339
+ rescue
340
+ log( "Clustered: Can't get clustered cache; falling back to local: #{$!}", 'ERROR' )
341
+ end
342
+
343
+ def local
344
+ log( "Configuring Infinispan local cache #{name}" )
345
+ bare_config = org.infinispan.config.Configuration.new
346
+ bare_config.class_loader = java.lang::Thread.current_thread.context_class_loader
347
+
348
+ config = bare_config.fluent
349
+ config.transaction.transactionMode( transaction_mode )
350
+
351
+ if transactional?
352
+ config.transaction.recovery.transactionManagerLookup( transaction_manager_lookup )
353
+ config.transaction.lockingMode( locking_mode )
354
+ end
355
+
356
+ if options[:persist]
357
+ log( "Configuring #{name} local cache for file-based persistence" )
358
+ store = org.infinispan.loaders.file.FileCacheStoreConfig.new
359
+ store.purge_on_startup( false )
360
+ store.location(options[:persist]) if File.exist?( options[:persist].to_s )
361
+ config.loaders.add_cache_loader( store )
362
+ end
363
+
364
+ if options[:index]
365
+ log( "Configuring #{name} local cache for local-only indexing" )
366
+ config.indexing.index_local_only(true)
367
+ end
368
+
369
+ if ((local_manager = Cache.find_local_manager(name)) == nil)
370
+ local_manager = org.infinispan.manager.DefaultCacheManager.new
371
+ local_manager.define_configuration( name, config.build )
372
+ Cache.local_managers << local_manager
373
+ end
374
+
375
+ local_manager.get_cache( self.name )
376
+ rescue Exception => e
377
+ log( "Unable to obtain local cache: #{$!}", 'ERROR' )
378
+ log( e.backtrace, 'ERROR' )
379
+ end
380
+
381
+ def nothing
382
+ result = Object.new
383
+ def result.method_missing(*args); end
384
+ log( "Nothing: Can't get or create an Infinispan cache. No caching will occur", 'ERROR' )
385
+ result
386
+ end
387
+
388
+ def __put(key, value, expires, operation)
389
+ args = [ operation, key, value ]
390
+ if expires > 0
391
+ # Set the Infinispan expire a few minutes into the future to support
392
+ # :race_condition_ttl on read
393
+ #expires_in = expires + 300 # 300 seconds == 5 minutes
394
+ expires_in = expires
395
+ args << expires_in << SECONDS
396
+ args << expires << SECONDS
397
+ end
398
+ #$stderr.puts "cache=#{cache.inspect}"
399
+ #$stderr.puts "*args=#{args.inspect}"
400
+ cache.send( *args ) && true
401
+ end
402
+
403
+ # Finds the CacheManager for a given cache and returns it - or nil if not found
404
+ def self.find_local_manager( cache_name )
405
+ local_managers.each do |m|
406
+ if m.cache_exists( cache_name )
407
+ log( ":-:-:-: local_manager already exists for #{cache_name}" )
408
+ return m
409
+ end
410
+ end
411
+ return nil
412
+ end
413
+
414
+ end
415
+
416
+ at_exit { Cache.shutdown }
417
+ end
418
+ end
419
+
420
+