1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package no.feide.moria.store;
22
23 import java.net.InetAddress;
24 import java.util.ArrayList;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.Timer;
30 import java.util.Vector;
31
32 import no.feide.moria.log.MessageLogger;
33
34 import org.jboss.cache.CacheException;
35 import org.jboss.cache.Fqn;
36 import org.jboss.cache.Node;
37 import org.jboss.cache.TreeCache;
38 import org.jboss.cache.eviction.EvictionAlgorithm;
39 import org.jboss.cache.eviction.EvictionTimerTask;
40 import org.jboss.cache.eviction.LRUPolicy;
41 import org.jboss.cache.eviction.Region;
42 import org.jboss.cache.eviction.RegionManager;
43 import org.jboss.cache.eviction.RegionNameConflictException;
44 import org.jboss.cache.lock.LockingException;
45 import org.jboss.cache.lock.TimeoutException;
46 import org.jgroups.MergeView;
47 import org.jgroups.View;
48 import org.jgroups.stack.IpAddress;
49 import org.w3c.dom.Element;
50 import org.w3c.dom.NodeList;
51 import org.w3c.dom.Text;
52
53 /***
54 * This eviction policy evicts tickets after a fixed period, aka Time To Live.
55 *
56 * @author Bjørn Ola Smievoll <b.o.smievoll@conduct.no>
57 * @version $Revision: 1.11 $
58 */
59
60 public final class TicketTTLEvictionPolicy extends LRUPolicy {
61
62 /*** The logger used by this class. */
63 private MessageLogger messageLogger = new MessageLogger(TicketTTLEvictionPolicy.class);
64
65 /*** Manages the regions for each ticket type. */
66 private RegionManager regionManager;
67
68 /*** The root node. */
69 private static final Fqn ROOT = new Fqn("");
70
71 /*** Default interval value for running the evictions. */
72 private static final int WAKEUP_INTERVAL_DEFAULT = 5;
73
74 /*** Default value for max nodes in each region. */
75 private static final int MAX_NODES_DEFAULT = 0x186a0;
76
77 /*** Constant representing the region name key. */
78 private static final String REGION_NAME = "region";
79
80 /*** Constant representing the attribute name key. */
81 private static final String ATTRIBUTE_NAME = "attribute";
82
83 /*** Constant representing the name attribute key. */
84 private static final String NAME_ATTRIBUTE_NAME = "name";
85
86 /*** Constant representing the wakeup interval key. */
87 private static final String WAKEUP_INTERVAL_NAME = "wakeUpIntervalSeconds";
88
89 /*** Constant representing the max nodes key. */
90 private static final String MAX_NODES_NAME = "maxNodes";
91
92 /*** Constant representing the ttl attribute key. */
93 private static final String TTL_ATTRIBUTE_NAME = "timeToLive";
94
95 /*** Constant holding default error message. */
96 private static final String OPERATION_FAILED_MESSAGE = "Operation failed";
97
98 /*** Contains the different regions registered for this policy. */
99 private RegionValue[] regionValues;
100
101 /*** The interval for the eviction threads to run. */
102 private int wakeUpIntervalSeconds;
103
104 /*** Maximal number of nodes in a region. Not really used, should be set high. */
105 private int maxNodes;
106
107 /*** The timer responsible for scheduling the eviction threads. */
108 private Timer evictionTimer;
109
110 /*** The cache the evictions are done on. */
111 private TreeCache cache;
112
113 /***
114 * Creates a new instance.
115 */
116 public TicketTTLEvictionPolicy() {
117 }
118
119 /***
120 * @see org.jboss.cache.eviction.EvictionPolicy#configure(org.jboss.cache.TreeCache)
121 */
122 public void configure(final TreeCache cache) {
123 parseConfig(cache.getEvictionPolicyConfig());
124 regionManager = new RegionManager(this);
125 this.cache = cache;
126
127 for (int i = 0; i < regionValues.length; i++) {
128 EvictionAlgorithm algorithm = new TicketTTLEvictionAlgorithm();
129
130 try {
131 Region region = regionManager.createRegion(ROOT.toString() + regionValues[i].regionName, algorithm);
132 region.setMaxNodes(maxNodes);
133 region.setTimeToLiveSeconds(wakeUpIntervalSeconds);
134 } catch (RegionNameConflictException e) {
135 messageLogger.logWarn("Name conflict in region naming.", e);
136 throw new EvictionConfigurationException("Unable to create region " + regionValues[i].regionName, e);
137 }
138 }
139
140 }
141
142 /***
143 * @see org.jboss.cache.eviction.EvictionPolicy#getRegions()
144 */
145 public Region[] getRegions() {
146 return regionManager.getRegions();
147 }
148
149 /***
150 * @see org.jboss.cache.eviction.EvictionPolicy#evict(org.jboss.cache.Fqn)
151 */
152 public void evict(final Fqn fqn)
153 throws Exception {
154 cache.evict(fqn);
155 }
156
157 /***
158 * @see org.jboss.cache.eviction.EvictionPolicy#getChildrenNames(org.jboss.cache.Fqn)
159 */
160 public Set getChildrenNames(final Fqn fqn) {
161
162 try {
163 return cache.getChildrenNames(fqn);
164 } catch (LockingException le) {
165 messageLogger.logWarn(OPERATION_FAILED_MESSAGE, le);
166 throw new RuntimeException(le);
167 } catch (TimeoutException te) {
168 messageLogger.logWarn(OPERATION_FAILED_MESSAGE, te);
169 throw new RuntimeException(te);
170 } catch (CacheException ce) {
171 messageLogger.logWarn(OPERATION_FAILED_MESSAGE, ce);
172 throw new RuntimeException(ce);
173 }
174 }
175
176 /***
177 * @see org.jboss.cache.eviction.EvictionPolicy#hasChild(org.jboss.cache.Fqn)
178 */
179 public boolean hasChild(final Fqn fqn) {
180 return cache.hasChild(fqn);
181 }
182
183 /***
184 * @see org.jboss.cache.eviction.EvictionPolicy#getCacheData(org.jboss.cache.Fqn, java.lang.Object)
185 */
186 public Object getCacheData(final Fqn fqn, final Object key) {
187
188 try {
189 return cache.get(fqn, key);
190 } catch (LockingException le) {
191 messageLogger.logWarn(OPERATION_FAILED_MESSAGE, le);
192 throw new RuntimeException(le);
193 } catch (TimeoutException te) {
194 messageLogger.logWarn(OPERATION_FAILED_MESSAGE, te);
195 throw new RuntimeException(te);
196 } catch (CacheException ce) {
197 messageLogger.logWarn(OPERATION_FAILED_MESSAGE, ce);
198 throw new RuntimeException(ce);
199 }
200 }
201
202 /***
203 * @see org.jboss.cache.eviction.EvictionPolicy#getWakeupIntervalSeconds()
204 */
205 public int getWakeupIntervalSeconds() {
206 return wakeUpIntervalSeconds;
207 }
208
209 /***
210 * @see org.jboss.cache.TreeCacheListener#nodeAdded(org.jboss.cache.Fqn)
211 */
212 public void nodeCreated(final Fqn fqn) {
213 if (fqn.equals(ROOT))
214 return;
215
216 if (regionManager != null) {
217 Region region = regionManager.getRegion(fqn.toString());
218
219 if (region != null) {
220 region.setAddedNode(fqn);
221 messageLogger.logDebug("Node created: " + fqn);
222 } else {
223 messageLogger.logInfo("No region returned for created node: " + fqn);
224 }
225 } else {
226 messageLogger.logWarn("regionManager is null");
227 }
228 }
229
230 /***
231 * @see org.jboss.cache.TreeCacheListener#nodeLoaded(org.jboss.cache.Fqn)
232 */
233 public void nodeLoaded(final Fqn fqn) {
234 if (fqn.equals(ROOT))
235 return;
236
237 if (regionManager != null) {
238 Region region = regionManager.getRegion(fqn.toString());
239
240 if (region != null) {
241 region.setAddedNode(fqn);
242 messageLogger.logDebug("Node loaded: " + fqn);
243 } else {
244 messageLogger.logInfo("No region returned for loaded node: " + fqn);
245 }
246 } else {
247 messageLogger.logWarn("regionManager is null");
248 }
249 }
250
251 /***
252 * @see org.jboss.cache.TreeCacheListener#nodeRemoved(org.jboss.cache.Fqn)
253 */
254 public void nodeRemoved(final Fqn fqn) {
255 if (fqn.equals(ROOT))
256 return;
257
258 if (regionManager != null) {
259 Region region = regionManager.getRegion(fqn.toString());
260
261 if (region != null) {
262 region.setRemovedNode(fqn);
263 messageLogger.logDebug("Listener got node removed: " + fqn);
264 } else {
265 messageLogger.logInfo("No region returned for removed node: " + fqn);
266 }
267 } else {
268 messageLogger.logWarn("regionManager is null");
269 }
270 }
271
272 /***
273 * @see org.jboss.cache.TreeCacheListener#nodeEvicted(org.jboss.cache.Fqn)
274 */
275 public void nodeEvicted(final Fqn fqn) {
276 if (fqn.equals(ROOT))
277 return;
278
279 messageLogger.logDebug("Listener got node evicted: " + fqn);
280 }
281
282 /***
283 * @see org.jboss.cache.TreeCacheListener#nodeModified(org.jboss.cache.Fqn)
284 */
285 public void nodeModified(final Fqn fqn) {
286 if (fqn.equals(ROOT))
287 return;
288
289 if (regionManager != null) {
290 Region region = regionManager.getRegion(fqn.toString());
291
292 if (region != null) {
293 region.setVisitedNode(fqn);
294 messageLogger.logDebug("Listener got node modified: " + fqn);
295 } else {
296 messageLogger.logInfo("No region returned for modified node: " + fqn);
297 }
298 } else {
299 messageLogger.logWarn("regionManager is null");
300 }
301 }
302
303 /***
304 * @see org.jboss.cache.TreeCacheListener#nodeVisited(org.jboss.cache.Fqn)
305 */
306 public void nodeVisited(final Fqn fqn) {
307 if (fqn.equals(ROOT))
308 return;
309
310 if (regionManager != null) {
311 Region region = regionManager.getRegion(fqn.toString());
312
313 if (region != null) {
314 region.setVisitedNode(fqn);
315 messageLogger.logDebug("Listener got node visited: " + fqn);
316 } else {
317 messageLogger.logInfo("No region returned for visited node: " + fqn);
318 }
319 } else {
320 messageLogger.logWarn("regionManager is null");
321 }
322 }
323
324 /***
325 * @see org.jboss.cache.TreeCacheListener#cacheStarted(org.jboss.cache.TreeCache)
326 */
327 public void cacheStarted(final TreeCache cache) {
328 messageLogger.logInfo("Starting eviction policy using provider: " + this.getClass().getName());
329
330 if (!this.cache.equals(cache)) {
331 messageLogger.logWarn("Cache instance given on start not equal to configured cache.");
332 }
333
334 evictionTimer = new Timer();
335 evictionTimer.schedule(new EvictionTimerTask(this), 1000, wakeUpIntervalSeconds * 1000);
336 }
337
338 /***
339 * @see org.jboss.cache.TreeCacheListener#cacheStopped(org.jboss.cache.TreeCache)
340 */
341 public void cacheStopped(final TreeCache cache) {
342
343 if (!this.cache.equals(cache)) {
344 messageLogger.logWarn("Cache instance given on stop not equal to configured cache.");
345 }
346
347 if (evictionTimer != null) {
348 evictionTimer.cancel();
349 messageLogger.logInfo("Stopped eviction policy timer.");
350 } else {
351 messageLogger.logInfo("Cache stop called with uninitialized eviction timer.");
352 }
353 }
354
355 /***
356 * @see org.jboss.cache.TreeCacheListener#viewChange(org.jgroups.View)
357 */
358 public void viewChange(final View view) {
359 messageLogger.logDebug("Listener got a view change.");
360
361 Vector subGroupMembers = null;
362 final Object myAddress = cache.getLocalAddress();
363
364
365 if (view instanceof MergeView) {
366 messageLogger.logDebug("View is a MergeView!");
367 MergeView mergeView = (MergeView) view;
368
369 Vector subgroups = mergeView.getSubgroups();
370
371 for (int v = 0; v < subgroups.size(); v++) {
372 subGroupMembers = ((View) subgroups.get(v)).getMembers();
373
374 if (subGroupMembers.get(0).equals(myAddress)) {
375 break;
376 } else {
377 subGroupMembers = null;
378 }
379 }
380 } else {
381 return;
382 }
383
384
385
386 if (subGroupMembers != null) {
387 messageLogger.logDebug("This was previously a coordinator.");
388
389
390 ArrayList memberIds = new ArrayList();
391
392 for (Iterator i = subGroupMembers.iterator(); i.hasNext();) {
393
394 String nodeId;
395
396 Object memberAddress = i.next();
397
398 if (memberAddress instanceof IpAddress) {
399 IpAddress ipAddress = (IpAddress) memberAddress;
400 InetAddress inetAddress = ipAddress.getIpAddress();
401 nodeId = inetAddress.getHostAddress() + ":" + ipAddress.getPort();
402 } else {
403 messageLogger.logWarn("Skipping a group member. Address not an IpAddress: " + memberAddress);
404 continue;
405 }
406
407 memberIds.add(RandomId.pseudoBase64Encode(RandomId.nodeIdToByteArray(nodeId)));
408 }
409
410
411
412 List branches = MoriaTicketType.TICKET_TYPES;
413
414 int ticketCount = 0;
415
416 for (Iterator i = branches.iterator(); i.hasNext();) {
417
418 Node branch;
419 Map tickets;
420
421 try {
422 branch = cache.get(new Fqn(i.next()));
423 } catch (LockingException le) {
424 messageLogger.logCritical("Locking cache failed.", le);
425 continue;
426 } catch (TimeoutException te) {
427 messageLogger.logCritical("Got timeout from cache.", te);
428 continue;
429 } catch (CacheException ce) {
430 messageLogger.logCritical("Got exception from cache.", ce);
431 continue;
432 }
433
434 if (branch != null) {
435 tickets = branch.getChildren();
436 } else {
437 continue;
438 }
439
440
441 if (tickets == null)
442 continue;
443
444 for (Iterator j = tickets.keySet().iterator(); j.hasNext();) {
445 Object ticketId = j.next();
446 String nodeId = ticketId.toString().substring(0, 7);
447
448 for (Iterator k = memberIds.iterator(); k.hasNext();) {
449 String memberId = (String) k.next();
450
451 if (nodeId.equals(memberId)) {
452 Node ticket = (Node) tickets.get(ticketId);
453
454 if (ticket != null) {
455 try {
456 cache.put(ticket.getFqn(), ticket.getData());
457 ticketCount++;
458 } catch (RuntimeException re) {
459
460 throw re;
461 } catch (Exception e) {
462 messageLogger.logCritical("Insertion into store failed. [" + ticket.getName() + "]", e);
463 continue;
464 }
465
466
467 try {
468 Thread.sleep(30);
469 } catch (InterruptedException ie) {
470 messageLogger.logWarn("Thread sleep interrupted", ie);
471 }
472 }
473 }
474 }
475 }
476 }
477 messageLogger.logDebug("Number of redistributed tickets: " + ticketCount);
478 } else {
479 messageLogger.logDebug("Got a MergeView, but this wasn't a coordinator before.");
480 return;
481 }
482 }
483
484 /***
485 * Parses the config retrieved from TreeCache.getEvictionPolicyConfig(). Populates
486 * the regionValues array.
487 *
488 * @param config configuration for this eviction policy
489 */
490 synchronized void parseConfig(final Element config) {
491
492 if (config == null)
493 throw new IllegalArgumentException("config cannot be null");
494
495
496 wakeUpIntervalSeconds = Integer.parseInt(getAttribute(config, WAKEUP_INTERVAL_NAME));
497
498 if (wakeUpIntervalSeconds < 1) {
499 wakeUpIntervalSeconds = 5;
500 messageLogger.logWarn("Illegal value for config parameter wakeUpIntervalSeconds. Using default: "
501 + wakeUpIntervalSeconds);
502 }
503
504
505 maxNodes = Integer.parseInt(getAttribute(config, MAX_NODES_NAME));
506
507 if (maxNodes < 1) {
508 maxNodes = MAX_NODES_DEFAULT;
509 messageLogger.logWarn("Illegal value for config parameter maxNodes. Using default: " + maxNodes);
510 }
511
512
513 NodeList regions = config.getElementsByTagName(REGION_NAME);
514 regionValues = new RegionValue[regions.getLength()];
515
516 for (int i = 0; i < regionValues.length; i++) {
517 RegionValue regionValue = new RegionValue();
518 org.w3c.dom.Node node = regions.item(i);
519
520 if (node.getNodeType() != 1)
521 continue;
522
523 Element region = (Element) node;
524 regionValue.regionName = region.getAttribute(NAME_ATTRIBUTE_NAME);
525 regionValue.timeToLive = Long.parseLong(getAttribute(region, TTL_ATTRIBUTE_NAME)) * 1000L;
526
527 if (regionValue.regionName == null || regionValue.regionName.equals(""))
528 throw new EvictionConfigurationException("Illegal value for region name: " + regionValue.regionName);
529
530 if (regionValue.timeToLive < 1000L)
531 throw new EvictionConfigurationException("Illegal value for time to live: " + regionValue.timeToLive);
532
533 regionValues[i] = regionValue;
534 messageLogger.logDebug("Added region: " + regionValue);
535 }
536 }
537
538 /***
539 * Retrieves the value of the first occurance of the given
540 * attribute in the given element.
541 *
542 * @param element the element containing an named attribute.
543 * @param attributeName the name of the requested attribute.
544 * @return the value of the requested attribute, null if no attribute exists in element.
545 */
546 String getAttribute(final Element element, final String attributeName) {
547 NodeList nodes = element.getElementsByTagName(ATTRIBUTE_NAME);
548
549 for (int i = 0; i < nodes.getLength(); i++) {
550 org.w3c.dom.Node node = nodes.item(i);
551
552 if (node.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE)
553 continue;
554
555 Element subElement = (Element) node;
556 String name = subElement.getAttribute(NAME_ATTRIBUTE_NAME);
557
558 if (name.equals(attributeName))
559 return getElementContent(subElement, true);
560 }
561
562 return null;
563 }
564
565 /***
566 * Retrieves the textual content of an xml element.
567 *
568 * @param element The element containing the data to be retrieved.
569 * @param trim Whether or not to trim whitespace before returing.
570 * @return A concatenated string of the text of the child nodes of the element.
571 */
572 String getElementContent(final Element element, boolean trim) {
573 NodeList nodes = element.getChildNodes();
574 String attributeText = "";
575
576 for (int i = 0; i < nodes.getLength(); i++) {
577 org.w3c.dom.Node node = nodes.item(i);
578 if (node instanceof Text)
579 attributeText = attributeText + ((Text) node).getData();
580 }
581
582 if (trim)
583 attributeText = attributeText.trim();
584
585 return attributeText;
586 }
587
588 /***
589 * Gets the region values.
590 *
591 * @return Array containing the region values.
592 */
593 RegionValue[] getRegionValues() {
594 return regionValues;
595 }
596
597 /***
598 * Gets a region value.
599 *
600 * @param fqn Fully quailified name.
601 * @return A region value.
602 */
603 final RegionValue getRegionValue(final String fqn) {
604
605 for (int i = 0; i < regionValues.length; i++) {
606 if (fqn.startsWith(ROOT.toString() + regionValues[i].regionName))
607 return regionValues[i];
608 }
609
610 return null;
611 }
612
613 /***
614 * Simple data container for region values.
615 */
616 static final class RegionValue {
617
618 /*** Region name. */
619 private String regionName;
620
621 /*** Time to live. */
622 private long timeToLive;
623
624 /***
625 * Returns a string representation.
626 * @return The string representation.
627 */
628 public String toString() {
629 return "[" + regionName + ", " + timeToLive + "]";
630 }
631
632 /***
633 * Gets region name.
634 * @return The region name.
635 */
636 String getRegionName() {
637 return regionName;
638 }
639
640 /***
641 * Gets time to live.
642 * @return Time to live.
643 */
644 long getTimeToLive() {
645 return timeToLive;
646 }
647 }
648 }