1 16 package org.apache.cocoon.components.flow; 17 18 import org.apache.avalon.framework.component.Component; 19 import org.apache.avalon.framework.configuration.Configurable; 20 import org.apache.avalon.framework.configuration.Configuration; 21 import org.apache.avalon.framework.context.Context; 22 import org.apache.avalon.framework.context.ContextException; 23 import org.apache.avalon.framework.context.Contextualizable; 24 import org.apache.avalon.framework.logger.AbstractLogEnabled; 25 import org.apache.avalon.framework.service.ServiceException; 26 import org.apache.avalon.framework.service.ServiceManager; 27 import org.apache.avalon.framework.service.Serviceable; 28 import org.apache.avalon.framework.thread.ThreadSafe; 29 import org.apache.cocoon.components.ContextHelper; 30 import org.apache.cocoon.components.thread.RunnableManager; 31 import org.apache.cocoon.environment.ObjectModelHelper; 32 import org.apache.cocoon.environment.Request; 33 import org.apache.cocoon.environment.Session; 34 35 import org.apache.excalibur.instrument.CounterInstrument; 36 import org.apache.excalibur.instrument.Instrument; 37 import org.apache.excalibur.instrument.Instrumentable; 38 import org.apache.excalibur.instrument.ValueInstrument; 39 40 import java.security.SecureRandom ; 41 import java.util.ArrayList ; 42 import java.util.Collections ; 43 import java.util.HashMap ; 44 import java.util.HashSet ; 45 import java.util.Iterator ; 46 import java.util.List ; 47 import java.util.Map ; 48 import java.util.Set ; 49 import java.util.SortedSet ; 50 import java.util.TreeSet ; 51 52 import javax.servlet.http.HttpSessionBindingEvent ; 53 import javax.servlet.http.HttpSessionBindingListener ; 54 55 76 public class ContinuationsManagerImpl 77 extends AbstractLogEnabled 78 implements ContinuationsManager, Component, Configurable, 79 ThreadSafe, Instrumentable, Serviceable, Contextualizable { 80 81 static final int CONTINUATION_ID_LENGTH = 20; 82 static final String EXPIRE_CONTINUATIONS = "expire-continuations"; 83 84 87 protected SecureRandom random; 88 protected byte[] bytes; 89 90 94 protected int defaultTimeToLive; 95 96 101 protected Set forest = Collections.synchronizedSet(new HashSet ()); 102 103 107 protected WebContinuationsHolder continuationsHolder; 108 109 114 protected SortedSet expirations = Collections.synchronizedSortedSet(new TreeSet ()); 115 116 protected String instrumentableName; 117 protected ValueInstrument continuationsCount; 118 protected int continuationsCounter; 119 protected ValueInstrument forestSize; 120 protected ValueInstrument expirationsSize; 121 protected CounterInstrument continuationsCreated; 122 protected CounterInstrument continuationsInvalidated; 123 protected boolean isContinuationSharingBugCompatible; 124 protected boolean bindContinuationsToSession; 125 126 protected ServiceManager serviceManager; 127 protected Context context; 128 129 public ContinuationsManagerImpl() throws Exception { 130 131 try { 132 random = SecureRandom.getInstance("SHA1PRNG"); 133 } catch(java.security.NoSuchAlgorithmException nsae) { 134 random = SecureRandom.getInstance("IBMSecureRandom"); 136 } 137 random.setSeed(System.currentTimeMillis()); 138 bytes = new byte[CONTINUATION_ID_LENGTH]; 139 140 continuationsCount = new ValueInstrument("count"); 141 continuationsCounter = 0; 142 forestSize = new ValueInstrument("forest-size"); 143 expirationsSize = new ValueInstrument("expirations-size"); 144 continuationsCreated = new CounterInstrument("creates"); 145 continuationsInvalidated = new CounterInstrument("invalidates"); 146 } 147 148 151 public void service(final ServiceManager manager) throws ServiceException { 152 this.serviceManager = manager; 153 } 154 155 158 public void configure(Configuration config) { 159 this.defaultTimeToLive = config.getAttributeAsInteger("time-to-live", (3600 * 1000)); 160 this.isContinuationSharingBugCompatible = config.getAttributeAsBoolean("continuation-sharing-bug-compatible", false); 161 this.bindContinuationsToSession = config.getAttributeAsBoolean( "session-bound-continuations", false ); 162 163 if(!this.bindContinuationsToSession) { 165 this.continuationsHolder = new WebContinuationsHolder(); 166 } 167 168 final Configuration expireConf = config.getChild("expirations-check"); 170 final long initialDelay = expireConf.getChild("offset", true).getValueAsLong(180000); 171 final long interval = expireConf.getChild("period", true).getValueAsLong(180000); 172 try { 173 final RunnableManager runnableManager = (RunnableManager)serviceManager.lookup(RunnableManager.ROLE); 174 runnableManager.execute( new Runnable () { 175 public void run() 176 { 177 expireContinuations(); 178 } 179 }, initialDelay, interval); 180 serviceManager.release(runnableManager); 181 } catch (Exception e) { 182 getLogger().warn("Could not enqueue continuations expiration task. " + 183 "Continuations will not automatically expire.", e); 184 } 185 } 186 187 190 public void setInstrumentableName(String instrumentableName) { 191 this.instrumentableName = instrumentableName; 192 } 193 194 197 public String getInstrumentableName() { 198 return instrumentableName; 199 } 200 201 204 public Instrument[] getInstruments() { 205 return new Instrument[]{ 206 continuationsCount, 207 continuationsCreated, 208 continuationsInvalidated, 209 forestSize 210 }; 211 } 212 213 216 public Instrumentable[] getChildInstrumentables() { 217 return Instrumentable.EMPTY_INSTRUMENTABLE_ARRAY; 218 } 219 220 223 public WebContinuation createWebContinuation(Object kont, 224 WebContinuation parent, 225 int timeToLive, 226 String interpreterId, 227 ContinuationsDisposer disposer) { 228 int ttl = (timeToLive == 0 ? defaultTimeToLive : timeToLive); 229 230 WebContinuation wk = generateContinuation(kont, parent, ttl, interpreterId, disposer); 231 wk.enableLogging(getLogger()); 232 233 if (parent == null) { 234 forest.add(wk); 235 forestSize.setValue(forest.size()); 236 } else { 237 handleParentContinuationExpiration(parent); 238 } 239 240 handleLeafContinuationExpiration(wk); 241 242 if (getLogger().isDebugEnabled()) { 243 getLogger().debug("WK: Created continuation " + wk.getId()); 244 } 245 246 return wk; 247 } 248 249 253 protected void handleLeafContinuationExpiration(WebContinuation wk) { 254 expirations.add(wk); 255 expirationsSize.setValue(expirations.size()); 256 } 257 258 263 protected void handleParentContinuationExpiration(WebContinuation parent) { 264 if (parent.getChildren().size() < 2) { 265 expirations.remove(parent); 266 } 267 } 268 269 270 273 public WebContinuation lookupWebContinuation(String id, String interpreterId) { 274 WebContinuationsHolder continuationsHolder = lookupWebContinuationsHolder(false); 277 if (continuationsHolder == null) { 278 return null; 279 } 280 281 WebContinuation kont = continuationsHolder.get(id); 282 if(kont != null) { 283 boolean interpreterMatches = kont.interpreterMatches(interpreterId); 284 if (!interpreterMatches && getLogger().isWarnEnabled()) { 285 getLogger().warn("WK: Continuation (" + kont.getId() 286 + ") lookup for wrong interpreter. Bound to: " 287 + kont.getInterpreterId() + ", looked up for: " 288 + interpreterId); 289 } 290 return interpreterMatches || isContinuationSharingBugCompatible ? kont : null; 291 } 292 return null; 293 } 294 295 308 protected WebContinuation generateContinuation(Object kont, 309 WebContinuation parent, 310 int ttl, 311 String interpreterId, 312 ContinuationsDisposer disposer) { 313 314 char[] result = new char[bytes.length * 2]; 315 WebContinuation wk = null; 316 WebContinuationsHolder continuationsHolder = lookupWebContinuationsHolder(true); 317 while (true) { 318 random.nextBytes(bytes); 319 320 for (int i = 0; i < CONTINUATION_ID_LENGTH; i++) { 321 byte ch = bytes[i]; 322 result[2 * i] = Character.forDigit(Math.abs(ch >> 4), 16); 323 result[2 * i + 1] = Character.forDigit(Math.abs(ch & 0x0f), 16); 324 } 325 326 final String id = new String (result); 327 synchronized (continuationsHolder) { 328 if (!continuationsHolder.contains(id)) { 329 if (this.bindContinuationsToSession) { 330 wk = new HolderAwareWebContinuation(id, kont, parent, 331 ttl, interpreterId, disposer, 332 continuationsHolder); 333 } 334 else { 335 wk = new WebContinuation(id, kont, parent, ttl, 336 interpreterId, disposer); 337 } 338 continuationsHolder.addContinuation(wk); 339 synchronized (continuationsCount) { 340 continuationsCounter++; 341 continuationsCount.setValue(continuationsCounter); 342 } 343 break; 344 } 345 } 346 } 347 348 continuationsCreated.increment(); 349 return wk; 350 } 351 352 355 public void invalidateWebContinuation(WebContinuation wk) { 356 WebContinuationsHolder continuationsHolder = lookupWebContinuationsHolder(false); 357 if (!continuationsHolder.contains(wk)) { 358 return; 360 } 361 _detach(wk); 362 _invalidate(continuationsHolder, wk); 363 } 364 365 protected void _invalidate(WebContinuationsHolder continuationsHolder, WebContinuation wk) { 366 if (getLogger().isDebugEnabled()) { 367 getLogger().debug("WK: Manual expire of continuation " + wk.getId()); 368 } 369 disposeContinuation(continuationsHolder, wk); 370 expirations.remove(wk); 371 expirationsSize.setValue(expirations.size()); 372 373 List children = wk.getChildren(); 375 int size = children.size(); 376 for (int i = 0; i < size; i++) { 377 _invalidate(continuationsHolder, (WebContinuation) children.get(i)); 378 } 379 } 380 381 387 private void _detach(WebContinuation wk) { 388 WebContinuation parent = wk.getParentContinuation(); 389 if (parent == null) { 390 forest.remove(wk); 391 forestSize.setValue(forest.size()); 392 } else 393 wk.detachFromParent(); 394 } 395 396 403 protected void disposeContinuation(WebContinuationsHolder continuationsHolder, WebContinuation wk) { 404 continuationsHolder.removeContinuation(wk); 405 synchronized( continuationsCount ) { 406 continuationsCounter--; 407 continuationsCount.setValue(continuationsCounter); 408 } 409 wk.dispose(); 410 continuationsInvalidated.increment(); 411 } 412 413 421 protected void removeContinuation(WebContinuationsHolder continuationsHolder, WebContinuation wk) { 422 if (wk.getChildren().size() != 0) { 423 return; 424 } 425 426 disposeContinuation(continuationsHolder, wk); 428 _detach(wk); 429 430 if (getLogger().isDebugEnabled()) { 431 getLogger().debug("WK: Deleted continuation: " + wk.getId()); 432 } 433 434 WebContinuation parent = wk.getParentContinuation(); 436 if (null != parent && parent.hasExpired()) { 437 removeContinuation(continuationsHolder, parent); 439 } 440 } 441 442 446 protected void displayExpireSet() { 447 StringBuffer wkSet = new StringBuffer ("\nWK; Expire set size: " + expirations.size()); 448 Iterator i = expirations.iterator(); 449 while (i.hasNext()) { 450 final WebContinuation wk = (WebContinuation) i.next(); 451 final long lat = wk.getLastAccessTime() + wk.getTimeToLive(); 452 wkSet.append("\nWK: ") 453 .append(wk.getId()) 454 .append(" ExpireTime ["); 455 456 if (lat < System.currentTimeMillis()) { 457 wkSet.append("Expired"); 458 } else { 459 wkSet.append(lat); 460 } 461 wkSet.append("]"); 462 } 463 464 getLogger().debug(wkSet.toString()); 465 } 466 467 471 public void displayAllContinuations() { 472 final Iterator i = forest.iterator(); 473 while (i.hasNext()) { 474 ((WebContinuation) i.next()).display(); 475 } 476 } 477 478 481 protected void expireContinuations() { 482 long now = 0; 483 if (getLogger().isDebugEnabled()) { 484 now = System.currentTimeMillis(); 485 486 487 getLogger().debug("WK: Forest before cleanup: " + forest.size()); 488 displayAllContinuations(); 489 displayExpireSet(); 490 491 } 492 493 int count = 0; 495 WebContinuation wk; 496 Iterator i = expirations.iterator(); 497 while(i.hasNext() && ((wk = (WebContinuation) i.next()).hasExpired())) { 498 i.remove(); 499 WebContinuationsHolder continuationsHolder = null; 500 if(wk instanceof HolderAwareWebContinuation) { 501 continuationsHolder = ((HolderAwareWebContinuation) wk).getContinuationsHolder(); 502 } 503 else { 504 continuationsHolder = this.continuationsHolder; 505 } 506 removeContinuation(continuationsHolder, wk); 507 count++; 508 } 509 expirationsSize.setValue(expirations.size()); 510 511 if (getLogger().isDebugEnabled()) { 512 getLogger().debug("WK Cleaned up " + count + " continuations in " + 513 (System.currentTimeMillis() - now) + " ms"); 514 515 516 520 } 521 } 522 523 528 protected void invalidateContinuations( 529 WebContinuationsHolder continuationsHolder) { 530 Object [] continuationIds = continuationsHolder.getContinuationIds() 533 .toArray(); 534 535 for (int i = 0; i < continuationIds.length; i++) { 536 WebContinuation wk = continuationsHolder.get(continuationIds[i]); 537 if (wk != null) { 538 _detach(wk); 539 _invalidate(continuationsHolder, wk); 540 } 541 } 542 } 543 544 550 public WebContinuationsHolder lookupWebContinuationsHolder(boolean createNew) { 551 if (!this.bindContinuationsToSession) 553 return this.continuationsHolder; 554 555 Map objectModel = ContextHelper.getObjectModel(this.context); 557 Request request = ObjectModelHelper.getRequest(objectModel); 558 559 if (!createNew && request.getSession(false) == null) 560 return null; 561 562 Session session = request.getSession(true); 563 WebContinuationsHolder holder = 564 (WebContinuationsHolder) session.getAttribute( 565 WebContinuationsHolder.CONTINUATIONS_HOLDER); 566 if (!createNew) 567 return holder; 568 569 if (holder != null) 570 return holder; 571 572 holder = new WebContinuationsHolder(); 573 session.setAttribute(WebContinuationsHolder.CONTINUATIONS_HOLDER, 574 holder); 575 return holder; 576 } 577 578 582 protected class WebContinuationsHolder implements HttpSessionBindingListener { 583 private final static String CONTINUATIONS_HOLDER = "o.a.c.c.f.SCMI.WebContinuationsHolder"; 584 585 private Map holder = Collections.synchronizedMap(new HashMap ()); 586 587 public WebContinuation get(Object id) { 588 return (WebContinuation) this.holder.get(id); 589 } 590 591 public void addContinuation(WebContinuation wk) { 592 this.holder.put(wk.getId(), wk); 593 } 594 595 public void removeContinuation(WebContinuation wk) { 596 this.holder.remove(wk.getId()); 597 } 598 599 public Set getContinuationIds() { 600 return holder.keySet(); 601 } 602 603 public boolean contains(String continuationId) { 604 return this.holder.containsKey(continuationId); 605 } 606 607 public boolean contains(WebContinuation wk) { 608 return contains(wk.getId()); 609 } 610 611 public void valueBound(HttpSessionBindingEvent event) { 612 } 613 614 public void valueUnbound(HttpSessionBindingEvent event) { 615 invalidateContinuations(this); 616 } 617 } 618 619 624 protected static class HolderAwareWebContinuation extends WebContinuation { 625 private WebContinuationsHolder continuationsHolder; 626 627 public HolderAwareWebContinuation(String id, Object continuation, 628 WebContinuation parentContinuation, int timeToLive, 629 String interpreterId, ContinuationsDisposer disposer, 630 WebContinuationsHolder continuationsHolder) { 631 super(id, continuation, parentContinuation, timeToLive, 632 interpreterId, disposer); 633 this.continuationsHolder = continuationsHolder; 634 } 635 636 public WebContinuationsHolder getContinuationsHolder() { 637 return continuationsHolder; 638 } 639 640 public int compareTo(Object other) { 642 return super.compareTo(other); 643 } 644 } 645 646 649 public void contextualize(Context context) throws ContextException { 650 this.context = context; 651 } 652 653 656 public List getWebContinuationsDataBeanList() { 657 List beanList = new ArrayList (); 658 for(Iterator it = this.forest.iterator(); it.hasNext();) { 659 beanList.add(new WebContinuationDataBean((WebContinuation) it.next())); 660 } 661 return beanList; 662 } 663 664 } 665 | Popular Tags |