001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest.server.view.mustache;
018
019import java.io.*;
020
021import org.apache.juneau.http.*;
022import org.apache.juneau.http.response.*;
023import org.apache.juneau.rest.server.*;
024import org.apache.juneau.rest.server.view.*;
025
026import com.github.mustachejava.*;
027
028/**
029 * Mixin that wires Mustache view-rendering onto any Juneau REST resource.
030 *
031 * <p>
032 * Compose into a host resource via
033 * {@link Rest#mixins() @Rest(mixins=MustacheMixin.class)}; the host then:
034 *
035 * <ol class='spaced-list'>
036 *    <li>Gains a default mount at {@code /mustache/*} that serves raw Mustache templates from the
037 *       importer's classpath by asking the configured {@link MustacheFactory} to compile and
038 *       render them with no scope (raw render path; callers who want attributes use
039 *       {@link MustacheView} from a typed handler instead).
040 *    <li>Routes the host's own {@code @RestOp}-method return values of type {@link MustacheView} through the
041 *       Mustache engine automatically. {@code MustacheMixin} declares
042 *       {@link Rest#mergeResponseProcessorsIntoHost() @Rest(mergeResponseProcessorsIntoHost=true)}, so a plain
043 *       {@code @Rest(mixins=MustacheMixin.class)} folds its {@link MustacheViewRenderer} into the host's own
044 *       response-processor chain &mdash; no {@code mergeIntoHost} or explicit {@code responseProcessors=} on the
045 *       host needed.
046 * </ol>
047 *
048 * <h5 class='figure'>Composition example (microservice):</h5>
049 *
050 * <p class='bjava'>
051 *    <ja>@Rest</ja>(path=<js>"/app"</js>, mixins=MustacheMixin.<jk>class</jk>)
052 *    <jk>public class</jk> AppResource <jk>extends</jk> RestServlet {
053 *
054 *       <ja>@Bean</ja> MustacheMixin mustache() {
055 *          <jk>return</jk> MustacheMixin.<jsm>create</jsm>()
056 *             .basePath(<js>"/templates/"</js>)
057 *             .templateSuffix(<js>".mustache"</js>)
058 *             .build();
059 *       }
060 *
061 *       <ja>@RestGet</ja>(<js>"/hello/{name}"</js>)
062 *       <jk>public</jk> View hello(<ja>@Path</ja> String <jv>name</jv>) {
063 *          <jk>return</jk> MustacheView.<jsm>of</jsm>(<js>"hello"</js>).attr(<js>"name"</js>, <jv>name</jv>);
064 *       }
065 *    }
066 * </p>
067 *
068 * <h5 class='section'>Configurable mount path:</h5>
069 *
070 * <p>
071 * The default mount {@code /mustache/*} can be overridden via the SVL variable
072 * {@code ${juneau.mustache.path:mustache}} &mdash; set via system property
073 * ({@code -Djuneau.mustache.path=views}), environment variable
074 * ({@code JUNEAU_MUSTACHE_PATH=views}), or {@code Config} key
075 * ({@code juneau.mustache.path = views}) to change the runtime mount without subclassing.
076 * Resolution happens once at {@link RestContext} construction time (SVL resolution in {@code @RestOp(path)}).
077 *
078 * <h5 class='section'>Mixin-only deployment:</h5>
079 *
080 * <p>
081 * This resource is designed for composition via {@code @Rest(mixins=...)}. The mount path is
082 * pinned at the op level by
083 * {@link RestGet @RestGet(path="/&#123;juneau.mustache.path:mustache&#125;/*")} on {@link #render};
084 * a class-level {@code @Rest(paths=...)} declaration would be silently ignored under the mixin
085 * pattern (see {@link Rest#paths() @Rest(paths)} Javadoc).
086 *
087 * <h5 class='section'>Choosing a MustacheFactory:</h5>
088 *
089 * <p>
090 * The {@code juneau-rest-server-view-mustache} module ships <b>only</b> the mustache.java
091 * compiler API in {@code provided} scope. <b>No factory bean is bundled.</b> The bridge resolves
092 * a {@link MustacheFactory} at first use from three sources, in order:
093 *
094 * <ul class='spaced-list'>
095 *    <li><b>User-supplied bean:</b> register a {@code @Bean MustacheFactory} (Spring) or
096 *       {@code BasicBeanStore.put(MustacheFactory.class, factory)} (microservice). Allows full
097 *       control of resolvers, object handlers, and encoders.
098 *    <li><b>Bridge default:</b> when nothing is registered, the bridge constructs a default
099 *       {@link DefaultMustacheFactory} anchored on a resource root derived from
100 *       {@link #getBasePath() basePath} (leading + trailing slashes trimmed; a {@code "/"} base
101 *       yields a no-prefix factory). With {@code basePath("/templates/")}, the factory resolves
102 *       template names like {@code "hello.mustache"} as classpath resource
103 *       {@code templates/hello.mustache}.
104 * </ul>
105 *
106 * <p>
107 * mustache.java's {@code DefaultMustacheFactory} caches compiled templates internally. The
108 * bridge-default factory honors {@link Builder#cacheTemplates(boolean) cacheTemplates(...)}
109 * (default {@code true} &mdash; production-safe): set {@code false} to have the bridge rebuild
110 * the factory from a fresh, empty compile-cache on every render (dev hot-reload). Consumers who
111 * supply their own {@code @Bean MustacheFactory} manage its caching themselves &mdash; the flag
112 * only affects the bridge-default factory.
113 *
114 * <p>
115 * When no Mustache engine is on the classpath, the renderer surfaces
116 * {@link MustacheViewRenderer#NO_ENGINE_DIAGNOSTIC} naming the missing dependency.
117 *
118 * <h5 class='section'>Template suffix:</h5>
119 *
120 * <p>
121 * Unlike the Thymeleaf bridge (which always appends {@code .html} via the engine resolver's
122 * suffix setting), mustache.java has no resolver-suffix concept &mdash; the literal template name
123 * is what {@link MustacheFactory#compile(String) factory.compile(...)} sees. The
124 * {@link Builder#templateSuffix(String) templateSuffix(...)} builder knob fills the gap: when
125 * non-blank, the bridge appends the configured suffix to template names that don't already end
126 * with it (idempotent), so callers can write {@code MustacheView.of("hello")} or request
127 * {@code /mustache/hello} and have it resolve to {@code hello.mustache} on the classpath.
128 *
129 * <h5 class='section'>Multiple base paths:</h5>
130 *
131 * <p>
132 * A host with {@code /templates/} and {@code /admin/templates/} template trees registers two
133 * {@code MustacheMixin} beans, each mounted via a subclass declaring its own op-level
134 * {@code @RestGet(path=...)} override and its own {@code basePath}. See the
135 * {@code MustacheMixin_MockRest_Test} test in {@code juneau-integration-tests} for the canonical
136 * pattern.
137 *
138 * <h5 class='section'>OpenAPI surface:</h5>
139 *
140 * <p>
141 * The greedy {@code /*} handler is not API-meaningful and is excluded from generated
142 * Swagger / OpenAPI specs via {@link OpSwagger#ignore() @OpSwagger(ignore=true)}.
143 *
144 * <h5 class='section'>See Also:</h5><ul>
145 *    <li class='jc'>{@link MustacheView}
146 *    <li class='jc'>{@link MustacheViewRenderer}
147 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/MustacheViewSupport">Mustache View Support</a>
148 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server — Mixins and Multi-Mount Paths</a>
149 * </ul>
150 *
151 * @since 10.0.0
152 */
153// @formatter:off
154@Rest(
155   responseProcessors={MustacheViewRenderer.class},
156   mergeResponseProcessorsIntoHost=true
157)
158public class MustacheMixin {
159
160   /** Default base path applied when no {@link Builder#basePath(String)} call has been made. */
161   public static final String DEFAULT_BASE_PATH = MustacheDispatcher.DEFAULT_BASE_PATH;
162
163   /** Default template suffix &mdash; empty (literal template names, no implicit suffix). */
164   public static final String DEFAULT_TEMPLATE_SUFFIX = MustacheDispatcher.DEFAULT_TEMPLATE_SUFFIX;
165
166   /** Default cache-templates setting &mdash; {@code true} (cache forever; production-safe). */
167   public static final boolean DEFAULT_CACHE_TEMPLATES = MustacheDispatcher.DEFAULT_CACHE_TEMPLATES;
168
169   private final MustacheDispatcher worker;
170
171   /**
172    * Creates a new builder.
173    *
174    * @return A new builder.
175    */
176   public static Builder create() {
177      return new Builder();
178   }
179
180   /**
181    * No-arg constructor &mdash; uses {@link #DEFAULT_BASE_PATH} as the base path and
182    * {@link #DEFAULT_TEMPLATE_SUFFIX} for the template suffix.
183    *
184    * <p>
185    * The mixin walk falls back to this constructor when the
186    * {@link org.apache.juneau.commons.inject.BeanStore BeanStore} does not have a registered
187    * {@code MustacheMixin} bean.
188    */
189   public MustacheMixin() {
190      this(create());
191   }
192
193   /**
194    * Builder constructor.
195    *
196    * @param builder The builder. Must not be {@code null}.
197    */
198   protected MustacheMixin(Builder builder) {
199      worker = builder.worker.build();
200   }
201
202   /**
203    * Returns the base path under which template resources are resolved.
204    *
205    * @return The base path. Never {@code null}.
206    */
207   public String getBasePath() {
208      return worker.getBasePath();
209   }
210
211   /**
212    * Returns the configured template-name suffix.
213    *
214    * @return The template suffix. Never {@code null}.
215    */
216   public String getTemplateSuffix() {
217      return worker.getTemplateSuffix();
218   }
219
220   /**
221    * Returns whether the bridge's default factory caches compiled templates.
222    *
223    * @return The cache flag. Defaults to {@link #DEFAULT_CACHE_TEMPLATES}.
224    */
225   public boolean isCacheTemplates() {
226      return worker.isCacheTemplates();
227   }
228
229   /**
230    * Appends {@link #getTemplateSuffix()} to {@code name} if not already present (idempotent).
231    *
232    * @param name The template name. Must not be {@code null}.
233    * @return The template name with the configured suffix appended (if applicable).
234    */
235   public String applyTemplateSuffix(String name) {
236      return worker.applyTemplateSuffix(name);
237   }
238
239   /**
240    * Resolves the active {@link MustacheFactory} via the shared {@link MustacheDispatcher} worker.
241    *
242    * @param req The current REST request.
243    * @return The active Mustache factory. Never {@code null}.
244    */
245   public MustacheFactory resolveMustacheFactory(RestRequest req) {
246      return worker.resolveMustacheFactory(req);
247   }
248
249   /**
250    * [GET /mustache/*] &mdash; render a raw template through the Mustache engine.
251    *
252    * <p>
253    * The {@code @Path("/*") String path} captures the multi-segment trailing remainder (e.g. a
254    * request for {@code /mustache/about.mustache} matches the mount with
255    * {@code path = "about.mustache"}). Delegates to the shared {@link MustacheDispatcher} worker
256    * (path-traversal hardening, suffix application, factory resolution, and render).
257    *
258    * <p>
259    * Missing template surfaces as the engine's own {@link MustacheNotFoundException}
260    * (HTTP 500 from the renderer's catch-all); missing engine surfaces as
261    * {@link MustacheViewRenderer#NO_ENGINE_DIAGNOSTIC}.
262    *
263    * @param path The trailing path segment after {@code /mustache/} (the template name relative
264    *    to the configured {@link #getBasePath() base path}; configured suffix appended if
265    *    missing).
266    * @param req The current REST request.
267    * @param res The current REST response.
268    * @throws IOException If the underlying response writer fails.
269    * @throws BasicHttpException On boundary violation (403), missing engine (500), or render
270    *    failure (500).
271    */
272   @RestGet(
273      path="/${juneau.mustache.path:mustache}/*",
274      summary="Mustache view",
275      description="Render a raw Mustache template under the configured base path.",
276      swagger=@OpSwagger(ignore=true)
277   )
278   public void render(@Path("/*") String path, RestRequest req, RestResponse res)
279         throws IOException, BasicHttpException {
280      worker.render(path, req, res);
281   }
282
283   /**
284    * Builder for {@link MustacheMixin}.
285    *
286    * <p>
287    * Mirrors {@link MustacheDispatcher.Builder}'s configuration methods on its own surface and
288    * forwards each call into a held {@link MustacheDispatcher.Builder} (worker-bean
289    * composition). Implements {@link ViewMixinBuilder} so {@link #basePath(String)} and
290    * {@link #cacheTemplates(boolean)} are guaranteed to transfer to the sibling JSP / Thymeleaf /
291    * FreeMarker bridge builders.
292    */
293   public static class Builder implements ViewMixinBuilder<Builder> {
294
295      private final MustacheDispatcher.Builder worker = MustacheDispatcher.create();
296
297      /** Constructor &mdash; package access for {@link MustacheMixin#create()}. */
298      protected Builder() {}
299
300      /**
301       * Sets the classpath base path under which template resources are resolved.
302       *
303       * <p>
304       * Defaults to {@link MustacheMixin#DEFAULT_BASE_PATH "/"}. A typical configured
305       * value is {@code "/templates/"} &mdash; the same Spring-Boot-compatible layout
306       * convention the sibling JSP / Thymeleaf bridges use.
307       *
308       * @param value The base path. {@code null} or blank values reset to the default
309       *    {@link MustacheMixin#DEFAULT_BASE_PATH}.
310       * @return This object.
311       */
312      @Override /* ViewMixinBuilder */
313      public Builder basePath(String value) {
314         worker.basePath(value);
315         return this;
316      }
317
318      /**
319       * Sets the suffix to append to template names that don't already end with it (idempotent).
320       *
321       * <p>
322       * Defaults to {@link MustacheMixin#DEFAULT_TEMPLATE_SUFFIX ""} (no implicit
323       * suffix &mdash; literal template names). Typical configured value is
324       * {@code ".mustache"}, the mustache.java convention.
325       *
326       * @param value The new suffix. {@code null} resets to
327       *    {@link MustacheMixin#DEFAULT_TEMPLATE_SUFFIX}.
328       * @return This object.
329       */
330      public Builder templateSuffix(String value) {
331         worker.templateSuffix(value);
332         return this;
333      }
334
335      /**
336       * Sets whether the bridge's default factory caches compiled templates.
337       *
338       * <p>
339       * Defaults to {@link MustacheMixin#DEFAULT_CACHE_TEMPLATES true} (cache forever
340       * &mdash; production-safe). Set {@code false} to enable dev hot-reload (the bridge rebuilds
341       * the factory from a fresh, empty compile-cache on every render). This setting only
342       * applies to the bridge's fallback factory; if a {@link MustacheFactory} bean is
343       * registered, its own caching behavior is honored.
344       *
345       * @param value The new value.
346       * @return This object.
347       */
348      @Override /* ViewMixinBuilder */
349      public Builder cacheTemplates(boolean value) {
350         worker.cacheTemplates(value);
351         return this;
352      }
353
354      /**
355       * Reads the current base path setting (test/inspection helper).
356       *
357       * @return The base path. Never {@code null}.
358       */
359      public String getBasePath() {
360         return worker.getBasePath();
361      }
362
363      /**
364       * Reads the current template-suffix setting (test/inspection helper).
365       *
366       * @return The template suffix. Never {@code null}.
367       */
368      public String getTemplateSuffix() {
369         return worker.getTemplateSuffix();
370      }
371
372      /**
373       * Reads the current cache-templates setting (test/inspection helper).
374       *
375       * @return The cache flag.
376       */
377      public boolean isCacheTemplates() {
378         return worker.isCacheTemplates();
379      }
380
381      /**
382       * Builds the {@link MustacheMixin}.
383       *
384       * @return A new {@link MustacheMixin} instance.
385       */
386      public MustacheMixin build() {
387         return new MustacheMixin(this);
388      }
389   }
390}