View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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     //-------------------------------- 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 }