Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
BeanRuleSet |
|
| 1.4090909090909092;1.409 |
1 | /* |
|
2 | * Copyright 2001-2004 The Apache Software Foundation. |
|
3 | * |
|
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
|
5 | * you may not use this file except in compliance with the License. |
|
6 | * You may obtain a copy of the License at |
|
7 | * |
|
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
|
9 | * |
|
10 | * Unless required by applicable law or agreed to in writing, software |
|
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
|
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
13 | * See the License for the specific language governing permissions and |
|
14 | * limitations under the License. |
|
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 | 487 | public class BeanRuleSet implements RuleSet { |
41 | ||
42 | /** Logger */ |
|
43 | 18164 | 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 | 0 | log = aLog; |
51 | 0 | } |
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 | 63337 | private DigesterReadContext context; |
60 | /** allows an attribute to be specified to overload the types of beans used */ |
|
61 | 1539 | 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 | 0 | public BeanRuleSet( |
74 | XMLIntrospector introspector, |
|
75 | String basePath, |
|
76 | ElementDescriptor baseElementDescriptor, |
|
77 | Class baseBeanClass, |
|
78 | boolean matchIDs) { |
|
79 | 0 | this.basePath = basePath; |
80 | 0 | this.baseElementDescriptor = baseElementDescriptor; |
81 | 0 | BindingConfiguration bindingConfiguration = new BindingConfiguration(); |
82 | 0 | bindingConfiguration.setMapIDs(matchIDs); |
83 | 0 | context = |
84 | 0 | new DigesterReadContext( |
85 | 0 | log, |
86 | 0 | bindingConfiguration, |
87 | 0 | new ReadConfiguration()); |
88 | 0 | context.setRootClass(baseBeanClass); |
89 | 0 | context.setXMLIntrospector(introspector); |
90 | 0 | } |
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 | 0 | public BeanRuleSet( |
103 | XMLIntrospector introspector, |
|
104 | String basePath, |
|
105 | ElementDescriptor baseElementDescriptor, |
|
106 | Context context) { |
|
107 | ||
108 | 0 | this.basePath = basePath; |
109 | 0 | this.baseElementDescriptor = baseElementDescriptor; |
110 | 0 | this.context = |
111 | 0 | new DigesterReadContext(context, new ReadConfiguration()); |
112 | 0 | this.context.setRootClass( |
113 | 0 | baseElementDescriptor.getSingularPropertyType()); |
114 | 0 | this.context.setXMLIntrospector(introspector); |
115 | 0 | } |
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 | 0 | this( |
135 | 0 | introspector, |
136 | 0 | basePath, |
137 | 0 | baseElementDescriptor, |
138 | 0 | baseBeanClass, |
139 | 0 | new ReadContext( context, new ReadConfiguration() )); |
140 | 0 | } |
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 | 1539 | public BeanRuleSet( |
153 | XMLIntrospector introspector, |
|
154 | String basePath, |
|
155 | ElementDescriptor baseElementDescriptor, |
|
156 | Class baseBeanClass, |
|
157 | ReadContext baseContext) { |
|
158 | 1539 | this.basePath = basePath; |
159 | 1539 | this.baseElementDescriptor = baseElementDescriptor; |
160 | 1539 | this.context = new DigesterReadContext(baseContext); |
161 | 1539 | this.context.setRootClass(baseBeanClass); |
162 | 1539 | this.context.setXMLIntrospector(introspector); |
163 | 1539 | } |
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 | 0 | 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 | 0 | context.setClassNameAttribute(classNameAttribute); |
189 | 0 | } |
190 | ||
191 | //-------------------------------- Ruleset implementation |
|
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 | 1539 | 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 | 1539 | if (log.isTraceEnabled()) { |
211 | 0 | log.trace("Adding rules to:" + digester); |
212 | } |
|
213 | ||
214 | 1539 | context.setDigester(digester); |
215 | ||
216 | // if the classloader is not set, set to the digester classloader |
|
217 | 1539 | if (context.getClassLoader() == null) { |
218 | 1539 | context.setClassLoader(digester.getClassLoader()); |
219 | } |
|
220 | ||
221 | // TODO: need to think about strategy for paths |
|
222 | // may need to provide a default path and then allow the user to override |
|
223 | 1539 | digester.addRule("!" + basePath + "/*", new ActionMappingRule()); |
224 | 1539 | } |
225 | ||
226 | /** |
|
227 | * Single rule that is used to map all elements. |
|
228 | * |
|
229 | * @author <a href='http://jakarta.apache.org/'>Apache Commons Team</a> |
|
230 | */ |
|
231 | 1539 | 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 | 8845 | if (log.isTraceEnabled()) { |
242 | 0 | int attributesLength = attributes.getLength(); |
243 | 0 | if (attributesLength > 0) { |
244 | 0 | log.trace("Attributes:"); |
245 | } |
|
246 | 0 | for (int i = 0, size = attributesLength; i < size; i++) { |
247 | 0 | log.trace("Local:" + attributes.getLocalName(i)); |
248 | 0 | log.trace("URI:" + attributes.getURI(i)); |
249 | 0 | log.trace("QName:" + attributes.getQName(i)); |
250 | } |
|
251 | } |
|
252 | ||
253 | 8845 | context.pushElement(name); |
254 | ||
255 | 8845 | MappingAction nextAction = |
256 | 8845 | nextAction(namespace, name, attributes, context); |
257 | ||
258 | 8845 | context.pushMappingAction(nextAction); |
259 | 8845 | } |
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 | 8845 | MappingAction result = null; |
278 | 8845 | MappingAction lastAction = context.currentMappingAction(); |
279 | 8845 | if (lastAction == null) |
280 | { |
|
281 | 942 | result = BeanBindAction.INSTANCE; |
282 | } else { |
|
283 | ||
284 | 7903 | result = lastAction.next(namespace, name, attributes, context); |
285 | } |
|
286 | 8845 | 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 | 8832 | if (log.isTraceEnabled()) log.trace("[BRS] Body with text " + text); |
300 | 8832 | if (digester.getCount() > 0) { |
301 | 8832 | MappingAction action = context.currentMappingAction(); |
302 | 8832 | action.body(text, context); |
303 | } else { |
|
304 | 0 | log.trace("[BRS] ZERO COUNT"); |
305 | } |
|
306 | 8819 | } |
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 | 8819 | MappingAction action = context.popMappingAction(); |
315 | 8819 | action.end(context); |
316 | 8819 | } |
317 | ||
318 | /** |
|
319 | * Tidy up. |
|
320 | */ |
|
321 | public void finish() { |
|
322 | // |
|
323 | // Clear indexed beans so that we're ready to process next document |
|
324 | // |
|
325 | 1500 | context.clearBeans(); |
326 | 1500 | } |
327 | ||
328 | } |
|
329 | ||
330 | /** |
|
331 | * Specialization of <code>ReadContext</code> when reading from <code>Digester</code>. |
|
332 | * @author <a href='http://jakarta.apache.org/'>Apache Commons Team</a> |
|
333 | * @version $Revision: 240113 $ |
|
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 | 0 | super(context, readConfiguration); |
347 | // TODO Auto-generated constructor stub |
|
348 | 0 | } |
349 | ||
350 | /** |
|
351 | * @param bindingConfiguration |
|
352 | * @param readConfiguration |
|
353 | */ |
|
354 | public DigesterReadContext( |
|
355 | BindingConfiguration bindingConfiguration, |
|
356 | ReadConfiguration readConfiguration) { |
|
357 | 0 | super(bindingConfiguration, readConfiguration); |
358 | 0 | } |
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 | 0 | super(log, bindingConfiguration, readConfiguration); |
370 | 0 | } |
371 | ||
372 | /** |
|
373 | * @param log |
|
374 | * @param bindingConfiguration |
|
375 | * @param readConfiguration |
|
376 | */ |
|
377 | public DigesterReadContext(ReadContext readContext) { |
|
378 | 1539 | super(readContext); |
379 | 1539 | } |
380 | ||
381 | public Digester getDigester() { |
|
382 | // TODO: replace with something better |
|
383 | 0 | return digester; |
384 | } |
|
385 | ||
386 | public void setDigester(Digester digester) { |
|
387 | // TODO: replace once moved to single Rule |
|
388 | 1539 | this.digester = digester; |
389 | 1539 | } |
390 | ||
391 | /* (non-Javadoc) |
|
392 | * @see org.apache.commons.betwixt.io.read.ReadContext#pushBean(java.lang.Object) |
|
393 | */ |
|
394 | public void pushBean(Object bean) { |
|
395 | 3489 | super.pushBean(bean); |
396 | 3489 | digester.push(bean); |
397 | 3489 | } |
398 | ||
399 | /* (non-Javadoc) |
|
400 | * @see org.apache.commons.betwixt.io.read.ReadContext#putBean(java.lang.Object) |
|
401 | */ |
|
402 | public Object popBean() { |
|
403 | 3476 | Object bean = super.popBean(); |
404 | // don't pop the last from the stack |
|
405 | 3476 | if (digester.getCount() > 0) { |
406 | 3476 | digester.pop(); |
407 | } |
|
408 | 3476 | return bean; |
409 | } |
|
410 | } |
|
411 | ||
412 | } |