001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.betwixt.io;
019
020 import org.apache.commons.betwixt.BindingConfiguration;
021 import org.apache.commons.betwixt.ElementDescriptor;
022 import org.apache.commons.betwixt.XMLIntrospector;
023 import org.apache.commons.betwixt.expression.Context;
024 import org.apache.commons.betwixt.io.read.BeanBindAction;
025 import org.apache.commons.betwixt.io.read.MappingAction;
026 import org.apache.commons.betwixt.io.read.ReadConfiguration;
027 import org.apache.commons.betwixt.io.read.ReadContext;
028 import org.apache.commons.digester.Digester;
029 import org.apache.commons.digester.Rule;
030 import org.apache.commons.digester.RuleSet;
031 import org.apache.commons.logging.Log;
032 import org.apache.commons.logging.LogFactory;
033 import org.xml.sax.Attributes;
034
035 /** <p>Sets <code>Betwixt</code> digestion rules for a bean class.</p>
036 *
037 * @author <a href="mailto:rdonkin@apache.org">Robert Burrell Donkin</a>
038 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
039 * @since 0.5
040 */
041 public class BeanRuleSet implements RuleSet {
042
043 /** Logger */
044 private static Log log = LogFactory.getLog(BeanRuleSet.class);
045
046 /**
047 * Set log to be used by <code>BeanRuleSet</code> instances
048 * @param aLog the <code>Log</code> implementation for this class to log to
049 */
050 public static void setLog(Log aLog) {
051 log = aLog;
052 }
053
054 /** The base path under which the rules will be attached */
055 private String basePath;
056 /** The element descriptor for the base */
057 private ElementDescriptor baseElementDescriptor;
058 /** The (empty) base context from which all Contexts
059 with beans are (directly or indirectly) obtained */
060 private DigesterReadContext context;
061 /** allows an attribute to be specified to overload the types of beans used */
062 private String classNameAttribute = "className";
063
064 /**
065 * Base constructor.
066 *
067 * @param introspector the <code>XMLIntrospector</code> used to introspect
068 * @param basePath specifies the (Digester-style) path under which the rules will be attached
069 * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules
070 * @param baseBeanClass the <code>Class</code> whose mapping rules will be created
071 * @param matchIDs should ID/IDREFs be used to match beans?
072 * @deprecated 0.5 use constructor which takes a ReadContext instead
073 */
074 public BeanRuleSet(
075 XMLIntrospector introspector,
076 String basePath,
077 ElementDescriptor baseElementDescriptor,
078 Class baseBeanClass,
079 boolean matchIDs) {
080 this.basePath = basePath;
081 this.baseElementDescriptor = baseElementDescriptor;
082 BindingConfiguration bindingConfiguration = new BindingConfiguration();
083 bindingConfiguration.setMapIDs(matchIDs);
084 context =
085 new DigesterReadContext(
086 log,
087 bindingConfiguration,
088 new ReadConfiguration());
089 context.setRootClass(baseBeanClass);
090 context.setXMLIntrospector(introspector);
091 }
092
093 /**
094 * Base constructor.
095 *
096 * @param introspector the <code>XMLIntrospector</code> used to introspect
097 * @param basePath specifies the (Digester-style) path under which the rules will be attached
098 * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules
099 * @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 //-------------------------------- Ruleset implementation
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 // if the classloader is not set, set to the digester classloader
218 if (context.getClassLoader() == null) {
219 context.setClassLoader(digester.getClassLoader());
220 }
221
222 // TODO: need to think about strategy for paths
223 // may need to provide a default path and then allow the user to override
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://commons.apache.org/'>Apache 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 // Clear indexed beans so that we're ready to process next document
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://commons.apache.org/'>Apache Commons Team</a>
334 * @version $Revision: 561314 $
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 // TODO Auto-generated constructor stub
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 // TODO: replace with something better
384 return digester;
385 }
386
387 public void setDigester(Digester digester) {
388 // TODO: replace once moved to single Rule
389 this.digester = digester;
390 }
391
392 /* (non-Javadoc)
393 * @see org.apache.commons.betwixt.io.read.ReadContext#pushBean(java.lang.Object)
394 */
395 public void pushBean(Object bean) {
396 super.pushBean(bean);
397 digester.push(bean);
398 }
399
400 /* (non-Javadoc)
401 * @see org.apache.commons.betwixt.io.read.ReadContext#putBean(java.lang.Object)
402 */
403 public Object popBean() {
404 Object bean = super.popBean();
405 // don't pop the last from the stack
406 if (digester.getCount() > 0) {
407 digester.pop();
408 }
409 return bean;
410 }
411 }
412
413 }