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}