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}