1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.betwixt.io;
19
20 import org.apache.commons.betwixt.BindingConfiguration;
21 import org.apache.commons.betwixt.ElementDescriptor;
22 import org.apache.commons.betwixt.XMLIntrospector;
23 import org.apache.commons.betwixt.expression.Context;
24 import org.apache.commons.betwixt.io.read.BeanBindAction;
25 import org.apache.commons.betwixt.io.read.MappingAction;
26 import org.apache.commons.betwixt.io.read.ReadConfiguration;
27 import org.apache.commons.betwixt.io.read.ReadContext;
28 import org.apache.commons.digester.Digester;
29 import org.apache.commons.digester.Rule;
30 import org.apache.commons.digester.RuleSet;
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33 import org.xml.sax.Attributes;
34
35 /*** <p>Sets <code>Betwixt</code> digestion rules for a bean class.</p>
36 *
37 * @author <a href="mailto:rdonkin@apache.org">Robert Burrell Donkin</a>
38 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
39 * @since 0.5
40 */
41 public class BeanRuleSet implements RuleSet {
42
43 /*** Logger */
44 private static Log log = LogFactory.getLog(BeanRuleSet.class);
45
46 /***
47 * Set log to be used by <code>BeanRuleSet</code> instances
48 * @param aLog the <code>Log</code> implementation for this class to log to
49 */
50 public static void setLog(Log aLog) {
51 log = aLog;
52 }
53
54 /*** The base path under which the rules will be attached */
55 private String basePath;
56 /*** The element descriptor for the base */
57 private ElementDescriptor baseElementDescriptor;
58 /*** The (empty) base context from which all Contexts
59 with beans are (directly or indirectly) obtained */
60 private DigesterReadContext context;
61 /*** allows an attribute to be specified to overload the types of beans used */
62 private String classNameAttribute = "className";
63
64 /***
65 * Base constructor.
66 *
67 * @param introspector the <code>XMLIntrospector</code> used to introspect
68 * @param basePath specifies the (Digester-style) path under which the rules will be attached
69 * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules
70 * @param baseBeanClass the <code>Class</code> whose mapping rules will be created
71 * @param matchIDs should ID/IDREFs be used to match beans?
72 * @deprecated 0.5 use constructor which takes a ReadContext instead
73 */
74 public BeanRuleSet(
75 XMLIntrospector introspector,
76 String basePath,
77 ElementDescriptor baseElementDescriptor,
78 Class baseBeanClass,
79 boolean matchIDs) {
80 this.basePath = basePath;
81 this.baseElementDescriptor = baseElementDescriptor;
82 BindingConfiguration bindingConfiguration = new BindingConfiguration();
83 bindingConfiguration.setMapIDs(matchIDs);
84 context =
85 new DigesterReadContext(
86 log,
87 bindingConfiguration,
88 new ReadConfiguration());
89 context.setRootClass(baseBeanClass);
90 context.setXMLIntrospector(introspector);
91 }
92
93 /***
94 * Base constructor.
95 *
96 * @param introspector the <code>XMLIntrospector</code> used to introspect
97 * @param basePath specifies the (Digester-style) path under which the rules will be attached
98 * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules
99 * @param context the root Context that bean carrying Contexts should be obtained from,
100 * not null
101 * @deprecated 0.6 use the constructor which takes a ReadContext instead
102 */
103 public BeanRuleSet(
104 XMLIntrospector introspector,
105 String basePath,
106 ElementDescriptor baseElementDescriptor,
107 Context context) {
108
109 this.basePath = basePath;
110 this.baseElementDescriptor = baseElementDescriptor;
111 this.context =
112 new DigesterReadContext(context, new ReadConfiguration());
113 this.context.setRootClass(
114 baseElementDescriptor.getSingularPropertyType());
115 this.context.setXMLIntrospector(introspector);
116 }
117
118 /***
119 * Base constructor.
120 *
121 * @param introspector the <code>XMLIntrospector</code> used to introspect
122 * @param basePath specifies the (Digester-style) path under which the rules will be attached
123 * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules
124 * @param baseBeanClass the <code>Class</code> whose mapping rules will be created
125 * @param context the root Context that bean carrying Contexts should be obtained from,
126 * not null
127 * @deprecated 0.5 use the constructor which takes a ReadContext instead
128 */
129 public BeanRuleSet(
130 XMLIntrospector introspector,
131 String basePath,
132 ElementDescriptor baseElementDescriptor,
133 Class baseBeanClass,
134 Context context) {
135 this(
136 introspector,
137 basePath,
138 baseElementDescriptor,
139 baseBeanClass,
140 new ReadContext( context, new ReadConfiguration() ));
141 }
142
143 /***
144 * Base constructor.
145 *
146 * @param introspector the <code>XMLIntrospector</code> used to introspect
147 * @param basePath specifies the (Digester-style) path under which the rules will be attached
148 * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules
149 * @param baseBeanClass the <code>Class</code> whose mapping rules will be created
150 * @param baseContext the root Context that bean carrying Contexts should be obtained from,
151 * not null
152 */
153 public BeanRuleSet(
154 XMLIntrospector introspector,
155 String basePath,
156 ElementDescriptor baseElementDescriptor,
157 Class baseBeanClass,
158 ReadContext baseContext) {
159 this.basePath = basePath;
160 this.baseElementDescriptor = baseElementDescriptor;
161 this.context = new DigesterReadContext(baseContext);
162 this.context.setRootClass(baseBeanClass);
163 this.context.setXMLIntrospector(introspector);
164 }
165
166 /***
167 * The name of the attribute which can be specified in the XML to override the
168 * type of a bean used at a certain point in the schema.
169 *
170 * <p>The default value is 'className'.</p>
171 *
172 * @return The name of the attribute used to overload the class name of a bean
173 */
174 public String getClassNameAttribute() {
175 return context.getClassNameAttribute();
176 }
177
178 /***
179 * Sets the name of the attribute which can be specified in
180 * the XML to override the type of a bean used at a certain
181 * point in the schema.
182 *
183 * <p>The default value is 'className'.</p>
184 *
185 * @param classNameAttribute The name of the attribute used to overload the class name of a bean
186 * @deprecated 0.5 set the <code>ReadContext</code> property instead
187 */
188 public void setClassNameAttribute(String classNameAttribute) {
189 context.setClassNameAttribute(classNameAttribute);
190 }
191
192
193
194 /***
195 * <p>Gets the namespace associated with this ruleset.</p>
196 *
197 * <p><strong>Note</strong> namespaces are not currently supported.</p>
198 *
199 * @return null
200 */
201 public String getNamespaceURI() {
202 return null;
203 }
204
205 /***
206 * Add rules for bean to given <code>Digester</code>.
207 *
208 * @param digester the <code>Digester</code> to which the rules for the bean will be added
209 */
210 public void addRuleInstances(Digester digester) {
211 if (log.isTraceEnabled()) {
212 log.trace("Adding rules to:" + digester);
213 }
214
215 context.setDigester(digester);
216
217
218 if (context.getClassLoader() == null) {
219 context.setClassLoader(digester.getClassLoader());
220 }
221
222
223
224 digester.addRule("!" + basePath + "/*", new ActionMappingRule());
225 }
226
227 /***
228 * Single rule that is used to map all elements.
229 *
230 * @author <a href='http://jakarta.apache.org/'>Jakarta Commons Team</a>
231 */
232 private final class ActionMappingRule extends Rule {
233
234 /***
235 * Processes the start of a new <code>Element</code>.
236 * The actual processing is delegated to <code>MappingAction</code>'s.
237 * @see Rule#begin(String, String, Attributes)
238 */
239 public void begin(String namespace, String name, Attributes attributes)
240 throws Exception {
241
242 if (log.isTraceEnabled()) {
243 int attributesLength = attributes.getLength();
244 if (attributesLength > 0) {
245 log.trace("Attributes:");
246 }
247 for (int i = 0, size = attributesLength; i < size; i++) {
248 log.trace("Local:" + attributes.getLocalName(i));
249 log.trace("URI:" + attributes.getURI(i));
250 log.trace("QName:" + attributes.getQName(i));
251 }
252 }
253
254 context.pushElement(name);
255
256 MappingAction nextAction =
257 nextAction(namespace, name, attributes, context);
258
259 context.pushMappingAction(nextAction);
260 }
261
262 /***
263 * Gets the next action to be executed
264 * @param namespace the element's namespace, not null
265 * @param name the element name, not null
266 * @param attributes the element's attributes, not null
267 * @param context the <code>ReadContext</code> against which the xml is being mapped.
268 * @return the initialized <code>MappingAction</code>, not null
269 * @throws Exception
270 */
271 private MappingAction nextAction(
272 String namespace,
273 String name,
274 Attributes attributes,
275 ReadContext context)
276 throws Exception {
277
278 MappingAction result = null;
279 MappingAction lastAction = context.currentMappingAction();
280 if (lastAction == null)
281 {
282 result = BeanBindAction.INSTANCE;
283 } else {
284
285 result = lastAction.next(namespace, name, attributes, context);
286 }
287 return result.begin(namespace, name, attributes, context);
288 }
289
290
291
292 /***
293 * Processes the body text for the current element.
294 * This is delegated to the current <code>MappingAction</code>.
295 * @see Rule#body(String, String, String)
296 */
297 public void body(String namespace, String name, String text)
298 throws Exception {
299
300 if (log.isTraceEnabled()) log.trace("[BRS] Body with text " + text);
301 if (digester.getCount() > 0) {
302 MappingAction action = context.currentMappingAction();
303 action.body(text, context);
304 } else {
305 log.trace("[BRS] ZERO COUNT");
306 }
307 }
308
309 /***
310 * Process the end of this element.
311 * This is delegated to the current <code>MappingAction</code>.
312 */
313 public void end(String namespace, String name) throws Exception {
314
315 MappingAction action = context.popMappingAction();
316 action.end(context);
317 }
318
319 /***
320 * Tidy up.
321 */
322 public void finish() {
323
324
325
326 context.clearBeans();
327 }
328
329 }
330
331 /***
332 * Specialization of <code>ReadContext</code> when reading from <code>Digester</code>.
333 * @author <a href='http://jakarta.apache.org/'>Jakarta Commons Team</a>
334 * @version $Revision: 438373 $
335 */
336 private static class DigesterReadContext extends ReadContext {
337
338 private Digester digester;
339
340 /***
341 * @param context
342 * @param readConfiguration
343 */
344 public DigesterReadContext(
345 Context context,
346 ReadConfiguration readConfiguration) {
347 super(context, readConfiguration);
348
349 }
350
351 /***
352 * @param bindingConfiguration
353 * @param readConfiguration
354 */
355 public DigesterReadContext(
356 BindingConfiguration bindingConfiguration,
357 ReadConfiguration readConfiguration) {
358 super(bindingConfiguration, readConfiguration);
359 }
360
361 /***
362 * @param log
363 * @param bindingConfiguration
364 * @param readConfiguration
365 */
366 public DigesterReadContext(
367 Log log,
368 BindingConfiguration bindingConfiguration,
369 ReadConfiguration readConfiguration) {
370 super(log, bindingConfiguration, readConfiguration);
371 }
372
373 /***
374 * @param log
375 * @param bindingConfiguration
376 * @param readConfiguration
377 */
378 public DigesterReadContext(ReadContext readContext) {
379 super(readContext);
380 }
381
382 public Digester getDigester() {
383
384 return digester;
385 }
386
387 public void setDigester(Digester digester) {
388
389 this.digester = digester;
390 }
391
392
393
394
395 public void pushBean(Object bean) {
396 super.pushBean(bean);
397 digester.push(bean);
398 }
399
400
401
402
403 public Object popBean() {
404 Object bean = super.popBean();
405
406 if (digester.getCount() > 0) {
407 digester.pop();
408 }
409 return bean;
410 }
411 }
412
413 }