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.util.resource;
018
019import java.io.ByteArrayInputStream;
020import java.io.ByteArrayOutputStream;
021import java.io.IOException;
022import java.io.InputStream;
023import java.time.Instant;
024import java.util.Map;
025import java.util.Map.Entry;
026import org.apache.wicket.util.io.IOUtils;
027import org.apache.wicket.util.lang.Bytes;
028
029import javax.xml.XMLConstants;
030import javax.xml.transform.Result;
031import javax.xml.transform.Source;
032import javax.xml.transform.Transformer;
033import javax.xml.transform.TransformerConfigurationException;
034import javax.xml.transform.TransformerFactory;
035import javax.xml.transform.stream.StreamResult;
036import javax.xml.transform.stream.StreamSource;
037
038/**
039 * {@link IResourceStream} that applies XSLT on an input {@link IResourceStream}. The XSL stylesheet
040 * itself is also an {@link IResourceStream}. Override {@link #getParameters()} to pass parameters
041 * to the XSL stylesheet.
042 * 
043 * <p>
044 * NOTE: this is an experimental feature which does not implement any kind of caching, use with
045 * care, running an XSL transformation for every request is very expensive! Please have a look at
046 * {@link ZipResourceStream} for an in-depth explanation of what needs to be done with respect to
047 * caching.
048 * </p>
049 * 
050 * @author <a href="mailto:jbq@apache.org">Jean-Baptiste Quenot</a>
051 */
052public class XSLTResourceStream extends AbstractResourceStream
053{
054        private static final long serialVersionUID = 1L;
055        private final transient ByteArrayOutputStream out;
056
057        /**
058         * @return a {@link Map} of XSLT parameters, appropriate for passing information to the XSL
059         *         stylesheet
060         */
061        protected Map<Object, Object> getParameters()
062        {
063                return null;
064        }
065
066        /**
067         * Construct.
068         *
069         * @param xsltResource
070         *            the XSL stylesheet as an {@link IResourceStream}
071         * @param xmlResource
072         *            the input XML document as an {@link IResourceStream}
073         */
074        public XSLTResourceStream(final IResourceStream xsltResource, final IResourceStream xmlResource)
075        {
076                this(xsltResource, xmlResource, defaultTransformerFactory());
077        }
078
079        /**
080         * Creates a default transformer factory with XMLConstants.FEATURE_SECURE_PROCESSING set to true
081         *
082         * @return a default transformer factory
083         */
084        private static TransformerFactory defaultTransformerFactory()
085        {
086                TransformerFactory factory = TransformerFactory.newInstance();
087                try
088                {
089                        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
090                } catch (TransformerConfigurationException e) {
091                        throw new RuntimeException(e);
092                }
093                return factory;
094        }
095
096        /**
097         * Construct.
098         * 
099         * @param xsltResource
100         *            the XSL stylesheet as an {@link IResourceStream}
101         * @param xmlResource
102         *            the input XML document as an {@link IResourceStream}
103         * @param transformerFactory
104         *                        the transformer factory used to transform the xmlResource
105         */
106        public XSLTResourceStream(final IResourceStream xsltResource, final IResourceStream xmlResource, TransformerFactory transformerFactory)
107        {
108                try
109                {
110                        Source xmlSource = new StreamSource(xmlResource.getInputStream());
111                        Source xsltSource = new StreamSource(xsltResource.getInputStream());
112                        out = new ByteArrayOutputStream();
113                        Result result = new StreamResult(out);
114
115                        Transformer trans = transformerFactory.newTransformer(xsltSource);
116
117                        Map<Object, Object> parameters = getParameters();
118                        if (parameters != null)
119                        {
120                                for (Entry<Object, Object> e : parameters.entrySet())
121                                {
122                                        trans.setParameter(e.getKey().toString(), e.getValue().toString());
123                                }
124                        }
125
126                        trans.transform(xmlSource, result);
127                }
128                catch (Exception e)
129                {
130                        throw new RuntimeException(e);
131                }
132                finally
133                {
134                        IOUtils.closeQuietly(xmlResource);
135                        IOUtils.closeQuietly(xsltResource);
136                }
137        }
138
139        /**
140         * @see org.apache.wicket.util.resource.IResourceStream#close()
141         */
142        @Override
143        public void close() throws IOException
144        {
145        }
146
147        /**
148         * Returns always null
149         * 
150         * @see org.apache.wicket.util.resource.IResourceStream#getContentType()
151         */
152        @Override
153        public String getContentType()
154        {
155                return null;
156        }
157
158        /**
159         * @see org.apache.wicket.util.resource.IResourceStream#getInputStream()
160         */
161        @Override
162        public InputStream getInputStream() throws ResourceStreamNotFoundException
163        {
164                return new ByteArrayInputStream(out.toByteArray());
165        }
166
167        /**
168         * @see org.apache.wicket.util.resource.IResourceStream#length()
169         */
170        @Override
171        public Bytes length()
172        {
173                return Bytes.bytes(out.size());
174        }
175
176        /**
177         * Returns always null
178         * 
179         * @see org.apache.wicket.util.watch.IModifiable#lastModifiedTime()
180         */
181        @Override
182        public Instant lastModifiedTime()
183        {
184                return null;
185        }
186
187}