Source code: org/scopemvc/view/servlet/xml/ModelToXML.java
1 /*
2 * Scope: a generic MVC framework.
3 * Copyright (c) 2000-2002, Steve Meyfroidt
4 * All rights reserved.
5 * Email: smeyfroi@users.sourceforge.net
6 *
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * Neither the name "Scope" nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
22 *
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
30 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
31 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
32 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
33 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
34 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 *
36 *
37 * $Id: ModelToXML.java,v 1.5 2002/01/26 09:46:20 smeyfroi Exp $
38 */
39
40
41 package org.scopemvc.view.servlet.xml;
42
43
44 import java.util.HashMap;
45 import java.util.Iterator;
46 import org.apache.commons.logging.Log;
47 import org.apache.commons.logging.LogSource;
48 import org.scopemvc.core.IntIndexSelector;
49 import org.scopemvc.core.PropertyManager;
50 import org.scopemvc.core.Selector;
51 import org.scopemvc.model.collection.ArrayModel;
52 import org.scopemvc.model.collection.ListModel;
53 import org.scopemvc.util.Debug;
54 import org.scopemvc.util.convertor.StringConvertor;
55 import org.scopemvc.util.convertor.StringConvertors;
56 import org.xml.sax.Attributes;
57 import org.xml.sax.ContentHandler;
58 import org.xml.sax.helpers.AttributesImpl;
59
60
61 /**
62 * <P>
63 * Converts a model into an XML document (as SAX events
64 * driving a ContentHandler). Uses a {@link PropertyIDGenerator} to
65 * create "path" attributes for all elements, and "id" attributes
66 * for model elements.
67 * </P>
68 * <P>
69 * Handles circular references using the "ID" and "IDREF" pattern.
70 * </P>
71 * <P>
72 * <PRE>
73 * (data id='_root')
74 * (name path='name')Steve(/name)
75 * (pets path='pets')
76 * (element index='0' path='pets.0')
77 * (data id='pets.0')
78 * (name path='pets.0.name')Trevor(/name)
79 * (/data)
80 * (/element)
81 * (/pets)
82 * (/data)
83 * </PRE>
84 * </P>
85 *
86 * @author <A HREF="mailto:smeyfroi@users.sourceforge.net">Steve Meyfroidt</A>
87 * @version $Revision: 1.5 $ $Date: 2002/01/26 09:46:20 $
88 */
89 public class ModelToXML {
90
91
92 private static final Log LOG = LogSource.getInstance(ModelToXML.class);
93
94
95 // Some constants for creating the XML
96 protected static final String ID_ATTRIBUTE = "id";
97 protected static final String IDREF_ATTRIBUTE = "idref";
98 protected static final String CDATA_TYPE = "CDATA";
99 protected static final String PATH_ATTRIBUTE = "id";
100
101
102 /**
103 * Element to contain contents of a collection model.
104 */
105 protected static String COLLECTION_ELEMENT = "element";
106
107
108 /**
109 * Attribute to index the contents of a collection model.
110 */
111 protected static String COLLECTION_INDEX_ATTRIBUTE = "index";
112
113
114 /**
115 * Element to contain a model object.
116 */
117 protected static String MODEL_ELEMENT = "data";
118
119
120 // Reuse these Attributes to avoid object creation (initialised in init()).
121 protected AttributesImpl idAttributes;
122 protected AttributesImpl idrefAttributes;
123 protected AttributesImpl pathIndexAttributes;
124 protected AttributesImpl indexAttributes;
125 protected AttributesImpl pathAttributes;
126 protected static final Attributes EMPTY_ATTRIBUTES = new AttributesImpl();
127
128
129 public ModelToXML() {
130 init();
131 }
132
133
134 /**
135 * Initialise the Attributes that are reused during SAX generation.
136 */
137 protected void init() {
138 idAttributes = new AttributesImpl();
139 idAttributes.addAttribute("", ID_ATTRIBUTE, ID_ATTRIBUTE, CDATA_TYPE, "");
140
141 idrefAttributes = new AttributesImpl();
142 idrefAttributes.addAttribute("", IDREF_ATTRIBUTE, IDREF_ATTRIBUTE, CDATA_TYPE, "");
143
144 pathIndexAttributes = new AttributesImpl();
145 pathIndexAttributes.addAttribute("", PATH_ATTRIBUTE, PATH_ATTRIBUTE, CDATA_TYPE, "");
146 pathIndexAttributes.addAttribute("", COLLECTION_INDEX_ATTRIBUTE, COLLECTION_INDEX_ATTRIBUTE, CDATA_TYPE, "");
147
148 indexAttributes = new AttributesImpl();
149 indexAttributes.addAttribute("", COLLECTION_INDEX_ATTRIBUTE, COLLECTION_INDEX_ATTRIBUTE, CDATA_TYPE, "");
150
151 pathAttributes = new AttributesImpl();
152 pathAttributes.addAttribute("", PATH_ATTRIBUTE, PATH_ATTRIBUTE, CDATA_TYPE, "");
153 }
154
155
156 /**
157 *
158 * @param inContentHandler
159 * Drive this ContentHandler with the Model's SAX events.
160 * @param inModel Model object to write. null generates no SAX.
161 * @param inWritePropertyIds
162 * Write the property id as the selector chain giving the full path from the top-level model
163 * to this model. null for the top-level model.
164 * @param inViewID ViewID to include in property id attributes
165 */
166 public void modelToXML(Object inModel, ContentHandler inContentHandler, PropertyIDGenerator inIDGenerator)
167 throws Exception {
168
169 inContentHandler.startDocument();
170
171 IdRefMap idRefMap = new IdRefMap();
172 String id = idRefMap.getNextId();
173 idAttributes.setValue(0, id);
174 idRefMap.storeModel(id, inModel);
175
176 inContentHandler.startElement("", MODEL_ELEMENT, MODEL_ELEMENT, idAttributes);
177
178 propertiesToXML(inModel, inContentHandler, inIDGenerator, idRefMap);
179
180 inContentHandler.endElement("", MODEL_ELEMENT, MODEL_ELEMENT);
181
182 inContentHandler.endDocument();
183 }
184
185
186 protected void propertiesToXML(Object inModel, ContentHandler inContentHandler, PropertyIDGenerator inIDGenerator, IdRefMap inIdRefMap)
187 throws Exception {
188 if (Debug.ON) Debug.assertTrue(inContentHandler != null, "null ContentHandler");
189 if (LOG.isDebugEnabled()) LOG.debug("propertiesToXML: idGenerator=" + inIDGenerator);
190
191 // Don't serialise null models
192 if (inModel == null) {
193 return;
194 }
195
196 // Get a PropertyManager for the model
197 PropertyManager manager = PropertyManager.getInstance(inModel);
198 if (Debug.ON) Debug.assertTrue(manager != null, "null manager");
199
200 // Serialise the properties using a SelectorIterator
201 Iterator i = manager.getSelectorIterator(inModel);
202 if (Debug.ON) Debug.assertTrue(i != null, "null Iterator");
203 while (i.hasNext()) {
204
205 try {
206 // selector
207 Object o = i.next();
208 if (Debug.ON) Debug.assertTrue(o instanceof Selector, "not a Selector: " + o);
209 Selector selector = (Selector)o;
210 Object property = manager.get(inModel, selector);
211
212 // Don't serialise null properties
213 if (property == null) {
214 continue;
215 }
216
217 // Don't serialise the array property of an ArrayModel ***** feels like a hack
218 if (inModel instanceof ArrayModel && ArrayModel.ARRAY.equals(selector)) {
219 continue;
220 }
221
222 // Don't serialise the list property of a ListModel ***** feels like a hack
223 if (inModel instanceof ListModel && ListModel.LIST.equals(selector)) {
224 continue;
225 }
226
227 StringConvertor convertor = StringConvertors.forClass(property.getClass());
228 inIDGenerator.startProperty(selector);
229 String path = inIDGenerator.getPropertyID();
230
231 // If selector is an IntIndexSelector then write the
232 // ... property like: <element index='0' path='xxx'>
233 // ... else write it like: <{selectorName} path='xxx'>
234 if (selector instanceof IntIndexSelector) {
235 if (path != null) {
236 pathIndexAttributes.setValue(0, path);
237 pathIndexAttributes.setValue(1, selector.getName());
238 inContentHandler.startElement("", COLLECTION_ELEMENT, COLLECTION_ELEMENT, pathIndexAttributes);
239 } else {
240 indexAttributes.setValue(0, selector.getName());
241 inContentHandler.startElement("", COLLECTION_ELEMENT, COLLECTION_ELEMENT, indexAttributes);
242 }
243 } else {
244 if (path != null) {
245 pathAttributes.setValue(0, path);
246 inContentHandler.startElement("", selector.getName(), selector.getName(), pathAttributes);
247 } else {
248 inContentHandler.startElement("", selector.getName(), selector.getName(), EMPTY_ATTRIBUTES);
249 }
250 }
251
252 // value, using StringConvertor
253 if (convertor != null) {
254 String value = convertor.valueAsString(property);
255 if (Debug.ON) Debug.assertTrue(value != null);
256 char[] valueElement = value.toCharArray();
257 inContentHandler.characters(valueElement, 0, valueElement.length);
258 } else {
259 // A Model so check if already serialised (look in the IdRefMap)
260 String previousId = inIdRefMap.findIdFor(property);
261 if (previousId != null) {
262 idrefAttributes.setValue(0, previousId);
263 inContentHandler.startElement("", MODEL_ELEMENT, MODEL_ELEMENT, idrefAttributes);
264 } else {
265 // ... if not previously serialised then do it here
266 String id = inIdRefMap.getNextId();
267 idAttributes.setValue(0, id);
268 inContentHandler.startElement("", MODEL_ELEMENT, MODEL_ELEMENT, idAttributes);
269 inIdRefMap.storeModel(id, property);
270 propertiesToXML(property, inContentHandler, inIDGenerator, inIdRefMap);
271 }
272 inContentHandler.endElement("", MODEL_ELEMENT, MODEL_ELEMENT);
273 }
274
275 // end of property
276 if (selector instanceof IntIndexSelector) {
277 inContentHandler.endElement("", COLLECTION_ELEMENT, COLLECTION_ELEMENT);
278 } else {
279 inContentHandler.endElement("", selector.getName(), selector.getName());
280 }
281
282 inIDGenerator.endProperty();
283
284 } catch (Exception e) {
285 // ignore: may not be able to get write-only properties
286 }
287 }
288 }
289 }
290
291
292 class IdRefMap {
293 private long nextId = 0;
294 private HashMap models = new HashMap();
295 String getNextId() {
296 String result = "_" + Long.toString(nextId);
297 nextId++;
298 return result;
299 }
300 String findIdFor(Object inModel) {
301 return (String)models.get(inModel);
302 }
303 void storeModel(String inId, Object inModel) {
304 models.put(inModel, inId);
305 }
306 }