001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tcspring;
006:
007: import org.apache.commons.logging.Log;
008: import org.apache.commons.logging.LogFactory;
009: import org.springframework.beans.factory.support.AbstractBeanDefinition;
010: import org.springframework.beans.factory.support.ChildBeanDefinition;
011:
012: import com.tc.object.TCClass;
013: import com.tc.object.bytecode.ByteCodeUtil;
014: import com.tc.object.bytecode.Manageable;
015: import com.tc.object.bytecode.Manager;
016: import com.tc.object.bytecode.ManagerUtil;
017: import com.tc.object.bytecode.hook.DSOContext;
018: import com.tc.object.config.DSOSpringConfigHelper;
019: import com.tc.object.field.TCField;
020:
021: import java.io.PrintWriter;
022: import java.io.StringWriter;
023: import java.io.UnsupportedEncodingException;
024: import java.lang.reflect.Field;
025: import java.lang.reflect.Modifier;
026: import java.security.MessageDigest;
027: import java.security.NoSuchAlgorithmException;
028: import java.util.ArrayList;
029: import java.util.Collections;
030: import java.util.HashMap;
031: import java.util.Iterator;
032: import java.util.List;
033: import java.util.Map;
034: import java.util.Set;
035:
036: /**
037: * Holds a unique ID for all the instance the mixin is applied to. Usage: 1. Call addLocation() 1 or more times 2. call
038: * registerBeanDefinitions() once 3. Call getters (is...() or get...()) as many times as you like
039: *
040: * @author Eugene Kuleshov
041: * @author Jonas Bonér TODO performance optimization of the access to <code>DSOSpringConfigHelper</code>
042: */
043: public final class DistributableBeanFactoryMixin implements
044: DistributableBeanFactory {
045:
046: private final transient Log logger = LogFactory.getLog(getClass());
047:
048: private final String appName;
049: private final DSOContext dsoContext;
050:
051: private List springConfigHelpers = Collections
052: .synchronizedList(new ArrayList());
053:
054: private Map beanDefinitions = Collections
055: .synchronizedMap(new HashMap());
056:
057: private String id;
058: private List locations = new ArrayList();
059:
060: private boolean isClustered = false;
061: private Map clusteredBeans = new HashMap();
062:
063: private final ManagerUtilWrapper managerUtilWrapper;
064: private final Set nonDistributables;
065:
066: public DistributableBeanFactoryMixin() {
067: ApplicationHelper applicationHelper = new ApplicationHelper(
068: getClass());
069: if (applicationHelper.isDSOApplication()) {
070: this .appName = applicationHelper.getAppName();
071: this .dsoContext = applicationHelper.getDsoContext();
072: } else {
073: this .appName = null;
074: this .dsoContext = null;
075: }
076:
077: this .managerUtilWrapper = new ManagerUtilWrapperImpl();
078:
079: // NonDistributableObjectRegistry nonDistributableObjectRegistry = NonDistributableObjectRegistry.getInstance();
080: // if (nonDistributableObjectRegistry.isAdded() == false) {
081: // ManagerUtil.addTraverseTest(nonDistributableObjectRegistry);
082: // nonDistributableObjectRegistry.setAdded();
083: // }
084: // this.nonDistributables = nonDistributableObjectRegistry.getNondistributables();
085: this .nonDistributables = Collections.EMPTY_SET;
086: }
087:
088: protected DistributableBeanFactoryMixin(String appName,
089: DSOContext dsoContext,
090: ManagerUtilWrapper managerUtilWrapper, Set nonDistributables) {
091: this .appName = appName;
092: this .dsoContext = dsoContext;
093: this .managerUtilWrapper = managerUtilWrapper;
094: this .nonDistributables = nonDistributables;
095: }
096:
097: public boolean isClustered() {
098: return isClustered;
099: }
100:
101: public String getAppName() {
102: return appName;
103: }
104:
105: public String getId() {
106: return id;
107: }
108:
109: public List getLocations() {
110: return locations;
111: }
112:
113: public List getSpringConfigHelpers() {
114: return this .springConfigHelpers;
115: }
116:
117: public boolean isDistributedEvent(String className) {
118: for (Iterator it = this .springConfigHelpers.iterator(); it
119: .hasNext();) {
120: DSOSpringConfigHelper springConfigHelper = (DSOSpringConfigHelper) it
121: .next();
122: if (springConfigHelper.isDistributedEvent(className)) {
123: return true;
124: }
125: }
126: return false;
127: }
128:
129: public boolean isDistributedScoped(String beanName) {
130: AbstractBeanDefinition definition = (AbstractBeanDefinition) beanDefinitions
131: .get(beanName);
132: // method definition.isPrototype() is Spring 2.0+
133: return definition != null && !definition.isSingleton()
134: && !definition.isPrototype();
135: }
136:
137: public boolean isDistributedSingleton(String beanName) {
138: AbstractBeanDefinition definition = (AbstractBeanDefinition) beanDefinitions
139: .get(beanName);
140: return definition != null && definition.isSingleton();
141: }
142:
143: public boolean isDistributedBean(String beanName) {
144: for (Iterator it = this .springConfigHelpers.iterator(); it
145: .hasNext();) {
146: DSOSpringConfigHelper springConfigHelper = (DSOSpringConfigHelper) it
147: .next();
148: if (springConfigHelper.isDistributedBean(beanName)) {
149: logger.debug(id + " bean " + beanName
150: + " is distributed");
151: return true;
152: }
153: }
154: logger.debug(id + " bean " + beanName + " is NOT distributed");
155: return false;
156: }
157:
158: public boolean isDistributedField(String beanName, String fieldName) {
159: for (Iterator it = this .springConfigHelpers.iterator(); it
160: .hasNext();) {
161: DSOSpringConfigHelper springConfigHelper = (DSOSpringConfigHelper) it
162: .next();
163: if (springConfigHelper.isDistributedField(beanName,
164: fieldName)) {
165: logger.debug(id + " field " + fieldName + " in bean "
166: + beanName + " is distributed");
167: return true;
168: }
169: }
170: logger.debug(id + " field " + fieldName + " in bean "
171: + beanName + " is NOT distributed");
172: return false;
173: }
174:
175: public void addLocation(String location) {
176: this .locations.add(location);
177: }
178:
179: private String calculateId(DSOSpringConfigHelper config) {
180: if (config != null && config.getRootName() != null) {
181: return config.getRootName();
182: }
183:
184: StringWriter sw = new StringWriter();
185: PrintWriter pw = new PrintWriter(sw);
186: pw.println("app " + this .appName);
187: for (Iterator iter = locations.iterator(); iter.hasNext();) {
188: String loc = (String) iter.next();
189: pw.println("params " + loc);
190: }
191:
192: if (config != null && config.isLocationInfoEnabled()) {
193: new Throwable().printStackTrace(pw);
194: }
195:
196: return getDigest(sw.toString());
197: }
198:
199: private void determineIfClustered() {
200: for (Iterator iter = this .dsoContext
201: .getDSOSpringConfigHelpers().iterator(); iter.hasNext();) {
202: DSOSpringConfigHelper config = (DSOSpringConfigHelper) iter
203: .next();
204: if (config.isMatchingApplication(this .appName)
205: && isMatchingLocations(config)) {
206: this .springConfigHelpers.add(config);
207: this .isClustered = true;
208: this .id = calculateId(config);
209: logger.info(id + " Matching locations:" + locations);
210: }
211: }
212:
213: if (this .isClustered) {
214: logger.info(id + " Context is distributed");
215: } else {
216: this .id = calculateId(null);
217: logger.info(id + " Context is NOT distributed");
218: }
219: }
220:
221: private boolean isMatchingLocations(DSOSpringConfigHelper config) {
222: for (Iterator iter = locations.iterator(); iter.hasNext();) {
223: String location = (String) iter.next();
224: if (config.isMatchingConfig(location)) {
225: return true;
226: }
227: }
228: return false;
229: }
230:
231: public void registerBeanDefinitions(Map beanMap) {
232: determineIfClustered();
233:
234: if (!this .isClustered) {
235: return;
236: }
237:
238: for (Iterator it = springConfigHelpers.iterator(); it.hasNext();) {
239: DSOSpringConfigHelper configHelper = (DSOSpringConfigHelper) it
240: .next();
241: if (configHelper.isMatchingApplication(this .appName)) {
242: registerDistributedEvents(configHelper
243: .getDistributedEvents());
244: registerDistributedBeans(configHelper
245: .getDistributedBeans(), beanMap);
246: }
247: }
248:
249: String lockName = "@spring_context_" + this .id;
250:
251: managerUtilWrapper.beginLock(lockName, Manager.LOCK_TYPE_WRITE);
252: try {
253: this .clusteredBeans = (Map) managerUtilWrapper
254: .lookupOrCreateRoot("tc:spring_context:" + this .id,
255: this .clusteredBeans);
256: } finally {
257: managerUtilWrapper.commitLock(lockName);
258: }
259: }
260:
261: protected void registerDistributedEvents(
262: final List distributedEvents) {
263: ClassHierarchyWalker walker = new ClassHierarchyWalker(id,
264: dsoContext);
265:
266: for (Iterator eventIterator = distributedEvents.iterator(); eventIterator
267: .hasNext();) {
268: String event = (String) eventIterator.next();
269: // instrument only exact classes to avoid conflicts
270: if (event.indexOf('*') == -1) {
271: walker.walkClass(event, getClass().getClassLoader());
272: }
273: }
274: }
275:
276: protected void registerDistributedBeans(Map distributedBeans,
277: Map beanMap) {
278: ClassHierarchyWalker walker = new ClassHierarchyWalker(id,
279: dsoContext);
280:
281: for (Iterator beanMapIterator = beanMap.entrySet().iterator(); beanMapIterator
282: .hasNext();) {
283: Map.Entry entry = (Map.Entry) beanMapIterator.next();
284: String beanName = (String) entry.getKey();
285: AbstractBeanDefinition definition = (AbstractBeanDefinition) entry
286: .getValue();
287:
288: Set excludedFields = (Set) distributedBeans.get(beanName);
289: if (excludedFields != null) {
290: beanDefinitions.put(beanName, definition); // need to unregister on reload/destroy
291:
292: String beanClassName = getBeanClassName(definition,
293: beanMap);
294:
295: walker.walkClass(beanClassName, getClass()
296: .getClassLoader());
297:
298: logger.info(this .id
299: + " registering transient fields for "
300: + beanName + " " + beanClassName);
301: for (Iterator fieldIterator = excludedFields.iterator(); fieldIterator
302: .hasNext();) {
303: String fieldName = (String) fieldIterator.next();
304: logger.info(this .id + " adding transient field "
305: + beanClassName + "." + fieldName);
306: dsoContext.addTransient(beanClassName, fieldName);
307: }
308: }
309:
310: // process bean metadata
311: // String[] names = definition.attributeNames();
312: // for (int i = 0; i < names.length; i++) {
313: // String name = names[i];
314: // Object value = definition.getAttribute(name);
315: // if ("com.terracotta.Distributed".equals(name)) {
316: // // TODO handle singleton attribute
317: //
318: // } else if ("com.terracotta.Transient".equals(name)) {
319: // // TODO handle transient attribute
320: //
321: // } else if ("com.terracotta.AutoLock".equals(name)) {
322: // // TODO handle autolock attribute
323: //
324: // } else {
325: // // etc...
326: //
327: // }
328: // }
329: }
330: }
331:
332: /**
333: * Get bean's class name. If necessary, walk to the parent beans.
334: */
335: static String getBeanClassName(AbstractBeanDefinition definition,
336: Map beanMap) {
337: String beanClassName = definition.getBeanClassName();
338: if (beanClassName == null
339: && definition instanceof ChildBeanDefinition) {
340: String parent = ((ChildBeanDefinition) definition)
341: .getParentName();
342: definition = (AbstractBeanDefinition) beanMap.get(parent);
343: if (definition != null) {
344: return getBeanClassName(definition, beanMap);
345: }
346: }
347: return beanClassName;
348: }
349:
350: private String getDigest(String s) {
351: try {
352: s = s
353: .replaceAll(System.getProperty("line.separator"),
354: "\n");
355: MessageDigest digest = MessageDigest.getInstance("MD5");
356: digest.update(s.getBytes("ASCII"));
357: byte[] b = digest.digest();
358:
359: StringBuffer sb = new StringBuffer();
360: String hex = "0123456789ABCDEF";
361: for (int i = 0; i < b.length; i++) {
362: int n = b[i];
363: sb.append(hex.charAt((n & 0xF) >> 4)).append(
364: hex.charAt(n & 0xF));
365: }
366: return sb.toString();
367:
368: } catch (NoSuchAlgorithmException e) {
369: // should never happens
370: throw new RuntimeException(e.getMessage());
371: } catch (UnsupportedEncodingException e) {
372: // should never happens
373: throw new RuntimeException(e.getMessage());
374: }
375: }
376:
377: public BeanContainer getBeanContainer(ComplexBeanId beanId) {
378: ManagerUtil.monitorEnter(this .clusteredBeans,
379: Manager.LOCK_TYPE_READ);
380: try {
381: return (BeanContainer) clusteredBeans.get(beanId);
382: } finally {
383: ManagerUtil.monitorExit(this .clusteredBeans);
384: }
385: }
386:
387: public BeanContainer putBeanContainer(ComplexBeanId beanId,
388: BeanContainer container) {
389: ManagerUtil.monitorEnter(this .clusteredBeans,
390: Manager.LOCK_TYPE_WRITE);
391: try {
392: return (BeanContainer) clusteredBeans
393: .put(beanId, container);
394: } finally {
395: ManagerUtil.monitorExit(this .clusteredBeans);
396: }
397: }
398:
399: public BeanContainer removeBeanContainer(ComplexBeanId beanId) {
400: ManagerUtil.monitorEnter(this .clusteredBeans,
401: Manager.LOCK_TYPE_WRITE);
402: try {
403: return (BeanContainer) clusteredBeans.remove(beanId);
404: } finally {
405: ManagerUtil.monitorExit(this .clusteredBeans);
406: }
407: }
408:
409: public void initializeBean(ComplexBeanId beanId, Object bean,
410: BeanContainer container) {
411: logger.info(getId() + " Initializing distributed bean "
412: + beanId);
413:
414: // TODO make initialization from shadow local copy optional
415:
416: Object distributed = container.getBean();
417: try {
418: copyTransientFields(beanId.getBeanName(), bean,
419: distributed, distributed.getClass(),
420: ((Manageable) distributed).__tc_managed()
421: .getTCClass());
422: } catch (Throwable e) {
423: // TODO should we fail here?
424: logger.warn(getId()
425: + " Error when copying transient fields to "
426: + beanId, e);
427: }
428: }
429:
430: private void copyTransientFields(String beanName,
431: Object sourceBean, Object targetBean, Class targetClass,
432: TCClass tcClass) throws IllegalAccessException {
433: if (tcClass.isLogical()) {
434: return;
435: }
436:
437: Field[] declaredFields = targetClass.getDeclaredFields();
438: for (int i = 0; i < declaredFields.length; i++) {
439: Field f = declaredFields[i];
440:
441: if ((f.getModifiers() & (Modifier.FINAL | Modifier.STATIC | Modifier.NATIVE)) != 0) {
442: continue;
443: }
444:
445: String fieldName = f.getName();
446: if (fieldName.startsWith(ByteCodeUtil.TC_FIELD_PREFIX)) {
447: continue;
448: }
449:
450: TCField tcf = tcClass.getField(targetClass.getName() + "."
451: + fieldName);
452: f.setAccessible(true);
453: Object value = f.get(sourceBean);
454:
455: if (tcf == null || !tcf.isPortable()
456: || !this .isDistributedField(beanName, fieldName)
457: || nonDistributables.contains(value)) {
458: logger.info(this .getId() + " Initializing field "
459: + fieldName + " in bean " + beanName);
460: f.set(targetBean, value);
461: }
462: }
463:
464: Class super class = targetClass.getSuperclass();
465: TCClass tcsuper class = tcClass.getSuperclass();
466: if (super class != null && tcsuper class != null) {
467: copyTransientFields(beanName, sourceBean, targetBean,
468: super class, tcsuper class);
469: }
470: }
471:
472: interface ManagerUtilWrapper {
473: void beginLock(String lockId, int type);
474:
475: Object lookupOrCreateRoot(String name, Object object);
476:
477: void commitLock(String lockId);
478: }
479:
480: private static class ManagerUtilWrapperImpl implements
481: ManagerUtilWrapper {
482:
483: public ManagerUtilWrapperImpl() {
484: //
485: }
486:
487: public void beginLock(String lockId, int type) {
488: ManagerUtil.beginLock(lockId, type);
489: }
490:
491: public Object lookupOrCreateRoot(String name, Object object) {
492: return ManagerUtil.lookupOrCreateRoot(name, object);
493: }
494:
495: public void commitLock(String lockId) {
496: ManagerUtil.commitLock(lockId);
497: }
498:
499: }
500:
501: }
|