001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright notice. All rights reserved.
003: */
004: package com.tcspring;
005:
006: import org.apache.commons.logging.Log;
007: import org.apache.commons.logging.LogFactory;
008: import org.springframework.beans.factory.BeanFactory;
009: import org.springframework.context.ApplicationEvent;
010: import org.springframework.context.support.AbstractApplicationContext;
011:
012: import com.tc.aspectwerkz.joinpoint.StaticJoinPoint;
013: import com.tc.object.bytecode.Manager;
014: import com.tc.object.bytecode.ManagerUtil;
015: import com.tc.object.config.DSOSpringConfigHelper;
016:
017: import java.util.ArrayList;
018: import java.util.Collection;
019: import java.util.HashMap;
020: import java.util.Iterator;
021: import java.util.List;
022: import java.util.Map;
023:
024: /**
025: * Manages the event multicast of ApplicationContext events. Preserves the context's ID, e.g. a multicast is a multicast
026: * only to the same "logical" contexts in the cluster (and its parents).
027: *
028: * @author Jonas Bonér
029: * @author Eugene Kuleshov
030: */
031: public class ApplicationContextEventProtocol {
032:
033: private static transient Log logger = LogFactory
034: .getLog(ApplicationContextEventProtocol.class);
035:
036: /**
037: * Local list of <code>ConfigurableApplicationContext</code> TODO use weak set or register for context disposal,
038: * e.g. close()
039: */
040: private static transient final Map contexts = new HashMap();
041:
042: private transient ThreadLocal isInMulticastEventCflow;
043:
044: private String appName;
045: private Collection configs;
046: private Collection beanNames;
047: private Collection eventTypes;
048:
049: /**
050: * Puts all clustered ApplicationContext instances into a node local HashMap .
051: * <p>
052: * Puts itself (the aspect) into a shared map, this is needed for DMI to work.
053: * <p>
054: * Puts some additional information into the aspect to be displayed as a regular info in the terracotta console.
055: * <p>
056: * Advises:
057: * <tt>
058: * before(
059: * withincode(* org.springframework.context.support.AbstractApplicationContext+.refresh())
060: * AND call(* org.springframework.context.support.AbstractApplicationContext+.publishEvent(..))
061: * AND target(ctx))
062: * </tt>
063: */
064: public void registerContext(StaticJoinPoint jp,
065: AbstractApplicationContext ctx) {
066: DistributableBeanFactory distributableBeanFactory = (DistributableBeanFactory) ctx
067: .getBeanFactory();
068: if (!distributableBeanFactory.isClustered()) {
069: return;
070: }
071:
072: String id = distributableBeanFactory.getId();
073: List configHelpers = distributableBeanFactory
074: .getSpringConfigHelpers();
075:
076: String lockName = "@spring_info_" + id;
077:
078: ManagerUtil.beginLock(lockName, Manager.LOCK_TYPE_WRITE);
079: try {
080: populateAspectWithApplicationContextInfo(
081: distributableBeanFactory, configHelpers);
082: List aspects = (List) ManagerUtil.lookupOrCreateRoot(
083: "tc:spring_info:" + id, new ArrayList());
084: aspects.add(this ); // needed for DMI
085: } finally {
086: ManagerUtil.commitLock(lockName);
087: }
088:
089: synchronized (contexts) {
090: if (!contexts.containsKey(id)) {
091: contexts.put(id, ctx);
092: }
093: }
094: }
095:
096: private void populateAspectWithApplicationContextInfo(
097: DistributableBeanFactory distributableBeanFactory,
098: List configHelpers) {
099: this .appName = distributableBeanFactory.getAppName();
100: this .configs = distributableBeanFactory.getLocations();
101: this .beanNames = new ArrayList();
102: this .eventTypes = new ArrayList();
103:
104: for (Iterator it = configHelpers.iterator(); it.hasNext();) {
105: DSOSpringConfigHelper configHelper = (DSOSpringConfigHelper) it
106: .next();
107: this .beanNames.addAll(configHelper.getDistributedBeans()
108: .keySet());
109: this .eventTypes.addAll(configHelper.getDistributedEvents());
110: }
111: }
112:
113: /**
114: * Intercepts the sending of a spring ApplicationContext event, this advice captures it and
115: * tries to publish it as a distributed event.
116: * Invoked around void org.springframework.context.support.AbstractApplicationContext.publishEvent(..)
117: */
118: public Object interceptEvent(StaticJoinPoint jp,
119: ApplicationEvent event, AbstractApplicationContext ctx)
120: throws Throwable {
121:
122: BeanFactory beanFactory = ctx.getBeanFactory();
123: if (tryToPublishDistributedEvent(beanFactory, event)) {
124: return null;
125: }
126:
127: return jp.proceed();
128: }
129:
130: /**
131: *Tries to publish the event as a distributed event, by invoking a distributed method invocation.
132: */
133: public boolean tryToPublishDistributedEvent(
134: BeanFactory beanFactory, ApplicationEvent event) {
135: if (isInMulticastEventCflow != null
136: && isInMulticastEventCflow.get() != null) {
137: // logger.info("Could not publish as distributed (nested) " + event);
138: return false;
139: }
140:
141: if (ignoredEventType(event)) {
142: // logger.info("Could not publish as distributed (ignored) " + event);
143: return false;
144: }
145: if (beanFactory instanceof DistributableBeanFactory) {
146: DistributableBeanFactory distributableBeanFactory = (DistributableBeanFactory) beanFactory;
147: if (distributableBeanFactory.isDistributedEvent(event
148: .getClass().getName())) {
149: String ctxId = distributableBeanFactory.getId();
150: // logger.info("Publishing distributed " + event);
151: boolean requireCommit = false;
152: try {
153: requireCommit = ManagerUtil
154: .distributedMethodCall(
155: this ,
156: "multicastEvent(Ljava/lang/String;Lorg/springframework/context/ApplicationEvent;)V",
157: new Object[] { ctxId, event });
158: multicastEvent(ctxId, event); // local call
159:
160: } catch (Throwable e) {
161: logger.error("Unable to send event " + event + "; "
162: + e.getMessage(), e);
163: } finally {
164: if (requireCommit) {
165: ManagerUtil.distributedMethodCallCommit();
166: }
167: }
168: return true;
169: // } else {
170: // logger.info("Could not publish as distributed (not distributed context) " + event);
171: }
172: }
173: return false;
174:
175: }
176:
177: // TODO make ignored classes configurable
178: private boolean ignoredEventType(ApplicationEvent event) {
179: String name = event.getClass().getName();
180: return "org.springframework.context.event.ContextRefreshedEvent"
181: .equals(name)
182: || "org.springframework.context.event.ContextClosedEvent"
183: .equals(name)
184: || "org.springframework.web.context.support.RequestHandledEvent"
185: .equals(name)
186: || "org.springframework.web.context.support.ServletRequestHandledEvent"
187: .equals(name); // since 2.0
188: }
189:
190: /**
191: * [Distributed Method Invocation]
192: * <p>
193: * 1. Gets the context by ID - needed since the DMI is invoked on another node and
194: * need to look up [it's context].
195: * <p>
196: * 2. Publish event in the context matching the ID
197: */
198: public void multicastEvent(final String ctxId,
199: final ApplicationEvent event) {
200: AbstractApplicationContext context;
201: synchronized (contexts) {
202: context = (AbstractApplicationContext) contexts.get(ctxId);
203: }
204:
205: logger.info(ctxId + " Publishing event " + event + " to "
206: + context + " " + Thread.currentThread());
207:
208: if (context != null) {
209: publish(context, event);
210: }
211: }
212:
213: private void publish(AbstractApplicationContext context,
214: final ApplicationEvent event) {
215: if (isInMulticastEventCflow == null) {
216: isInMulticastEventCflow = new ThreadLocal();
217: }
218: Integer n = (Integer) isInMulticastEventCflow.get();
219: if (n == null) {
220: isInMulticastEventCflow.set(new Integer(0));
221: } else {
222: isInMulticastEventCflow.set(new Integer(n.intValue() + 1));
223: }
224: try {
225: context.publishEvent(event);
226: } finally {
227: n = (Integer) isInMulticastEventCflow.get();
228: if (n == null || n.intValue() == 0) {
229: isInMulticastEventCflow.set(null);
230: } else {
231: isInMulticastEventCflow.set(new Integer(
232: n.intValue() - 1));
233: }
234: }
235: }
236:
237: public String toString() {
238: return appName + ":" + configs;
239: }
240: }
|