001package org.andromda.maven.plugin.site; 002 003import java.io.ByteArrayInputStream; 004import java.io.ByteArrayOutputStream; 005import java.io.File; 006import java.io.FileWriter; 007import java.io.IOException; 008import java.io.InputStream; 009import java.net.URL; 010import java.util.List; 011import javax.xml.parsers.DocumentBuilder; 012import javax.xml.parsers.DocumentBuilderFactory; 013import javax.xml.parsers.ParserConfigurationException; 014import javax.xml.transform.Result; 015import javax.xml.transform.Source; 016import javax.xml.transform.TransformerFactory; 017import javax.xml.transform.dom.DOMSource; 018import javax.xml.transform.stream.StreamResult; 019import javax.xml.transform.stream.StreamSource; 020import org.apache.commons.lang.StringUtils; 021import org.dom4j.Element; 022import org.dom4j.io.SAXReader; 023import org.dom4j.io.XMLWriter; 024import org.w3c.dom.Document; 025import org.xml.sax.EntityResolver; 026import org.xml.sax.InputSource; 027import org.xml.sax.SAXException; 028 029/** 030 * Used to perform the transformation of XSL documents 031 * within the site plugin. 032 * 033 * @author Chad Brandon 034 * @author Vance Karimi 035 */ 036public class XslTransformer 037{ 038 private String projectName; 039 040 /** 041 * Default constructor 042 * 043 */ 044 public XslTransformer() 045 { 046 // Default constructor 047 } 048 049 /** 050 * Constructor that sets the project name used to replace variable inside generated 051 * xdoc xml. 052 * 053 * @param projectNameIn 054 */ 055 public XslTransformer(String projectNameIn) 056 { 057 this.projectName = projectNameIn; 058 } 059 060 /** 061 * Applies the given XSLT files to the model in the order in which they are found. 062 * 063 * @param xmlDocument The full path of the original XML 064 * @param transformation The full path of the XSLT 065 * @param outputLocation The full path of the output xdoc XML 066 */ 067 public void transform( 068 final String xmlDocument, 069 final String transformation, 070 final String outputLocation) 071 { 072 try 073 { 074 this.transform(xmlDocument, new File(transformation).toURI().toURL(), outputLocation); 075 } 076 catch (final Exception exception) 077 { 078 throw new RuntimeException(exception); 079 } 080 } 081 082 /** 083 * Applies the given XSLT files to the model in the order in which they are found. 084 * 085 * @param xmlDocument The full path of the original XML 086 * @param xslt The URL of the XSLT 087 * @param outputLocation The full path of the output xdoc XML 088 */ 089 public void transform( 090 final String xmlDocument, 091 final URL xslt, 092 final String outputLocation) 093 { 094 try 095 { 096 if (StringUtils.isNotBlank(xmlDocument)) 097 { 098 final Source xmlSource = new DOMSource(this.urlToDocument(xmlDocument)); 099 final TransformerFactory factory = TransformerFactory.newInstance(); 100 if (xslt != null) 101 { 102 final Source xsltSource = new StreamSource(xslt.openStream()); 103 final javax.xml.transform.Transformer transformer = factory.newTransformer(xsltSource); 104 final ByteArrayOutputStream output = new ByteArrayOutputStream(); 105 final Result result = new StreamResult(output); 106 transformer.transform( 107 xmlSource, 108 result); 109 110 final byte[] outputResult = output.toByteArray(); 111 final org.dom4j.Document document = replaceVariableProperties(outputResult); 112 113 if (StringUtils.isNotBlank(outputLocation)) 114 { 115 final File fileOutput = new File(outputLocation); 116 final File parent = fileOutput.getParentFile(); 117 if (parent != null) 118 { 119 parent.mkdirs(); 120 } 121 122 FileWriter fwriter = new FileWriter(fileOutput); 123 XMLWriter writer = new XMLWriter(fwriter); 124 writer.write(document); 125 writer.flush(); 126 writer.close(); 127 fwriter.close(); 128 } 129 } 130 } 131 } 132 catch (final Exception exception) 133 { 134 throw new RuntimeException(exception); 135 } 136 } 137 138 /** 139 * Parses the XML retrieved from the String URL and returns a Document object. 140 * @param url the url of the XML to parse. 141 * @return Document newly created Document object. 142 * @throws ParserConfigurationException 143 * @throws IOException 144 * @throws SAXException 145 */ 146 @SuppressWarnings("static-method") 147 private Document urlToDocument(String url) 148 throws Exception 149 { 150 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 151 DocumentBuilder builder = factory.newDocumentBuilder(); 152 builder.setEntityResolver(new XslTransformerEntityResolver(url)); 153 return builder.parse(new InputSource(url)); 154 } 155 156 /** 157 * The prefix that the systemId should start with when attempting 158 * to resolve it within a jar. 159 */ 160 private static final String SYSTEM_ID_FILE = "file:"; 161 162 /** 163 * Provides the resolution of external entities. 164 */ 165 private static final class XslTransformerEntityResolver 166 implements EntityResolver 167 { 168 private String xmlDocument; 169 170 XslTransformerEntityResolver(final String xmlDocument) 171 { 172 this.xmlDocument = xmlDocument; 173 } 174 175 /** 176 * @see org.xml.sax.EntityResolver#resolveEntity(String, String) 177 */ 178 @SuppressWarnings("resource") 179 public InputSource resolveEntity( 180 final String publicId, 181 final String systemId) 182 throws SAXException, IOException 183 { 184 InputSource source = null; 185 String path = systemId; 186 if (path != null && path.startsWith(SYSTEM_ID_FILE)) 187 { 188 final String xmlResource = this.xmlDocument; 189 path = path.replaceFirst( 190 SYSTEM_ID_FILE, 191 ""); 192 193 // - remove any extra starting slashes 194 path = path.replaceAll( 195 "\\+", 196 "/").replaceAll( 197 "/+", 198 "/"); 199 200 // - if we still have one starting slash, remove it 201 if (path.startsWith("/")) 202 { 203 path = path.substring( 204 1, 205 path.length()); 206 } 207 final String xmlResourceName = xmlResource.replaceAll( 208 ".*(\\+|/)", 209 ""); 210 211 InputStream inputStream = null; 212 URL uri = new File(StringUtils.replace( 213 xmlResource, 214 xmlResourceName, 215 path)).toURI().toURL(); 216 if (uri != null) 217 { 218 inputStream = uri.openStream(); 219 } 220 if (inputStream != null) 221 { 222 source = new InputSource(inputStream); 223 source.setPublicId(publicId); 224 if (uri != null) 225 { 226 source.setSystemId(uri.toString()); 227 } 228 } 229 } 230 return source; 231 } 232 } 233 234 /** 235 * Replace the variable property defined by %module% in the output generated 236 * xdoc file. Uses dom4j XPath to locate the variable. 237 * 238 * @param documentBuffer The byte array representing the xdoc XML 239 * @return the org.dom4j.Document object of the xdoc XML 240 * @throws Exception 241 */ 242 private org.dom4j.Document replaceVariableProperties(byte[] documentBuffer) 243 throws Exception 244 { 245 SAXReader reader = new SAXReader(); 246 org.dom4j.Document document = reader.read(new ByteArrayInputStream(documentBuffer)); 247 248 // List elements = document.selectNodes("//*[contains(text(),'%module%')]"); 249 List<Element> elements = document.selectNodes("//*"); 250 for (final Element element : elements) 251 { 252 if (StringUtils.contains(element.getText(), "%module%")) 253 { 254 element.setText( 255 StringUtils.replace( 256 element.getText(), 257 "%module%", 258 this.getProjectName())); 259 } 260 } 261 elements.clear(); 262 263 elements = document.selectNodes("//*[contains(@*,'%module%')]"); 264 for (final Element element : elements) 265 { 266 element.addAttribute( 267 "name", 268 StringUtils.replace( 269 element.attributeValue("name"), 270 "%module%", 271 this.getProjectName())); 272 } 273 return document; 274 } 275 276 /** 277 * @return Returns the projectName. 278 */ 279 public String getProjectName() 280 { 281 return projectName; 282 } 283 284 /** 285 * @param projectName The projectName to set. 286 */ 287 public void setProjectName(String projectName) 288 { 289 this.projectName = projectName; 290 } 291}