001: /*
002: * All content copyright (c) 2003-2007 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tc.object.bytecode.hook.impl;
006:
007: import org.apache.commons.io.CopyUtils;
008:
009: import com.tc.aspectwerkz.reflect.ClassInfo;
010: import com.tc.aspectwerkz.transform.InstrumentationContext;
011: import com.tc.aspectwerkz.transform.WeavingStrategy;
012: import com.tc.config.schema.L2ConfigForL1.L2Data;
013: import com.tc.config.schema.setup.ConfigurationSetupException;
014: import com.tc.config.schema.setup.FatalIllegalConfigurationChangeHandler;
015: import com.tc.config.schema.setup.L1TVSConfigurationSetupManager;
016: import com.tc.config.schema.setup.StandardTVSConfigurationSetupManagerFactory;
017: import com.tc.logging.TCLogger;
018: import com.tc.logging.TCLogging;
019: import com.tc.object.bytecode.Manageable;
020: import com.tc.object.bytecode.Manager;
021: import com.tc.object.bytecode.ManagerImpl;
022: import com.tc.object.bytecode.hook.ClassLoaderPreProcessorImpl;
023: import com.tc.object.bytecode.hook.DSOContext;
024: import com.tc.object.config.DSOClientConfigHelper;
025: import com.tc.object.config.IncompleteBootJarException;
026: import com.tc.object.config.StandardDSOClientConfigHelperImpl;
027: import com.tc.object.config.UnverifiedBootJarException;
028: import com.tc.object.loaders.ClassProvider;
029: import com.tc.object.logging.InstrumentationLoggerImpl;
030: import com.tc.plugins.ModulesLoader;
031: import com.tc.util.Assert;
032: import com.tc.util.TCTimeoutException;
033: import com.terracottatech.config.ConfigurationModel;
034:
035: import java.io.ByteArrayOutputStream;
036: import java.io.IOException;
037: import java.io.InputStream;
038: import java.net.ConnectException;
039: import java.net.MalformedURLException;
040: import java.net.URL;
041: import java.net.UnknownHostException;
042: import java.util.Collection;
043: import java.util.HashMap;
044:
045: public class DSOContextImpl implements DSOContext {
046:
047: private static final TCLogger logger = TCLogging
048: .getLogger(DSOContextImpl.class);
049:
050: private static DSOClientConfigHelper staticConfigHelper;
051: private static PreparedComponentsFromL2Connection preparedComponentsFromL2Connection;
052:
053: private final DSOClientConfigHelper configHelper;
054: private final Manager manager;
055: private final WeavingStrategy weavingStrategy;
056:
057: /**
058: * Creates a "global" DSO Context. This context is appropriate only when there is only one DSO Context that applies to
059: * the entire VM
060: */
061: public static DSOContext createGlobalContext(
062: ClassProvider globalProvider)
063: throws ConfigurationSetupException {
064: DSOClientConfigHelper configHelper = getGlobalConfigHelper();
065: Manager manager = new ManagerImpl(configHelper, globalProvider,
066: preparedComponentsFromL2Connection);
067: return new DSOContextImpl(configHelper, globalProvider, manager);
068: }
069:
070: /**
071: * For tests
072: */
073: public static DSOContext createContext(
074: DSOClientConfigHelper configHelper,
075: ClassProvider classProvider, Manager manager) {
076: return new DSOContextImpl(configHelper, classProvider, manager);
077: }
078:
079: public static boolean isDSOSessions(String appName)
080: throws ConfigurationSetupException {
081: return getGlobalConfigHelper().isDSOSessions(appName);
082: }
083:
084: private DSOContextImpl(DSOClientConfigHelper configHelper,
085: ClassProvider classProvider, Manager manager) {
086: checkForProperlyInstrumentedBaseClasses();
087: if (configHelper == null) {
088: throw new NullPointerException();
089: }
090:
091: this .configHelper = configHelper;
092: this .manager = manager;
093: weavingStrategy = new DefaultWeavingStrategy(configHelper,
094: new InstrumentationLoggerImpl(configHelper
095: .instrumentationLoggingOptions()));
096:
097: ModulesLoader.initModules(configHelper, classProvider, false);
098: validateBootJar();
099: }
100:
101: private void validateBootJar() {
102: try {
103: configHelper.verifyBootJarContents();
104: } catch (final UnverifiedBootJarException ubjex) {
105: final StringBuffer msg = new StringBuffer(ubjex
106: .getMessage()
107: + " ");
108: msg
109: .append("Unable to verify the contents of the boot jar; ");
110: msg
111: .append("Please check the client logs for more information.");
112: logger.error(ubjex);
113: throw new RuntimeException(msg.toString());
114: } catch (final IncompleteBootJarException ibjex) {
115: final StringBuffer msg = new StringBuffer(ibjex
116: .getMessage()
117: + " ");
118: msg
119: .append("The DSO boot jar appears to be incomplete --- some pre-instrumented classes ");
120: msg
121: .append("listed in your tc-config is not included in the boot jar file. This could ");
122: msg
123: .append("happen if you've modified your DSO clients' tc-config file to specify additional ");
124: msg
125: .append("classes for inclusion in the boot jar, but forgot to rebuild the boot jar. Or, you ");
126: msg
127: .append("could be a using an older boot jar against a newer Terracotta client installation. ");
128: msg
129: .append("Please check the client logs for the list of classes that were not found in your boot jar.");
130: logger.error(ibjex);
131: throw new RuntimeException(msg.toString());
132: }
133: }
134:
135: private void checkForProperlyInstrumentedBaseClasses() {
136: if (!Manageable.class.isAssignableFrom(HashMap.class)) {
137: StringBuffer msg = new StringBuffer();
138: msg
139: .append("The DSO boot jar is not prepended to your bootclasspath! ");
140: msg.append("Generate it using the make-boot-jar script ");
141: msg
142: .append("and place the generated jar file in the bootclasspath ");
143: msg
144: .append("(i.e. -Xbootclasspath/p:/path/to/terracotta/lib/dso-boot/dso-boot-xxx.jar)");
145: throw new RuntimeException(msg.toString());
146: }
147: }
148:
149: public Manager getManager() {
150: return this .manager;
151: }
152:
153: /**
154: * XXX::NOTE:: ClassLoader checks the returned byte array to see if the class is instrumented or not to maintain the
155: * offset.
156: *
157: * @return new byte array if the class is instrumented and same input byte array if not.
158: * @see ClassLoaderPreProcessorImpl
159: */
160: public byte[] preProcess(String name, byte[] data, int offset,
161: int length, ClassLoader caller) {
162: InstrumentationContext context = new InstrumentationContext(
163: name, data, caller);
164: weavingStrategy.transform(name, context);
165: return context.getCurrentBytecode();
166: }
167:
168: public void postProcess(Class clazz, ClassLoader caller) {
169: // NOP
170: }
171:
172: // Needed by Spring
173: public void addTransient(String className, String fieldName) {
174: this .configHelper.addTransient(className, fieldName);
175: }
176:
177: // Needed by Spring
178: public void addInclude(String expression,
179: boolean callConstructorOnLoad, String lockExpression,
180: ClassInfo classInfo) {
181: this .configHelper
182: .addIncludeAndLockIfRequired(expression, true,
183: callConstructorOnLoad, false, lockExpression,
184: classInfo);
185: }
186:
187: // Needed by Spring
188: public Collection getDSOSpringConfigHelpers() {
189: return this .configHelper.getDSOSpringConfigs();
190: }
191:
192: private synchronized static DSOClientConfigHelper getGlobalConfigHelper()
193: throws ConfigurationSetupException {
194: if (staticConfigHelper == null) {
195: StandardTVSConfigurationSetupManagerFactory factory = new StandardTVSConfigurationSetupManagerFactory(
196: false, new FatalIllegalConfigurationChangeHandler());
197:
198: logger
199: .debug("Created StandardTVSConfigurationSetupManagerFactory.");
200: L1TVSConfigurationSetupManager config = factory
201: .createL1TVSConfigurationSetupManager();
202: config.setupLogging();
203: logger.debug("Created L1TVSConfigurationSetupManager.");
204:
205: try {
206: preparedComponentsFromL2Connection = validateMakeL2Connection(config);
207: } catch (Exception e) {
208: throw new ConfigurationSetupException(e
209: .getLocalizedMessage(), e);
210: }
211: staticConfigHelper = new StandardDSOClientConfigHelperImpl(
212: config);
213: }
214:
215: return staticConfigHelper;
216: }
217:
218: private static PreparedComponentsFromL2Connection validateMakeL2Connection(
219: L1TVSConfigurationSetupManager config)
220: throws UnknownHostException, IOException,
221: TCTimeoutException {
222: L2Data[] l2Data = (L2Data[]) config.l2Config().l2Data()
223: .getObjects();
224: Assert.assertNotNull(l2Data);
225:
226: String serverHost = l2Data[0].host();
227:
228: if (false && !config.loadedFromTrustedSource()) {
229: String serverConfigMode = getServerConfigMode(serverHost,
230: l2Data[0].dsoPort());
231:
232: if (serverConfigMode != null
233: && serverConfigMode
234: .equals(ConfigurationModel.PRODUCTION)) {
235: String text = "Configuration constraint violation: "
236: + "untrusted client configuration not allowed against production server";
237: throw new AssertionError(text);
238: }
239: }
240:
241: return new PreparedComponentsFromL2Connection(config);
242: }
243:
244: private static final long MAX_HTTP_FETCH_TIME = 30 * 1000; // 30 seconds
245: private static final long HTTP_FETCH_RETRY_INTERVAL = 1 * 1000; // 1 second
246:
247: private static String getServerConfigMode(String serverHost,
248: int httpPort) throws MalformedURLException,
249: TCTimeoutException, IOException {
250: URL theURL = new URL("http", serverHost, httpPort,
251: "/config?query=mode");
252: long startTime = System.currentTimeMillis();
253: long lastTrial = 0;
254:
255: while (System.currentTimeMillis() < (startTime + MAX_HTTP_FETCH_TIME)) {
256: try {
257: long untilNextTrial = HTTP_FETCH_RETRY_INTERVAL
258: - (System.currentTimeMillis() - lastTrial);
259:
260: if (untilNextTrial > 0) {
261: try {
262: Thread.sleep(untilNextTrial);
263: } catch (InterruptedException ie) {
264: // whatever; just try again now
265: }
266: }
267:
268: logger.debug("Opening connection to: " + theURL
269: + " to fetch server configuration.");
270:
271: lastTrial = System.currentTimeMillis();
272: InputStream in = theURL.openStream();
273: logger.debug("Got input stream to: " + theURL);
274: ByteArrayOutputStream baos = new ByteArrayOutputStream();
275:
276: CopyUtils.copy(in, baos);
277:
278: return baos.toString();
279: } catch (ConnectException ce) {
280: logger
281: .warn("Unable to fetch configuration mode from L2 at '"
282: + theURL
283: + "'; trying again. "
284: + "(Is an L2 running at that address?): "
285: + ce.getLocalizedMessage());
286: // oops -- try again
287: }
288: }
289:
290: throw new TCTimeoutException(
291: "We tried for "
292: + (int) ((System.currentTimeMillis() - startTime) / 1000)
293: + " seconds, but couldn't fetch system configuration mode from the L2 "
294: + "at '" + theURL + "'. Is the L2 running?");
295: }
296:
297: public int getSessionLockType(String appName) {
298: return configHelper.getSessionLockType(appName);
299: }
300:
301: public URL getClassResource(String className) {
302: return configHelper.getClassResource(className);
303: }
304: }
|