torquebox-cache 2.0.0.beta1-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+