[Yanel-dev] [yanel] Implement Templates for doing XSLT transformations (#67)
Michael Wechner
michael.wechner at wyona.com
Thu Apr 10 19:43:49 CEST 2014
Dear Balz
Great :-), thanks very much. Will try to integrate it shortly.
All the best
Michael
Am 10.04.14 15:34, schrieb baszero:
> This is a short update on how I introduced XSL Template objects (and caching) into my realm. Performance has been improved dramatically in our case, up to 10 times faster than the current Yanel implementation.
>
> **BasicXMLResource.getTransformedInputStream()**
>
> I replaced this line
> `xsltHandlers[i] = tf.newTransformerHandler(source);`
>
> by
> `xsltHandlers[i] = getTransformerHandler(source, tf);`
>
> In the same class I added this protected method:
> ```
> protected TransformerHandler getTransformerHandler(Source source, SAXTransformerFactory tf) throws TransformerConfigurationException {
> return tf.newTransformerHandler(source);
> }
> ```
>
> So the change above actually does not change anything at all, except that now it is possible to overwrite the `getTransformerHandler()` method.
>
> **A new class extending BasicXmlResource with caching capabilities**
> As a next step I created a new class that extends BasicXmlResource and which overrides the method above. The class as such does not do anything else than overriding the BasicXmlResource's method from above:
>
> ```
> @Override
> protected TransformerHandler getTransformerHandler(Source source, SAXTransformerFactory tf) {
> TransformerHandler th = null;
> String sourceId = null;
>
> // Caching ?
> boolean useCaching = Boolean.FALSE;
> try {
> useCaching = retrieveFlagFromWhereYouWant();
> } catch (Exception e) {
> log.error(e,e);
> }
>
> try {
> sourceId = source.getSystemId();
> if (!useCaching) {
> th = tf.newTransformerHandler(source); // the normal way
> } else {
> // We use the cached templates
> Templates template = XslTemplatesCache.get(source.getSystemId());
> if (template == null) {
> Templates newTemplate = tf.newTemplates(source);
> XslTemplatesCache.put(source.getSystemId(), newTemplate);
> template = newTemplate;
> }
> th = tf.newTransformerHandler(template);
> }
>
> } catch (Exception e) {
> log.error(e,e);
> }
> return th;
> }
> ```
>
> As you can see, if caching is enabled (you have to implement it at your own, on resource level or globally), it uses the `Templates`.
>
> **The Templates Cache Class**
> And here is the class implementing the cache. Please note that it uses the `java.util.concurrent.locks` package, in particular the **`ReentrantReadWriteLock`** . This lock guarantees the following:
> - Multiple readers can read from the cache simultaneously if there is no writer thread
> - A writer can only write to the cache if all reading threads have got their value from the cache.
>
> The usage of `ReentrantReadWriteLock` is crucial for a high-performing cache like this!!
>
> ```
> package com.zwischengas.jaxp;
>
> import java.util.HashMap;
> import java.util.Map;
> import java.util.Set;
> import java.util.concurrent.locks.Lock;
> import java.util.concurrent.locks.ReentrantReadWriteLock;
>
> import javax.xml.transform.Templates;
>
> import org.apache.log4j.Logger;
>
> /**
> * This cache uses the ReentrantReadWriteLock from the java.util.concurrent.locks package instead of using synchronized code sections.
> * Reason to use ReentrantReadWriteLock : we expect many concurrent Read Threads and only very few write threads.
> * So we want to allow multiple read threads in parallel in order to improve performance.
> *
> * @author baszero
> */
> public class XslTemplatesCache {
> protected static Logger log = Logger.getLogger(XslTemplatesCache.class);
>
> private static volatile Map<String, Templates> templatesCache = new HashMap<String, Templates>();
> private static final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(Boolean.TRUE); // true = fair policy, order of lock acquisition is preserved
> private static final Lock r = rwl.readLock();
> private static final Lock w = rwl.writeLock();
>
> public static Templates get(String key) {
> r.lock(); // here it only waits if another thread is writing to the cache
> try {
> return templatesCache.get(key);
>
> } finally {
> r.unlock();
> }
> }
>
> public static Templates put(String key, Templates value) {
> w.lock(); // this thread waits until all preceding read threads have finished
> try {
> return templatesCache.put(key, value);
>
> } finally {
> w.unlock();
> }
> }
>
> public static void clear() {
> w.lock();
> try {
> templatesCache.clear();
> } finally {
> w.unlock();
> }
> }
>
> /**
> * @return number of entries
> */
> public static int size() {
> int result = -1;
> r.lock();
> try {
> result = templatesCache.size();
> } finally {
> r.unlock();
> }
> return result;
> }
>
> public static Set<String> getKeys() {
> Set<String> result = null;
> r.lock();
> try {
> result = templatesCache.keySet();
> } finally {
> r.unlock();
> }
> return result;
> }
>
> }
> ```
>
> **Final remarks**
>
> If you use the cached templates as described above, you will get the following behaviour:
> - Today you can quickly modify an XSL and you instantly see the changes on the page
> - With caching enabled, this live-editing does not work anymore. But: I implemented an admin section in my application where I can clear all caches. This way I can still make hot-changes in an XSL and make the changes live immediately, but only upon explicit request.
> - Performance gets improved in any case. If you use MANY includes, it will help to improve a lot. In my case, the XSL rendering part got 10 times quicker.
>
>
> ---
> Reply to this email directly or view it on GitHub:
> https://github.com/wyona/yanel/issues/67#issuecomment-40081762
More information about the Yanel-development
mailing list