View Javadoc

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  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     //-------------------------------- 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         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         // if the classloader is not set, set to the digester classloader
217         if (context.getClassLoader() == null) {
218             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         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             // Clear indexed beans so that we're ready to process next document
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             // TODO Auto-generated constructor stub
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             // TODO: replace with something better
383             return digester;
384         }
385 
386         public void setDigester(Digester digester) {
387             // TODO: replace once moved to single Rule
388             this.digester = digester;
389         }
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             super.pushBean(bean);
396             digester.push(bean);
397         }
398 
399         /* (non-Javadoc)
400          * @see org.apache.commons.betwixt.io.read.ReadContext#putBean(java.lang.Object)
401          */
402         public Object popBean() {
403             Object bean = super.popBean();
404             Object top = digester.pop();
405             return bean;
406         }
407     }
408 
409 }