1 package pl.caltha.forms.internal;
2
3 import java.util.HashMap;
4 import java.util.Properties;
5
6 import org.dom4j.Document;
7 import org.jcontainer.dna.Configuration;
8 import org.jcontainer.dna.Logger;
9 import org.objectledge.ComponentInitializationError;
10 import org.objectledge.web.HttpContext;
11 import org.xml.sax.InputSource;
12
13 import pl.caltha.forms.ConstructionException;
14 import pl.caltha.forms.Form;
15 import pl.caltha.forms.FormsException;
16 import pl.caltha.forms.FormsService;
17 import pl.caltha.forms.Instance;
18 import pl.caltha.forms.internal.model.InstanceImpl;
19 import pl.caltha.forms.internal.ui.UI;
20 import pl.caltha.forms.internal.ui.UIBuilder;
21 import pl.caltha.services.xml.LoggingErrorHandler;
22 import pl.caltha.services.xml.XMLDataReader;
23 import pl.caltha.services.xml.XMLService;
24
25 /***
26 *
27 * @author <a href="mailto:zwierzem@ngo.pl">Damian Gajda</a>
28 * @version $Id: FormsServiceImpl.java,v 1.7 2005/03/23 13:41:55 zwierzem Exp $
29 */
30 public class FormsServiceImpl
31 implements FormsService
32 {
33 private Logger log;
34
35 private XMLService xmlService;
36
37 /*** Form definition objects keyed by their Id's. */
38 private HashMap formsById = new HashMap();
39
40 /*** Form definition objects keyed by their application given names. */
41 private HashMap formsByName = new HashMap();
42
43
44 /*** String containing an URI to form definition schema. */
45 private String formSchemaURI;
46 /*** String containing an URI to ui deifinition schema. */
47 private String uiSchemaURI;
48
49 /*** TODO: NOT IMPLEMENTED YET!
50 * If this flag is set to <code>true</code>, form definition's
51 * are reloaded if they are changed on disk.
52 * This feature is for development only. Rebuilding form definitions
53 * is a CPU intensive operation.
54 */
55 private boolean reloadFormDefinitions = false;
56
57
58
59 /*** Called when the broker is starting.
60 */
61 public FormsServiceImpl(Configuration config, Logger logger, XMLService xmlService
62 )
63 {
64 this.log = logger;
65 this.xmlService = xmlService;
66 reloadFormDefinitions = config.getChild("form.definition.reload").getValueAsBoolean(false);
67 formSchemaURI = config.getChild("uri.schema.form").getValue("classpath:pl/caltha/forms/internal/formtool-form.xsd");
68 uiSchemaURI = config.getChild("uri.schema.ui").getValue("classpath:pl/caltha/forms/internal/formtool-ui.xsd");
69
70
71
72 log.info("Preloading schemas for 'formtool' service");
73 preloadSchema(formSchemaURI);
74 preloadSchema(uiSchemaURI);
75 }
76
77 private void preloadSchema(String uri)
78 {
79 log.info("Preloading schema '"+uri+"'");
80 try
81 {
82 xmlService.loadGrammar(uri);
83 }
84 catch(Exception e)
85 {
86 throw new ComponentInitializationError("Cannot load schema with URI '"+uri+"'");
87 }
88 }
89
90
91
92
93 private void checkInputValue(String name, String value)
94 throws FormsException
95 {
96 if(value == null || value.length() == 0)
97 {
98 throw new FormsException(name+" cannot be null or empty");
99 }
100 }
101
102 /*** Returns a Form definition object based on it's definition URI.
103 * It also builds and caches such an object. */
104 public Form getForm(String formDefinitionURI, String formName)
105 throws ConstructionException, FormsException
106 {
107
108 checkInputValue("Form definition URI", formDefinitionURI);
109
110
111 checkInputValue("Form name", formName);
112
113
114
115 Form form = null;
116
117
118 if(formsByName.containsKey(formName))
119 {
120 form = (Form)(formsByName.get(formName));
121
122
123 String secondFormDefURI = form.getDefinitionURI();
124 if(secondFormDefURI.equals(formDefinitionURI))
125 {
126
127 formsByName.put(formName, form);
128 }
129 else
130 {
131 throw new FormsException("Duplicate name '"+formName
132 +"' for different form definitions: "
133 +formDefinitionURI+" and "+secondFormDefURI);
134 }
135 }
136
137 else
138 {
139
140 synchronized(formsById)
141 {
142
143
144 char o = '-';
145 String formId = formDefinitionURI.replace('/',o).replace(':',o).replace('.',o);
146
147
148 form = buildForm(formDefinitionURI, formId);
149
150
151 formsById.put(formId, form);
152 formsByName.put(formName, form);
153
154 log.info("Added new form definition '"+formDefinitionURI+"' with name '"+formName+"'");
155 }
156 }
157
158 return form;
159 }
160
161 /*** Builds a form definition object. */
162 private FormImpl buildForm(String formDefinitionURI, String formId)
163 throws ConstructionException
164 {
165 LoggingErrorHandler errorHandler = new LoggingErrorHandler(log);
166
167
168
169 XMLDataReader reader = getXMLDataReader();
170 org.xml.sax.InputSource is = getInputSource(formDefinitionURI);
171 FormBuilder formBuilder = new FormBuilder(FormsService.ACCEPTED_NS_FORM, formSchemaURI);
172 FormImpl form = new FormImpl(this, xmlService, formDefinitionURI, formId);
173 formBuilder.build(form, reader, is, errorHandler);
174
175
176
177 reader = getXMLDataReader();
178 is = getInputSource(form.getDefaultInstanceURI());
179
180 errorHandler.init();
181 Document doc = null;
182 try
183 {
184 doc = reader.readDOM4J(is, form.getInstanceSchemaURI(), errorHandler);
185 doc.normalize();
186 }
187 catch(Exception e)
188 {
189 throw new ConstructionException("Cannot load DefaultInstance document '"+form.getDefaultInstanceURI()+"' from Form definition '"+formDefinitionURI+"'", e);
190 }
191
192 if(errorHandler.hadErrors())
193 {
194 throw new ConstructionException("DefaultInstance document '"+form.getDefaultInstanceURI()+"' had errors");
195 }
196
197 form.setDefaultInstance(new pl.caltha.forms.internal.model.DefaultInstance(form, form.getInstanceSchemaURI(), doc));
198
199
200 reader = getXMLDataReader();
201 String uiURI = form.getUIDefinitionURI();
202
203 is = getInputSource(uiURI);
204
205 errorHandler.init();
206 UIBuilder uiBuilder = new UIBuilder(FormsService.ACCEPTED_NS_UI, uiSchemaURI);
207 UI ui = new UI(form, uiURI);
208 uiBuilder.build(ui, reader, is, errorHandler);
209
210 form.init(ui);
211
212 return form;
213 }
214
215 /*** Get an XMLDataReader for use while building a form definition. */
216 private XMLDataReader getXMLDataReader()
217 throws ConstructionException
218 {
219 try
220 {
221 return xmlService.getXMLDataReader();
222 }
223 catch(Exception e)
224 {
225 throw new ConstructionException("Cannot get XMLDataReader", e);
226 }
227 }
228
229 /*** Creates an InputSource from a given URI. */
230 private InputSource getInputSource(String definitionURI)
231 throws ConstructionException
232 {
233 try
234 {
235 return xmlService.getInputSource(definitionURI);
236 }
237 catch(Exception e)
238 {
239 throw new ConstructionException("Cannot get InputSource for URI '"+definitionURI+"'", e);
240 }
241 }
242
243
244
245
246 /*** Returns an {@link pl.caltha.forms.Instance} object
247 * depending on RunData parameters. If this object cannot be found it
248 * creates one depending on a given {@link pl.caltha.forms.Form}
249 * object.
250 * @param formName Form's system wide identifier, this one is used to allow
251 * same form definitions to be used in different site contexts.
252 * @param httpContext HttpConext for current request.
253 * @throws FormsException thrown when a found Instance is not an instance
254 * for a given Form definition.
255 * @return found or newly created Instance object
256 */
257 public Instance getInstance(String formName, HttpContext httpContext)
258 throws FormsException
259 {
260
261 checkInputValue("Form name", formName);
262
263 if(!formsByName.containsKey(formName))
264 {
265 throw new FormsException("Form object with name '"+formName+"' cannot be found");
266 }
267
268 FormImpl form = (FormImpl)(formsByName.get(formName));
269
270 FormData formData = getFormData(httpContext);
271 InstanceImpl instance = (InstanceImpl)(formData.get(formName));
272
273 if(instance == null)
274 {
275
276 instance = form.createInstance(formName);
277
278 formData.put(instance);
279 }
280
281
282 FormImpl instanceForm = instance.getForm();
283 if(instanceForm != form)
284 {
285 throw new FormsException("Instance retrived for form definition named '"+formName
286 +"' is not an instance for form definition '"+form.getDefinitionURI()+"'");
287 }
288
289 return (Instance)instance;
290 }
291
292 /*** Returns an Instance object creating it from a given saved state.
293 * @param formName Form's system wide identifier, this one is used to allow
294 * same form definitions to be used in different site contexts.
295 * @param httpContext HttpConext for current request.
296 * @param savedState Serialized Instance data.
297 * @throws Exception thrown on problems with deserialization.
298 * @return Deserialized Instance object.
299 */
300 public Instance getInstance(String formName, HttpContext httpContext, byte[] savedState)
301 throws Exception
302 {
303 if(!formsByName.containsKey(formName))
304 {
305 throw new FormsException("Form object with name '"+formName+"' cannot be found");
306 }
307
308 FormImpl form = (FormImpl)(formsByName.get(formName));
309 FormData formData = getFormData(httpContext);
310
311
312 InstanceImpl instance = ((FormImpl)form).createInstance(formName, savedState);
313
314 formData.put(instance);
315
316 return (Instance)instance;
317 }
318
319 /*** Removes an instance from users session - it should be used after instance
320 * processing is finished.
321 * Otherwise heavy instance data will be kept during whole user session. */
322 public void removeInstance(HttpContext httpContext, Instance instance)
323 {
324 FormData formData = getFormData(httpContext);
325 formData.remove(instance);
326 }
327
328 /*** Key for FormData session object. */
329 public static final String FORMDATA_NAME = "formtool.formdata";
330
331 private FormData getFormData(HttpContext httpContext)
332 {
333 FormData formData = (FormData)(httpContext.getSessionAttribute(FORMDATA_NAME));
334 if(formData == null)
335 {
336 formData = new FormData();
337 httpContext.setSessionAttribute(FORMDATA_NAME, formData);
338 }
339 return formData;
340 }
341
342
343
344
345
346 public Logger getLogFacility()
347 {
348 return log;
349 }
350
351 public Properties getTidyConfiguration()
352 {
353
354 return new Properties();
355
356 }
357
358 /*** FormData is a container for storing form Instances in users session.
359 *
360 * @author <a href="mailto:zwierzem@ngo.pl">Damian Gajda</a>
361 * @version $Id: FormsServiceImpl.java,v 1.7 2005/03/23 13:41:55 zwierzem Exp $
362 */
363 public class FormData
364 {
365 private HashMap instancesById = new HashMap();
366
367 /*** Puts an instance inside this FormData.
368 * @param instance Instance to be stored.
369 */
370 public void put(Instance instance)
371 {
372 instancesById.put(instance.getId(), instance);
373 }
374
375 /*** Gets an instance from this FormData.
376 * @param id Id of an instance to be retrieved.
377 * @return Instance found in this FormData.
378 */
379 public Instance get(String id)
380 {
381 return (Instance)(instancesById.get(id));
382 }
383
384 /*** Removes an instance from this FormData. */
385 public void remove(Instance instance)
386 {
387 instancesById.remove(instance.getId());
388 }
389 }
390 }