001package io.jstach.opt.jmustache; 002 003import java.io.IOException; 004import java.io.Reader; 005import java.lang.System.Logger; 006import java.lang.System.Logger.Level; 007import java.util.concurrent.atomic.AtomicBoolean; 008 009import org.eclipse.jdt.annotation.Nullable; 010 011import com.samskivert.mustache.Template; 012 013import io.jstach.jstache.JStacheLambda; 014import io.jstach.jstachio.JStachio; 015import io.jstach.jstachio.TemplateInfo; 016import io.jstach.jstachio.spi.JStachioConfig; 017import io.jstach.jstachio.spi.JStachioExtension; 018import io.jstach.jstachio.spi.Templates; 019import io.jstach.svc.ServiceProvider; 020 021/** 022 * Use JMustache instead of JStachio for rendering. The idea of this extension is to allow 023 * you to edit Mustache templates in real time without waiting for the compile reload 024 * cycle. 025 * <p> 026 * You are probably asking yourself <em>why do I need JMustache if I have JStachio</em>? 027 * Unfortunately JStachio needs the annotation processor to run <em>every time a template 028 * is changed!</em>. While there are incremental compilers like Eclipse that do support 029 * incrementally compiling annotations they are often not triggered via editing resources. 030 * Furthermore incremental compilation often just doesn't work. 031 * <p> 032 * Enter JMustache. Through reflection you can edit your templates while an application is 033 * running. Luckily <em>JMustache and JStachio are almost entirely compatible especially 034 * through this extension</em> which configures JMustache to act like JStachio. Even 035 * {@link JStacheLambda} will work. 036 * <p> 037 * <strong>The only major compatibility issue is that JMustache currently does not support 038 * mustache inheritance (parents and blocks)!</strong> 039 * <p> 040 * If this extension is enabled which it is by default if the ServiceLoader finds it 041 * JMustache will be used when a runtime filtered rendering call is made (see 042 * {@link io.jstach.jstachio.JStachio}). 043 * <p> 044 * How this works is this extension is a filter that checks to see if the statically 045 * generated renderer (template) can render and that its template is up-to-date. If it is 046 * not then JMustache will use the template meta data to construct its own template and 047 * then execute it. In some cases the annotation processor does not even have to run for 048 * this to work (see {@link Templates#getInfoByReflection(Class)}. 049 * <p> 050 * <strong>Strongly recommended you disable this in production via 051 * {@link #JSTACHIO_JMUSTACHE_DISABLE} or {@link #use}</strong> 052 * 053 * @author agentgt 054 * @see JStachio 055 * @deprecated This extension does not reliably mimic JStachio's mustache support 056 * unfortunately based on feedback we have decided to deprecate this and recommend using 057 * other mechanisms for hot reload. 058 */ 059@Deprecated 060@ServiceProvider(JStachioExtension.class) 061public class JMustacheRenderer extends AbstractJStacheEngine { 062 063 /** 064 * Property key of where jmustache will try to load template files. Default is 065 * <code>src/main/resources</code>. 066 */ 067 public static final String JSTACHIO_JMUSTACHE_SOURCE_PATH = "jstachio.jmustache.source"; 068 069 /** 070 * Property key to disable jmustache. Default is <code>false</code>. 071 */ 072 public static final String JSTACHIO_JMUSTACHE_DISABLE = "jstachio.jmustache.disable"; 073 074 private final AtomicBoolean use; 075 076 private volatile @Nullable String prefix = null; 077 078 private volatile @Nullable String suffix = null; 079 080 private String sourcePath = "src/main/resources"; 081 082 private Logger logger = JStachioConfig.noopLogger(); 083 084 private long initTime = System.currentTimeMillis(); 085 086 /** 087 * Enables JMustache 088 * @param flag true enables 089 * @return return this for builder like config 090 */ 091 public JMustacheRenderer use(boolean flag) { 092 use.set(flag); 093 log(flag); 094 return this; 095 } 096 097 /** 098 * A prefix to add to the output to know that JMustache is being used. 099 * @param prefix string to prefix output 100 * @return return this for builder like config 101 */ 102 public JMustacheRenderer prefix(@Nullable String prefix) { 103 this.prefix = prefix; 104 return this; 105 } 106 107 /** 108 * A suffix to append to the output to know that JMustache is being used. 109 * @param suffix string to suffix output 110 * @return return this for builder like config 111 */ 112 public JMustacheRenderer suffix(@Nullable String suffix) { 113 this.suffix = suffix; 114 return this; 115 } 116 117 /** 118 * Sets the relative to the project sourcePath for runtime lookup of templates. By 119 * default is <code>src/main/resources</code>. 120 * @param sourcePath by default is <code>src/main/resources</code> 121 * @return sourcePath should not be null 122 */ 123 public JMustacheRenderer sourcePath(String sourcePath) { 124 this.sourcePath = sourcePath; 125 return this; 126 } 127 128 /** 129 * Log plugin on reload. 130 * @param flag true is if extension is enabled. 131 */ 132 protected void log(boolean flag) { 133 logger.log(Level.INFO, 134 "JMustache is now: " + (flag ? "enabled" : "disabled") + " using sourcePath: " + sourcePath); 135 } 136 137 /** 138 * Log template execution through jmustache 139 * @param template template to execute. 140 */ 141 protected void log(TemplateInfo template) { 142 if (logger.isLoggable(Level.DEBUG)) { 143 logger.log(Level.DEBUG, "Using JMustache. template: " + template.description()); 144 } 145 } 146 147 /** 148 * No-arg constructor for ServiceLoader 149 */ 150 public JMustacheRenderer() { 151 use = new AtomicBoolean(); 152 } 153 154 @Override 155 public void init(JStachioConfig config) { 156 logger = config.getLogger(getClass().getCanonicalName()); 157 sourcePath(config.requireProperty(JSTACHIO_JMUSTACHE_SOURCE_PATH, sourcePath)); 158 use(!config.getBoolean(JSTACHIO_JMUSTACHE_DISABLE)); 159 160 } 161 162 private CompilerAdapter createCompiler(TemplateInfo template, Class<?> modelClass) { 163 Loader loader = new Loader(logger, sourcePath, initTime); 164 return new CompilerAdapter(template, modelClass, loader); 165 } 166 167 @Override 168 protected boolean execute(Object context, Appendable a, TemplateInfo template, boolean broken) throws IOException { 169 if (!use.get()) { 170 return false; 171 } 172 173 Loader loader = new Loader(logger, sourcePath, initTime); 174 175 Reader reader = loader.open(template, broken); 176 177 if (reader != null) { 178 Template t = createCompiler(template, context.getClass()).compile(reader); 179 String result = t.execute(context); 180 if (prefix != null) { 181 a.append(prefix); 182 } 183 a.append(result); 184 if (suffix != null) { 185 a.append(suffix); 186 } 187 return true; 188 } 189 return false; 190 } 191 192}