KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > tc > config > schema > dynamic > XPathBasedConfigItem


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.dynamic;
5
6 import org.apache.xmlbeans.XmlException;
7 import org.apache.xmlbeans.XmlObject;
8
9 import com.tc.config.schema.context.ConfigContext;
10 import com.tc.config.schema.listen.ConfigurationChangeListener;
11 import com.tc.util.Assert;
12
13 import java.lang.reflect.InvocationTargetException JavaDoc;
14 import java.lang.reflect.Method JavaDoc;
15
16 /**
17  * A {@link ConfigItem} that uses XPaths to find its data. Caches the current value for efficiency, and provides for
18  * specification of a default, which it will use if the value is <code>null</code>.
19  * </p>
20  * <p>
21  * Subclasses must take care of extracting the actual required value from the {@link XmlObject}.
22  * </p>
23  * <p>
24  * Normally, this class would be doing too much stuff &mdash; it handles defaults, caching, and data extraction, all at
25  * once. However, because of the restrictions of Java's type system, doing it any other way seems to lead to an
26  * explosion of classes. If you can figure out a way to factor it that splits up this class but doesn't lead to a class
27  * explosion, by all means, do so.
28  */

29 public abstract class XPathBasedConfigItem implements ConfigItem, ConfigurationChangeListener {
30
31   private final ConfigContext context;
32   private final String JavaDoc xpath;
33
34   private Object JavaDoc defaultValue;
35   private boolean defaultInitialized;
36
37   private final CompoundConfigItemListener listener;
38
39   private boolean haveCurrentValue;
40   private Object JavaDoc currentValue;
41
42   public XPathBasedConfigItem(ConfigContext context, String JavaDoc xpath) {
43     Assert.assertNotNull(context);
44     Assert.assertNotBlank(xpath);
45
46     this.context = context;
47     this.xpath = xpath;
48
49     this.defaultInitialized = false;
50
51     this.listener = new CompoundConfigItemListener();
52
53     this.haveCurrentValue = false;
54     this.currentValue = null;
55
56     this.context.itemCreated(this);
57   }
58
59   private synchronized void initializeDefaultIfNecessary() {
60     if (!this.defaultInitialized) {
61       try {
62         if (this.context.hasDefaultFor(xpath)) {
63           this.defaultValue = fetchDataFromXmlObject(this.context.defaultFor(this.xpath));
64         } else {
65           this.defaultValue = null;
66         }
67         this.defaultInitialized = true;
68       } catch (XmlException xmle) {
69         throw Assert.failure("Couldn't use XPath '" + this.xpath + "' to fetch a default value", xmle);
70       }
71     }
72   }
73
74   /**
75    * Generally, you <strong>SHOULD NOT</strong> use this constructor. Instead, you should let the schema specify the
76    * default, as we want that to be the canonical repository for default values. However, certain things &mdash; like
77    * attributes, arrays, and other complex structures &mdash; can't have defaults provided in a schema, so we must
78    * provide this method instead.
79    */

80   public XPathBasedConfigItem(ConfigContext context, String JavaDoc xpath, Object JavaDoc defaultValue) {
81     Assert.assertNotNull(context);
82     Assert.assertNotBlank(xpath);
83
84     this.context = context;
85     this.xpath = xpath;
86
87     this.defaultValue = defaultValue;
88     this.defaultInitialized = true;
89
90     this.listener = new CompoundConfigItemListener();
91
92     this.haveCurrentValue = false;
93     this.currentValue = null;
94
95     this.context.itemCreated(this);
96   }
97
98   public synchronized Object JavaDoc getObject() {
99     if (!this.haveCurrentValue) {
100       synchronized (this.context.syncLockForBean()) {
101         this.currentValue = fetchDataFromTopLevelBean(this.context.bean());
102         this.haveCurrentValue = true;
103       }
104     }
105
106     return this.currentValue;
107   }
108
109   private Object JavaDoc fetchDataFromTopLevelBean(XmlObject bean) {
110     Object JavaDoc out = null;
111
112     initializeDefaultIfNecessary();
113
114     if (bean != null) {
115       XmlObject[] targetList;
116
117       // We synchronize on the bean for test code; test code might be changing it while we're selecting from it.
118
synchronized (bean) {
119         targetList = bean.selectPath(this.xpath);
120       }
121
122       if (targetList == null || targetList.length == 0 || (targetList.length == 1 && targetList[0] == null)) out = fetchDataFromXmlObject(null);
123       else if (targetList.length == 1) out = fetchDataFromXmlObject(targetList[0]);
124       else throw Assert.failure("From " + bean + ", XPath '" + this.xpath + "' selected " + targetList.length
125                                 + " nodes, not " + "just 1. This should never happen; there is a bug in the software.");
126     }
127
128     if (out == null) out = this.defaultValue;
129
130     return out;
131   }
132
133   protected abstract Object JavaDoc fetchDataFromXmlObject(XmlObject xmlObject);
134
135   protected final Object JavaDoc fetchDataFromXmlObjectByReflection(XmlObject xmlObject, String JavaDoc methodName) {
136     if (xmlObject == null) return null;
137
138     Method JavaDoc method = getMethodWithNoParametersByName(xmlObject.getClass(), methodName);
139
140     if (method == null) {
141       // formatting
142
throw Assert.failure("There is no method named '" + methodName + "' on object " + xmlObject + " (of class "
143                            + xmlObject.getClass().getName() + ") with no parameters.");
144     }
145
146     try {
147       return method.invoke(xmlObject, new Object JavaDoc[0]);
148     } catch (IllegalArgumentException JavaDoc iae) {
149       throw Assert.failure("Unable to invoke method " + method + ".", iae);
150     } catch (IllegalAccessException JavaDoc iae) {
151       throw Assert.failure("Unable to invoke method " + method + ".", iae);
152     } catch (InvocationTargetException JavaDoc ite) {
153       throw Assert.failure("Unable to invoke method " + method + ".", ite);
154     }
155   }
156
157   protected final Method JavaDoc getMethodWithNoParametersByName(Class JavaDoc theClass, String JavaDoc methodName) {
158     Method JavaDoc[] allMethods = theClass.getMethods();
159     for (int i = 0; i < allMethods.length; ++i) {
160       if (allMethods[i].getName().equals(methodName) && allMethods[i].getParameterTypes().length == 0) { return allMethods[i]; }
161     }
162
163     return null;
164   }
165
166   public synchronized void addListener(ConfigItemListener changeListener) {
167     Assert.assertNotNull(changeListener);
168     this.listener.addListener(changeListener);
169   }
170
171   public synchronized void removeListener(ConfigItemListener changeListener) {
172     Assert.assertNotNull(changeListener);
173     this.listener.removeListener(changeListener);
174   }
175
176   public synchronized void configurationChanged(XmlObject oldConfig, XmlObject newConfig) {
177     Object JavaDoc oldValue, newValue;
178
179     synchronized (this.context.syncLockForBean()) {
180       if (this.haveCurrentValue) oldValue = this.currentValue;
181       else oldValue = fetchDataFromTopLevelBean(oldConfig);
182
183       newValue = fetchDataFromTopLevelBean(newConfig);
184
185       this.currentValue = newValue;
186       this.haveCurrentValue = true;
187     }
188
189     if (((oldValue == null) != (newValue == null)) || ((oldValue != null) && (!oldValue.equals(newValue)))) {
190       this.listener.valueChanged(oldValue, newValue);
191     }
192   }
193
194   public String JavaDoc toString() {
195     return "configuration item at XPath '" + this.xpath + "'";
196   }
197
198   /**
199    * For <strong>TESTS ONLY</strong>.
200    */

201   public ConfigContext context() {
202     return this.context;
203   }
204
205   /**
206    * For <strong>TESTS ONLY</strong>.
207    */

208   public String JavaDoc xpath() {
209     return this.xpath;
210   }
211
212   /**
213    * For <strong>TESTS ONLY</strong>.
214    */

215   public Object JavaDoc defaultValue() {
216     initializeDefaultIfNecessary();
217     return this.defaultValue;
218   }
219
220 }
221
Popular Tags