001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.markup.transformer;
018
019import java.io.FileNotFoundException;
020import java.io.StringReader;
021import java.io.StringWriter;
022
023import javax.xml.transform.Transformer;
024import javax.xml.transform.TransformerFactory;
025import javax.xml.transform.stream.StreamResult;
026import javax.xml.transform.stream.StreamSource;
027
028import org.apache.wicket.Application;
029import org.apache.wicket.Component;
030import org.apache.wicket.util.resource.IResourceStream;
031import org.apache.wicket.core.util.resource.locator.IResourceStreamLocator;
032
033
034/**
035 * A processor to XSLT transform the output generated by a Component.
036 * 
037 * @see org.apache.wicket.markup.transformer.XsltOutputTransformerContainer
038 * @see org.apache.wicket.markup.transformer.XsltTransformerBehavior
039 * 
040 * @author Juergen Donnerstag
041 */
042public class XsltTransformer implements ITransformer
043{
044        private final static String extension = "xsl";
045
046        /** an optional XSL file */
047        private final String xslFile;
048
049        /**
050         * Construct.
051         */
052        public XsltTransformer()
053        {
054                xslFile = null;
055        }
056
057        /**
058         * Instead of using the default mechanism to determine the associated XSL file, it is given by
059         * the user.
060         * 
061         * @param xslFile
062         *            XSL input file path relative to the component's package. If the path does not end
063         *            with <tt>.xsl</tt>, then it is considered as a basename and will be passed as-is
064         *            to
065         *            {@link IResourceStreamLocator#locate(Class, String, String, String, java.util.Locale, String, boolean)}
066         *            . All stylesheets must have the <tt>.xsl</tt> extension.
067         */
068        public XsltTransformer(final String xslFile)
069        {
070                if ((xslFile != null) && xslFile.endsWith(extension))
071                {
072                        this.xslFile = xslFile.substring(0, xslFile.length() - extension.length() - 1);
073                }
074                else
075                {
076                        this.xslFile = xslFile;
077                }
078        }
079
080        /**
081         * Apply a XSL transformation to the markup generated by a component. The *.xsl resource must be
082         * located in the same path as the nearest parent with an associated markup and must have a
083         * filename equal to the component's id.
084         */
085        @Override
086        public CharSequence transform(final Component component, final CharSequence output)
087                throws Exception
088        {
089                IResourceStream resourceStream = getResourceStream(component);
090
091                if (resourceStream == null)
092                {
093                        throw new FileNotFoundException("Unable to find XSLT resource for " +
094                                component.toString());
095                }
096
097                try
098                {
099                        // 1. Instantiate a TransformerFactory.
100                        TransformerFactory tFactory = TransformerFactory.newInstance();
101
102                        // 2. Use the TransformerFactory to process the stylesheet Source
103                        // and
104                        // generate a Transformer.
105                        Transformer transformer = tFactory.newTransformer(new StreamSource(
106                                resourceStream.getInputStream()));
107
108                        // 3. Use the Transformer to transform an XML Source and send the
109                        // output to a Result object.
110                        StringWriter writer = new StringWriter();
111                        transformer.transform(new StreamSource(new StringReader(output.toString())),
112                                new StreamResult(writer));
113
114                        return writer.getBuffer();
115                }
116                finally
117                {
118                        resourceStream.close();
119                }
120        }
121
122        /**
123         * Get the XSL resource stream
124         * 
125         * @param component
126         * 
127         * @return The XSLT file resource stream
128         */
129        private IResourceStream getResourceStream(final Component component)
130        {
131                final IResourceStream resourceStream;
132
133                String filePath = xslFile;
134                if (filePath == null)
135                {
136                        filePath = component.findParentWithAssociatedMarkup()
137                                .getClass()
138                                .getPackage()
139                                .getName()
140                                .replace('.', '/') +
141                                "/" + component.getId();
142                }
143
144                resourceStream = Application.get()
145                        .getResourceSettings()
146                        .getResourceStreamLocator()
147                        .locate(getClass(), filePath, component.getStyle(), component.getVariation(),
148                                component.getLocale(), XsltTransformer.extension, false);
149
150                return resourceStream;
151        }
152}