All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springsource.loaded.agent.JVMPlugin Maven / Gradle / Ivy

There is a newer version: 1.2.8.RELEASE
Show newest version
/*
 * Copyright 2010-2012 VMware and contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springsource.loaded.agent;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.lang.ref.Reference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;

import org.springsource.loaded.GlobalConfiguration;
import org.springsource.loaded.LoadtimeInstrumentationPlugin;
import org.springsource.loaded.ReloadEventProcessorPlugin;


/**
 * Reloading plugin for 'poking' JVM classes that are known to cache reflective state. Some of the behaviour is switched
 * ON based on which classes are loaded. For example the Introspector clearing logic is only activated if the
 * Introspector gets loaded.
 *
 * @author Andy Clement
 * @since 0.7.3
 */
public class JVMPlugin implements ReloadEventProcessorPlugin, LoadtimeInstrumentationPlugin {

	private boolean pluginBroken = false;

	private boolean introspectorLoaded = false;

	private boolean threadGroupContextLoaded = false;

	private Field beanInfoCacheField;

	private Field declaredMethodCacheField;

	private Method putMethod;

	private Class threadGroupContextClass;

	private Field threadGroupContext_contextsField; /* Map */

	private Method threadGroupContext_removeBeanInfoMethod; /*  removeBeanInfo(Class type) { */


	private void tidySerialization(Class reloadedClass) {
		//		if (true) return;
		try {
			Class clazz = Class.forName("java.io.ObjectStreamClass$Caches");
			Field localDescsField = clazz.getDeclaredField("localDescs");
			localDescsField.setAccessible(true);
			ConcurrentMap cm = (ConcurrentMap) localDescsField.get(null);
			// TODO [serialization] a bit extreme to wipe out everything
			cm.clear();
			// For some reason clearing the reflectors damages serialization - is it not a true cache?
			//			Field reflectorsField = clazz.getDeclaredField("reflectors");
			//			reflectorsField.setAccessible(true);
			//			cm = (ConcurrentMap)reflectorsField.get(null);
			//			cm.clear();
		}
		catch (ClassNotFoundException e) {
			throw new IllegalStateException(e);
		}
		catch (NoSuchFieldException e) {
			throw new IllegalStateException(e);
		}
		catch (SecurityException e) {
			throw new IllegalStateException(e);
		}
		catch (IllegalArgumentException e) {
			throw new IllegalStateException(e);
		}
		catch (IllegalAccessException e) {
			throw new IllegalStateException(e);
		}


		//		private static class Caches {
		//	        /** cache mapping local classes -> descriptors */
		//	        static final ConcurrentMap> localDescs =
		//	            new ConcurrentHashMap<>();
		//
		//	        /** cache mapping field group/local desc pairs -> field reflectors */
		//	        static final ConcurrentMap> reflectors =
		//	            new ConcurrentHashMap<>();
		//
		//	        /** queue for WeakReferences to local classes */
		//	        private static final ReferenceQueue> localDescsQueue =
		//	            new ReferenceQueue<>();
		//	        /** queue for WeakReferences to field reflectors keys */
		//	        private static final ReferenceQueue> reflectorsQueue =
		//	            new ReferenceQueue<>();
		//	    }
	}


	@SuppressWarnings({ "restriction", "unchecked" })
	public void reloadEvent(String typename, Class clazz, String encodedTimestamp) {
		if (pluginBroken) {
			return;
		}
		tidySerialization(clazz);
		if (introspectorLoaded) {
			// Clear out the Introspector BeanInfo cache entry that might exist for this class

			boolean beanInfoCacheCleared = false;
			// In Java7 the AppContext stuff is gone, replaced by a ThreadGroupContext.
			// This code grabs the contexts map from the ThreadGroupContext object and clears out the bean info for the reloaded clazz
			if (threadGroupContextLoaded) { // In Java 7
				beanInfoCacheCleared = clearThreadGroupContext(clazz);
			}

			// GRAILS-9505 - had to introduce the flushFromCaches(). The appcontext we seem to be able to
			// access from AppContext.getAppContext() isn't the same one the Introspector will be using
			// so we can fail to clean up the cache.  Strangely calling getAppContexts() and clearing them
			// all (the code commented out below) doesn't fetch all the contexts. I'm sure it is a nuance of
			// app context handling but for now the introspector call is sufficient.
			// TODO doesn't this just only clear the beaninfocache for the thread the reload event
			// is occurring on? which may not be the thread that was actually using the cache.
			if (!beanInfoCacheCleared) {
				try {
					if (beanInfoCacheField == null) {
						beanInfoCacheField = Introspector.class.getDeclaredField("BEANINFO_CACHE");
					}
					beanInfoCacheField.setAccessible(true);
					Object key = beanInfoCacheField.get(null);
					Map, BeanInfo> map = (Map, BeanInfo>) sun.awt.AppContext.getAppContext().get(key);
					if (map != null) {
						if (GlobalConfiguration.debugplugins) {
							System.err.println("JVMPlugin: clearing out BeanInfo for " + clazz.getName());
						}
						map.remove(clazz);
					}

					//					Set appcontexts = sun.awt.AppContext.getAppContexts();
					//					for (sun.awt.AppContext appcontext: appcontexts) {
					//						map = (Map, BeanInfo>) appcontext.get(key);
					//						if (map != null) {
					//							if (GlobalConfiguration.debugplugins) {
					//								System.err.println("JVMPlugin: clearing out BeanInfo for " + clazz.getName());
					//							}
					//							map.remove(clazz);
					//						}
					//					}
					Introspector.flushFromCaches(clazz);
				}
				catch (NoSuchFieldException nsfe) {
					// this can happen on Java7 as the field isn't there any more, see the code above.
					System.out.println("Reloading: JVMPlugin: warning: unable to clear BEANINFO_CACHE, cant find field");
				}
				catch (Exception e) {
					e.printStackTrace();
				}
			}

			// Clear out the declaredMethodCache that may exist for this class
			try {
				if (declaredMethodCacheField == null) {
					declaredMethodCacheField = Introspector.class.getDeclaredField("declaredMethodCache");
				}
				declaredMethodCacheField.setAccessible(true);
				Object theCache = declaredMethodCacheField.get(null);
				if (putMethod == null) {
					putMethod = theCache.getClass().getDeclaredMethod("put", Object.class, Object.class);
				}
				putMethod.setAccessible(true);

				if (GlobalConfiguration.debugplugins) {
					System.err.println("JVMPlugin: clearing out declaredMethodCache in Introspector for class "
							+ clazz.getName());
				}
				putMethod.invoke(theCache, clazz, null);
			}
			catch (NoSuchFieldException nsfe) {
				pluginBroken = true;
				System.out
						.println("Reloading: JVMPlugin: warning: unable to clear declaredMethodCache, cant find field (JDK update may fix it)");
			}
			catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	private boolean clearThreadGroupContext(Class clazz) {
		boolean beanInfoCacheCleared = false;
		try {
			if (threadGroupContextClass == null) {
				threadGroupContextClass = Class.forName("java.beans.ThreadGroupContext", true,
						Introspector.class.getClassLoader());
			}
			if (threadGroupContextClass != null) {
				if (threadGroupContext_contextsField == null) {
					threadGroupContext_contextsField = threadGroupContextClass.getDeclaredField("contexts");
					threadGroupContext_removeBeanInfoMethod = threadGroupContextClass.getDeclaredMethod(
							"removeBeanInfo",
							Class.class);
				}
				if (threadGroupContext_contextsField != null) {
					threadGroupContext_contextsField.setAccessible(true);
					Object threadGroupContext_contextsField_value = threadGroupContext_contextsField.get(null);
					if (threadGroupContext_contextsField_value == null) {
						beanInfoCacheCleared = true;
					}
					else {
						if (threadGroupContext_contextsField_value instanceof Map) {
							// Indicates Java 7 up to rev21
							Map m = (Map) threadGroupContext_contextsField_value;
							Collection threadGroupContexts = m.values();
							for (Object o : threadGroupContexts) {
								threadGroupContext_removeBeanInfoMethod.setAccessible(true);
								threadGroupContext_removeBeanInfoMethod.invoke(o, clazz);
							}
							beanInfoCacheCleared = true;
						}
						else {
							// At update Java7u21 it changes
							Class weakIdentityMapClazz = threadGroupContext_contextsField.getType();
							Field tableField = weakIdentityMapClazz.getDeclaredField("table");
							tableField.setAccessible(true);
							Reference[] refs = (Reference[]) tableField.get(threadGroupContext_contextsField_value);
							Field valueField = null;
							if (refs != null) {
								for (int i = 0; i < refs.length; i++) {
									Reference r = refs[i];
									Object o = (r == null ? null : r.get());
									if (o != null) {
										if (valueField == null) {
											valueField = r.getClass().getDeclaredField("value");
										}
										valueField.setAccessible(true);
										Object threadGroupContext = valueField.get(r);
										threadGroupContext_removeBeanInfoMethod.setAccessible(true);
										threadGroupContext_removeBeanInfoMethod.invoke(threadGroupContext, clazz);
									}
								}
							}
							beanInfoCacheCleared = true;
						}
					}
				}
			}
		}
		catch (Throwable t) {
			System.err.println("Unexpected problem clearing ThreadGroupContext beaninfo: ");
			t.printStackTrace();
		}
		return beanInfoCacheCleared;
	}

	public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain,
			byte[] bytes) {
		if (slashedTypeName != null) {
			if (slashedTypeName.equals("java/beans/Introspector")) {
				introspectorLoaded = true;
			}
			else if (slashedTypeName.equals("java/beans/ThreadGroupContext")) {
				threadGroupContextLoaded = true;
			}
		}
		return false;
	}

	public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) {
		return null;
	}

	public boolean shouldRerunStaticInitializer(String typename, Class clazz, String encodedTimestamp) {
		return false;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy