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