001    /*
002     * 
003     * $Revision: 15201 $ $Date: 2009-05-15 16:49:11 +0200 (Fri, 15 May 2009) $
004     *
005     * This file is part of ***  M y C o R e  ***
006     * See http://www.mycore.de/ for details.
007     *
008     * This program is free software; you can use it, redistribute it
009     * and / or modify it under the terms of the GNU General Public License
010     * (GPL) as published by the Free Software Foundation; either version 2
011     * of the License or (at your option) any later version.
012     *
013     * This program is distributed in the hope that it will be useful, but
014     * WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program, in a file called gpl.txt or license.txt.
020     * If not, write to the Free Software Foundation Inc.,
021     * 59 Temple Place - Suite 330, Boston, MA  02111-1307 USA
022     */
023    
024    package org.mycore.common.xml;
025    
026    import java.io.ByteArrayInputStream;
027    import java.io.ByteArrayOutputStream;
028    import java.io.File;
029    import java.io.FileInputStream;
030    import java.io.IOException;
031    import java.io.InputStream;
032    import java.io.OutputStream;
033    import java.io.Writer;
034    import java.util.Enumeration;
035    import java.util.Hashtable;
036    import java.util.List;
037    import java.util.Map;
038    import java.util.Properties;
039    import java.util.StringTokenizer;
040    
041    import javax.servlet.http.HttpServletRequest;
042    import javax.servlet.http.HttpServletResponse;
043    import javax.servlet.http.HttpSession;
044    import javax.xml.transform.ErrorListener;
045    import javax.xml.transform.Result;
046    import javax.xml.transform.Source;
047    import javax.xml.transform.SourceLocator;
048    import javax.xml.transform.Templates;
049    import javax.xml.transform.Transformer;
050    import javax.xml.transform.TransformerConfigurationException;
051    import javax.xml.transform.TransformerException;
052    import javax.xml.transform.TransformerFactory;
053    import javax.xml.transform.dom.DOMSource;
054    import javax.xml.transform.sax.SAXResult;
055    import javax.xml.transform.sax.SAXTransformerFactory;
056    import javax.xml.transform.stream.StreamResult;
057    import javax.xml.transform.stream.StreamSource;
058    
059    import org.apache.avalon.framework.logger.Log4JLogger;
060    import org.apache.fop.apps.Driver;
061    import org.apache.fop.messaging.MessageHandler;
062    import org.apache.log4j.Logger;
063    import org.apache.xalan.templates.ElemTemplate;
064    import org.apache.xalan.templates.ElemTemplateElement;
065    import org.apache.xalan.trace.GenerateEvent;
066    import org.apache.xalan.trace.SelectionEvent;
067    import org.apache.xalan.trace.TraceManager;
068    import org.apache.xalan.trace.TracerEvent;
069    import org.apache.xml.utils.WrappedRuntimeException;
070    import org.jdom.Document;
071    import org.jdom.transform.JDOMResult;
072    import org.jdom.transform.JDOMSource;
073    import org.mycore.common.MCRCache;
074    import org.mycore.common.MCRConfiguration;
075    import org.mycore.common.MCRConfigurationException;
076    import org.mycore.common.MCRException;
077    import org.mycore.common.MCRSession;
078    import org.mycore.common.MCRSessionMgr;
079    import org.mycore.common.MCRUtils;
080    import org.mycore.datamodel.ifs.MCRContentInputStream;
081    import org.mycore.frontend.servlets.MCRServlet;
082    import org.mycore.frontend.servlets.MCRServletJob;
083    import org.mycore.user.MCRUser;
084    import org.mycore.user.MCRUserMgr;
085    import org.w3c.dom.Node;
086    import org.xml.sax.SAXException;
087    
088    /**
089     * Does the layout for other MyCoRe servlets by transforming XML input to
090     * various output formats, using XSL stylesheets.
091     * 
092     * @author Frank L�tzenkirchen
093     * @author Thomas Scheffler (yagee)
094     * 
095     * @version $Revision: 15201 $ $Date: 2008-05-21 15:53:52 +0200 (Mi, 21. Mai
096     *          2008) $
097     */
098    public class MCRLayoutService implements org.apache.xalan.trace.TraceListener {
099    
100        /** A cache of already compiled stylesheets */
101        private static MCRCache STYLESHEETS_CACHE = new MCRCache(MCRConfiguration.instance().getInt("MCR.LayoutService.XSLCacheSize", 100),
102                "XSLT Stylesheets");
103    
104        private static MCRXMLResource XML_RESOURCE = MCRXMLResource.instance();
105    
106        /** The XSL transformer factory to use */
107        private SAXTransformerFactory factory;
108    
109        /** The logger */
110        private final static Logger LOGGER = Logger.getLogger(MCRLayoutService.class);
111    
112        private final static org.apache.avalon.framework.logger.Logger FOPLOG = new Log4JLogger(LOGGER);
113    
114        private static final MCRLayoutService singleton = new MCRLayoutService();
115    
116        public static MCRLayoutService instance() {
117            return singleton;
118        }
119    
120        private MCRLayoutService() {
121            // System.setProperty("javax.xml.transform.TransformerFactory",
122            // "org.apache.xalan.xsltc.trax.TransformerFactoryImpl");
123            System.setProperty("javax.xml.transform.TransformerFactory", "org.apache.xalan.processor.TransformerFactoryImpl");
124            TransformerFactory tf = TransformerFactory.newInstance();
125            LOGGER.info("Transformerfactory: " + tf.getClass().getName());
126    
127            if (!tf.getFeature(SAXTransformerFactory.FEATURE)) {
128                throw new MCRConfigurationException("Could not load a SAXTransformerFactory for use with XSLT");
129            }
130    
131            factory = (SAXTransformerFactory) (tf);
132            factory.setURIResolver(MCRURIResolver.instance());
133            factory.setErrorListener(new ErrorListener() {
134                public void error(TransformerException ex) {
135                    throw new MCRException("Error in transformer factory", ex);
136                }
137    
138                public void fatalError(TransformerException ex) {
139                    throw new MCRException("Fatal error in transformer factory", ex);
140                }
141    
142                public void warning(TransformerException ex) {
143                    LOGGER.warn(ex.getMessageAndLocation());
144                }
145            });
146    
147            MessageHandler.setScreenLogger(FOPLOG);
148        }
149    
150        public void sendXML(HttpServletRequest req, HttpServletResponse res, org.jdom.Document jdom) throws IOException {
151            res.setContentType("text/xml");
152            OutputStream out = res.getOutputStream();
153            new org.jdom.output.XMLOutputter().output(jdom, out);
154            out.close();
155        }
156    
157        public void sendXML(HttpServletRequest req, HttpServletResponse res, org.w3c.dom.Document dom) throws IOException {
158            sendXML(req, res, new org.jdom.input.DOMBuilder().build(dom));
159        }
160    
161        public void sendXML(HttpServletRequest req, HttpServletResponse res, InputStream in) throws IOException {
162            res.setContentType("text/xml");
163            OutputStream out = res.getOutputStream();
164            MCRUtils.copyStream(in, out);
165            out.close();
166        }
167    
168        public void sendXML(HttpServletRequest req, HttpServletResponse res, File file) throws IOException {
169            FileInputStream fis = new FileInputStream(file);
170            sendXML(req, res, fis);
171            fis.close();
172        }
173    
174        public void doLayout(HttpServletRequest req, HttpServletResponse res, org.jdom.Document jdom) throws IOException {
175            String docType = (jdom.getDocType() == null ? jdom.getRootElement().getName() : jdom.getDocType().getElementName());
176            Properties parameters = buildXSLParameters(req);
177            String resourceName = getResourceName(req, parameters, docType);
178            if (resourceName == null)
179                sendXML(req, res, jdom);
180            else
181                transform(res, new org.jdom.transform.JDOMSource(jdom), docType, parameters, resourceName);
182        }
183    
184        /**
185         * writes the transformation result directly into the Writer uses the
186         * HttpServletResponse only for error messages
187         */
188        public void doLayout(HttpServletRequest req, HttpServletResponse res, Writer out, org.jdom.Document jdom) throws IOException {
189            String docType = (jdom.getDocType() == null ? jdom.getRootElement().getName() : jdom.getDocType().getElementName());
190            Properties parameters = buildXSLParameters(req);
191            String resourceName = getResourceName(req, parameters, docType);
192            if (resourceName == null)
193                new org.jdom.output.XMLOutputter().output(jdom, out);
194            else {
195                Templates stylesheet = buildCompiledStylesheet(resourceName);
196                Transformer transformer = buildTransformer(stylesheet);
197                setXSLParameters(transformer, parameters);
198                try {
199                    transformer.transform(new org.jdom.transform.JDOMSource(jdom), new StreamResult(out));
200                } catch (TransformerException ex) {
201                    String msg = "Error while transforming XML using XSL stylesheet: " + ex.getMessageAndLocation();
202                    throw new MCRException(msg, ex);
203                } catch (MCRException ex) {
204                    // Check if it is an error page to suppress later recursively
205                    // generating an error page when there is an error in the
206                    // stylesheet
207                    if (!"mcr_error".equals(docType))
208                        throw ex;
209                    String msg = "Error while generating error page!";
210                    LOGGER.warn(msg, ex);
211                    res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, msg);
212                }
213            }
214        }
215    
216        public void doLayout(HttpServletRequest req, HttpServletResponse res, org.w3c.dom.Document dom) throws IOException {
217            String docType = (dom.getDoctype() == null ? dom.getDocumentElement().getTagName() : dom.getDoctype().getName());
218            Properties parameters = buildXSLParameters(req);
219            String resourceName = getResourceName(req, parameters, docType);
220            if (resourceName == null)
221                sendXML(req, res, dom);
222            else
223                transform(res, new DOMSource(dom), docType, parameters, resourceName);
224        }
225    
226        public void doLayout(HttpServletRequest req, HttpServletResponse res, InputStream is) throws IOException {
227            MCRContentInputStream cis = new MCRContentInputStream(is);
228            String docType = MCRUtils.parseDocumentType(new ByteArrayInputStream(cis.getHeader()));
229    
230            Properties parameters = buildXSLParameters(req);
231            String resourceName = getResourceName(req, parameters, docType);
232            if (resourceName == null)
233                sendXML(req, res, cis);
234            else
235                transform(res, new StreamSource(cis), docType, parameters, resourceName);
236        }
237    
238        public void doLayout(HttpServletRequest req, HttpServletResponse res, File file) throws IOException {
239            FileInputStream fis = new FileInputStream(file);
240            doLayout(req, res, fis);
241            fis.close();
242        }
243    
244        public Document doLayout(Document doc, String stylesheetName, Hashtable<String, String> params) throws Exception {
245            MCRSession mcrSession = MCRSessionMgr.getCurrentSession();
246            MCRServletJob job = (MCRServletJob) mcrSession.get("MCRServletJob");
247            Templates stylesheet = buildCompiledStylesheet(stylesheetName);
248            Transformer transformer = buildTransformer(stylesheet);
249            Properties parameters = new Properties();
250            if (job != null) {
251                parameters = buildXSLParameters(job.getRequest());
252                if (null != params)
253                    parameters = new Properties();
254            }
255            setXSLParameters(transformer, parameters);
256            JDOMResult out = new JDOMResult();
257            transformer.transform(new JDOMSource(doc), out);
258            return out.getDocument();
259        }
260    
261        private void transform(HttpServletResponse res, Source sourceXML, String docType, Properties parameters, Templates stylesheet)
262                throws IOException {
263            Transformer transformer = buildTransformer(stylesheet);
264            setXSLParameters(transformer, parameters);
265    
266            try {
267                transform(sourceXML, stylesheet, transformer, res);
268            } catch (IOException ex) {
269                LOGGER.error("IOException while XSL-transforming XML document", ex);
270            } catch (MCRException ex) {
271                // Check if it is an error page to suppress later recursively
272                // generating an error page when there is an error in the stylesheet
273                if (!"mcr_error".equals(docType))
274                    throw ex;
275    
276                String msg = "Error while generating error page!";
277                LOGGER.warn(msg, ex);
278                res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, msg);
279            }
280        }
281    
282        private void transform(HttpServletResponse res, Source sourceXML, String docType, Properties parameters, String resourceName)
283                throws IOException {
284            Templates stylesheet = buildCompiledStylesheet(resourceName);
285            transform(res, sourceXML, docType, parameters, stylesheet);
286        }
287    
288        private String getResourceName(HttpServletRequest req, Properties parameters, String docType) {
289            String style = parameters.getProperty("Style", "default");
290            LOGGER.debug("MCRLayoutService using style " + style);
291    
292            String styleName = buildStylesheetName(docType, style);
293            boolean resourceExist = false;
294            try {
295                resourceExist = XML_RESOURCE.exists(styleName, this.getClass().getClassLoader());
296                if (resourceExist) {
297                    return styleName;
298                }
299            } catch (Exception e) {
300                throw new MCRException("Error while loading stylesheet: " + styleName, e);
301            }
302    
303            // If no stylesheet exists, forward raw xml instead
304            // You can transform raw xml code by providing a stylesheed named
305            // [doctype]-xml.xsl now
306            if (style.equals("xml") || style.equals("default"))
307                return null;
308            throw new MCRException("XSL stylesheet not found: " + styleName);
309        }
310    
311        public static Properties buildXSLParameters(HttpServletRequest request) {
312            // PROPERTIES: Read all properties from system configuration
313            Properties parameters = (Properties) (MCRConfiguration.instance().getProperties().clone());
314            // added properties of MCRSession and request
315            if (request != null) {
316                parameters.putAll(mergeProperties(request));
317    
318                // handle HttpSession
319                HttpSession session = request.getSession(false);
320                if (session != null) {
321                    String jSessionID = MCRConfiguration.instance().getString("MCR.Session.Param", ";jsessionid=");
322                    if (!request.isRequestedSessionIdFromCookie()) {
323                        parameters.put("HttpSession", jSessionID + session.getId());
324                    }
325                    parameters.put("JSessionID", jSessionID + session.getId());
326                }
327            }
328    
329            String uid = MCRSessionMgr.getCurrentSession().getCurrentUserID();
330    
331            boolean setCurrentGroups = MCRConfiguration.instance().getBoolean("MCR.Users.SetCurrentGroups", true);
332            if (setCurrentGroups) { // for MyCoRe applications, always true
333                final MCRUser mcrUser = MCRUserMgr.instance().retrieveUser(uid);
334                StringBuffer groups = new StringBuffer(mcrUser.getPrimaryGroupID());
335                List<String> groupList = mcrUser.getGroupIDs();
336                for (int i = 0; i < groupList.size(); i++) {
337                    if (i != 0)
338                        groups.append(' ');
339                    groups.append((String) groupList.get(i));
340                }
341                parameters.put("CurrentGroups", groups.toString());
342            }
343    
344            MCRSession mcrSession = MCRSessionMgr.getCurrentSession();
345    
346            // set parameters
347            parameters.put("CurrentUser", uid);
348            parameters.put("DefaultLang", MCRConfiguration.instance().getString("MCR.Metadata.DefaultLang", "en"));
349            parameters.put("CurrentLang", mcrSession.getCurrentLanguage());
350            parameters.put("WebApplicationBaseURL", MCRServlet.getBaseURL());
351            parameters.put("ServletsBaseURL", MCRServlet.getServletBaseURL());
352    
353            if (request != null) {
354                parameters.put("RequestURL", getCompleteURL(request));
355                parameters.put("Referer", (request.getHeader("Referer") != null) ? request.getHeader("Referer") : "");
356            }
357    
358            LOGGER.debug("LayoutServlet XSL.MCRSessionID=" + parameters.getProperty("MCRSessionID"));
359            LOGGER.debug("LayoutServlet XSL.CurrentUser =" + mcrSession.getCurrentUserID());
360            LOGGER.debug("LayoutServlet HttpSession =" + parameters.getProperty("HttpSession"));
361            LOGGER.debug("LayoutServlet JSessionID =" + parameters.getProperty("JSessionID"));
362            LOGGER.debug("LayoutServlet RefererURL =" + parameters.getProperty("Referer"));
363    
364            return parameters;
365        }
366    
367        /**
368         * returns a merged list of XSL parameters.
369         * 
370         * First parameters stored in current HttpSession are used. These are
371         * overwritten by Parameters of MCRSession. Finally parameters, then
372         * attributes of HttpServletRequest overwrite the previously defined.
373         * 
374         * @param request
375         * @return merged XSL.* properties of MCR|HttpSession and HttpServletRequest
376         */
377        private final static Properties mergeProperties(HttpServletRequest request) {
378            Properties props = new Properties();
379    
380            HttpSession session = request.getSession(false);
381            MCRSession mcrSession = MCRSessionMgr.getCurrentSession();
382            if (session != null) {
383                for (@SuppressWarnings("unchecked")
384                Enumeration<String> e = session.getAttributeNames(); e.hasMoreElements();) {
385                    String name = e.nextElement();
386                    if (name.startsWith("XSL."))
387                        props.put(name.substring(4), session.getAttribute(name));
388                }
389            }
390            for (Map.Entry<Object, Object> entry : mcrSession.getMapEntries()) {
391                String key = entry.getKey().toString();
392                if (key.startsWith("XSL.")) {
393                    props.put(key.substring(4), entry.getValue());
394                }
395            }
396            for (@SuppressWarnings("unchecked")
397            Enumeration<String> e = request.getParameterNames(); e.hasMoreElements();) {
398                String name = e.nextElement();
399                if (name.startsWith("XSL.") && !name.endsWith(".SESSION")) {
400                    props.put(name.substring(4), request.getParameter(name));
401                }
402            }
403            for (@SuppressWarnings("unchecked")
404            Enumeration<String> e = request.getAttributeNames(); e.hasMoreElements();) {
405                String name = e.nextElement();
406                if (name.startsWith("XSL.") && !name.endsWith(".SESSION")) {
407                    final Object attributeValue = request.getAttribute(name);
408                    if (attributeValue != null)
409                        props.put(name.substring(4), attributeValue.toString());
410                }
411            }
412            return props;
413        }
414    
415        private final static String getCompleteURL(HttpServletRequest request) {
416            StringBuffer buffer = request.getRequestURL();
417            String queryString = request.getQueryString();
418            if (queryString != null && queryString.length() > 0) {
419                buffer.append("?").append(queryString);
420            }
421            LOGGER.debug("Complete request URL : " + buffer.toString());
422            return buffer.toString();
423        }
424    
425        /**
426         * Builds the filename of the stylesheet to use, e. g. "playlist-simple.xsl"
427         */
428        private String buildStylesheetName(String docType, String style) {
429            StringBuffer filename = new StringBuffer("xsl/").append(docType);
430    
431            if (!"default".equals(style)) {
432                filename.append("-");
433                filename.append(style);
434            }
435    
436            filename.append(".xsl");
437    
438            return filename.toString();
439        }
440    
441        private Templates buildCompiledStylesheet(String resource) {
442            Templates stylesheet = null;
443            try {
444                stylesheet = (Templates) (STYLESHEETS_CACHE.getIfUpToDate(resource, XML_RESOURCE.getLastModified(resource, this.getClass()
445                        .getClassLoader())));
446            } catch (IOException e) {
447                LOGGER.warn("Could not determine last modified date of resource " + resource);
448            }
449            if (stylesheet == null) {
450                try {
451                    byte[] bytes = XML_RESOURCE.getRawResource(resource, this.getClass().getClassLoader());
452                    stylesheet = factory.newTemplates(new StreamSource(new ByteArrayInputStream(bytes)));
453                    LOGGER.debug("MCRLayoutService compiled stylesheet resource " + resource);
454                } catch (Exception exc) {
455                    reportCompileError(resource, exc);
456                }
457                STYLESHEETS_CACHE.put(resource, stylesheet);
458            } else {
459                LOGGER.debug("MCRLayoutService using cached stylesheet " + resource);
460            }
461    
462            return stylesheet;
463        }
464    
465        private void reportCompileError(String resource, Exception exc) {
466            StringBuffer msg = new StringBuffer("Error compiling XSL stylesheet ");
467            msg.append(resource);
468    
469            while (exc != null) {
470                if (exc instanceof MCRException) {
471                    MCRException mex = (MCRException) exc;
472                    msg.append("\n").append(mex.getMessage());
473                    exc = mex.getException();
474                } else if (exc instanceof TransformerException) {
475                    TransformerException tex = (TransformerException) exc;
476                    msg.append("\n").append(tex.getMessage());
477                    SourceLocator sl = tex.getLocator();
478                    if (sl != null)
479                        msg.append(" at line ").append(sl.getLineNumber()).append(" column ").append(sl.getColumnNumber());
480    
481                    if (tex.getCause() instanceof Exception)
482                        exc = (Exception) (tex.getCause());
483                    else
484                        exc = null;
485                } else if (exc instanceof WrappedRuntimeException) {
486                    exc = ((WrappedRuntimeException) exc).getException();
487                } else {
488                    msg.append("\n").append(exc.getMessage());
489                    if (exc.getCause() instanceof Exception)
490                        exc = (Exception) (exc.getCause());
491                    else
492                        exc = null;
493                }
494            }
495    
496            LOGGER.error(msg);
497            throw new MCRConfigurationException(msg.toString(), exc);
498        }
499    
500        /**
501         * Builds a XSL transformer that uses the given XSL stylesheet
502         * 
503         * @param stylesheet
504         *            the compiled XSL stylesheet to use
505         * @return the XSL transformer that can be used to do the XSL transformation
506         */
507        private Transformer buildTransformer(Templates stylesheet) {
508            try {
509                Transformer tf = factory.newTransformerHandler(stylesheet).getTransformer();
510    
511                // In debug mode, add a TraceListener to log stylesheet execution
512                if (LOGGER.isDebugEnabled()) {
513                    try {
514                        TraceManager tm = ((org.apache.xalan.transformer.TransformerImpl) tf).getTraceManager();
515                        tm.addTraceListener(this);
516    
517                    } catch (Exception ex) {
518                        LOGGER.warn(ex);
519                    }
520                }
521    
522                return tf;
523            } catch (TransformerConfigurationException exc) {
524                String msg = "Error while building XSL transformer: " + exc.getMessageAndLocation();
525                throw new MCRConfigurationException(msg, exc);
526            }
527        }
528    
529        /**
530         * Sets XSL parameters for the given transformer by taking them from the
531         * properties object provided.
532         * 
533         * @param transformer
534         *            the Transformer object thats parameters should be set
535         * @param parameters
536         *            the XSL parameters as name-value pairs
537         */
538        private void setXSLParameters(Transformer transformer, Properties parameters) {
539            Enumeration<?> names = parameters.propertyNames();
540    
541            while (names.hasMoreElements()) {
542                String name = names.nextElement().toString();
543                String value = parameters.getProperty(name);
544    
545                transformer.setParameter(name, value);
546            }
547        }
548    
549        /**
550         * Transforms XML input with the given XSL stylesheet and sends the output
551         * as HTTP Servlet Response to the client browser.
552         * 
553         * @param xml
554         *            the XML input document
555         * @param xsl
556         *            the compiled XSL stylesheet
557         * @param transformer
558         *            the XSL transformer to use
559         * @param response
560         *            the response object to send the result to
561         */
562        private void transform(Source xml, Templates xsl, Transformer transformer, HttpServletResponse response) throws IOException,
563                MCRException {
564    
565            // Set content type from "<xsl:output media-type = "...">
566            // Set char encoding from "<xsl:output encoding = "...">
567            String ct = xsl.getOutputProperties().getProperty("media-type");
568            String enc = xsl.getOutputProperties().getProperty("encoding");
569            response.setCharacterEncoding(enc);
570            response.setContentType(ct + "; charset=" + enc);
571    
572            ByteArrayOutputStream out = new ByteArrayOutputStream();
573            Result result = null;
574    
575            if ("application/pdf".equals(ct)) {
576                Driver driver = new Driver();
577                driver.setLogger(FOPLOG);
578                driver.setRenderer(Driver.RENDER_PDF);
579                driver.setOutputStream(out);
580                result = new SAXResult(driver.getContentHandler());
581            } else {
582                result = new StreamResult(out);
583            }
584    
585            LOGGER.debug("MCRLayoutService starts to output " + response.getContentType());
586    
587            try {
588                transformer.transform(xml, result);
589            } catch (TransformerException ex) {
590                String msg = "Error while transforming XML using XSL stylesheet: " + ex.getMessageAndLocation();
591                throw new MCRException(msg, ex);
592            } finally {
593                out.close();
594            }
595    
596            OutputStream sos = response.getOutputStream();
597            sos.write(out.toByteArray());
598            sos.close();
599        }
600    
601        /**
602         * Traces the execution of xsl stylesheet elements in debug mode. The trace
603         * is written to the log, and in parallel as comment elements to the output
604         * html.
605         */
606        public void trace(TracerEvent ev) {
607            ElemTemplateElement ete = ev.m_styleNode; // Current position in
608            // stylesheet
609    
610            StringBuffer log = new StringBuffer();
611    
612            // Find the name of the stylesheet file currently processed
613            try {
614                StringTokenizer st = new StringTokenizer(ete.getBaseIdentifier(), "/\\");
615                String stylesheet = null;
616                while (st.hasMoreTokens())
617                    stylesheet = st.nextToken();
618                if (stylesheet != null)
619                    log.append(" ").append(stylesheet);
620            } catch (Exception ignored) {
621            }
622    
623            // Output current line number and column number
624            log.append(" line " + ete.getLineNumber() + " col " + ete.getColumnNumber());
625    
626            // Find the name of the xsl:template currently processed
627            try {
628                ElemTemplate et = ev.m_processor.getCurrentTemplate();
629                log.append(" in <xsl:template");
630                if (et.getMatch() != null)
631                    log.append(" match=\"" + et.getMatch().getPatternString() + "\"");
632                if (et.getName() != null)
633                    log.append(" name=\"" + et.getName().getLocalName() + "\"");
634                if (et.getMode() != null)
635                    log.append(" mode=\"" + et.getMode().getLocalName() + "\"");
636                log.append(">");
637            } catch (Exception ignored) {
638            }
639    
640            // Output name of the xsl or html element currently processed
641            log.append(" " + ete.getTagName());
642            LOGGER.debug("Trace" + log.toString());
643    
644            // Output xpath of current xml source node in context
645            StringBuffer path = new StringBuffer();
646            Node node = ev.m_sourceNode;
647            if (node != null) {
648                path.append(node.getLocalName());
649                while ((node = node.getParentNode()) != null) {
650                    path.insert(0, node.getLocalName() + "/");
651                }
652            }
653            if (path.length() > 0) {
654                LOGGER.debug("Source " + path.toString());
655            }
656            try {
657                if ("true".equals(ev.m_processor.getParameter("DEBUG"))) {
658                    ev.m_processor.getResultTreeHandler().comment(log.toString() + " ");
659                    if (path.length() > 0) {
660                        ev.m_processor.getResultTreeHandler().comment(" source " + path.toString() + " ");
661                    }
662                }
663            } catch (Exception ignored) {
664            }
665        }
666    
667        /**
668         * When a stylesheet generates characters, they will be logged in debug
669         * mode.
670         */
671        public void generated(GenerateEvent ev) {
672            if (ev.m_eventtype == 12)
673                LOGGER.debug("Output " + new String(ev.m_characters, ev.m_start, ev.m_length).trim());
674        }
675    
676        /**
677         * When a stylesheet does a selection, like in &lt;xsl:value-of /&gt; or
678         * similar elements, the selection element and xpath is logged in debug
679         * mode.
680         */
681        public void selected(SelectionEvent ev) {
682            String log = "Selection <xsl:" + ev.m_styleNode.getTagName() + " " + ev.m_attributeName + "=\"" + ev.m_xpath.getPatternString()
683                    + "\">";
684            LOGGER.debug(log);
685            try {
686                if ("true".equals(ev.m_processor.getParameter("DEBUG")))
687                    ev.m_processor.getResultTreeHandler().comment(" " + log + " ");
688            } catch (SAXException ignored) {
689            }
690        }
691    }