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: */package com.tc.config.schema.setup;
005:
006: import org.apache.xmlbeans.XmlObject;
007: import org.apache.xmlbeans.XmlOptions;
008: import org.apache.xmlbeans.impl.common.XPath;
009:
010: import com.tc.config.schema.IllegalConfigurationChangeHandler;
011: import com.tc.config.schema.NewCommonL1Config;
012: import com.tc.config.schema.NewCommonL2Config;
013: import com.tc.config.schema.NewSystemConfig;
014: import com.tc.config.schema.SettableConfigItem;
015: import com.tc.config.schema.TestConfigObjectInvocationHandler;
016: import com.tc.config.schema.dynamic.ConfigItem;
017: import com.tc.config.schema.dynamic.XPathBasedConfigItem;
018: import com.tc.config.schema.repository.MutableBeanRepository;
019: import com.tc.object.config.schema.NewDSOApplicationConfig;
020: import com.tc.object.config.schema.NewL1DSOConfig;
021: import com.tc.object.config.schema.NewL2DSOConfig;
022: import com.tc.util.Assert;
023: import com.terracottatech.config.Application;
024: import com.terracottatech.config.PersistenceMode;
025: import com.terracottatech.config.Server;
026: import com.terracottatech.config.Servers;
027: import com.terracottatech.config.PersistenceMode.Enum;
028:
029: import java.lang.reflect.Proxy;
030: import java.util.ArrayList;
031: import java.util.Arrays;
032: import java.util.HashSet;
033: import java.util.Iterator;
034: import java.util.List;
035: import java.util.Set;
036:
037: /**
038: * A {@link com.tc.config.schema.setup.TVSConfigurationSetupManagerFactory} that creates config appropriate for usage in
039: * tests. This config behaves just like normal config, except that it reads no files; everything is in-memory instead.
040: * You can specify whether you want this config to act like centralized config (all at L2), or distributed config (every
041: * L1 has its own copy of the config, too).
042: * </p>
043: * <p>
044: * To use this class, simply get the appropriate config object that you need by calling a method (<em>e.g.</em>,
045: * {@link #systemConfig()}). Then, call a method on it (like {@link com.tc.config.schema.NewSystemConfig#dsoEnabled()},
046: * for example); this will give you back a {@link ConfigItem}, or a subinterface thereof. Cast this item to a
047: * {@link com.tc.config.schema.SettableConfigItem}, and then call {@link SettableConfigItem#setValue(Object)} on it (or
048: * one of the similar methods that takes a primitive type), passing it the value you want this class to return for that
049: * item. Make sure you get the class right — if you don't, you'll get a nasty {@link ClassCastException} when
050: * production code tries to access the value in that {@link ConfigItem}.
051: * </p>
052: * <p>
053: * Only one little trick: if you're setting something that's a complex object — <em>i.e.</em>, not just a
054: * primitive type, {@link String}, array of {@link String}s or something like that (specifically, the object types
055: * returned by the top-level subclasses of {@link com.tc.config.schema.dynamic.XPathBasedConfigItem}) — then you
056: * need to set an implementation of {@link XmlObject}, not the actual Terracotta-defined types that the real
057: * {@link ConfigItem}s return. (This is because we're using the real config system — see below for details
058: * — and it expects {@link XmlObject}s of the appropriate type so it can translate them to the Terracotta-defined
059: * types that we really return.) Fortunately, all XML beans have <code>Factory</code> inner classes that will let you
060: * create them. If you then wrap these calls in a function and reuse it, you'll be in fine shape if/when the actual XML
061: * beans are changed.
062: * </p>
063: * <p>
064: * Note: There is no support yet for different L1s having different config, or config that differs from L2's.
065: * </p>
066: * <h3>Maintenance:</h3>
067: * <p>
068: * If you create new typed subinterfaces of {@link ConfigItem}, you do need to make
069: * {@link com.tc.config.schema.TestConfigObjectInvocationHandler.OurSettableConfigItem} implement them. Don't worry,
070: * though; the methods can just throw {@link com.tc.util.TCAssertionError}, and don't need to (nor should they)
071: * actually do anything.
072: * </p>
073: * <p>
074: * If you introduce new config objects or new beans to the system, you'll need to do a lot more, but, then, presumably
075: * if you're doing that you understand more about the way the config system works than this comment is going to tell
076: * you, even if it is long.
077: * </p>
078: * <p>
079: * That's it. In particular, there's no need to actually do anything when you add new items to existing config objects:
080: * this is significant.
081: * </p>
082: * <h3>How it works:</h3>
083: * <p>
084: * How this all works is a little interesting. It's involved, but it buys us two hugely useful properties: clients use
085: * basically the same APIs to change config parameters as they do to get them, and we need to do zero (really!) work
086: * when new {@link ConfigItem}s are added to config objects. This means it's impossible for test code to get
087: * "out-of-sync" with respect to config, so it always works, and Eclipse's refactoring tools also work.
088: * </p>
089: * <p>
090: * First, a little overview: for our test config, we use the real production config system, all the way down to the
091: * level of the actual {@link XmlObject}s that get stuffed in the config system's
092: * {@link com.tc.config.schema.repository.BeanRepository} objects. <em>Those</em> are realy {@link XmlObject}s of the
093: * appropriate type, created by the {@link TestConfigBeanSet} and modified by calls to the pseudo-'config objects' that
094: * this class exposes. However, everything else is real: you're exercising real config code, real XPath-based
095: * {@link ConfigItem}s reading from real {@link XmlObject}s, real L1-L2 protocol handling, and so on. This has many
096: * benefits, including making your tests behave more closely to the way real production code works (a good thing), and
097: * exercising more of the config system in your tests (also a good thing).
098: * </p>
099: * <p>
100: * Details of how it all works:
101: * </p>
102: * <ul>
103: * <li>A {@link TestConfigBeanSet} holds on to the set of all root {@link XmlObject}s that we need to configure the
104: * system — for example, the {@link L1} we use for L1 config, the {@link L2}s representing each L2's config (and
105: * the {@link L2S} that wraps them all up together), the {@link com.terracottatech.configV1.System} we use for system
106: * config, the {@link Application} for each application's config, and so on.</li>
107: * <li>These {@link XmlObject}s are honest-to-God real instances, as created by their factories (for example,
108: * {@link L1.Factory}. At the start, they have just enough configuration populated into them to make sure they
109: * validate.</li>
110: * <li>This class exposes what look like instances of the normal config objects available to the system. However, these
111: * are actually proxies created with {@link java.lang.reflect.Proxy}, using a
112: * {@link com.tc.config.schema.TestConfigObjectInvocationHandler}.</li>
113: * <li>That invocation handler, in response to method calls, parcels out {@link ConfigItem}s that are instances of
114: * {@link com.tc.config.schema.TestConfigObjectInvocationHandler.OurSettableConfigItem}. When you call
115: * <code>setValue</code> on them, they do their magic: using the {@link XPath} they get from the corresponding
116: * "sample" {@link ConfigItem} (see below), they descend the tree of {@link XmlObject}s, starting at the root, creating
117: * children along the way as necessary, and finally set the correct property on the correct bean. (This is conceptually
118: * easy but actually full of all kinds of nasty mess; this is why {@link OurSettableConfigItem} is such a messy class.) .</li>
119: * <li>Okay, but how does it know what XPath to use to descend the tree? That's where the "sample" config objects below
120: * (fields in this object) come in. They are actual, real config objects that are created around the bean set, before
121: * any values are set — but that doesn't matter, because the only thing we use them for is to get the
122: * {@link XPathBasedConfigItem}s out of them and extract the XPath from them. So, when you call the method that gets a
123: * {@link com.tc.config.schema.TestConfigObjectInvocationHandler.OurSettableConfigItem} from the proxied-up config
124: * object, it calls the exact same method on the "sample" config object, grabbing the {@link ConfigItem} returned,
125: * casting it to an {@link com.tc.config.schema.dynamic.XPathBasedConfigItem}, and extracting the XPath out of that.</li>
126: * </ul>
127: * </p>
128: * <p>
129: * Is this whole thing complicated? Yes, absolutely. Can it probably be simplified? Yes. Is the design bad? I don't
130: * think so, and here's why: it gives a very clean, very simple API for setting config values, and it's maintainable.
131: * Other potential solutions either tend to hurt in terms of maintenance — you have to do something whenever you
132: * add a config item to the system, and if you mess it up, your new config just gets silently ignored — or in
133: * terms of API — they create a much more complex (and much harder to maintain) API for setting config values.
134: * This way, all the complexity is wrapped in three smallish classes (this one, {@link TestConfigBeanSet}, and
135: * {@link com.tc.config.schema.TestConfigObjectInvocationHandler}) in the config package in the source tree, and not
136: * spread all over the place throughout our code, causing massive pain if we ever have to change anything.
137: */
138: public class TestTVSConfigurationSetupManagerFactory extends
139: BaseTVSConfigurationSetupManagerFactory {
140:
141: public static final int MODE_CENTRALIZED_CONFIG = 0;
142: public static final int MODE_DISTRIBUTED_CONFIG = 1;
143:
144: private final TestConfigBeanSet beanSet;
145: private final TestConfigBeanSet l1_beanSet;
146:
147: private final TestConfigurationCreator l1ConfigurationCreator;
148: private final TestConfigurationCreator l2ConfigurationCreator;
149:
150: private final NewSystemConfig sampleSystem;
151: private final NewCommonL1Config sampleL1Common;
152: private final NewL1DSOConfig sampleL1DSO;
153: private final NewCommonL2Config sampleL2Common;
154: private final NewL2DSOConfig sampleL2DSO;
155: private final NewDSOApplicationConfig sampleDSOApplication;
156:
157: private final String defaultL2Identifier;
158:
159: private final int mode;
160:
161: // TODO: fix the way settableObjects are used
162: // this is temporary
163: private Enum persistenceMode = PersistenceMode.TEMPORARY_SWAP_ONLY;
164: private boolean gcEnabled = true;
165: private boolean gcVerbose = false;
166: private int gcIntervalInSec = 3600;
167:
168: public TestTVSConfigurationSetupManagerFactory(
169: int mode,
170: String l2Identifier,
171: IllegalConfigurationChangeHandler illegalConfigurationChangeHandler) {
172: super (illegalConfigurationChangeHandler);
173:
174: this .beanSet = new TestConfigBeanSet();
175: this .l1_beanSet = new TestConfigBeanSet();
176:
177: this .l2ConfigurationCreator = new TestConfigurationCreator(
178: this .beanSet, true);
179:
180: this .mode = mode;
181: if (mode == MODE_CENTRALIZED_CONFIG) {
182: this .l1ConfigurationCreator = new TestConfigurationCreator(
183: this .l1_beanSet, true);
184: } else if (mode == MODE_DISTRIBUTED_CONFIG) {
185: this .l1ConfigurationCreator = new TestConfigurationCreator(
186: this .l1_beanSet, false);
187: } else {
188: throw Assert.failure("Unknown mode: " + mode);
189: }
190:
191: if (l2Identifier != null) {
192: Assert.assertNotBlank(l2Identifier);
193: this .defaultL2Identifier = l2Identifier;
194: } else {
195: String defaultName = this .beanSet.serversBean()
196: .getServerArray()[0].getName();
197: if (!TestConfigBeanSet.DEFAULT_SERVER_NAME
198: .equals(defaultName)) {
199: this .defaultL2Identifier = this .beanSet.serversBean()
200: .getServerArray()[0].getName();
201: } else {
202: this .defaultL2Identifier = TestConfigBeanSet.DEFAULT_HOST;
203: }
204: }
205:
206: Assert.assertNotNull(this .defaultL2Identifier);
207:
208: // FIXME 2005-11-30 andrew -- This stinks like mad...we should be able to do something better than perverting the
209: // existing config-setup managers here.
210: L1TVSConfigurationSetupManager sampleL1Manager;
211: L2TVSConfigurationSetupManager sampleL2Manager;
212:
213: try {
214: sampleL1Manager = this
215: .createL1TVSConfigurationSetupManager(new TestConfigurationCreator(
216: this .l1_beanSet, true));
217: sampleL2Manager = this
218: .createL2TVSConfigurationSetupManager(null);
219: } catch (ConfigurationSetupException cse) {
220: throw Assert.failure("Huh?", cse);
221: }
222:
223: this .sampleSystem = sampleL2Manager.systemConfig();
224: this .sampleL1Common = sampleL1Manager.commonL1Config();
225: this .sampleL1DSO = sampleL1Manager.dsoL1Config();
226: this .sampleL2Common = sampleL2Manager.commonl2Config();
227: this .sampleL2DSO = sampleL2Manager.dsoL2Config();
228: this .sampleDSOApplication = sampleL1Manager
229: .dsoApplicationConfigFor(TVSConfigurationSetupManagerFactory.DEFAULT_APPLICATION_NAME);
230:
231: applyDefaultTestConfig();
232: }
233:
234: public TestConfigBeanSet beanSet() {
235: return this .beanSet;
236: }
237:
238: private static final String BOGUS_FILENAME = "nonexistent-directory-SHOULD-NEVER-EXIST/../";
239:
240: private void applyDefaultTestConfig() {
241: // // Use a license that lets us do anything.
242: // try {
243: // String path = getEverythingLicensePath();
244: // ((SettableConfigItem) systemConfig().licenseLocation()).setValue(path);
245: // } catch (IOException ioe) {
246: // throw Assert.failure("Unable to fetch data directory root to find license for tests.", ioe);
247: // }
248: //
249: // ((SettableConfigItem) systemConfig().licenseType()).setValue(LicenseType.PRODUCTION);
250: //
251: // // Make servers use dynamic ports, by default.
252: // ((SettableConfigItem) l2DSOConfig().listenPort()).setValue(0);
253: ((SettableConfigItem) l2CommonConfig().jmxPort()).setValue(0);
254:
255: // We also set the data and log directories to strings that shouldn't be valid on any platform: you need to set
256: // these yourself before you use this config. If you don't, you'll write all over the place as we create 'data' and
257: // 'logs' directories willy-nilly. Don't do that.
258: ((SettableConfigItem) l1CommonConfig().logsPath())
259: .setValue(BOGUS_FILENAME);
260: ((SettableConfigItem) l2CommonConfig().dataPath())
261: .setValue(BOGUS_FILENAME);
262: ((SettableConfigItem) l2CommonConfig().logsPath())
263: .setValue(BOGUS_FILENAME);
264: }
265:
266: public void activateConfigurationChange()
267: throws ConfigurationSetupException {
268: Set allRepositories = collectAllRepositories();
269:
270: Iterator iter = allRepositories.iterator();
271: while (iter.hasNext()) {
272: MutableBeanRepository repository = (MutableBeanRepository) iter
273: .next();
274: checkValidates(repository.bean());
275: repository.didMutateBean();
276: }
277:
278: iter = allRepositories.iterator();
279: while (iter.hasNext()) {
280: MutableBeanRepository repository = (MutableBeanRepository) iter
281: .next();
282: repository.saveCopyOfBeanInAnticipationOfFutureMutation();
283: }
284: }
285:
286: private void checkValidates(XmlObject bean)
287: throws ConfigurationSetupException {
288: List errors = new ArrayList();
289: XmlOptions options = new XmlOptions().setErrorListener(errors);
290: boolean valid = bean.validate(options);
291:
292: if ((!valid) || (errors.size() > 0)) {
293: // formatting
294: throw new ConfigurationSetupException(
295: "You have errors in your config: " + errors);
296: }
297: }
298:
299: private Set collectAllRepositories() {
300: Set allRepositories = new HashSet();
301:
302: allRepositories.addAll(Arrays
303: .asList(this .l1ConfigurationCreator
304: .allRepositoriesStoredInto()));
305: allRepositories.addAll(Arrays
306: .asList(this .l2ConfigurationCreator
307: .allRepositoriesStoredInto()));
308: return allRepositories;
309: }
310:
311: private Object proxify(Class theClass, XmlObject[] destObjects,
312: Object realImplementation, String xpathPrefix) {
313: return Proxy.newProxyInstance(getClass().getClassLoader(),
314: new Class[] { theClass },
315: new TestConfigObjectInvocationHandler(theClass,
316: destObjects, realImplementation, xpathPrefix));
317: }
318:
319: private Object proxify(Class theClass, XmlObject destObject,
320: Object realImplementation, String xpathPrefix) {
321: return proxify(theClass, new XmlObject[] { destObject },
322: realImplementation, xpathPrefix);
323: }
324:
325: private XmlObject[] allServerBeans() {
326: return this .beanSet.serversBean().getServerArray();
327: }
328:
329: public NewSystemConfig systemConfig() {
330: return (NewSystemConfig) proxify(NewSystemConfig.class,
331: this .beanSet.systemBean(), this .sampleSystem, null);
332: }
333:
334: public NewCommonL1Config l1CommonConfig() {
335: return (NewCommonL1Config) proxify(NewCommonL1Config.class,
336: this .l1_beanSet.clientBean(), this .sampleL1Common, null);
337: }
338:
339: public NewL1DSOConfig l1DSOConfig() {
340: return (NewL1DSOConfig) proxify(NewL1DSOConfig.class,
341: this .l1_beanSet.clientBean(), this .sampleL1DSO, "dso");
342: }
343:
344: private void cleanBeanSetServersIfNeeded(
345: TestConfigBeanSet beanSetArg) {
346: if (beanSetArg == null) {
347: throw new AssertionError("beanSetArg is null");
348: }
349:
350: Servers l2s = beanSetArg.serversBean();
351: if (l2s.sizeOfServerArray() == 1) {
352: Server l2 = l2s.getServerArray(0);
353: if (l2.getName() != null
354: && l2.getName().equals(
355: TestConfigBeanSet.DEFAULT_SERVER_NAME)
356: && l2.getHost().equals(
357: TestConfigBeanSet.DEFAULT_HOST)) {
358: l2s.removeServer(0);
359: if (l2s.sizeOfServerArray() != 0) {
360: throw new AssertionError(
361: "Default server has not been cleared");
362: }
363: }
364: }
365: }
366:
367: public void addServerToL1Config(String name, int dsoPort,
368: int jmxPort) {
369: cleanBeanSetServersIfNeeded(l1_beanSet);
370:
371: Server newL2 = l1_beanSet.serversBean().addNewServer();
372:
373: if (name != null && !name.equals("")) {
374: newL2.setName(name);
375: }
376: newL2.setHost(TestConfigBeanSet.DEFAULT_HOST);
377:
378: newL2.setDsoPort(dsoPort);
379:
380: if (jmxPort >= 0) {
381: newL2.setJmxPort(jmxPort);
382: }
383:
384: newL2.setData(BOGUS_FILENAME);
385: newL2.setLogs(BOGUS_FILENAME);
386: }
387:
388: public void setGCEnabled(boolean val) {
389: gcEnabled = val;
390: ((SettableConfigItem) l2DSOConfig().garbageCollectionEnabled())
391: .setValue(gcEnabled);
392: }
393:
394: public void setGCVerbose(boolean val) {
395: gcVerbose = val;
396: ((SettableConfigItem) l2DSOConfig().garbageCollectionVerbose())
397: .setValue(gcVerbose);
398: }
399:
400: public void setGCIntervalInSec(int val) {
401: gcIntervalInSec = val;
402: ((SettableConfigItem) l2DSOConfig().garbageCollectionInterval())
403: .setValue(gcIntervalInSec);
404: }
405:
406: public void setPersistenceMode(Enum val) {
407: persistenceMode = val;
408: ((SettableConfigItem) l2DSOConfig().persistenceMode())
409: .setValue(persistenceMode);
410: }
411:
412: public boolean getGCEnabled() {
413: return gcEnabled;
414: }
415:
416: public boolean getGCVerbose() {
417: return gcVerbose;
418: }
419:
420: public int getGCIntervalInSec() {
421: return gcIntervalInSec;
422: }
423:
424: public Enum getPersistenceMode() {
425: return persistenceMode;
426: }
427:
428: private Server findL2Bean(String name) {
429: Server[] allServers = this .beanSet.serversBean()
430: .getServerArray();
431:
432: if (allServers == null || allServers.length == 0)
433: throw Assert.failure("No L2s are defined.");
434:
435: if (name == null) {
436: if (allServers.length == 1)
437: return allServers[0];
438: else
439: throw Assert
440: .failure("You passed in null for the L2 name, but there's more than one L2. Please specify which one you want.");
441: }
442:
443: for (int i = 0; i < allServers.length; ++i) {
444: if (allServers[i].getName().equals(name))
445: return allServers[i];
446: }
447: throw Assert.failure("There is no L2 defined named '" + name
448: + "'.");
449: }
450:
451: public NewCommonL2Config l2CommonConfig(String l2Name) {
452: return (NewCommonL2Config) proxify(NewCommonL2Config.class,
453: findL2Bean(l2Name), this .sampleL2Common, null);
454: }
455:
456: public NewL2DSOConfig l2DSOConfig(String l2Name) {
457: return (NewL2DSOConfig) proxify(NewL2DSOConfig.class,
458: findL2Bean(l2Name), this .sampleL2DSO, null);
459: }
460:
461: public NewCommonL2Config l2CommonConfig() {
462: return (NewCommonL2Config) proxify(NewCommonL2Config.class,
463: allServerBeans(), this .sampleL2Common, null);
464: }
465:
466: public NewL2DSOConfig l2DSOConfig() {
467: return (NewL2DSOConfig) proxify(NewL2DSOConfig.class,
468: allServerBeans(), this .sampleL2DSO, null);
469: }
470:
471: public NewDSOApplicationConfig dsoApplicationConfig(
472: String applicationName) {
473: return (NewDSOApplicationConfig) proxify(
474: NewDSOApplicationConfig.class, this .beanSet
475: .applicationBeanFor(applicationName),
476: this .sampleDSOApplication, "dso");
477: }
478:
479: public NewDSOApplicationConfig dsoApplicationConfig() {
480: return dsoApplicationConfig(TVSConfigurationSetupManagerFactory.DEFAULT_APPLICATION_NAME);
481: }
482:
483: public TestTVSConfigurationSetupManagerFactory(
484: String l2Identifier,
485: IllegalConfigurationChangeHandler illegalConfigurationChangeHandler) {
486: this (MODE_CENTRALIZED_CONFIG, l2Identifier,
487: illegalConfigurationChangeHandler);
488: }
489:
490: public TestTVSConfigurationSetupManagerFactory(
491: IllegalConfigurationChangeHandler illegalConfigurationChangeHandler) {
492: this (null, illegalConfigurationChangeHandler);
493: }
494:
495: public L1TVSConfigurationSetupManager createL1TVSConfigurationSetupManager()
496: throws ConfigurationSetupException {
497: return createL1TVSConfigurationSetupManager(this .l1ConfigurationCreator);
498: }
499:
500: public L1TVSConfigurationSetupManager createL1TVSConfigurationSetupManager(
501: TestConfigurationCreator configCreator)
502: throws ConfigurationSetupException {
503: if (mode == MODE_CENTRALIZED_CONFIG) {
504: StringBuffer l2sSpec = new StringBuffer();
505:
506: Server[] allServers = (Server[]) this .allServerBeans();
507: for (int i = 0; i < allServers.length; ++i) {
508: Server this Server = allServers[i];
509:
510: if (i > 0)
511: l2sSpec.append(",");
512:
513: String hostname = this Server.getHost();
514: if (hostname == null)
515: hostname = this Server.getName();
516: Assert.assertNotBlank(hostname);
517:
518: l2sSpec
519: .append(hostname + ":"
520: + this Server.getDsoPort());
521: }
522:
523: System
524: .setProperty(
525: TVSConfigurationSetupManagerFactory.CONFIG_FILE_PROPERTY_NAME,
526: l2sSpec.toString());
527: }
528:
529: StandardL1TVSConfigurationSetupManager configSetupManager = new StandardL1TVSConfigurationSetupManager(
530: configCreator, this .defaultValueProvider,
531: this .xmlObjectComparator, this .illegalChangeHandler);
532:
533: return configSetupManager;
534: }
535:
536: public L2TVSConfigurationSetupManager createL2TVSConfigurationSetupManager(
537: String l2Identifier) throws ConfigurationSetupException {
538: String effectiveL2Identifier = l2Identifier == null ? this .defaultL2Identifier
539: : l2Identifier;
540: return new StandardL2TVSConfigurationSetupManager(
541: this.l2ConfigurationCreator, effectiveL2Identifier,
542: this.defaultValueProvider, this.xmlObjectComparator,
543: this.illegalChangeHandler);
544: }
545:
546: }
|