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(XMLIntrospector introspector, String basePath,
075: ElementDescriptor baseElementDescriptor,
076: Class baseBeanClass, boolean matchIDs) {
077: this .basePath = basePath;
078: this .baseElementDescriptor = baseElementDescriptor;
079: BindingConfiguration bindingConfiguration = new BindingConfiguration();
080: bindingConfiguration.setMapIDs(matchIDs);
081: context = new DigesterReadContext(log, bindingConfiguration,
082: new ReadConfiguration());
083: context.setRootClass(baseBeanClass);
084: context.setXMLIntrospector(introspector);
085: }
086:
087: /**
088: * Base constructor.
089: *
090: * @param introspector the <code>XMLIntrospector</code> used to introspect
091: * @param basePath specifies the (Digester-style) path under which the rules will be attached
092: * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules
093: * @param context the root Context that bean carrying Contexts should be obtained from,
094: * not null
095: * @deprecated 0.6 use the constructor which takes a ReadContext instead
096: */
097: public BeanRuleSet(XMLIntrospector introspector, String basePath,
098: ElementDescriptor baseElementDescriptor, Context context) {
099:
100: this .basePath = basePath;
101: this .baseElementDescriptor = baseElementDescriptor;
102: this .context = new DigesterReadContext(context,
103: new ReadConfiguration());
104: this .context.setRootClass(baseElementDescriptor
105: .getSingularPropertyType());
106: this .context.setXMLIntrospector(introspector);
107: }
108:
109: /**
110: * Base constructor.
111: *
112: * @param introspector the <code>XMLIntrospector</code> used to introspect
113: * @param basePath specifies the (Digester-style) path under which the rules will be attached
114: * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules
115: * @param baseBeanClass the <code>Class</code> whose mapping rules will be created
116: * @param context the root Context that bean carrying Contexts should be obtained from,
117: * not null
118: * @deprecated 0.5 use the constructor which takes a ReadContext instead
119: */
120: public BeanRuleSet(XMLIntrospector introspector, String basePath,
121: ElementDescriptor baseElementDescriptor,
122: Class baseBeanClass, Context context) {
123: this (introspector, basePath, baseElementDescriptor,
124: baseBeanClass, new ReadContext(context,
125: new ReadConfiguration()));
126: }
127:
128: /**
129: * Base constructor.
130: *
131: * @param introspector the <code>XMLIntrospector</code> used to introspect
132: * @param basePath specifies the (Digester-style) path under which the rules will be attached
133: * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules
134: * @param baseBeanClass the <code>Class</code> whose mapping rules will be created
135: * @param baseContext the root Context that bean carrying Contexts should be obtained from,
136: * not null
137: */
138: public BeanRuleSet(XMLIntrospector introspector, String basePath,
139: ElementDescriptor baseElementDescriptor,
140: Class baseBeanClass, ReadContext baseContext) {
141: this .basePath = basePath;
142: this .baseElementDescriptor = baseElementDescriptor;
143: this .context = new DigesterReadContext(baseContext);
144: this .context.setRootClass(baseBeanClass);
145: this .context.setXMLIntrospector(introspector);
146: }
147:
148: /**
149: * The name of the attribute which can be specified in the XML to override the
150: * type of a bean used at a certain point in the schema.
151: *
152: * <p>The default value is 'className'.</p>
153: *
154: * @return The name of the attribute used to overload the class name of a bean
155: */
156: public String getClassNameAttribute() {
157: return context.getClassNameAttribute();
158: }
159:
160: /**
161: * Sets the name of the attribute which can be specified in
162: * the XML to override the type of a bean used at a certain
163: * point in the schema.
164: *
165: * <p>The default value is 'className'.</p>
166: *
167: * @param classNameAttribute The name of the attribute used to overload the class name of a bean
168: * @deprecated 0.5 set the <code>ReadContext</code> property instead
169: */
170: public void setClassNameAttribute(String classNameAttribute) {
171: context.setClassNameAttribute(classNameAttribute);
172: }
173:
174: //-------------------------------- Ruleset implementation
175:
176: /**
177: * <p>Gets the namespace associated with this ruleset.</p>
178: *
179: * <p><strong>Note</strong> namespaces are not currently supported.</p>
180: *
181: * @return null
182: */
183: public String getNamespaceURI() {
184: return null;
185: }
186:
187: /**
188: * Add rules for bean to given <code>Digester</code>.
189: *
190: * @param digester the <code>Digester</code> to which the rules for the bean will be added
191: */
192: public void addRuleInstances(Digester digester) {
193: if (log.isTraceEnabled()) {
194: log.trace("Adding rules to:" + digester);
195: }
196:
197: context.setDigester(digester);
198:
199: // if the classloader is not set, set to the digester classloader
200: if (context.getClassLoader() == null) {
201: context.setClassLoader(digester.getClassLoader());
202: }
203:
204: // TODO: need to think about strategy for paths
205: // may need to provide a default path and then allow the user to override
206: digester
207: .addRule("!" + basePath + "/*", new ActionMappingRule());
208: }
209:
210: /**
211: * Single rule that is used to map all elements.
212: *
213: * @author <a href='http://jakarta.apache.org/'>Jakarta Commons Team</a>
214: */
215: private final class ActionMappingRule extends Rule {
216:
217: /**
218: * Processes the start of a new <code>Element</code>.
219: * The actual processing is delegated to <code>MappingAction</code>'s.
220: * @see Rule#begin(String, String, Attributes)
221: */
222: public void begin(String namespace, String name,
223: Attributes attributes) throws Exception {
224:
225: if (log.isTraceEnabled()) {
226: int attributesLength = attributes.getLength();
227: if (attributesLength > 0) {
228: log.trace("Attributes:");
229: }
230: for (int i = 0, size = attributesLength; i < size; i++) {
231: log.trace("Local:" + attributes.getLocalName(i));
232: log.trace("URI:" + attributes.getURI(i));
233: log.trace("QName:" + attributes.getQName(i));
234: }
235: }
236:
237: context.pushElement(name);
238:
239: MappingAction nextAction = nextAction(namespace, name,
240: attributes, context);
241:
242: context.pushMappingAction(nextAction);
243: }
244:
245: /**
246: * Gets the next action to be executed
247: * @param namespace the element's namespace, not null
248: * @param name the element name, not null
249: * @param attributes the element's attributes, not null
250: * @param context the <code>ReadContext</code> against which the xml is being mapped.
251: * @return the initialized <code>MappingAction</code>, not null
252: * @throws Exception
253: */
254: private MappingAction nextAction(String namespace, String name,
255: Attributes attributes, ReadContext context)
256: throws Exception {
257:
258: MappingAction result = null;
259: MappingAction lastAction = context.currentMappingAction();
260: if (lastAction == null) {
261: result = BeanBindAction.INSTANCE;
262: } else {
263:
264: result = lastAction.next(namespace, name, attributes,
265: context);
266: }
267: return result.begin(namespace, name, attributes, context);
268: }
269:
270: /**
271: * Processes the body text for the current element.
272: * This is delegated to the current <code>MappingAction</code>.
273: * @see Rule#body(String, String, String)
274: */
275: public void body(String namespace, String name, String text)
276: throws Exception {
277:
278: if (log.isTraceEnabled())
279: log.trace("[BRS] Body with text " + text);
280: if (digester.getCount() > 0) {
281: MappingAction action = context.currentMappingAction();
282: action.body(text, context);
283: } else {
284: log.trace("[BRS] ZERO COUNT");
285: }
286: }
287:
288: /**
289: * Process the end of this element.
290: * This is delegated to the current <code>MappingAction</code>.
291: */
292: public void end(String namespace, String name) throws Exception {
293:
294: MappingAction action = context.popMappingAction();
295: action.end(context);
296: }
297:
298: /**
299: * Tidy up.
300: */
301: public void finish() {
302: //
303: // Clear indexed beans so that we're ready to process next document
304: //
305: context.clearBeans();
306: }
307:
308: }
309:
310: /**
311: * Specialization of <code>ReadContext</code> when reading from <code>Digester</code>.
312: * @author <a href='http://jakarta.apache.org/'>Jakarta Commons Team</a>
313: * @version $Revision: 438373 $
314: */
315: private static class DigesterReadContext extends ReadContext {
316:
317: private Digester digester;
318:
319: /**
320: * @param context
321: * @param readConfiguration
322: */
323: public DigesterReadContext(Context context,
324: ReadConfiguration readConfiguration) {
325: super (context, readConfiguration);
326: // TODO Auto-generated constructor stub
327: }
328:
329: /**
330: * @param bindingConfiguration
331: * @param readConfiguration
332: */
333: public DigesterReadContext(
334: BindingConfiguration bindingConfiguration,
335: ReadConfiguration readConfiguration) {
336: super (bindingConfiguration, readConfiguration);
337: }
338:
339: /**
340: * @param log
341: * @param bindingConfiguration
342: * @param readConfiguration
343: */
344: public DigesterReadContext(Log log,
345: BindingConfiguration bindingConfiguration,
346: ReadConfiguration readConfiguration) {
347: super (log, bindingConfiguration, readConfiguration);
348: }
349:
350: /**
351: * @param log
352: * @param bindingConfiguration
353: * @param readConfiguration
354: */
355: public DigesterReadContext(ReadContext readContext) {
356: super (readContext);
357: }
358:
359: public Digester getDigester() {
360: // TODO: replace with something better
361: return digester;
362: }
363:
364: public void setDigester(Digester digester) {
365: // TODO: replace once moved to single Rule
366: this .digester = digester;
367: }
368:
369: /* (non-Javadoc)
370: * @see org.apache.commons.betwixt.io.read.ReadContext#pushBean(java.lang.Object)
371: */
372: public void pushBean(Object bean) {
373: super .pushBean(bean);
374: digester.push(bean);
375: }
376:
377: /* (non-Javadoc)
378: * @see org.apache.commons.betwixt.io.read.ReadContext#putBean(java.lang.Object)
379: */
380: public Object popBean() {
381: Object bean = super .popBean();
382: // don't pop the last from the stack
383: if (digester.getCount() > 0) {
384: digester.pop();
385: }
386: return bean;
387: }
388: }
389:
390: }
|