Author: mck
Date: Sat Oct 29 12:06:47 2011
New Revision: 1194877

URL: http://svn.apache.org/viewvc?rev=1194877&view=rev
Log:
TILES-539 - OptionsRenderer

Added:
    
tiles/framework/trunk/tiles-parent/tiles-extras/src/main/java/org/apache/tiles/extras/renderer/
    
tiles/framework/trunk/tiles-parent/tiles-extras/src/main/java/org/apache/tiles/extras/renderer/OptionsRenderer.java
   (with props)

Added: 
tiles/framework/trunk/tiles-parent/tiles-extras/src/main/java/org/apache/tiles/extras/renderer/OptionsRenderer.java
URL: 
http://svn.apache.org/viewvc/tiles/framework/trunk/tiles-parent/tiles-extras/src/main/java/org/apache/tiles/extras/renderer/OptionsRenderer.java?rev=1194877&view=auto
==============================================================================
--- 
tiles/framework/trunk/tiles-parent/tiles-extras/src/main/java/org/apache/tiles/extras/renderer/OptionsRenderer.java
 (added)
+++ 
tiles/framework/trunk/tiles-parent/tiles-extras/src/main/java/org/apache/tiles/extras/renderer/OptionsRenderer.java
 Sat Oct 29 12:06:47 2011
@@ -0,0 +1,141 @@
+package org.apache.tiles.extras.renderer;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.tiles.Attribute;
+import org.apache.tiles.ListAttribute;
+import org.apache.tiles.access.TilesAccess;
+import org.apache.tiles.request.ApplicationContext;
+import org.apache.tiles.request.Request;
+import org.apache.tiles.request.render.TypeDetectingRenderer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides a custom "options" syntax for attributes.
+ * The first option that can be rendered is.
+ * Comes from http://tech.finn.no/the-ultimate-view/<p/>
+ *
+ * Actual rendering is delegated to the TypeDetectingRenderer that's supplied 
in the constructor.
+ *
+ * For example:
+ * "/WEB-INF/tiles/fragments/${options[myoptions]}/content.jsp"
+ * given the myptions list-attribute is defined like:
+ * <pre>
+        <put-list-attribute name="myoptions">
+            <add-list-attribute>
+                <add-attribute value="car"/>
+                <add-attribute value="vechile"/>
+                <add-attribute value="advert"/>
+            </add-list-attribute>
+        </put-list-attribute>
+   </pre>
+ * will look for content.jsp
+ * first in "/WEB-INF/tiles/fragments/car/" then
+ * second in "/WEB-INF/tiles/fragments/vechile/" and
+ * last in "/WEB-INF/tiles/fragments/advert".
+ * <p/>
+ * <p/>
+ * Currently only supports one occurrance of such an "option" pattern in the 
attribute's value.
+ *
+ */
+public final class OptionsRenderer implements TypeDetectingRenderer {
+
+    private static final Pattern OPTIONS_PATTERN
+            = Pattern.compile(Pattern.quote("${options[") + ".+" + 
Pattern.quote("]}"));
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(OptionsRenderer.class);
+
+    private final ApplicationContext applicationContext;
+    private final TypeDetectingRenderer renderer;
+
+    public OptionsRenderer(final ApplicationContext applicationContext, final 
TypeDetectingRenderer renderer){
+        this.applicationContext = applicationContext;
+        this.renderer = renderer;
+    }
+
+    @Override
+    public boolean isRenderable(final String path, final Request request) {
+        return renderer.isRenderable(path, request);
+    }
+
+    @Override
+    public void render(final String path, final Request request) throws 
IOException {
+
+        final Matcher matcher =  OPTIONS_PATTERN.matcher((String) path);
+
+        if (null != matcher && matcher.find()) {
+            boolean done = false;
+            final String match = matcher.group();
+            ListAttribute fallbacks = (ListAttribute) TilesAccess
+                    .getCurrentContainer(request)
+                    .getAttributeContext(request)
+                    .getLocalAttribute(match);
+
+            for (Attribute option : (List<Attribute>) fallbacks.getValue()) {
+                final String template = 
path.replaceFirst(Pattern.quote(match), (String)option.getValue());
+                if(!Cache.isTemplateMissing(template)){
+                    try {
+                        if (null != applicationContext.getResource(template)) 
{ // can throw FileNotFoundException !
+                            renderer.render(template, request); // can throw 
FileNotFoundException !
+                            done = true;
+                            Cache.setIfAbsentTemplateFound(template, true);
+                        }
+                    } catch (FileNotFoundException ex) {
+                        if(ex.getMessage().contains(template)){
+                            // expected outcome. continue loop.
+                            LOG.trace(ex.getMessage());
+                        }else{
+                            // comes from an inner 
templateAttribute.render(..) so throw on
+                            throw ex;
+                        }
+                    } catch(IOException ex){ //xxx ???
+                        throw ex;
+                    }
+                    Cache.setIfAbsentTemplateFound(template, false);
+                }
+                if(done){ break; }
+            }
+            if (!done) {
+              throw new IOException("None of the fallback options existed for 
" + path);
+            }
+        } else {
+            renderer.render(path, request);
+        }
+    }
+
+    private static final class Cache{
+
+        /** It takes CACHE_LIFE milliseconds for any hot deployments to 
register.
+         */
+        private static final ConcurrentMap<String,Boolean> TEMPLATE_EXISTS
+                = new ConcurrentHashMap<String,Boolean>();
+
+        private volatile static long cacheLastCleaned = 
System.currentTimeMillis();
+        private static final long CACHE_LIFE = 1000 * 60 * 5;
+
+        static boolean isTemplateMissing(final String template){
+            if(System.currentTimeMillis() > cacheLastCleaned + CACHE_LIFE){
+                cacheLastCleaned = System.currentTimeMillis();
+                TEMPLATE_EXISTS.clear();
+                return false;
+            }else{
+                return TEMPLATE_EXISTS.containsKey(template) && 
!TEMPLATE_EXISTS.get(template);
+            }
+        }
+
+        static void setIfAbsentTemplateFound(final String template, final 
boolean found){
+            if(!TEMPLATE_EXISTS.containsKey(template)){
+                TEMPLATE_EXISTS.putIfAbsent(template, found);
+            }
+        }
+
+        private Cache(){}
+    }
+}

Propchange: 
tiles/framework/trunk/tiles-parent/tiles-extras/src/main/java/org/apache/tiles/extras/renderer/OptionsRenderer.java
------------------------------------------------------------------------------
    svn:keywords = Id


Reply via email to