source: greenstone3/trunk/src/java/org/greenstone/gsdl3/core/TransformingReceptionist.java@ 18433

Last change on this file since 18433 was 18433, checked in by max, 15 years ago
  1. Using high-level language (gslib elements coming from a library) that can be used in transformations (skins) so that people creating skins will find it less complicated to understand what to do now.
  2. gsf statements are now processed before gslib elements so we can call gslib element from a gsf element.
  3. We're adding the DOCTYPE to the final page (that will result from transformations) in the proper way, since DOCTYPE is now defined in the stylesheet file itself instead of fixing it programmatically afterwards.
  4. Added some errorhandling (which makes use of new additions to XMLConverter and XMLTransformer classes) so that we don't get null pages on parsing, transforming and file not found exceptions and errors.
  • Property svn:keywords set to Author Date Id Revision
File size: 20.2 KB
Line 
1package org.greenstone.gsdl3.core;
2
3import org.greenstone.gsdl3.util.*;
4import org.greenstone.gsdl3.action.*;
5// XML classes
6import org.w3c.dom.Node;
7import org.w3c.dom.NodeList;
8import org.w3c.dom.Document;
9import org.w3c.dom.Element;
10import org.xml.sax.InputSource;
11
12// other java classes
13import java.io.File;
14import java.io.StringWriter;
15import java.io.FileReader;
16import java.io.FileNotFoundException;
17import java.util.HashMap;
18import java.util.Enumeration;
19
20import javax.xml.parsers.*;
21import javax.xml.transform.*;
22import javax.xml.transform.dom.*;
23import javax.xml.transform.stream.*;
24import org.apache.log4j.*;
25import org.apache.xerces.dom.*;
26import org.apache.xerces.parsers.DOMParser;
27
28/** A receptionist that uses xslt to transform the page_data before returning it. . Receives requests consisting
29 * of an xml representation of cgi args, and returns the page of data - in
30 * html by default. The requests are processed by the appropriate action class
31 *
32 * @see Action
33 */
34public class TransformingReceptionist extends Receptionist{
35
36 static Logger logger = Logger.getLogger(org.greenstone.gsdl3.core.TransformingReceptionist.class.getName());
37
38 /** The preprocess.xsl file is in a fixed location */
39 static final String preprocess_xsl_filename = GlobalProperties.getGSDL3Home() + File.separatorChar
40 + "ui" + File.separatorChar + "xslt" + File.separatorChar + "preProcess.xsl";
41
42 /** the list of xslt to use for actions */
43 protected HashMap xslt_map = null;
44
45 /** a transformer class to transform xml using xslt */
46 protected XMLTransformer transformer=null;
47
48 protected TransformerFactory transformerFactory=null;
49 protected DOMParser parser = null;
50 public TransformingReceptionist() {
51 super();
52 this.xslt_map = new HashMap();
53 this.transformer = new XMLTransformer();
54 try {
55 transformerFactory = org.apache.xalan.processor.TransformerFactoryImpl.newInstance();
56 this.converter = new XMLConverter();
57 //transformerFactory.setURIResolver(new MyUriResolver()) ;
58
59 parser = new DOMParser();
60 parser.setFeature("http://xml.org/sax/features/validation", false);
61 // don't try and load external DTD - no need if we are not validating, and may cause connection errors if a proxy is not set up.
62 parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
63 // a performance test showed that having this on lead to increased
64 // memory use for small-medium docs, and not much gain for large
65 // docs.
66 // http://www.sosnoski.com/opensrc/xmlbench/conclusions.html
67 parser.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", false);
68 parser.setFeature("http://apache.org/xml/features/continue-after-fatal-error", true);
69 // setting a handler for when fatal errors, errors or warnings happen during xml parsing
70 // call XMLConverter's getParseErrorMessage() to get the errorstring that can be rendered as web page
71 this.parser.setErrorHandler(new XMLConverter.ParseErrorHandler());
72 }
73 catch (Exception e) {
74 e.printStackTrace();
75 }
76
77 }
78
79 /** configures the receptionist - overwrite this to set up the xslt map*/
80 public boolean configure() {
81
82 if (this.config_params==null) {
83 logger.error(" config variables must be set before calling configure");
84 return false;
85 }
86 if (this.mr==null) {
87 logger.error(" message router must be set before calling configure");
88 return false;
89 }
90
91 // find the config file containing a list of actions
92 File interface_config_file = new File(GSFile.interfaceConfigFile(GSFile.interfaceHome(GlobalProperties.getGSDL3Home(), (String)this.config_params.get(GSConstants.INTERFACE_NAME))));
93 if (!interface_config_file.exists()) {
94 logger.error(" interface config file: "+interface_config_file.getPath()+" not found!");
95 return false;
96 }
97 Document config_doc = this.converter.getDOM(interface_config_file, "utf-8");
98 if (config_doc == null) {
99 logger.error(" could not parse interface config file: "+interface_config_file.getPath());
100 return false;
101 }
102 Element config_elem = config_doc.getDocumentElement();
103 String base_interface = config_elem.getAttribute("baseInterface");
104 setUpBaseInterface(base_interface);
105 setUpInterfaceOptions(config_elem);
106
107 Element action_list = (Element)GSXML.getChildByTagName(config_elem, GSXML.ACTION_ELEM+GSXML.LIST_MODIFIER);
108 NodeList actions = action_list.getElementsByTagName(GSXML.ACTION_ELEM);
109
110 for (int i=0; i<actions.getLength(); i++) {
111 Element action = (Element) actions.item(i);
112 String class_name = action.getAttribute("class");
113 String action_name = action.getAttribute("name");
114 Action ac = null;
115 try {
116 ac = (Action)Class.forName("org.greenstone.gsdl3.action."+class_name).newInstance();
117 } catch (Exception e) {
118 logger.error(" couldn't load in action "+class_name);
119 e.printStackTrace();
120 continue;
121 }
122 ac.setConfigParams(this.config_params);
123 ac.setMessageRouter(this.mr);
124 ac.configure();
125 ac.getActionParameters(this.params);
126 this.action_map.put(action_name, ac);
127
128 // now do the xslt map
129 String xslt = action.getAttribute("xslt");
130 if (!xslt.equals("")) {
131 this.xslt_map.put(action_name, xslt);
132 }
133 NodeList subactions = action.getElementsByTagName(GSXML.SUBACTION_ELEM);
134 for (int j=0; j<subactions.getLength(); j++) {
135 Element subaction = (Element)subactions.item(j);
136 String subname = subaction.getAttribute(GSXML.NAME_ATT);
137 String subxslt = subaction.getAttribute("xslt");
138
139 String map_key = action_name+":"+subname;
140 logger.debug("adding in to xslt map, "+map_key+"->"+subxslt);
141 this.xslt_map.put(map_key, subxslt);
142 }
143 }
144 Element lang_list = (Element)GSXML.getChildByTagName(config_elem, "languageList");
145 if (lang_list == null) {
146 logger.error(" didn't find a language list in the config file!!");
147 } else {
148 this.language_list = (Element) this.doc.importNode(lang_list, true);
149 }
150
151 return true;
152 }
153
154
155 protected Node postProcessPage(Element page) {
156 // might need to add some data to the page
157 addExtraInfo(page);
158 // transform the page using xslt
159 Node transformed_page = transformPage(page);
160
161 return transformed_page;
162 }
163
164 /** overwrite this to add any extra info that might be needed in the page before transformation */
165 protected void addExtraInfo(Element page) {}
166
167 /** transform the page using xslt
168 * we need to get any format element out of the page and add it to the xslt
169 * before transforming */
170 protected Node transformPage(Element page) {
171
172 logger.debug("page before transfomring:");
173 logger.debug(this.converter.getPrettyString(page));
174
175 Element request = (Element)GSXML.getChildByTagName(page, GSXML.PAGE_REQUEST_ELEM);
176 String action = request.getAttribute(GSXML.ACTION_ATT);
177 String subaction = request.getAttribute(GSXML.SUBACTION_ATT);
178
179 String output = request.getAttribute(GSXML.OUTPUT_ATT);
180 // we should choose how to transform the data based on output, eg diff
181 // choice for html, and wml??
182 // for now, if output=xml, we don't transform the page, we just return
183 // the page xml
184 if (output.equals("xml")) {
185 return page;
186 }
187
188
189 Element cgi_param_list = (Element)GSXML.getChildByTagName(request, GSXML.PARAM_ELEM+GSXML.LIST_MODIFIER);
190 String collection = "";
191 if (cgi_param_list != null) {
192 HashMap params = GSXML.extractParams(cgi_param_list, false);
193 collection = (String)params.get(GSParams.COLLECTION);
194 if (collection == null) collection = "";
195 }
196
197 String xslt_file = getXSLTFileName(action, subaction, collection);
198 if (xslt_file==null) {
199 // returning file not found error page to indicate which file is missing
200 return fileNotFoundErrorPage(xslt_file);
201 }
202
203 Document style_doc = this.converter.getDOM(new File(xslt_file), "UTF-8");
204 String errorPage = this.converter.getParseErrorMessage();
205 if(errorPage != null) {
206 return XMLTransformer.constructErrorXHTMLPage(
207 "Cannot parse the xslt file: " + xslt_file + "\n" + errorPage);
208 }
209 if (style_doc == null) {
210 logger.error(" cant parse the xslt file needed, so returning the original page!");
211 return page;
212 }
213
214 // put the page into a document - this is necessary for xslt to get
215 // the paths right if you have paths relative to the document root
216 // eg /page.
217 Document doc = this.converter.newDOM();
218 doc.appendChild(doc.importNode(page, true));
219 Element page_response = (Element)GSXML.getChildByTagName(page, GSXML.PAGE_RESPONSE_ELEM);
220 Element format_elem = (Element)GSXML.getChildByTagName(page_response, GSXML.FORMAT_ELEM);
221 if (output.equals("formatelem")) {
222 return format_elem;
223 }
224 if (format_elem != null) {
225 //page_response.removeChild(format_elem);
226 logger.debug("format elem="+this.converter.getPrettyString(format_elem));
227 // need to transform the format info
228 String configStylesheet_file = GSFile.stylesheetFile(GlobalProperties.getGSDL3Home(), (String)this.config_params.get(GSConstants.SITE_NAME), collection, (String)this.config_params.get(GSConstants.INTERFACE_NAME), base_interfaces, "config_format.xsl");
229 Document configStylesheet_doc = this.converter.getDOM(new File(configStylesheet_file));
230 if (configStylesheet_doc != null) {
231 Document format_doc = this.converter.newDOM();
232 format_doc.appendChild(format_doc.importNode(format_elem, true));
233 Node result = this.transformer.transform(configStylesheet_doc, format_doc);
234
235 // Since we started creating documents with DocTypes, we can end up with
236 // Document objects here. But we will be working with an Element instead,
237 // so we grab the DocumentElement() of the Document object in such a case.
238 Element new_format;
239 if(result.getNodeType() == Node.DOCUMENT_NODE) {
240 new_format = ((Document)result).getDocumentElement();
241 } else {
242 new_format = (Element)result;
243 }
244 logger.debug("new format elem="+this.converter.getPrettyString(new_format));
245 if (output.equals("newformat")) {
246 return new_format;
247 }
248
249 // add extracted GSF statements in to the main stylesheet
250 GSXSLT.mergeStylesheets(style_doc, new_format);
251 //System.out.println("added extracted GSF statements into the main stylesheet") ;
252
253 // add extracted GSF statements in to the debug test stylesheet
254 //GSXSLT.mergeStylesheets(oldStyle_doc, new_format);
255 } else {
256 logger.error(" couldn't parse the config_format stylesheet, adding the format info as is");
257 GSXSLT.mergeStylesheets(style_doc, format_elem);
258 // GSXSLT.mergeStylesheets(oldStyle_doc, format_elem);
259 }
260 logger.debug("the converted stylesheet is:");
261 logger.debug(this.converter.getPrettyString(style_doc.getDocumentElement()));
262 }
263 //for debug purposes only
264 Document oldStyle_doc = style_doc;
265
266
267 Document preprocessingXsl ;
268 try {
269 preprocessingXsl = getPreprocessDoc();
270 String errMsg = ((XMLConverter.ParseErrorHandler)parser.getErrorHandler()).getErrorMessage();
271 if(errMsg != null) {
272 return XMLTransformer.constructErrorXHTMLPage("error loading preprocess xslt file: "
273 + preprocess_xsl_filename + "\n" + errMsg);
274 }
275 } catch (java.io.FileNotFoundException e) {
276 return fileNotFoundErrorPage(e.getMessage());
277 } catch (Exception e) {
278 e.printStackTrace() ;
279 System.out.println("error loading preprocess xslt") ;
280 return XMLTransformer.constructErrorXHTMLPage("error loading preprocess xslt\n" + e.getMessage());
281 }
282
283 Document libraryXsl = null;
284 try {
285 libraryXsl = getLibraryDoc() ;
286 String errMsg = ((XMLConverter.ParseErrorHandler)parser.getErrorHandler()).getErrorMessage();
287 if(errMsg != null) {
288 return XMLTransformer.constructErrorXHTMLPage("Error loading xslt file: "
289 + this.getLibraryXSLFilename() + "\n" + errMsg);
290 }
291 } catch (java.io.FileNotFoundException e) {
292 return fileNotFoundErrorPage(e.getMessage());
293 } catch (Exception e) {
294 e.printStackTrace() ;
295 System.out.println("error loading library xslt") ;
296 return constructErrorXHTMLPage("error loading library xslt\n" + e.getMessage()) ;
297 }
298
299 // Combine the skin file and library variables/templates into one document.
300 // Please note: We dont just use xsl:import because the preprocessing stage
301 // needs to know what's available in the library.
302
303 Document skinAndLibraryXsl = null ;
304 Document skinAndLibraryDoc = converter.newDOM();
305 try {
306
307 skinAndLibraryXsl = converter.newDOM();
308 Element root = skinAndLibraryXsl.createElement("skinAndLibraryXsl") ;
309 skinAndLibraryXsl.appendChild(root) ;
310
311 Element s = skinAndLibraryXsl.createElement("skinXsl") ;
312 s.appendChild(skinAndLibraryXsl.importNode(style_doc.getDocumentElement(), true)) ;
313 root.appendChild(s) ;
314
315 Element l = skinAndLibraryXsl.createElement("libraryXsl") ;
316 Element libraryXsl_el = libraryXsl.getDocumentElement();
317 l.appendChild(skinAndLibraryXsl.importNode(libraryXsl_el, true)) ;
318 root.appendChild(l) ;
319 //System.out.println("Skin and Library XSL are now together") ;
320
321
322 //System.out.println("Pre-processing the skin file...") ;
323
324 //pre-process the skin style sheet
325 //In other words, apply the preProcess.xsl to 'skinAndLibraryXsl' in order to
326 //expand all GS-Lib statements into complete XSL statements and also to create
327 //a valid xsl style sheet document.
328
329 Transformer preProcessor = transformerFactory.newTransformer(new DOMSource(preprocessingXsl));
330 preProcessor.setErrorListener(new XMLTransformer.TransformErrorListener());
331 DOMResult result = new DOMResult();
332 result.setNode(skinAndLibraryDoc);
333 preProcessor.transform(new DOMSource(skinAndLibraryXsl), result);
334 System.out.println("GS-Lib statements are now expanded") ;
335
336 }
337 catch (TransformerException e) {
338 e.printStackTrace() ;
339 System.out.println("TransformerException while preprocessing the skin xslt") ;
340 return XMLTransformer.constructErrorXHTMLPage(e.getMessage()) ;
341 }
342 catch (Exception e) {
343 e.printStackTrace() ;
344 System.out.println("Error while preprocessing the skin xslt") ;
345 return XMLTransformer.constructErrorXHTMLPage(e.getMessage()) ;
346 }
347
348 // there is a thing called a URIResolver which you can set for a
349 // transformer or transformer factory. may be able to use this
350 // instead of this absoluteIncludepaths hack
351
352 GSXSLT.absoluteIncludePaths(skinAndLibraryDoc, GlobalProperties.getGSDL3Home(),
353 (String)this.config_params.get(GSConstants.SITE_NAME),
354 collection, (String)this.config_params.get(GSConstants.INTERFACE_NAME),
355 base_interfaces);
356
357
358 //Same but for the debug version when we want the do the transformation like we use to do
359 //without any gslib elements.
360 GSXSLT.absoluteIncludePaths(oldStyle_doc, GlobalProperties.getGSDL3Home(),
361 (String)this.config_params.get(GSConstants.SITE_NAME),
362 collection, (String)this.config_params.get(GSConstants.INTERFACE_NAME),
363 base_interfaces);
364
365 //Send different stages of the skin xslt to the browser for debug purposes only
366 //using &o=skindoc or &o=skinandlib etc...
367 if (output.equals("skindoc")) {
368 return converter.getDOM(getStringFromDocument(style_doc));
369 }
370 if (output.equals("skinandlib")) {
371 return converter.getDOM(getStringFromDocument(skinAndLibraryXsl));
372 }
373 if (output.equals("skinandlibdoc")) {
374 return converter.getDOM(getStringFromDocument(skinAndLibraryDoc));
375 }
376 if (output.equals("oldskindoc")) {
377 return converter.getDOM(getStringFromDocument(oldStyle_doc));
378 }
379
380 // DocType defaults in case the skin doesn't have an "xsl:output" element
381 String qualifiedName = "html";
382 String publicID = "-//W3C//DTD HTML 4.01 Transitional//EN";
383 String systemID = "http://www.w3.org/TR/html4/loose.dtd";
384
385 // Try to get the system and public ID from the current skin xsl document
386 // otherwise keep the default values.
387 Element root = skinAndLibraryDoc.getDocumentElement();
388 NodeList nodes = root.getElementsByTagName("xsl:output");
389 // If there is at least one "xsl:output" command in the final xsl then...
390 if(nodes.getLength() != 0) {
391 // There should be only one element called xsl:output,
392 // but if this is not the case get the last one
393 Element xsl_output = (Element)nodes.item(nodes.getLength()-1);
394 if (xsl_output != null) {
395 // Qualified name will always be html even for xhtml pages
396 //String attrValue = xsl_output.getAttribute("method");
397 //qualifiedName = attrValue.equals("") ? qualifiedName : attrValue;
398
399 String attrValue = xsl_output.getAttribute("doctype-system");
400 systemID = attrValue.equals("") ? systemID : attrValue;
401
402 attrValue = xsl_output.getAttribute("doctype-public");
403 publicID = attrValue.equals("") ? publicID : attrValue;
404 }
405 }
406
407 // We need to create an empty document with a predefined DocType,
408 // that will then be used for the transformation by the DOMResult
409 Document docWithDoctype = converter.newDOM(qualifiedName, publicID, systemID);
410
411 //System.out.println(converter.getPrettyString(docWithDoctype));
412 //System.out.println("Doctype vals: " + qualifiedName + " " + publicID + " " + systemID) ;
413
414 //System.out.println("Generate final HTML from current skin") ;
415 //Transformation of the XML message from the receptionist to HTML with doctype
416 return this.transformer.transform(skinAndLibraryDoc, doc, config_params, docWithDoctype);
417
418
419 // The line below will do the transformation like we use to do before having Skin++ implemented,
420 // it will not contain any GS-Lib statements expanded, and the result will not contain any doctype.
421
422 //return (Element)this.transformer.transform(style_doc, doc, config_params);
423
424 }
425
426
427 // method to convert Document to a proper XML string for debug purposes only
428 protected String getStringFromDocument(Document doc)
429 {
430 String content = "";
431 try
432 {
433 DOMSource domSource = new DOMSource(doc);
434 StringWriter writer = new StringWriter();
435 StreamResult result = new StreamResult(writer);
436 TransformerFactory tf = TransformerFactory.newInstance();
437 Transformer transformer = tf.newTransformer();
438 transformer.transform(domSource, result);
439 content = writer.toString();
440 System.out.println("Change the & to &Amp; for proper debug dispay") ;
441 content = content.replaceAll("&", "&amp;");
442 writer.flush();
443 }
444 catch(TransformerException ex)
445 {
446 ex.printStackTrace();
447 return null;
448 }
449 return content;
450 }
451
452
453 protected Document getPreprocessDoc() throws Exception {
454
455 File xslt_file = new File(preprocess_xsl_filename) ;
456
457 FileReader reader = new FileReader(xslt_file);
458 InputSource xml_source = new InputSource(reader);
459 this.parser.parse(xml_source);
460 Document doc = this.parser.getDocument();
461
462 return doc ;
463 }
464
465 protected Document getLibraryDoc() throws Exception {
466 Document doc = null;
467 File xslt_file = new File(this.getLibraryXSLFilename()) ;
468
469 FileReader reader = new FileReader(xslt_file);
470 InputSource xml_source = new InputSource(reader);
471 this.parser.parse(xml_source);
472
473 doc = this.parser.getDocument();
474 return doc ;
475 }
476
477 protected String getXSLTFileName(String action, String subaction,
478 String collection) {
479
480 String name = null;
481 if (!subaction.equals("")) {
482 String key = action+":"+subaction;
483 name = (String) this.xslt_map.get(key);
484 }
485 // try the action by itself
486 if (name==null) {
487 name = (String) this.xslt_map.get(action);
488 }
489 // now find the absolute path
490 String stylesheet = GSFile.stylesheetFile(GlobalProperties.getGSDL3Home(), (String)this.config_params.get(GSConstants.SITE_NAME), collection, (String)this.config_params.get(GSConstants.INTERFACE_NAME), base_interfaces, name);
491 if (stylesheet==null) {
492 logger.info(" cant find stylesheet for "+name);
493 }
494 return stylesheet;
495 }
496
497 // returns the library.xsl path of the library file that is applicable for the current interface
498 protected String getLibraryXSLFilename() {
499 return GSFile.xmlTransformDir(GSFile.interfaceHome(
500 GlobalProperties.getGSDL3Home(), (String)this.config_params.get(GSConstants.INTERFACE_NAME)))
501 + File.separatorChar + "library.xsl";
502 }
503
504 // Call this when a FileNotFoundException could be thrown when loading an xsl (xml) file.
505 // Returns an error xhtml page indicating which xsl (or other xml) file is missing.
506 protected Document fileNotFoundErrorPage(String filenameMessage) {
507 String errorMessage = "ERROR missing file: " + filenameMessage;
508 Element errPage = XMLTransformer.constructErrorXHTMLPage(errorMessage);
509 logger.error(errorMessage);
510 System.err.println("****" + errorMessage);
511 return errPage.getOwnerDocument();
512 }
513}
Note: See TracBrowser for help on using the repository browser.