View Javadoc

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          //formSchemaURI = config.getChild("uri.schema.form").getValue("pl/caltha/forms/internal/formtool-form.xsd");
70          //uiSchemaURI   = config.getChild("uri.schema.ui").getValue("pl/caltha/forms/internal/formtool-ui.xsd");
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      // pl.caltha.forms.FormsService methods
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         // guard from null form definition URIs
108         checkInputValue("Form definition URI", formDefinitionURI);
109 
110         // guard from null formNames
111         checkInputValue("Form name", formName);
112 
113         //TODO: if debug set - check for timestamp on form definitionURI - is it possible with Ledge
114 
115         Form form = null;
116 
117         // get form definition from map
118         if(formsByName.containsKey(formName))
119         {
120             form = (Form)(formsByName.get(formName));
121 
122             // check for duplicate formName -> form mapping
123             String secondFormDefURI =  form.getDefinitionURI();
124             if(secondFormDefURI.equals(formDefinitionURI))
125             {
126                 // store a new name mapping for this form
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         // or build it and cache it
137         else
138         {
139             // synchronize to prevent duplicate form definition creation
140             synchronized(formsById)
141             {
142                 // WARN: This is the place in which we create formId's.
143                 // FormId's need to be compliant with XML ID strings
144                 char o = '-';
145                 String formId = formDefinitionURI.replace('/',o).replace(':',o).replace('.',o);
146 
147                 // create form
148                 form = buildForm(formDefinitionURI, formId);
149 
150                 // store it
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         // Build Form
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         // Build DefaultInstance
176         // URI is expanded in FormBuilder
177         reader = getXMLDataReader();
178         is = getInputSource(form.getDefaultInstanceURI());
179         //reset errorHandler
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         // Build UI
200         reader = getXMLDataReader();
201         String uiURI = form.getUIDefinitionURI();
202         // URI is expanded in FormBuilder
203         is = getInputSource(uiURI);
204         //reset errorHandler
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    // Instance access methods
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         // guard from null formNames
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             // create new Instance
276             instance = form.createInstance(formName);
277             // store it in FormData
278             formData.put(instance);
279         }
280 
281         // check if retrieved instance is connected to a proper Form object
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         // create new Instance
312         InstanceImpl instance = ((FormImpl)form).createInstance(formName, savedState);
313         // store it in FormData
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     // Other methods
345 
346     public Logger getLogFacility()
347     {
348         return log;
349     }
350 
351     public Properties getTidyConfiguration()
352     {
353         //TODO implement it!
354         return new Properties();
355         //throw new UnsupportedOperationException("not implemented yet!");
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 }