Source code: org/scopemvc/view/servlet/xml/XSLPage.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: XSLPage.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 java.util.LinkedList;
47 import java.util.List;
48 import org.apache.commons.logging.Log;
49 import org.apache.commons.logging.LogSource;
50 import org.scopemvc.core.PropertyManager;
51 import org.scopemvc.core.Selector;
52 import org.scopemvc.util.Debug;
53 import org.scopemvc.util.ScopeConfig;
54 import org.scopemvc.util.convertor.StringConvertor;
55 import org.scopemvc.util.convertor.StringConvertors;
56 import org.scopemvc.view.servlet.ValidationFailure;
57 import org.xml.sax.ContentHandler;
58
59
60
61 /**
62 * <P>
63 * A concrete {@link AbstractXSLPage}
64 * that uses Scope's ModelManager implementations
65 * to serialise its entire bound model object to an XML
66 * document. A better strategy would be to use a
67 * more intelligent view that selectively serialises
68 * relevant parts of the model object.
69 * </P>
70 * <P>
71 * Handles circular references using the "ID" and "IDREF" pattern.
72 * </P>
73 *
74 * @author <A HREF="mailto:smeyfroi@users.sourceforge.net">Steve Meyfroidt</A>
75 * @version $Revision: 1.5 $ $Date: 2002/01/26 09:46:20 $
76 */
77 public class XSLPage extends AbstractXSLPage {
78
79
80 private static final Log LOG = LogSource.getInstance(XSLPage.class);
81
82
83 /**
84 * The prefix on form parameters to identify a property/value
85 * pair used to repopulate the View's bound model.
86 * A property/value pair looks like:
87 * <CODE>
88 * <PROPERTY_ID_PREFIX>ViewID<VIEW_ID_SUFFIX>SelectorDescription
89 * </CODE>
90 *
91 * @see #populateModel
92 */
93 static char PROPERTY_ID_PREFIX;
94
95
96 /**
97 * Optional setting for whether the SAX
98 * convertor should write all the property "paths"
99 * out when this view is streamed. Some views might
100 * want this if they send back form parameters like
101 * <CODE>{path}=newValue</CODE> for
102 * automatic repopulation back into the bound model object.
103 */
104 protected boolean requiresPropertyDescriptions;
105
106
107 protected ModelToXML xmlGenerator = new ModelToXML();
108
109
110 /**
111 *
112 * @param inViewID unique View ID for
113 * routing incoming Controls
114 * @param inXslURI the XSLT this View uses to transform
115 * its model objects after they convert
116 * to XML
117 */
118 public XSLPage(String inViewID, String inXslURI) {
119 this(inViewID, inXslURI, false);
120 }
121
122
123 /**
124 *
125 * @param inViewID unique View ID for
126 * routing incoming Controls
127 * @param inXslURI the XSLT this View uses to transform
128 * its model objects after they convert
129 * to XML
130 * @param inRequiresModelIds
131 * Does this view need the SAX
132 * convertor to write out property description
133 * attributes for properties? For repopulation
134 * back into the bound model via
135 * {@link #populateModel}
136 */
137 public XSLPage(String inViewID, String inXslURI, boolean inRequiresModelIds) {
138 super(inViewID, inXslURI);
139 requiresPropertyDescriptions = inRequiresModelIds;
140 init();
141 }
142
143
144 /**
145 * Do it like this so that we can pick up application-specific
146 * ScopeConfig... static initializers would happen before user
147 * got a chance to setup the custom config properties.
148 */
149 protected void init() {
150 PROPERTY_ID_PREFIX = ScopeConfig.getChar("ServletFormParameter.propertyIDPrefix");
151 if (PROPERTY_ID_PREFIX == 0) LOG.fatal("No propertyIDPrefix in config.");
152 }
153
154
155 /**
156 *
157 * @param inContentHandler
158 * Drive this ContentHandler with the Model's SAX events.
159 * @param inModel Model object to write. null generates no SAX.
160 * @param inWritePropertyIds
161 * Write the property id as the selector chain giving the full path from the top-level model
162 * to this model. null for the top-level model.
163 * @param inViewID ViewID to include in property id attributes
164 */
165 protected void generateXMLDocument(ContentHandler inContentHandler)
166 throws Exception {
167 if (requiresPropertyDescriptions) {
168 xmlGenerator.modelToXML(getBoundModel(), inContentHandler,
169 new FullIDGenerator());
170 } else {
171 xmlGenerator.modelToXML(getBoundModel(), inContentHandler,
172 new NoIDGenerator());
173 }
174 }
175
176
177 /**
178 * <P>
179 * Interprets any form parameters like
180 * <CODE>
181 * <PROPERTY_ID_PREFIX>SelectorDescription
182 * </CODE>
183 * as [property_description, property_value] pairs
184 * </P>
185 * <P>
186 * Extracts the property_description
187 * from the form parameter key, then
188 * populates its model object using
189 * the String value. Any parameters treated this
190 * way are removed from the HashMap.
191 * </P>
192 *
193 * @param ioParameters
194 * form parameters to parse for [property_description, property_value]
195 * pairs, removing any processed pairs from the parameters
196 * before return
197 * @return PopulateModelFailedException that will be handled as a validation failure.
198 */
199 public List populateModel(HashMap ioParameters) {
200 if (LOG.isDebugEnabled()) LOG.debug("populateModel: " + ioParameters);
201
202 List errors = new LinkedList(); // collect ValidationFailures in here
203 List toRemove = new LinkedList();
204 for (Iterator i = ioParameters.keySet().iterator(); i.hasNext(); ) {
205 Object o = i.next();
206 if (Debug.ON) Debug.assertTrue(o instanceof String);
207 String parameterKey = (String)o;
208 if (LOG.isDebugEnabled()) LOG.debug("populateModel: " + parameterKey);
209
210 // Recognise prefixed form parameters for model population
211 if (parameterKey.charAt(0) == PROPERTY_ID_PREFIX) {
212
213 // get the form parameter value and remove from the parameter list
214 o = ioParameters.get(parameterKey);
215 // ... if multiple values take the first
216 if (o instanceof Object[]) {
217 if (Debug.ON) Debug.assertTrue( ((Object[])o).length > 0);
218 o = ((Object[])o)[0];
219 }
220 if (Debug.ON) Debug.assertTrue(o instanceof String);
221 String stringValue = (String)o;
222
223 // Mark it for removal (avoid concurrency problem with Iterator)
224 toRemove.add(o);
225
226 // find the property description
227 String propertyDescription = parameterKey.substring(1);
228 try {
229 populateBoundModelProperty(propertyDescription, stringValue);
230 } catch (Exception e) {
231 if (LOG.isDebugEnabled()) LOG.debug("populateModel: got an exception: " + e);
232 errors.add(new ValidationFailure(propertyDescription, stringValue, e));
233 }
234 }
235 }
236
237 for (Iterator i = toRemove.iterator(); i.hasNext(); ) {
238 ioParameters.remove(i.next());
239 }
240
241 if (errors.size() == 0) {
242 errors = null;
243 }
244 return errors;
245 }
246
247
248 /**
249 * Use the property_description, property_value pair
250 * passed to set a property in the bound model to
251 * a new value. Use StringConvertor if available to
252 * convert from String to the property's native datatype.
253 *
254 * @throws Exception on any failure
255 */
256 protected void populateBoundModelProperty(String inPropertyDescription, String inValue)
257 throws Exception {
258 if (LOG.isDebugEnabled()) LOG.debug("populateBoundModelProperty: " + inPropertyDescription + ", " + inValue);
259
260 if (getBoundModel() == null) {
261 LOG.error("No bound model for: " + this);
262 return;
263 }
264
265 // Get the PropertyManager for the bound model
266 Object model = getBoundModel();
267 PropertyManager manager = PropertyManager.getInstance(model);
268 if (Debug.ON) Debug.assertTrue(manager != null, "null manager");
269
270 // Get the Selector from the property description
271 Selector selector = Selector.fromString(inPropertyDescription);
272
273 // Use StringConvertors if available, else just set(Selector, String)
274 StringConvertor convertor = StringConvertors.forClass(manager.getPropertyClass(model, selector));
275 if (convertor != null) {
276 if (LOG.isDebugEnabled()) LOG.debug("populateProperty: got " + convertor.getClass());
277 Object value = convertor.stringAsValue(inValue);
278 manager.set(model, selector, value);
279 } else {
280 if (LOG.isDebugEnabled()) LOG.debug("populateProperty: no StringConvertor ");
281 manager.set(model, selector, inValue);
282 }
283 }
284 }
285
286
287 final class NoIDGenerator extends PropertyIDGenerator {
288 public String getPropertyID() {
289 return null;
290 }
291 }
292
293
294 final class FullIDGenerator extends PropertyIDGenerator {
295 public String getPropertyID() {
296 if (currentPropertySelector == null) {
297 return null;
298 }
299 return XSLPage.PROPERTY_ID_PREFIX
300 + Selector.asString(currentPropertySelector);
301 }
302 }