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