001    package org.apache.fulcrum.xslt;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import java.io.Reader;
023    import java.io.StringWriter;
024    import java.io.Writer;
025    import java.net.MalformedURLException;
026    import java.net.URL;
027    import java.util.Hashtable;
028    import java.util.Iterator;
029    import java.util.Map;
030    
031    import javax.xml.transform.Result;
032    import javax.xml.transform.Source;
033    import javax.xml.transform.Templates;
034    import javax.xml.transform.Transformer;
035    import javax.xml.transform.TransformerFactory;
036    import javax.xml.transform.dom.DOMSource;
037    import javax.xml.transform.stream.StreamResult;
038    import javax.xml.transform.stream.StreamSource;
039    import javax.xml.parsers.DocumentBuilder;
040    import javax.xml.parsers.DocumentBuilderFactory;
041    
042    import org.apache.avalon.framework.activity.Initializable;
043    import org.apache.avalon.framework.configuration.Configurable;
044    import org.apache.avalon.framework.configuration.Configuration;
045    import org.apache.avalon.framework.configuration.ConfigurationException;
046    import org.apache.avalon.framework.context.Context;
047    import org.apache.avalon.framework.context.ContextException;
048    import org.apache.avalon.framework.context.Contextualizable;
049    import org.apache.avalon.framework.logger.AbstractLogEnabled;
050    import org.apache.avalon.framework.service.ServiceManager;
051    import org.apache.avalon.framework.service.Serviceable;
052    import org.w3c.dom.Node;
053    import org.w3c.dom.Document;
054    
055    /**
056     * Implementation of the Turbine XSLT Service. It transforms xml with a given
057     * xsl file. XSL stylesheets are compiled and cached (if the service property is
058     * set) to improve speeds.
059     *
060     * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
061     * @author <a href="mailto:rubys@us.ibm.com">Sam Ruby</a>
062     * @author <a href="mailto:epugh@opensourceconnections.com">Eric Pugh</a>
063     * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
064     */
065    public class DefaultXSLTService extends AbstractLogEnabled implements
066            XSLTService, Initializable, Configurable, Contextualizable, Serviceable
067    {
068        /**
069         * The application root
070         */
071        private String applicationRoot;
072    
073        /**
074         * Property to control the caching of Templates.
075         */
076        protected boolean caching = false;
077    
078        /**
079         * Path to style sheets used for tranforming well-formed XML documents. The
080         * path is relative to the webapp context.
081         */
082        protected String path;
083    
084        /**
085         * Cache of compiled Templates.
086         */
087        protected Hashtable cache = new Hashtable();
088    
089        protected final static String STYLESHEET_PATH = "path";
090    
091        protected final static String STYLESHEET_CACHING = "cache";
092    
093        /**
094         * Factory for producing templates and null transformers
095         */
096        private static TransformerFactory tfactory;
097    
098        /**
099         * Try to create a valid url object from the style parameter.
100         *
101         * @param style
102         *            the xsl-Style
103         * @return a <code>URL</code> object or null if the style sheet could not
104         *         be found
105         */
106        private URL getStyleURL(String style)
107        {
108            StringBuffer sb = new StringBuffer();
109    
110            sb.append(path);
111    
112            // we chop off the existing extension
113            int colon = style.lastIndexOf(".");
114    
115            if (colon > 0)
116            {
117                sb.append(style.substring(0, colon));
118            }
119            else
120            {
121                sb.append(style);
122            }
123    
124            sb.append(".xslt");
125    
126            URL url = null;
127    
128            try
129            {
130                url = new URL(sb.toString());
131            }
132            catch (MalformedURLException e)
133            {
134                getLogger().error("Malformed URL: " + sb, e);
135            }
136    
137            return url;
138        }
139    
140        /**
141         * Compile Templates from an input file.
142         *
143         * @param source the source URL
144         * @return the compiled template
145         * @throws Exception the compilation failed
146         */
147        protected Templates compileTemplates(URL source) throws Exception
148        {
149            StreamSource xslin = new StreamSource(source.openStream());
150            return tfactory.newTemplates(xslin);
151        }
152    
153        /**
154         * Retrieves Templates. If caching is switched on the first attempt is to
155         * load Templates from the cache. If caching is switched of or if the
156         * Stylesheet is not found in the cache new Templates are compiled from an
157         * input file.
158         * <p>
159         * This method is synchronized on the xsl cache so that a thread does not
160         * attempt to load Templates from the cache while it is still being
161         * compiled.
162         *
163         * @param xslName the name of the XSL file
164         * @return the correspondint template or null if the XSL was not found
165         * @throws Exception getting the template failed
166         */
167        protected Templates getTemplates(String xslName) throws Exception
168        {
169            synchronized (cache)
170            {
171                URL fn = getStyleURL(xslName);
172                if (fn == null)
173                    return null;
174    
175                if (caching && cache.containsKey(fn))
176                {
177                    return (Templates) cache.get(fn);
178                }
179    
180                Templates sr = compileTemplates(fn);
181    
182                if (caching)
183                {
184                    cache.put(fn, sr);
185                }
186    
187                return sr;
188            }
189        }
190    
191        protected void transform(String xslName, Source xmlin, Result xmlout, Map params)
192                throws Exception
193        {
194            try
195            {
196                long startTime = System.currentTimeMillis();
197                Transformer transformer = getTransformer(xslName);
198    
199                if (params != null)
200                {
201                    for (Iterator it = params.entrySet().iterator(); it.hasNext(); )
202                    {
203                        Map.Entry entry = (Map.Entry) it.next();
204                        transformer.setParameter(String.valueOf(entry.getKey()), entry.getValue());
205                    }
206                }
207    
208                transformer.transform(xmlin, xmlout);
209    
210                if(getLogger().isDebugEnabled())
211                {
212                    long duration = System.currentTimeMillis() - startTime;
213                    getLogger().debug("The transforamtion '" + xslName + "' took " + duration + " ms");
214                }
215            }
216            catch(Exception e)
217            {
218                getLogger().debug("The transformation '" + xslName + "' failed due to : " + e.getMessage());
219                throw e;
220            }
221        }
222    
223        /**
224         * Uses an xsl file to transform xml input from a reader and writes the
225         * output to a writer.
226         *
227         * @param xslName
228         *            The name of the file that contains the xsl stylesheet.
229         * @param in
230         *            The reader that passes the xml to be transformed
231         * @param out
232         *            The writer for the transformed output
233         */
234        public void transform(String xslName, Reader in, Writer out)
235                throws Exception
236        {
237            Source xmlin = new StreamSource(in);
238            Result xmlout = new StreamResult(out);
239            transform(xslName, xmlin, xmlout, null);
240        }
241    
242        /**
243         * Uses an xsl file to transform xml input from a reader and returns a
244         * string containing the transformed output.
245         *
246         * @param xslName
247         *            The name of the file that contains the xsl stylesheet.
248         * @param in
249         *            The reader that passes the xml to be transformed
250         */
251        public String transform(String xslName, Reader in) throws Exception
252        {
253            StringWriter sw = new StringWriter();
254            transform(xslName, in, sw, null);
255            return sw.toString();
256        }
257    
258        /**
259         * Uses an xsl file to transform xml input from a DOM note and writes the
260         * output to a writer.
261         *
262         * @param xslName
263         *            The name of the file that contains the xsl stylesheet.
264         * @param in
265         *            The DOM Node to be transformed
266         * @param out
267         *            The writer for the transformed output
268         */
269        public void transform(String xslName, org.w3c.dom.Node in, Writer out)
270                throws Exception
271        {
272            Source xmlin = new DOMSource(in);
273            Result xmlout = new StreamResult(out);
274            transform(xslName, xmlin, xmlout, null);
275        }
276    
277        /**
278         * Uses an xsl file to transform xml input from a DOM note and returns a
279         * string containing the transformed output.
280         *
281         * @param xslName
282         *            The name of the file that contains the xsl stylesheet.
283         * @param in
284         *            The DOM Node to be transformed
285         */
286        public String transform(String xslName, org.w3c.dom.Node in)
287                throws Exception
288        {
289            StringWriter sw = new StringWriter();
290            transform(xslName, in, sw);
291            return sw.toString();
292        }
293    
294        /**
295         * Uses an xsl file to transform xml input from a reader and writes the
296         * output to a writer.
297         *
298         * @param xslName
299         *            The name of the file that contains the xsl stylesheet.
300         * @param in
301         *            The reader that passes the xml to be transformed
302         * @param out
303         *            The writer for the transformed output
304         * @param params
305         *            A set of parameters that will be forwarded to the XSLT
306         */
307        public void transform(String xslName, Reader in, Writer out, Map params)
308                throws Exception
309        {
310            Source xmlin = new StreamSource(in);
311            Result xmlout = new StreamResult(out);
312            transform(xslName, xmlin, xmlout, params);
313        }
314    
315        /**
316         * Uses an xsl file to transform xml input from a reader and returns a
317         * string containing the transformed output.
318         *
319         * @param xslName
320         *            The name of the file that contains the xsl stylesheet.
321         * @param in
322         *            The reader that passes the xml to be transformed
323         * @param params
324         *            A set of parameters that will be forwarded to the XSLT
325         */
326        public String transform(String xslName, Reader in, Map params)
327                throws Exception
328        {
329            StringWriter sw = new StringWriter();
330            transform(xslName, in, sw, params);
331            return sw.toString();
332        }
333    
334        /**
335         * Uses an xsl file to transform xml input from a DOM note and writes the
336         * output to a writer.
337         *
338         * @param xslName
339         *            The name of the file that contains the xsl stylesheet.
340         * @param in
341         *            The DOM Node to be transformed
342         * @param out
343         *            The writer for the transformed output
344         * @param params
345         *            A set of parameters that will be forwarded to the XSLT
346         */
347        public void transform(String xslName, Node in, Writer out, Map params)
348                throws Exception
349        {
350            Source xmlin = new DOMSource(in);
351            Result xmlout = new StreamResult(out);
352            transform(xslName, xmlin, xmlout, params);
353        }
354    
355        /**
356         * Uses an xsl file to transform xml input from a DOM note and returns a
357         * string containing the transformed output.
358         *
359         * @param xslName
360         *            The name of the file that contains the xsl stylesheet.
361         * @param in
362         *            The DOM Node to be transformed
363         * @param params
364         *            A set of parameters that will be forwarded to the XSLT
365         */
366        public String transform(String xslName, Node in, Map params)
367                throws Exception
368        {
369            StringWriter sw = new StringWriter();
370            transform(xslName, in, sw, params);
371            return sw.toString();
372        }
373    
374        /**
375         * Uses an xsl file without any input.
376         *
377         * @param xslName The name of the file that contains the xsl stylesheet.
378         * @param params A set of parameters that will be forwarded to the XSLT
379         * @return the transformed output
380         * @throws Exception the transformation failed
381         */
382        public String transform(String xslName, Map params) throws Exception {
383    
384            StringWriter sw = new StringWriter();
385            transform(xslName, sw, params);
386            return sw.toString();
387        }
388    
389        /**
390         * Uses an xsl file without any xml input (simplified stylesheet)
391         *
392         * @param xslName The name of the file that contains the xsl stylesheet
393         * @param out The writer for the transformed output.
394         * @param params A set of parameters that will be forwarded to the XSLT
395         * @throws Exception the transformation failed
396         */
397        public void transform(String xslName, Writer out, Map params) throws Exception {
398    
399            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
400            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
401            Document document = documentBuilder.newDocument();
402    
403            transform(xslName, document.getDocumentElement(), out, params);
404        }
405    
406        /**
407         * Retrieve a transformer for the given stylesheet name. If no stylesheet is
408         * available for the provided name, an identity transformer will be
409         * returned. This allows clients of this service to perform more complex
410         * transformations (for example, where parameters must be set). When
411         * possible prefer using one of the forms of {@link #transform}.
412         *
413         * @param xslName
414         *            Identifies stylesheet to get transformer for
415         * @return A transformer for that stylesheet
416         * @throws Exception retrieving the transformer failed
417         */
418        public Transformer getTransformer(String xslName) throws Exception
419        {
420            Templates sr = getTemplates(xslName);
421    
422            if (sr == null)
423            {
424                return tfactory.newTransformer();
425            }
426            else
427            {
428                return sr.newTransformer();
429            }
430        }
431    
432        // ---------------- Avalon Lifecycle Methods ---------------------
433        /**
434         * Avalon component lifecycle method
435         *
436         * This method processes the repository path, to make it relative to the web
437         * application root, if neccessary. It supports URL-style repositories.
438         */
439        public void configure(Configuration conf) throws ConfigurationException
440        {
441            StringBuffer sb = new StringBuffer(conf.getAttribute(STYLESHEET_PATH,
442                    "/"));
443    
444            // is URL?
445            if (!sb.toString().matches("[a-zA-Z]{3,}://.*"))
446            {
447                // No
448                if (sb.charAt(0) != '/')
449                {
450                    sb.insert(0, '/');
451                }
452                sb.insert(0, applicationRoot);
453                sb.insert(0, "file:");
454            }
455    
456            if (sb.charAt(sb.length() - 1) != '/')
457            {
458                sb.append('/');
459            }
460    
461            path = sb.toString();
462            caching = conf.getAttributeAsBoolean(STYLESHEET_CACHING, false);
463        }
464    
465        /**
466         * Initializes the service.
467         */
468        public void initialize() throws Exception
469        {
470            tfactory = TransformerFactory.newInstance();
471        }
472    
473        public void contextualize(Context context) throws ContextException
474        {
475            this.applicationRoot = context.get("urn:avalon:home").toString();
476        }
477    
478        /**
479         * Avalon component lifecycle method
480         */
481        public void service(ServiceManager manager)
482        {
483            XSLTServiceFacade.setService(this);
484        }
485    }