KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > tc > config > schema > setup > TestTVSConfigurationSetupManagerFactory


1 /**
2  * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright notice. All rights reserved.
3  */

4 package com.tc.config.schema.setup;
5
6 import org.apache.xmlbeans.XmlObject;
7 import org.apache.xmlbeans.XmlOptions;
8 import org.apache.xmlbeans.impl.common.XPath;
9
10 import com.tc.config.schema.IllegalConfigurationChangeHandler;
11 import com.tc.config.schema.NewCommonL1Config;
12 import com.tc.config.schema.NewCommonL2Config;
13 import com.tc.config.schema.NewSystemConfig;
14 import com.tc.config.schema.SettableConfigItem;
15 import com.tc.config.schema.TestConfigObjectInvocationHandler;
16 import com.tc.config.schema.dynamic.ConfigItem;
17 import com.tc.config.schema.dynamic.XPathBasedConfigItem;
18 import com.tc.config.schema.repository.MutableBeanRepository;
19 import com.tc.object.config.schema.NewDSOApplicationConfig;
20 import com.tc.object.config.schema.NewL1DSOConfig;
21 import com.tc.object.config.schema.NewL2DSOConfig;
22 import com.tc.util.Assert;
23 import com.terracottatech.config.Application;
24 import com.terracottatech.config.Server;
25
26 import java.lang.reflect.Proxy JavaDoc;
27 import java.util.ArrayList JavaDoc;
28 import java.util.Arrays JavaDoc;
29 import java.util.HashSet JavaDoc;
30 import java.util.Iterator JavaDoc;
31 import java.util.List JavaDoc;
32 import java.util.Set JavaDoc;
33
34 /**
35  * A {@link com.tc.config.schema.setup.TVSConfigurationSetupManagerFactory} that creates config appropriate for usage in
36  * tests. This config behaves just like normal config, except that it reads no files; everything is in-memory instead.
37  * You can specify whether you want this config to act like centralized config (all at L2), or distributed config (every
38  * L1 has its own copy of the config, too).
39  * </p>
40  * <p>
41  * To use this class, simply get the appropriate config object that you need by calling a method (<em>e.g.</em>,
42  * {@link #systemConfig()}). Then, call a method on it (like {@link com.tc.config.schema.NewSystemConfig#dsoEnabled()},
43  * for example); this will give you back a {@link ConfigItem}, or a subinterface thereof. Cast this item to a
44  * {@link com.tc.config.schema.SettableConfigItem}, and then call {@link SettableConfigItem#setValue(Object)} on it (or
45  * one of the similar methods that takes a primitive type), passing it the value you want this class to return for that
46  * item. Make sure you get the class right &mdash; if you don't, you'll get a nasty {@link ClassCastException} when
47  * production code tries to access the value in that {@link ConfigItem}.
48  * </p>
49  * <p>
50  * Only one little trick: if you're setting something that's a complex object &mdash; <em>i.e.</em>, not just a
51  * primitive type, {@link String}, array of {@link String}s or something like that (specifically, the object types
52  * returned by the top-level subclasses of {@link com.tc.config.schema.dynamic.XPathBasedConfigItem}) &mdash; then you
53  * need to set an implementation of {@link XmlObject}, not the actual Terracotta-defined types that the real
54  * {@link ConfigItem}s return. (This is because we're using the real config system &mdash; see below for details
55  * &mdash; and it expects {@link XmlObject}s of the appropriate type so it can translate them to the Terracotta-defined
56  * types that we really return.) Fortunately, all XML beans have <code>Factory</code> inner classes that will let you
57  * 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
58  * beans are changed.
59  * </p>
60  * <p>
61  * Note: There is no support yet for different L1s having different config, or config that differs from L2's.
62  * </p>
63  * <h3>Maintenance:</h3>
64  * <p>
65  * If you create new typed subinterfaces of {@link ConfigItem}, you do need to make
66  * {@link com.tc.config.schema.TestConfigObjectInvocationHandler.OurSettableConfigItem} implement them. Don't worry,
67  * though; the methods can just throw {@link com.tc.util.TCAssertionError}, and don't need to (nor should they)
68  * actually do anything.
69  * </p>
70  * <p>
71  * If you introduce new config objects or new beans to the system, you'll need to do a lot more, but, then, presumably
72  * if you're doing that you understand more about the way the config system works than this comment is going to tell
73  * you, even if it is long.
74  * </p>
75  * <p>
76  * That's it. In particular, there's no need to actually do anything when you add new items to existing config objects:
77  * this is significant.
78  * </p>
79  * <h3>How it works:</h3>
80  * <p>
81  * How this all works is a little interesting. It's involved, but it buys us two hugely useful properties: clients use
82  * basically the same APIs to change config parameters as they do to get them, and we need to do zero (really!) work
83  * when new {@link ConfigItem}s are added to config objects. This means it's impossible for test code to get
84  * "out-of-sync" with respect to config, so it always works, and Eclipse's refactoring tools also work.
85  * </p>
86  * <p>
87  * First, a little overview: for our test config, we use the real production config system, all the way down to the
88  * level of the actual {@link XmlObject}s that get stuffed in the config system's
89  * {@link com.tc.config.schema.repository.BeanRepository} objects. <em>Those</em> are realy {@link XmlObject}s of the
90  * appropriate type, created by the {@link TestConfigBeanSet} and modified by calls to the pseudo-'config objects' that
91  * this class exposes. However, everything else is real: you're exercising real config code, real XPath-based
92  * {@link ConfigItem}s reading from real {@link XmlObject}s, real L1-L2 protocol handling, and so on. This has many
93  * benefits, including making your tests behave more closely to the way real production code works (a good thing), and
94  * exercising more of the config system in your tests (also a good thing).
95  * </p>
96  * <p>
97  * Details of how it all works:
98  * </p>
99  * <ul>
100  * <li>A {@link TestConfigBeanSet} holds on to the set of all root {@link XmlObject}s that we need to configure the
101  * system &mdash; for example, the {@link L1} we use for L1 config, the {@link L2}s representing each L2's config (and
102  * the {@link L2S} that wraps them all up together), the {@link com.terracottatech.configV1.System} we use for system
103  * config, the {@link Application} for each application's config, and so on.</li>
104  * <li>These {@link XmlObject}s are honest-to-God real instances, as created by their factories (for example,
105  * {@link L1.Factory}. At the start, they have just enough configuration populated into them to make sure they
106  * validate.</li>
107  * <li>This class exposes what look like instances of the normal config objects available to the system. However, these
108  * are actually proxies created with {@link java.lang.reflect.Proxy}, using a
109  * {@link com.tc.config.schema.TestConfigObjectInvocationHandler}.</li>
110  * <li>That invocation handler, in response to method calls, parcels out {@link ConfigItem}s that are instances of
111  * {@link com.tc.config.schema.TestConfigObjectInvocationHandler.OurSettableConfigItem}. When you call
112  * <code>setValue</code> on them, they do their magic: using the {@link XPath} they get from the corresponding
113  * "sample" {@link ConfigItem} (see below), they descend the tree of {@link XmlObject}s, starting at the root, creating
114  * children along the way as necessary, and finally set the correct property on the correct bean. (This is conceptually
115  * easy but actually full of all kinds of nasty mess; this is why {@link OurSettableConfigItem} is such a messy class.) .</li>
116  * <li>Okay, but how does it know what XPath to use to descend the tree? That's where the "sample" config objects below
117  * (fields in this object) come in. They are actual, real config objects that are created around the bean set, before
118  * any values are set &mdash; but that doesn't matter, because the only thing we use them for is to get the
119  * {@link XPathBasedConfigItem}s out of them and extract the XPath from them. So, when you call the method that gets a
120  * {@link com.tc.config.schema.TestConfigObjectInvocationHandler.OurSettableConfigItem} from the proxied-up config
121  * object, it calls the exact same method on the "sample" config object, grabbing the {@link ConfigItem} returned,
122  * casting it to an {@link com.tc.config.schema.dynamic.XPathBasedConfigItem}, and extracting the XPath out of that.</li>
123  * </ul>
124  * </p>
125  * <p>
126  * Is this whole thing complicated? Yes, absolutely. Can it probably be simplified? Yes. Is the design bad? I don't
127  * think so, and here's why: it gives a very clean, very simple API for setting config values, and it's maintainable.
128  * Other potential solutions either tend to hurt in terms of maintenance &mdash; you have to do something whenever you
129  * add a config item to the system, and if you mess it up, your new config just gets silently ignored &mdash; or in
130  * terms of API &mdash; they create a much more complex (and much harder to maintain) API for setting config values.
131  * This way, all the complexity is wrapped in three smallish classes (this one, {@link TestConfigBeanSet}, and
132  * {@link com.tc.config.schema.TestConfigObjectInvocationHandler}) in the config package in the source tree, and not
133  * spread all over the place throughout our code, causing massive pain if we ever have to change anything.
134  */

135 public class TestTVSConfigurationSetupManagerFactory extends BaseTVSConfigurationSetupManagerFactory {
136
137   public static final int MODE_CENTRALIZED_CONFIG = 0;
138   public static final int MODE_DISTRIBUTED_CONFIG = 1;
139
140   private final TestConfigBeanSet beanSet;
141
142   private final TestConfigurationCreator l1ConfigurationCreator;
143   private final TestConfigurationCreator l2ConfigurationCreator;
144
145   private final NewSystemConfig sampleSystem;
146   private final NewCommonL1Config sampleL1Common;
147   private final NewL1DSOConfig sampleL1DSO;
148   private final NewCommonL2Config sampleL2Common;
149   private final NewL2DSOConfig sampleL2DSO;
150   private final NewDSOApplicationConfig sampleDSOApplication;
151
152   private final String JavaDoc defaultL2Identifier;
153
154   private final int mode;
155
156   public TestTVSConfigurationSetupManagerFactory(int mode, String JavaDoc l2Identifier,
157                                                  IllegalConfigurationChangeHandler illegalConfigurationChangeHandler) {
158     super(illegalConfigurationChangeHandler);
159
160     this.beanSet = new TestConfigBeanSet();
161
162     this.l2ConfigurationCreator = new TestConfigurationCreator(this.beanSet, true);
163
164     this.mode = mode;
165     if (mode == MODE_CENTRALIZED_CONFIG) {
166       this.l1ConfigurationCreator = new TestConfigurationCreator(this.beanSet, true);
167     } else if (mode == MODE_DISTRIBUTED_CONFIG) {
168       this.l1ConfigurationCreator = new TestConfigurationCreator(this.beanSet, false);
169     } else {
170       throw Assert.failure("Unknown mode: " + mode);
171     }
172
173     if (l2Identifier != null) {
174       Assert.assertNotBlank(l2Identifier);
175       this.defaultL2Identifier = l2Identifier;
176     } else {
177       this.defaultL2Identifier = this.beanSet.serversBean().getServerArray()[0].getName();
178     }
179
180     Assert.assertNotNull(this.defaultL2Identifier);
181
182     // FIXME 2005-11-30 andrew -- This stinks like mad...we should be able to do something better than perverting the
183
// existing config-setup managers here.
184
L1TVSConfigurationSetupManager sampleL1Manager;
185     L2TVSConfigurationSetupManager sampleL2Manager;
186
187     try {
188       sampleL1Manager = this.createL1TVSConfigurationSetupManager(new TestConfigurationCreator(this.beanSet, true));
189       sampleL2Manager = this.createL2TVSConfigurationSetupManager(null);
190     } catch (ConfigurationSetupException cse) {
191       throw Assert.failure("Huh?", cse);
192     }
193
194     this.sampleSystem = sampleL2Manager.systemConfig();
195     this.sampleL1Common = sampleL1Manager.commonL1Config();
196     this.sampleL1DSO = sampleL1Manager.dsoL1Config();
197     this.sampleL2Common = sampleL2Manager.commonl2Config();
198     this.sampleL2DSO = sampleL2Manager.dsoL2Config();
199     this.sampleDSOApplication = sampleL1Manager
200         .dsoApplicationConfigFor(TVSConfigurationSetupManagerFactory.DEFAULT_APPLICATION_NAME);
201
202     applyDefaultTestConfig();
203   }
204
205   public TestConfigBeanSet beanSet() {
206     return this.beanSet;
207   }
208
209   private static final String JavaDoc BOGUS_FILENAME = "nonexistent-directory-SHOULD-NEVER-EXIST/../";
210
211   private void applyDefaultTestConfig() {
212 // // Use a license that lets us do anything.
213
// try {
214
// String path = getEverythingLicensePath();
215
// ((SettableConfigItem) systemConfig().licenseLocation()).setValue(path);
216
// } catch (IOException ioe) {
217
// throw Assert.failure("Unable to fetch data directory root to find license for tests.", ioe);
218
// }
219
//
220
// ((SettableConfigItem) systemConfig().licenseType()).setValue(LicenseType.PRODUCTION);
221
//
222
// // Make servers use dynamic ports, by default.
223
// ((SettableConfigItem) l2DSOConfig().listenPort()).setValue(0);
224
((SettableConfigItem) l2CommonConfig().jmxPort()).setValue(0);
225
226     // We also set the data and log directories to strings that shouldn't be valid on any platform: you need to set
227
// these yourself before you use this config. If you don't, you'll write all over the place as we create 'data' and
228
// 'logs' directories willy-nilly. Don't do that.
229
((SettableConfigItem) l1CommonConfig().logsPath()).setValue(BOGUS_FILENAME);
230     ((SettableConfigItem) l2CommonConfig().dataPath()).setValue(BOGUS_FILENAME);
231     ((SettableConfigItem) l2CommonConfig().logsPath()).setValue(BOGUS_FILENAME);
232   }
233
234   public void activateConfigurationChange() throws ConfigurationSetupException {
235     Set JavaDoc allRepositories = collectAllRepositories();
236
237     Iterator JavaDoc iter = allRepositories.iterator();
238     while (iter.hasNext()) {
239       MutableBeanRepository repository = (MutableBeanRepository) iter.next();
240       checkValidates(repository.bean());
241       repository.didMutateBean();
242     }
243
244     iter = allRepositories.iterator();
245     while (iter.hasNext()) {
246       MutableBeanRepository repository = (MutableBeanRepository) iter.next();
247       repository.saveCopyOfBeanInAnticipationOfFutureMutation();
248     }
249   }
250
251   private void checkValidates(XmlObject bean) throws ConfigurationSetupException {
252     List JavaDoc errors = new ArrayList JavaDoc();
253     XmlOptions options = new XmlOptions().setErrorListener(errors);
254     boolean valid = bean.validate(options);
255
256     if ((!valid) || (errors.size() > 0)) {
257       // formatting
258
throw new ConfigurationSetupException("You have errors in your config: " + errors);
259     }
260   }
261
262   private Set JavaDoc collectAllRepositories() {
263     Set JavaDoc allRepositories = new HashSet JavaDoc();
264
265     allRepositories.addAll(Arrays.asList(this.l1ConfigurationCreator.allRepositoriesStoredInto()));
266     allRepositories.addAll(Arrays.asList(this.l2ConfigurationCreator.allRepositoriesStoredInto()));
267     return allRepositories;
268   }
269
270   private Object JavaDoc proxify(Class JavaDoc theClass, XmlObject[] destObjects, Object JavaDoc realImplementation, String JavaDoc xpathPrefix) {
271     return Proxy.newProxyInstance(getClass().getClassLoader(), new Class JavaDoc[] { theClass },
272                                   new TestConfigObjectInvocationHandler(theClass, destObjects, realImplementation,
273                                                                         xpathPrefix));
274   }
275
276   private Object JavaDoc proxify(Class JavaDoc theClass, XmlObject destObject, Object JavaDoc realImplementation, String JavaDoc xpathPrefix) {
277     return proxify(theClass, new XmlObject[] { destObject }, realImplementation, xpathPrefix);
278   }
279
280   private XmlObject[] allServerBeans() {
281     return this.beanSet.serversBean().getServerArray();
282   }
283
284   public NewSystemConfig systemConfig() {
285     return (NewSystemConfig) proxify(NewSystemConfig.class, this.beanSet.systemBean(), this.sampleSystem, null);
286   }
287
288   public NewCommonL1Config l1CommonConfig() {
289     return (NewCommonL1Config) proxify(NewCommonL1Config.class, this.beanSet.clientBean(), this.sampleL1Common, null);
290   }
291
292   public NewL1DSOConfig l1DSOConfig() {
293     return (NewL1DSOConfig) proxify(NewL1DSOConfig.class, this.beanSet.clientBean(), this.sampleL1DSO, "dso");
294   }
295
296   public void addServerConfig(String JavaDoc name) {
297     Server newL2 = this.beanSet.serversBean().addNewServer();
298
299     newL2.setName(name);
300     newL2.setHost("localhost");
301
302     newL2.setDsoPort(0);
303     newL2.setJmxPort(0);
304
305     newL2.setData(BOGUS_FILENAME);
306     newL2.setLogs(BOGUS_FILENAME);
307   }
308
309   private Server findL2Bean(String JavaDoc name) {
310     Server[] allServers = this.beanSet.serversBean().getServerArray();
311
312     if (allServers == null || allServers.length == 0) throw Assert.failure("No L2s are defined.");
313
314     if (name == null) {
315       if (allServers.length == 1) return allServers[0];
316       else throw Assert
317           .failure("You passed in null for the L2 name, but there's more than one L2. Please specify which one you want.");
318     }
319
320     for (int i = 0; i < allServers.length; ++i) {
321       if (allServers[i].getName().equals(name)) return allServers[i];
322     }
323     throw Assert.failure("There is no L2 defined named '" + name + "'.");
324   }
325
326   public NewCommonL2Config l2CommonConfig(String JavaDoc l2Name) {
327     return (NewCommonL2Config) proxify(NewCommonL2Config.class, findL2Bean(l2Name), this.sampleL2Common, null);
328   }
329
330   public NewL2DSOConfig l2DSOConfig(String JavaDoc l2Name) {
331     return (NewL2DSOConfig) proxify(NewL2DSOConfig.class, findL2Bean(l2Name), this.sampleL2DSO, null);
332   }
333
334   public NewCommonL2Config l2CommonConfig() {
335     return (NewCommonL2Config) proxify(NewCommonL2Config.class, allServerBeans(), this.sampleL2Common, null);
336   }
337
338   public NewL2DSOConfig l2DSOConfig() {
339     return (NewL2DSOConfig) proxify(NewL2DSOConfig.class, allServerBeans(), this.sampleL2DSO, null);
340   }
341
342   public NewDSOApplicationConfig dsoApplicationConfig(String JavaDoc applicationName) {
343     return (NewDSOApplicationConfig) proxify(NewDSOApplicationConfig.class, this.beanSet
344         .applicationBeanFor(applicationName), this.sampleDSOApplication, "dso");
345   }
346
347   public NewDSOApplicationConfig dsoApplicationConfig() {
348     return dsoApplicationConfig(TVSConfigurationSetupManagerFactory.DEFAULT_APPLICATION_NAME);
349   }
350
351   public TestTVSConfigurationSetupManagerFactory(String JavaDoc l2Identifier,
352                                                  IllegalConfigurationChangeHandler illegalConfigurationChangeHandler) {
353     this(MODE_CENTRALIZED_CONFIG, l2Identifier, illegalConfigurationChangeHandler);
354   }
355
356   public TestTVSConfigurationSetupManagerFactory(IllegalConfigurationChangeHandler illegalConfigurationChangeHandler) {
357     this(null, illegalConfigurationChangeHandler);
358   }
359
360   public L1TVSConfigurationSetupManager createL1TVSConfigurationSetupManager() throws ConfigurationSetupException {
361     return createL1TVSConfigurationSetupManager(this.l1ConfigurationCreator);
362   }
363
364   public L1TVSConfigurationSetupManager createL1TVSConfigurationSetupManager(TestConfigurationCreator configCreator)
365       throws ConfigurationSetupException {
366     if (mode == MODE_CENTRALIZED_CONFIG) {
367       StringBuffer JavaDoc l2sSpec = new StringBuffer JavaDoc();
368
369       Server[] allServers = (Server[]) this.allServerBeans();
370       for (int i = 0; i < allServers.length; ++i) {
371         Server thisServer = allServers[i];
372
373         if (i > 0) l2sSpec.append(",");
374
375         String JavaDoc hostname = thisServer.getHost();
376         if (hostname == null) hostname = thisServer.getName();
377         Assert.assertNotBlank(hostname);
378
379         l2sSpec.append(hostname + ":" + thisServer.getDsoPort());
380       }
381
382       System.setProperty(TVSConfigurationSetupManagerFactory.CONFIG_FILE_PROPERTY_NAME, l2sSpec.toString());
383     }
384
385     return new StandardL1TVSConfigurationSetupManager(configCreator, this.defaultValueProvider,
386                                                       this.xmlObjectComparator, this.illegalChangeHandler);
387   }
388
389   public L2TVSConfigurationSetupManager createL2TVSConfigurationSetupManager(String JavaDoc l2Identifier)
390       throws ConfigurationSetupException {
391     String JavaDoc effectiveL2Identifier = l2Identifier == null ? this.defaultL2Identifier : l2Identifier;
392     return new StandardL2TVSConfigurationSetupManager(this.l2ConfigurationCreator, effectiveL2Identifier,
393                                                       this.defaultValueProvider, this.xmlObjectComparator,
394                                                       this.illegalChangeHandler);
395   }
396
397 }
398
Popular Tags