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}