| A
com.tc.config.schema.setup.TVSConfigurationSetupManagerFactory that creates config appropriate for usage in
tests. This config behaves just like normal config, except that it reads no files; everything is in-memory instead.
You can specify whether you want this config to act like centralized config (all at L2), or distributed config (every
L1 has its own copy of the config, too).
To use this class, simply get the appropriate config object that you need by calling a method (e.g.,
TestTVSConfigurationSetupManagerFactory.systemConfig() ). Then, call a method on it (like
com.tc.config.schema.NewSystemConfig.dsoEnabled ,
for example); this will give you back a
ConfigItem , or a subinterface thereof. Cast this item to a
com.tc.config.schema.SettableConfigItem , and then call
SettableConfigItem.setValue(Object) on it (or
one of the similar methods that takes a primitive type), passing it the value you want this class to return for that
item. Make sure you get the class right — if you don't, you'll get a nasty
ClassCastException when
production code tries to access the value in that
ConfigItem .
Only one little trick: if you're setting something that's a complex object — i.e., not just a
primitive type,
String , array of
String s or something like that (specifically, the object types
returned by the top-level subclasses of
com.tc.config.schema.dynamic.XPathBasedConfigItem ) — then you
need to set an implementation of
XmlObject , not the actual Terracotta-defined types that the real
ConfigItem s return. (This is because we're using the real config system — see below for details
— and it expects
XmlObject s of the appropriate type so it can translate them to the Terracotta-defined
types that we really return.) Fortunately, all XML beans have Factory inner classes that will let you
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
beans are changed.
Note: There is no support yet for different L1s having different config, or config that differs from L2's.
Maintenance:
If you create new typed subinterfaces of
ConfigItem , you do need to make
com.tc.config.schema.TestConfigObjectInvocationHandler.OurSettableConfigItem implement them. Don't worry,
though; the methods can just throw
com.tc.util.TCAssertionError , and don't need to (nor should they)
actually do anything.
If you introduce new config objects or new beans to the system, you'll need to do a lot more, but, then, presumably
if you're doing that you understand more about the way the config system works than this comment is going to tell
you, even if it is long.
That's it. In particular, there's no need to actually do anything when you add new items to existing config objects:
this is significant.
How it works:
How this all works is a little interesting. It's involved, but it buys us two hugely useful properties: clients use
basically the same APIs to change config parameters as they do to get them, and we need to do zero (really!) work
when new
ConfigItem s are added to config objects. This means it's impossible for test code to get
"out-of-sync" with respect to config, so it always works, and Eclipse's refactoring tools also work.
First, a little overview: for our test config, we use the real production config system, all the way down to the
level of the actual
XmlObject s that get stuffed in the config system's
com.tc.config.schema.repository.BeanRepository objects. Those are realy
XmlObject s of the
appropriate type, created by the
TestConfigBeanSet and modified by calls to the pseudo-'config objects' that
this class exposes. However, everything else is real: you're exercising real config code, real XPath-based
ConfigItem s reading from real
XmlObject s, real L1-L2 protocol handling, and so on. This has many
benefits, including making your tests behave more closely to the way real production code works (a good thing), and
exercising more of the config system in your tests (also a good thing).
Details of how it all works:
- A
TestConfigBeanSet holds on to the set of all root
XmlObject s that we need to configure the
system — for example, the
L1 we use for L1 config, the
L2 s representing each L2's config (and
the
L2S that wraps them all up together), the
com.terracottatech.configV1.System we use for system
config, the
Application for each application's config, and so on.
- These
XmlObject s are honest-to-God real instances, as created by their factories (for example,
L1.Factory . At the start, they have just enough configuration populated into them to make sure they
validate.
- This class exposes what look like instances of the normal config objects available to the system. However, these
are actually proxies created with
java.lang.reflect.Proxy , using a
com.tc.config.schema.TestConfigObjectInvocationHandler .
- That invocation handler, in response to method calls, parcels out
ConfigItem s that are instances of
com.tc.config.schema.TestConfigObjectInvocationHandler.OurSettableConfigItem . When you call
setValue on them, they do their magic: using the
XPath they get from the corresponding
"sample"
ConfigItem (see below), they descend the tree of
XmlObject s, starting at the root, creating
children along the way as necessary, and finally set the correct property on the correct bean. (This is conceptually
easy but actually full of all kinds of nasty mess; this is why
OurSettableConfigItem is such a messy class.) .
- Okay, but how does it know what XPath to use to descend the tree? That's where the "sample" config objects below
(fields in this object) come in. They are actual, real config objects that are created around the bean set, before
any values are set — but that doesn't matter, because the only thing we use them for is to get the
XPathBasedConfigItem s out of them and extract the XPath from them. So, when you call the method that gets a
com.tc.config.schema.TestConfigObjectInvocationHandler.OurSettableConfigItem from the proxied-up config
object, it calls the exact same method on the "sample" config object, grabbing the
ConfigItem returned,
casting it to an
com.tc.config.schema.dynamic.XPathBasedConfigItem , and extracting the XPath out of that.
Is this whole thing complicated? Yes, absolutely. Can it probably be simplified? Yes. Is the design bad? I don't
think so, and here's why: it gives a very clean, very simple API for setting config values, and it's maintainable.
Other potential solutions either tend to hurt in terms of maintenance — you have to do something whenever you
add a config item to the system, and if you mess it up, your new config just gets silently ignored — or in
terms of API — they create a much more complex (and much harder to maintain) API for setting config values.
This way, all the complexity is wrapped in three smallish classes (this one,
TestConfigBeanSet , and
com.tc.config.schema.TestConfigObjectInvocationHandler ) in the config package in the source tree, and not
spread all over the place throughout our code, causing massive pain if we ever have to change anything.
|