001: package org.enhydra.util.chiba;
002:
003: import java.io.File;
004: import java.net.URI;
005: import java.net.URISyntaxException;
006: import java.net.URLEncoder;
007: import java.util.ArrayList;
008: import java.util.HashMap;
009: import java.util.Properties;
010:
011: import javax.xml.parsers.DocumentBuilderFactory;
012: import javax.xml.transform.ErrorListener;
013: import javax.xml.transform.Source;
014: import javax.xml.transform.Templates;
015: import javax.xml.transform.Transformer;
016: import javax.xml.transform.TransformerConfigurationException;
017: import javax.xml.transform.TransformerException;
018: import javax.xml.transform.TransformerFactory;
019: import javax.xml.transform.TransformerFactoryConfigurationError;
020: import javax.xml.transform.dom.DOMSource;
021: import javax.xml.transform.sax.SAXTransformerFactory;
022: import javax.xml.transform.sax.TransformerHandler;
023:
024: import org.chiba.xml.xforms.NamespaceCtx;
025: import org.chiba.xml.xforms.config.Config;
026: import org.chiba.xml.xforms.config.XFormsConfigException;
027: import org.w3c.dom.Document;
028: import org.w3c.dom.Element;
029: import org.w3c.dom.Node;
030:
031: public class StylesheetLoader extends
032: org.chiba.tools.xslt.StylesheetLoader {
033:
034: private String stylesheetPath = null;
035: private boolean useXsltc = false;
036:
037: // The TransformerFactory used to compile the XSLT
038: private TransformerFactory transformerFactory = null;
039:
040: /**
041: *
042: * @param stylesheetPath
043: * a string representing an absolute file expression to the
044: * stylesheet directory
045: */
046: public StylesheetLoader(String stylesheetPath) {
047: super (stylesheetPath);
048: this .stylesheetPath = stylesheetPath;
049: initTransformerFactory();
050: }
051:
052: /**
053: *
054: * @param stylesheetPath
055: * a string representing an absolute file expression to the
056: * stylesheet directory
057: */
058: public StylesheetLoader(String stylesheetPath, boolean useXsltc) {
059: super (stylesheetPath);
060: this .stylesheetPath = stylesheetPath;
061: initTransformerFactory();
062:
063: if (useXsltc) {
064: String key = "javax.xml.transform.TransformerFactory";
065: String value = "org.apache.xalan.xsltc.trax.TransformerFactoryImpl";
066: Properties props = System.getProperties();
067: props.put(key, value);
068: System.setProperties(props);
069: }
070: }
071:
072: /**
073: * Prepares system props required by XSLTC Xalan option!
074: */
075: private void initTransformerFactory() {
076: String keyValue = null;
077:
078: String key = "javax.xml.transform.TransformerFactory";
079: String value = "org.apache.xalan.xsltc.trax.TransformerFactoryImpl";
080:
081: if (useXsltc) {
082: keyValue = System.getProperty(key);
083: Properties props = System.getProperties();
084: props.put(key, value);
085: System.setProperties(props);
086:
087: }
088:
089: try {
090: transformerFactory = TransformerFactory.newInstance();
091: } catch (TransformerFactoryConfigurationError tfce) {
092: tfce.printStackTrace();
093: }
094:
095: if (useXsltc) {
096: // We are setting System properties back!
097: // Not forcing XSLTC option on other (JVM) Xalan transformations.
098: Properties props = System.getProperties();
099: if (keyValue != null) {
100: props.put(key, keyValue);
101: } else {
102: props.remove(key);
103: }
104: System.setProperties(props);
105:
106: transformerFactory.setAttribute("auto-translet", Boolean
107: .valueOf(true));
108: }
109:
110: transformerFactory.setErrorListener(new ErrorListener() {
111: public void error(TransformerException exception) {
112: System.out.println("Chiba StyleshhetLoader - "
113: + exception.getMessageAndLocation());
114:
115: }
116:
117: public void fatalError(TransformerException exception) {
118: System.out.println("Chiba StyleshhetLoader - "
119: + exception.getMessageAndLocation());
120:
121: }
122:
123: public void warning(TransformerException exception) {
124: System.out.println("Chiba StyleshhetLoader - "
125: + exception.getMessageAndLocation());
126:
127: }
128: });
129: }
130:
131: /**
132: * implements javax.xml.transform.URIResolver. This method is called by the
133: * Transformer when it hits e.g. a 'document()' function or an 'import'
134: * statement to resolve the location of files.
135: *
136: * @param href
137: * the local href used
138: * @param base
139: * the base to resolve against
140: * @return a Source object that can be used to load a resource
141: * @throws TransformerException
142: * if transformation errors occur
143: */
144: public Source resolve(String href, String base)
145: throws TransformerException {
146: try {
147: return resolveExt(href, URLEncoder.encode(stylesheetPath,
148: "UTF-8"));
149: } catch (Exception ex) {
150: return null;
151: }
152: }
153:
154: // --------------------------------------------------------
155: // org.chiba.tools.xslt.StylesheetLoader
156: // --------------------------------------------------------
157:
158: private String stylesheetFile = null;
159:
160: public Transformer createTransformer(String styleId, Node input)
161: throws TransformerException,
162: TransformerConfigurationException {
163:
164: // use styleheet from chiba:stylesheet Attribute on root-element if
165: // present
166: String fileName = getStyleheetName(input, styleId);
167: if (stylesheetPath != null)
168: fileName = stylesheetPath + '/' + fileName;
169:
170: return getTransformer(fileName);
171: }
172:
173: /**
174: * allows to set the stylesheet file to be used by the Transformer
175: *
176: * @param stylesheetFile -
177: * the filename of the stylesheet file (stylesheet must be
178: * present in the stylesheetPath
179: */
180: public void setStylesheetFile(String stylesheetFile) {
181: this .stylesheetFile = stylesheetFile;
182: }
183:
184: /**
185: * this returns the name of the stylesheet file (e.g. 'mystyles.xsl'). Uses
186: * the following order of precedence:<br>
187: * [1] if stylesheetFile is not null, this value is used<br>
188: * [2] if the input XML has a chiba:stylesheet Attribute on the root Element
189: * this is used<br>
190: * [3] finally, the stylesheet filename is grabbed from the config-file.
191: * Here the symbolic name (the value of the 'name' attribute) must be used
192: * to map to a configured stylesheet.<br>
193: * <br>
194: * If all fails a TransformerException is thrown. <p/> If the input Document
195: * container has a chiba:stylesheet Attribute this is used. Otherwise the
196: * stylesheet-name is read from the config-file.
197: *
198: * @return - the filename of the stylesheet to use for transformation
199: */
200: private String getStyleheetName(Node input, String styleId)
201: throws TransformerException {
202:
203: if (this .stylesheetFile != null) {
204: return this .stylesheetFile;
205: }
206:
207: Element root;
208:
209: if (input.getNodeType() == Node.DOCUMENT_NODE) {
210: root = ((Document) input).getDocumentElement();
211: } else {
212: root = (Element) input;
213: }
214:
215: if (root.hasAttributeNS(NamespaceCtx.CHIBA_NS, "stylesheet")) {
216: return root.getAttributeNS(NamespaceCtx.CHIBA_NS,
217: "stylesheet");
218: }
219:
220: try {
221: return Config.getInstance().getStylesheet(styleId);
222: } catch (XFormsConfigException e) {
223: throw new TransformerException(e);
224: }
225: }
226:
227: // --------------------------------------------------------
228: // org.chiba.tools.xslt.StylesheetCache
229: // --------------------------------------------------------
230:
231: /**
232: * Stylesheet holds information about a compiled stylesheet (note that this is
233: * top-level only - included stylesheets should NOT be cached here).
234: *
235: */
236: private static class Stylesheet {
237:
238: /**
239: * Records a file that is used to compile the XSLT file.
240: *
241: */
242: private static class SheetFile {
243: private File file;
244: private long lastModified;
245:
246: public SheetFile(File file) {
247: this .file = file;
248: this .lastModified = file.lastModified();
249: }
250:
251: public boolean hasChanged() {
252: long fileModified = file.lastModified();
253: return lastModified != fileModified;
254: }
255: }
256:
257: // List of SheetFile's that comprise the XSLT
258: private ArrayList files = new ArrayList();
259:
260: // Compiles XSLT
261: private Templates templates;
262:
263: /**
264: * Constructor
265: * @param file The file that is the top-level XSLT file (ie NOT an included file)
266: */
267: public Stylesheet(File file) {
268: addDependsOnFile(file);
269: }
270:
271: /**
272: * Called to record a file that the XSLT file is dependent on (ie an included file)
273: * @param file
274: */
275: public void addDependsOnFile(File file) {
276: if (!file.exists())
277: throw new RuntimeException("dependant file '"
278: + file.getAbsolutePath() + "' does not exist!");
279: files.add(new SheetFile(file));
280: }
281:
282: /**
283: * Called to set the compiled XSLT
284: * @param templates
285: */
286: public void setTemplates(Templates templates) {
287: this .templates = templates;
288: }
289:
290: /**
291: * Tests to see whether the XSLT file or any included files have changed
292: * @return
293: */
294: public boolean hasChanged() {
295: for (int i = 0; i < files.size(); i++) {
296: SheetFile file = (SheetFile) files.get(i);
297: if (file.hasChanged())
298: return true;
299: }
300: return false;
301: }
302: }
303:
304: // List of cached Stylesheet objects
305: private static HashMap stylesheets = new HashMap();
306:
307: // The Stylesheet currently being compiled by the current thread (used by URIResolver.resolve())
308: private ThreadLocal currentSheet = new ThreadLocal();
309:
310: /**
311: * Loads, parses, and caches the specified stylesheet, returning a TransformerHandler
312: * for it.
313: *
314: * @param filename The absolute path to the XSLT file to load
315: * @return A TransformerHandler for the cached XSLT file
316: * @throws GrasshopperException
317: */
318: public TransformerHandler getStylesheet(String filename)
319: throws TransformerException {
320: Stylesheet sheet = loadStylesheet(filename);
321:
322: if (!(transformerFactory
323: .getFeature(javax.xml.transform.sax.SAXSource.FEATURE) && transformerFactory
324: .getFeature(javax.xml.transform.sax.SAXResult.FEATURE)))
325: throw new TransformerException(
326: "TransformerFactory does not support SAX!!");
327:
328: SAXTransformerFactory saxTFactory = (SAXTransformerFactory) transformerFactory;
329: try {
330: return saxTFactory.newTransformerHandler(sheet.templates);
331: } catch (TransformerConfigurationException e) {
332: // log.fatal(e.getMessage(), e);
333: throw new TransformerException(e.getMessage(), e);
334: }
335: }
336:
337: /**
338: * Loads, parses, and caches the specified stylesheet, returning a Transformer
339: * for it.
340: *
341: * @param filename The absolute path to the XSLT file to load
342: * @return A Transformer for the cached XSLT file
343: * @throws GrasshopperException
344: */
345: public Transformer getTransformer(String filename)
346: throws TransformerException {
347: Stylesheet sheet = loadStylesheet(filename);
348:
349: try {
350: return sheet.templates.newTransformer();
351: } catch (TransformerConfigurationException e) {
352: e.printStackTrace();
353: // log.fatal(e.getMessage(), e);
354: throw new TransformerException(e.getMessage(), e);
355: }
356: }
357:
358: /**
359: * Loads, parses, and caches the specified stylesheet, returning the cached
360: * Stylesheet control object that holds the XSLT file's compiled form
361: * @param filename
362: * @return
363: * @throws GrasshopperException
364: */
365: private Stylesheet loadStylesheet(String filename)
366: throws TransformerException {
367: //filename = filename.toLowerCase();
368: Stylesheet sheet;
369:
370: // Check to see if there's one cached
371: synchronized (stylesheets) {
372: sheet = (Stylesheet) stylesheets.get(filename);
373: }
374:
375: // If not cached or the file on disk has changed, (re-)load it
376: if (sheet == null || sheet.hasChanged()) {
377: File file = new File(filename);
378:
379: try {
380: // Create a new Stylesheet and record it as the current
381: sheet = new Stylesheet(file);
382:
383: currentSheet.set(sheet);
384:
385: SAXTransformerFactory saxFactory = (SAXTransformerFactory) transformerFactory;
386:
387: // Get the XSLT file as a DOM tree, and set it's system Id so that we can find
388: // includes later in resolve().
389: DOMSource domSource = new DOMSource(parseXml(file));
390: domSource.setSystemId(file.getAbsolutePath());
391:
392: // Create the new templates
393: Templates templates = saxFactory
394: .newTemplates(domSource);
395:
396: // Add the new sheet to the cache
397: sheet.setTemplates(templates);
398: currentSheet.set(null);
399: synchronized (stylesheets) {
400: stylesheets.put(filename, sheet);
401: }
402:
403: } catch (Exception e) {
404: // log.fatal(e.getMessage(), e);
405: e.printStackTrace();
406: throw new TransformerException(e.getMessage(), e);
407: }
408: }
409:
410: // Return the Stylesheet
411: return sheet;
412: }
413:
414: /**
415: * implements javax.xml.transform.URIResolver. This method is called by the Transformer
416: * when it hits e.g. a 'document()' function or an 'import' statement to resolve the location
417: * of files.
418: *
419: * @param href the local href used
420: * @param base the base to resolve against
421: * @return a Source object that can be used to load a resource
422: * @throws TransformerException if transformation errors occur
423: */
424: public Source resolveExt(String href, String base)
425: throws TransformerException {
426:
427: // if (log.isDebugEnabled()) {
428: // log.debug("URIRESOLVER CALLED");
429: // log.debug("URIRESOLVER href: " + href);
430: // log.debug("URIRESOLVER base: " + base);
431: // }
432:
433: // If it's a relative path, make it relative to base
434: File file = null;
435: try {
436: URI uri = new URI(href);
437: if (base != null && !uri.isAbsolute()) {
438: int pos = base.lastIndexOf('/');
439: if (pos > -1)
440: uri = new URI(base.substring(0, pos + 1) + href);
441: }
442: file = new File(uri.getPath());
443: } catch (URISyntaxException e) {
444: // log.fatal(e.getMessage(), e);
445: throw new TransformerException(e.getMessage(), e);
446: }
447:
448: // Find the sheet we're working on and tell it about the dependancy
449: Stylesheet sheet = (Stylesheet) currentSheet.get();
450: sheet.addDependsOnFile(file);
451:
452: // Load the stylesheet; do not cache it, because it's part of an include and
453: // will be integrated into the stylesheet's Templates object anyway.
454: try {
455: return new DOMSource(parseXml(file));
456: } catch (Exception e) {
457: // log.error(e.getMessage(), e);
458: throw new TransformerException(e.getMessage(), e);
459: }
460: }
461:
462: /**
463: * Parses and loads the XML file
464: */
465: private Document parseXml(File file) throws TransformerException {
466: DocumentBuilderFactory factory = DocumentBuilderFactory
467: .newInstance();
468: factory.setNamespaceAware(true);
469: factory.setValidating(false);
470:
471: try {
472: return factory.newDocumentBuilder().parse(file);
473: } catch (Exception e) {
474: throw new TransformerException(e.getMessage(), e);
475: }
476: }
477:
478: /**
479: * Returns the TransformerFactory used by the cache
480: * @return
481: */
482: public TransformerFactory getTransformerFactory() {
483: return transformerFactory;
484: }
485:
486: /**
487: * Sets the TransformerFactory used by the cache
488: * @return
489: */
490: public void setTransformerFactory(
491: TransformerFactory transformerFactory) {
492: this.transformerFactory = transformerFactory;
493: }
494:
495: }
|