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.marshall.html;
018
019import java.io.*;
020import java.util.function.*;
021
022import org.apache.juneau.commons.collections.*;
023import org.apache.juneau.commons.svl.*;
024import org.apache.juneau.marshall.serializer.*;
025
026/**
027 * Context object that lives for the duration of a single serialization of {@link HtmlSerializer} and its subclasses.
028 *
029 * <p>
030 * See {@link Serializer} for details.
031 *
032 * <h5 class='section'>Notes:</h5><ul>
033 *    <li class='warn'>This class is not thread safe and is typically discarded after one use.
034 * </ul>
035 *
036 * <h5 class='section'>See Also:</h5><ul>
037 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/HtmlSupport">HTML</a>
038
039 * </ul>
040 */
041@SuppressWarnings({
042   "java:S110", // Inheritance depth acceptable for this class hierarchy
043   "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants
044})
045public class HtmlDocSerializerSession extends HtmlStrippedDocSerializerSession {
046
047   /**
048    * Builder class.
049    */
050   @SuppressWarnings({
051      "java:S119" // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name.
052   })
053   public abstract static class Builder<SELF extends Builder<SELF>> extends HtmlStrippedDocSerializerSession.Builder<SELF> {
054
055      private final HtmlDocSerializer ctx;
056      private String nonce;
057
058      /**
059       * Constructor
060       *
061       * @param ctx The context creating this session.
062       */
063      protected Builder(HtmlDocSerializer ctx) {
064         super(ctx);
065         this.ctx = ctx;
066      }
067
068      /**
069       * Sets the per-response Content-Security-Policy nonce stamped onto inline {@code <script>}/{@code <style>}
070       * tags emitted during this render.
071       *
072       * <p>
073       * This is a per-render (session-scoped) value — it intentionally does NOT participate in the serializer
074       * cache key, since a per-request nonce would otherwise force a cache miss on every request.
075       *
076       * @param value The nonce token. May be <jk>null</jk> to disable nonce stamping.
077       * @return This object.
078       */
079      public SELF nonce(String value) {
080         nonce = value;
081         return self();
082      }
083
084      @Override
085      public HtmlDocSerializerSession build() {
086         return new HtmlDocSerializerSession(this);
087      }
088
089   }
090
091   /**
092    * Concrete default builder leaf for the non-subclassed {@code create()} path (CRTP terminal).
093    */
094   public static final class DefaultBuilder extends Builder<DefaultBuilder> {
095
096      DefaultBuilder(HtmlDocSerializer ctx) {
097         super(ctx);
098      }
099   }
100
101   private static final VarResolver DEFAULT_VR = VarResolver.create().defaultVars().vars(HtmlWidgetVar.class).build();
102
103   /**
104    * Creates a new builder for this object.
105    *
106    * @param ctx The context creating this session.
107    * @return A new builder.
108    */
109   @SuppressWarnings({
110      "java:S1452" // Builder<?> wildcard return intentional; callers use it to construct session instances polymorphically
111   })
112   public static Builder<?> create(HtmlDocSerializer ctx) {
113      return new DefaultBuilder(ctx);
114   }
115
116   private final HtmlDocSerializer ctx;
117   private final String nonce;
118
119   /**
120    * Constructor.
121    *
122    * @param builder The builder for this object.
123    */
124   protected HtmlDocSerializerSession(Builder<?> builder) {
125      super(builder);
126      ctx = builder.ctx;
127      nonce = builder.nonce;
128      addVarBean(HtmlWidgetMap.class, ctx.getWidgets());
129   }
130
131   /**
132    * The per-response Content-Security-Policy nonce for this render.
133    *
134    * <p>
135    * When non-<jk>null</jk>, inline {@code <script>}/{@code <style>} tags emitted by the HtmlDoc template are
136    * stamped with a matching {@code nonce="..."} attribute so they satisfy a nonce-based CSP.
137    *
138    * @see HtmlDocSerializerSession.Builder#nonce(String)
139    * @return The nonce token, or <jk>null</jk> if nonce stamping is disabled.
140    */
141   public final String getNonce() { return nonce; }
142
143   /**
144    * Returns the {@link HtmlDocSerializer.Builder#navlinks(String...)} setting value in this context.
145    *
146    * @return
147    *    The {@link HtmlDocSerializer.Builder#navlinks(String...)} setting value in this context.
148    *    <jk>null</jk> if not specified.
149    *    Never an empty map.
150    */
151   public final String[] getNavLinks() { return ctx.navlinks; }
152
153   /**
154    * Calls the parent {@link #doWrite(SerializerPipe, Object)} method which invokes just the HTML serializer.
155    *
156    * @param out
157    *    Where to send the output from the serializer.
158    * @param o The object being serialized.
159    * @throws Exception Error occurred during serialization.
160    */
161   @SuppressWarnings({
162      "java:S112" // throws Exception intentional - callback/lifecycle method for parent serializer
163   })
164   public void parentSerialize(Object out, Object o) throws Exception {
165      try (var pipe = createPipe(out)) {
166         super.doWrite(pipe, o);
167      }
168   }
169
170   @Override /* Overridden from SerializerSession */
171   protected VarResolverSession createDefaultVarResolverSession() {
172      return DEFAULT_VR.createSession();
173   }
174
175   @Override /* Overridden from Serializer */
176   protected void doWrite(SerializerPipe out, Object o) throws IOException, SerializeException {
177
178      try (var w = getHtmlWriter(out)) {
179         try {
180            getTemplate().writeTo(this, w, o);
181         } catch (Exception e) {
182            throw new SerializeException(e);
183         }
184      }
185   }
186
187   /**
188    * Performs an action on all widgets defined in his session.
189    *
190    * @param action The action to perform.
191    * @see HtmlDocSerializer.Builder#widgets(Class...)
192    * @return This object.
193    */
194   protected final HtmlDocSerializerSession forEachWidget(Consumer<HtmlWidget> action) {
195      ctx.forEachWidget(action);
196      return this;
197   }
198
199   /**
200    * Aside section contents.
201    *
202    * @see HtmlDocSerializer.Builder#aside(String...)
203    * @return
204    *    The overridden contents of the aside section on the HTML page.
205    */
206   protected final String[] getAside() { return ctx.aside; }
207
208   /**
209    * Aside section contents float.
210    *
211    * @see HtmlDocSerializer.Builder#asideFloat(AsideFloat)
212    * @return
213    *    The location of where to place the aside section.
214    */
215   protected final AsideFloat getAsideFloat() { return ctx.asideFloat; }
216
217   /**
218    * Footer section contents.
219    *
220    * @see HtmlDocSerializer.Builder#footer(String...)
221    * @return
222    *    The overridden contents of the footer section on the HTML page.
223    */
224   protected final String[] getFooter() { return ctx.footer; }
225
226   /**
227    * Additional head section content.
228    *
229    * @see HtmlDocSerializer.Builder#head(String...)
230    * @return
231    *    HTML content to add to the head section of the HTML page.
232    */
233   protected final String[] getHead() { return ctx.head; }
234
235   /**
236    * Header section contents.
237    *
238    * @see HtmlDocSerializer.Builder#header(String...)
239    * @return
240    *    The overridden contents of the header section on the HTML page.
241    */
242   protected final String[] getHeader() { return ctx.header; }
243
244   /**
245    * Nav section contents.
246    *
247    * @see HtmlDocSerializer.Builder#nav(String...)
248    * @return
249    *    The overridden contents of the nav section on the HTML page.
250    */
251   protected final String[] getNav() { return ctx.nav; }
252
253   /**
254    * Page navigation links.
255    *
256    * @see HtmlDocSerializer.Builder#navlinks(String...)
257    * @return
258    *    Navigation links to add to the HTML page.
259    */
260   @SuppressWarnings({
261      "java:S1845" // Method name intentionally differs only by case from getNavLinks for backward compatibility
262   })
263   protected final String[] getNavlinks() { return ctx.navlinks; }
264
265   /**
266    * No-results message.
267    *
268    * @see HtmlDocSerializer.Builder#noResultsMessage(String)
269    * @return
270    *    The message used when serializing an empty array or empty list.
271    */
272   protected final String getNoResultsMessage() { return ctx.getNoResultsMessage(); }
273
274   /**
275    * Javascript code.
276    *
277    * @see HtmlDocSerializer.Builder#script(String...)
278    * @return
279    *    Arbitrary Javascript to add to the HTML page.
280    */
281   protected final String[] getScript() { return ctx.script; }
282
283   /**
284    * CSS style code.
285    *
286    * @see HtmlDocSerializer.Builder#style(String...)
287    * @return
288    *    The CSS instructions to add to the HTML page.
289    */
290   protected final String[] getStyle() { return ctx.style; }
291
292   /**
293    * Stylesheet import URLs.
294    *
295    * @see HtmlDocSerializer.Builder#stylesheet(String...)
296    * @return
297    *    The link to the stylesheet of the HTML page.
298    */
299   protected final String[] getStylesheet() { return ctx.stylesheet; }
300
301   /**
302    * HTML document template.
303    *
304    * @see HtmlDocSerializer.Builder#template(Class)
305    * @return
306    *    The template to use for serializing the page.
307    */
308   protected final HtmlDocTemplate getTemplate() { return ctx.getTemplate(); }
309
310   /**
311    * Prevent word wrap on page.
312    *
313    * @see HtmlDocSerializer.Builder#nowrap()
314    * @return
315    *    <jk>true</jk> if <js>"* {white-space:nowrap}"</js> should be added to the CSS instructions on the page to prevent word wrapping.
316    */
317   protected final boolean isNowrap() { return ctx.nowrap; }
318
319   /**
320    * Resolve $ variables in serialized POJO.
321    *
322    * @see HtmlDocSerializer.Builder#resolveBodyVars()
323    * @return
324    *    <jk>true</jk> if $ variables in serialized POJO should be resolved.
325    */
326   protected final boolean isResolveBodyVars() { return ctx.resolveBodyVars; }
327
328   /**
329    * Resolves SVL variables for HTML-doc chrome fields that write raw HTML.
330    *
331    * <p>
332    * Literal characters from the template (including tags) pass through unchanged. Each SVL
333    * substitution is XML-escaped before insert, except the HTML-passthrough vars {@code $W},
334    * {@code $C}, and {@code $RS}.
335    *
336    * <p>
337    * Plain {@link #resolve(String)} is unchanged. Navlink {@code href} values should keep using
338    * {@link #resolve(String)} plus attribute encoding.
339    *
340    * @param string The chrome template. Can be <jk>null</jk>.
341    * @return The resolved chrome string, or <jk>null</jk> if the input was <jk>null</jk>.
342    */
343   public String resolveHtmlDoc(String string) {
344      return getVarResolver().resolveHtmlDoc(string);
345   }
346
347   /**
348    * Resolves SVL variables for {@code <script>} and {@code <style>} chrome.
349    *
350    * <p>
351    * Request-derived vars ({@code $RQ}, {@code $RH}, {@code $RF}, {@code $RP}, {@code $RA},
352    * {@code $SA}, {@code $R}) are left unresolved (same as an unknown var). Other vars including
353    * {@code $W}, {@code $C}, and {@code $RS} still resolve.
354    *
355    * @param string The script or style template. Can be <jk>null</jk>.
356    * @return The resolved string, or <jk>null</jk> if the input was <jk>null</jk>.
357    */
358   public String resolveScriptStyle(String string) {
359      return getVarResolver().resolveScriptStyle(string);
360   }
361
362   @Override /* Overridden from HtmlStrippedDocSerializerSession */
363   protected FluentMap<String,Object> properties() {
364      return super.properties()
365         .a("ctx", ctx)
366         .a("varResolver", getVarResolver());
367   }
368}