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.freemarker; 018 019import java.io.*; 020import java.util.*; 021import java.util.concurrent.*; 022 023import org.apache.juneau.http.*; 024import org.apache.juneau.http.response.*; 025import org.apache.juneau.rest.server.*; 026import org.apache.juneau.rest.server.view.*; 027 028import freemarker.template.*; 029 030/** 031 * Mixin that wires Apache FreeMarker view-rendering onto any Juneau REST resource. 032 * 033 * <p> 034 * Compose into a host resource via 035 * {@link Rest#mixins() @Rest(mixins=FreemarkerMixin.class)}; the host then: 036 * 037 * <ol class='spaced-list'> 038 * <li>Gains a default mount at {@code /freemarker/*} that serves raw FreeMarker templates from 039 * the importer's classpath by asking the configured {@link Configuration} for the named 040 * template and rendering it with an empty data model (raw render path; callers who want 041 * attributes use {@link FreemarkerView} from a typed handler instead). 042 * <li>Registers {@link FreemarkerViewRenderer} for the mixin's <b>own</b> endpoints (e.g. the 043 * {@code /freemarker/*} route above) via the mixin's own 044 * {@link Rest#responseProcessors() @Rest(responseProcessors=...)} declaration. 045 * </ol> 046 * 047 * <h5 class='section'>Auto-wiring the renderer into the host's own endpoints:</h5> 048 * 049 * <p> 050 * A bare {@code @Rest(mixins=FreemarkerMixin.class)} is enough — a host whose own {@code @RestOp} method 051 * returns {@link FreemarkerView} renders it through the FreeMarker engine with no extra wiring. That works 052 * because {@code FreemarkerMixin} declares 053 * {@link Rest#mergeResponseProcessorsIntoHost() @Rest(mergeResponseProcessorsIntoHost=true)} on its own class, 054 * so the framework folds {@code FreemarkerMixin}'s {@link FreemarkerViewRenderer} into the host's own 055 * response-processor chain automatically. 056 * 057 * <p> 058 * This fold is per-mixin, opt-in, and response-processor-scoped: it happens only because 059 * {@code FreemarkerMixin} declares the opt-in (a mixin that does not is still scoped to its own endpoints, the 060 * standard {@link Rest#mixins() @Rest(mixins=...)} rule), and it folds only the renderer — never 061 * {@code guards}, {@code serializers}, or any other list-shaped attribute. 062 * 063 * <p> 064 * Two explicit wirings remain supported — useful when you want to be explicit, or to fold more than the 065 * renderer: 066 * 067 * <ol class='spaced-list'> 068 * <li><b>{@link Mixin#mergeIntoHost() @Mixin(mergeIntoHost=true)}</b> via the rich {@link Rest#mixinDefs() 069 * mixinDefs} form — folds <b>all</b> of {@code FreemarkerMixin}'s list-shaped {@code @Rest} 070 * attributes into the host's own chain, not just the renderer. 071 * <li><b>List {@link FreemarkerViewRenderer FreemarkerViewRenderer.class} explicitly</b> in the host's own 072 * {@code @Rest(responseProcessors=...)} — the fully manual equivalent. 073 * </ol> 074 * 075 * <p> 076 * The {@link org.apache.juneau.rest.server.processor.ResponseProcessorList} partition pass repositions 077 * {@link FreemarkerViewRenderer} (a 078 * {@link ViewRenderer ViewRenderer}) ahead of 079 * {@code SerializedPojoProcessor} in every case, so the {@link FreemarkerView} bean is dispatched to the 080 * FreeMarker engine rather than bean-serialized. 081 * 082 * <h5 class='figure'>Composition example (microservice):</h5> 083 * 084 * <p class='bjava'> 085 * <ja>@Rest</ja>(path=<js>"/app"</js>, mixins=FreemarkerMixin.<jk>class</jk>) 086 * <jk>public class</jk> AppResource <jk>extends</jk> RestServlet { 087 * 088 * <ja>@Bean</ja> FreemarkerMixin freemarker() { 089 * <jk>return</jk> FreemarkerMixin.<jsm>create</jsm>() 090 * .basePath(<js>"/templates/"</js>) 091 * .templateSuffix(<js>".ftlh"</js>) 092 * .build(); 093 * } 094 * 095 * <ja>@RestGet</ja>(<js>"/hello/{name}"</js>) 096 * <jk>public</jk> View hello(<ja>@Path</ja> String <jv>name</jv>) { 097 * <jk>return</jk> FreemarkerView.<jsm>of</jsm>(<js>"hello"</js>).attr(<js>"name"</js>, <jv>name</jv>); 098 * } 099 * } 100 * </p> 101 * 102 * <h5 class='section'>Configurable mount path:</h5> 103 * 104 * <p> 105 * The default mount {@code /freemarker/*} can be overridden via the SVL variable 106 * {@code ${juneau.freemarker.path:freemarker}} — set via system property 107 * ({@code -Djuneau.freemarker.path=views}), environment variable 108 * ({@code JUNEAU_FREEMARKER_PATH=views}), or {@code Config} key 109 * ({@code juneau.freemarker.path = views}) to change the runtime mount without subclassing. 110 * Resolution happens once at {@link RestContext} construction time (SVL resolution in {@code @RestOp(path)}). 111 * 112 * <h5 class='section'>Mixin-only deployment:</h5> 113 * 114 * <p> 115 * This resource is designed for composition via {@code @Rest(mixins=...)}. The mount path is 116 * pinned at the op level by 117 * {@link RestGet @RestGet(path="/{juneau.freemarker.path:freemarker}/*")} on {@link #render}; 118 * a class-level {@code @Rest(paths=...)} declaration would be silently ignored under the mixin 119 * pattern (see {@link Rest#paths() @Rest(paths)} Javadoc). 120 * 121 * <h5 class='section'>Choosing a Configuration:</h5> 122 * 123 * <p> 124 * The {@code juneau-rest-server-view-freemarker} module ships <b>only</b> 125 * {@code org.freemarker:freemarker} in {@code provided} scope. <b>No configuration bean is 126 * bundled.</b> The bridge resolves a {@link Configuration} at first use from three sources, in 127 * order: 128 * 129 * <ul class='spaced-list'> 130 * <li><b>User-supplied bean:</b> register a {@code @Bean freemarker.template.Configuration} 131 * (Spring) or 132 * {@code BasicBeanStore.put(freemarker.template.Configuration.class, cfg)} (microservice). 133 * Spring Boot's {@code spring-boot-starter-freemarker} autoconfigures one out of the box; 134 * the bridge picks it up via {@code BeanStore.getBean(Configuration.class)}. 135 * <li><b>Bridge default:</b> when nothing is registered, the bridge constructs a default 136 * {@link Configuration} anchored on a classpath resource root derived from 137 * {@link #getBasePath() basePath} ({@code "/templates/"} becomes 138 * {@code ClassLoaderTemplateResolver}-equivalent prefix {@code "/templates"}). The default 139 * pins {@code IncompatibleImprovements} to the bridge-tested minor version 140 * ({@code Configuration.VERSION_2_3_34}), sets {@code DefaultEncoding} to {@code UTF-8}, 141 * uses {@code HTMLOutputFormat} so HTML escaping is the natural target, applies 142 * {@code TemplateUpdateDelayMilliseconds} per the {@link #isCacheTemplates() cacheTemplates} 143 * flag (production-safe by default; users opt into hot-reload via 144 * {@link Builder#cacheTemplates(boolean) cacheTemplates(false)}), and sets an 145 * {@code ObjectWrapper} with {@code exposeFields=true} (see below) unless overridden. 146 * </ul> 147 * 148 * <p> 149 * When no FreeMarker engine is on the classpath, the renderer surfaces 150 * {@link FreemarkerViewRenderer#NO_ENGINE_DIAGNOSTIC} naming the missing dependency. 151 * 152 * <h5 class='section'>Public-field DTOs (getter-only default trap):</h5> 153 * 154 * <p> 155 * FreeMarker's own version-default {@code ObjectWrapper} exposes only JavaBean getters — a 156 * view-model bean written as a simple DTO with public fields and no getters resolves every 157 * {@code ${bean.field}} reference to {@code null}/missing, <b>silently</b> (a template using 158 * {@code !'default'} fallbacks renders the default with no error at all; a bare {@code ${bean.field}} 159 * with no fallback throws at render time). This bridge avoids that trap by default: the bridge-built 160 * {@link Configuration} sets a {@link DefaultObjectWrapper} with {@code exposeFields=true} 161 * ({@link #DEFAULT_EXPOSE_FIELDS}), so public-field DTOs render their field values out of the 162 * box — matching how Juneau's own marshalling is comfortable with public-field beans elsewhere. 163 * Use {@link Builder#exposeFields(boolean) exposeFields(false)} to restore FreeMarker's getter-only 164 * behavior, or {@link Builder#objectWrapper(ObjectWrapper) objectWrapper(...)} for full control 165 * (e.g. a {@code BeansWrapper} with custom exposure rules). Both knobs only affect the 166 * bridge-default {@link Configuration}; a user-supplied {@code @Bean Configuration} is used as-is. 167 * 168 * <h5 class='section'>Template suffix:</h5> 169 * 170 * <p> 171 * FreeMarker's API does not expose a configuration-level resolver suffix — the literal 172 * template name is what {@link Configuration#getTemplate(String) cfg.getTemplate(...)} sees. The 173 * {@link Builder#templateSuffix(String) templateSuffix(...)} builder knob fills the gap: when 174 * non-blank, the bridge appends the configured suffix to template names that don't already end 175 * with it (idempotent), so callers can write {@code FreemarkerView.of("hello")} or request 176 * {@code /freemarker/hello} and have it resolve to {@code hello.ftlh} on the classpath. 177 * 178 * <h5 class='section'>{@code .ftl} vs {@code .ftlh}:</h5> 179 * 180 * <p> 181 * FreeMarker auto-selects HTML escaping by file extension: {@code .ftlh} templates emit 182 * HTML-escaped output; {@code .ftl} templates emit raw output. For HTML responses, prefer 183 * {@code .ftlh} so a future attribute-binding change can't introduce an XSS regression. 184 * 185 * <h5 class='section'>Multiple base paths:</h5> 186 * 187 * <p> 188 * A host with {@code /templates/} and {@code /admin/templates/} template trees registers two 189 * {@code FreemarkerMixin} beans, each mounted via a subclass declaring its own op-level 190 * {@code @RestGet(path=...)} override and its own {@code basePath}. See the 191 * {@code FreemarkerMixin_MockRest_Test} test in {@code juneau-integration-tests} for the canonical 192 * pattern. 193 * 194 * <h5 class='section'>OpenAPI surface:</h5> 195 * 196 * <p> 197 * The greedy {@code /*} handler is not API-meaningful and is excluded from generated 198 * Swagger / OpenAPI specs via {@link OpSwagger#ignore() @OpSwagger(ignore=true)}. 199 * 200 * <h5 class='section'>See Also:</h5><ul> 201 * <li class='jc'>{@link FreemarkerView} 202 * <li class='jc'>{@link FreemarkerViewRenderer} 203 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/FreemarkerViewSupport">FreeMarker View Support</a> 204 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server — Mixins and Multi-Mount Paths</a> 205 * </ul> 206 * 207 * @since 10.0.0 208 */ 209// @formatter:off 210@Rest( 211 responseProcessors={FreemarkerViewRenderer.class}, 212 mergeResponseProcessorsIntoHost=true 213) 214public class FreemarkerMixin { 215 216 /** Default base path applied when no {@link Builder#basePath(String)} call has been made. */ 217 public static final String DEFAULT_BASE_PATH = FreemarkerDispatcher.DEFAULT_BASE_PATH; 218 219 /** Default template suffix — empty (literal template names, no implicit suffix). */ 220 public static final String DEFAULT_TEMPLATE_SUFFIX = FreemarkerDispatcher.DEFAULT_TEMPLATE_SUFFIX; 221 222 /** Default template-cache flag — {@code true} (production-safe). */ 223 public static final boolean DEFAULT_CACHE_TEMPLATES = FreemarkerDispatcher.DEFAULT_CACHE_TEMPLATES; 224 225 /** Default field-exposure flag — {@code true} (public-field DTOs render out of the box). */ 226 public static final boolean DEFAULT_EXPOSE_FIELDS = FreemarkerDispatcher.DEFAULT_EXPOSE_FIELDS; 227 228 private static final Set<Class<? extends FreemarkerMixin>> SUBTYPES = new CopyOnWriteArraySet<>(); 229 230 /** 231 * Registers a subclass so {@link FreemarkerViewRenderer} also finds a bean declared with that subclass as its 232 * {@code @Bean} return type. 233 * 234 * <p> 235 * The bean store is keyed by declared type, so without this a {@code @Bean public ConsoleFreemarkerMixin 236 * freemarker()} was not found and the renderer silently used a plain mixin. Subclasses call this from a static 237 * initializer. 238 * 239 * <h5 class='section'>Example:</h5> 240 * <p class='bjava'> 241 * <jk>public class</jk> MyMixin <jk>extends</jk> FreemarkerMixin { 242 * <jk>static</jk> { FreemarkerMixin.<jsm>registerSubtype</jsm>(MyMixin.<jk>class</jk>); } 243 * } 244 * </p> 245 * 246 * @param type The subclass. Must not be {@code null}. 247 */ 248 public static void registerSubtype(Class<? extends FreemarkerMixin> type) { 249 if (type == null) 250 throw new IllegalArgumentException("registerSubtype(type): type must not be null."); 251 SUBTYPES.add(type); 252 } 253 254 /** @return The registered subclasses, in registration order. */ 255 public static Set<Class<? extends FreemarkerMixin>> registeredSubtypes() { 256 return Collections.unmodifiableSet(SUBTYPES); 257 } 258 259 private final FreemarkerDispatcher worker; 260 261 /** 262 * Creates a new builder. 263 * 264 * @return A new builder. 265 */ 266 public static Builder create() { 267 return new Builder(); 268 } 269 270 /** 271 * No-arg constructor — uses {@link #DEFAULT_BASE_PATH} as the base path, 272 * {@link #DEFAULT_TEMPLATE_SUFFIX} as the template suffix, and 273 * {@link #DEFAULT_CACHE_TEMPLATES} as the cache flag. 274 * 275 * <p> 276 * The mixin walk falls back to this constructor when the 277 * {@link org.apache.juneau.commons.inject.BeanStore BeanStore} does not have a registered 278 * {@code FreemarkerMixin} bean. 279 */ 280 public FreemarkerMixin() { 281 this(create()); 282 } 283 284 /** 285 * Builder constructor. 286 * 287 * @param builder The builder. Must not be {@code null}. 288 */ 289 protected FreemarkerMixin(Builder builder) { 290 worker = builder.worker.build(); 291 } 292 293 /** 294 * Returns the base path under which template resources are resolved. 295 * 296 * @return The base path. Never {@code null}. 297 */ 298 public String getBasePath() { 299 return worker.getBasePath(); 300 } 301 302 /** 303 * Returns the configured template-name suffix. 304 * 305 * @return The template suffix. Never {@code null}. 306 */ 307 public String getTemplateSuffix() { 308 return worker.getTemplateSuffix(); 309 } 310 311 /** 312 * Returns the template-cache flag. 313 * 314 * @return The cache flag. 315 */ 316 public boolean isCacheTemplates() { 317 return worker.isCacheTemplates(); 318 } 319 320 /** 321 * Returns the field-exposure flag applied to the bridge-default object wrapper. 322 * 323 * @return The field-exposure flag. Ignored once {@link #getObjectWrapper()} returns non-{@code null}. 324 */ 325 public boolean isExposeFields() { 326 return worker.isExposeFields(); 327 } 328 329 /** 330 * Returns the user-supplied {@link ObjectWrapper} override, if any. 331 * 332 * @return The object wrapper override, or {@code null} if the bridge builds its own default. 333 */ 334 public ObjectWrapper getObjectWrapper() { 335 return worker.getObjectWrapper(); 336 } 337 338 /** 339 * Appends {@link #getTemplateSuffix()} to {@code name} if not already present (idempotent). 340 * 341 * @param name The template name. Must not be {@code null}. 342 * @return The template name with the configured suffix appended (if applicable). 343 */ 344 public String applyTemplateSuffix(String name) { 345 return worker.applyTemplateSuffix(name); 346 } 347 348 /** 349 * Resolves the active {@link Configuration} via the shared {@link FreemarkerDispatcher} worker. 350 * 351 * @param req The current REST request. 352 * @return The active FreeMarker configuration. Never {@code null}. 353 */ 354 public Configuration resolveConfiguration(RestRequest req) { 355 return worker.resolveConfiguration(req); 356 } 357 358 /** 359 * [GET /freemarker/*] — render a raw template through the FreeMarker engine. 360 * 361 * <p> 362 * The {@code @Path("/*") String path} captures the multi-segment trailing remainder (e.g. a 363 * request for {@code /freemarker/about.ftlh} matches the mount with 364 * {@code path = "about.ftlh"}). Delegates to the shared {@link FreemarkerDispatcher} worker 365 * (path-traversal hardening, suffix application, configuration resolution, and render). 366 * 367 * <p> 368 * Missing template surfaces as the engine's own {@link TemplateNotFoundException} 369 * (HTTP 500 from the renderer's catch-all); missing engine surfaces as 370 * {@link FreemarkerViewRenderer#NO_ENGINE_DIAGNOSTIC}. 371 * 372 * @param path The trailing path segment after {@code /freemarker/} (the template name 373 * relative to the configured {@link #getBasePath() base path}; configured suffix appended 374 * if missing). 375 * @param req The current REST request. 376 * @param res The current REST response. 377 * @throws IOException If the underlying response writer fails. 378 * @throws BasicHttpException On boundary violation (403), missing engine (500), or render 379 * failure (500). 380 */ 381 @RestGet( 382 path="/${juneau.freemarker.path:freemarker}/*", 383 summary="FreeMarker view", 384 description="Render a raw FreeMarker template under the configured base path.", 385 swagger=@OpSwagger(ignore=true) 386 ) 387 public void render(@Path("/*") String path, RestRequest req, RestResponse res) 388 throws IOException, BasicHttpException { 389 worker.render(path, req, res); 390 } 391 392 /** 393 * Builder for {@link FreemarkerMixin}. 394 * 395 * <p> 396 * Mirrors {@link FreemarkerDispatcher.Builder}'s configuration methods on its own surface and 397 * forwards each call into a held {@link FreemarkerDispatcher.Builder} (worker-bean 398 * composition). Implements {@link ViewMixinBuilder} so {@link #basePath(String)} and 399 * {@link #cacheTemplates(boolean)} are guaranteed to transfer to the sibling JSP / Thymeleaf / 400 * Mustache bridge builders. 401 */ 402 public static class Builder implements ViewMixinBuilder<Builder> { 403 404 private final FreemarkerDispatcher.Builder worker = FreemarkerDispatcher.create(); 405 406 /** Constructor — package access for {@link FreemarkerMixin#create()}. */ 407 protected Builder() {} 408 409 /** 410 * Sets the classpath base path under which template resources are resolved. 411 * 412 * <p> 413 * Defaults to {@link FreemarkerMixin#DEFAULT_BASE_PATH "/"}. A typical configured 414 * value is {@code "/templates/"} — the same Spring-Boot-compatible layout 415 * convention the sibling JSP / Thymeleaf / Mustache bridges use. 416 * 417 * @param value The base path. {@code null} or blank values reset to the default 418 * {@link FreemarkerMixin#DEFAULT_BASE_PATH}. 419 * @return This object. 420 */ 421 @Override /* ViewMixinBuilder */ 422 public Builder basePath(String value) { 423 worker.basePath(value); 424 return this; 425 } 426 427 /** 428 * Sets the suffix to append to template names that don't already end with it 429 * (idempotent). 430 * 431 * <p> 432 * Defaults to {@link FreemarkerMixin#DEFAULT_TEMPLATE_SUFFIX ""} (no implicit 433 * suffix — literal template names). Typical configured values are {@code ".ftlh"} 434 * (HTML auto-escape) or {@code ".ftl"} (no auto-escape). 435 * 436 * @param value The new suffix. {@code null} resets to 437 * {@link FreemarkerMixin#DEFAULT_TEMPLATE_SUFFIX}. 438 * @return This object. 439 */ 440 public Builder templateSuffix(String value) { 441 worker.templateSuffix(value); 442 return this; 443 } 444 445 /** 446 * Sets the template-cache flag for the bridge-default configuration. 447 * 448 * <p> 449 * Defaults to {@link FreemarkerMixin#DEFAULT_CACHE_TEMPLATES true} (cache 450 * forever — production-safe). Set {@code false} to enable dev hot-reload (FreeMarker 451 * re-checks the underlying resource on every render). 452 * 453 * <p> 454 * This flag only affects the bridge-default {@link Configuration}; user-supplied 455 * {@code @Bean Configuration}s are not modified. 456 * 457 * @param value The cache flag. 458 * @return This object. 459 */ 460 @Override /* ViewMixinBuilder */ 461 public Builder cacheTemplates(boolean value) { 462 worker.cacheTemplates(value); 463 return this; 464 } 465 466 /** 467 * Sets whether the bridge-default {@code ObjectWrapper} exposes public fields (not just 468 * JavaBean getters) to templates. 469 * 470 * <p> 471 * Defaults to {@link FreemarkerMixin#DEFAULT_EXPOSE_FIELDS true} so simple view-model 472 * DTOs written with public fields and no getters render their field values instead of 473 * silently resolving to {@code null}/missing (see the class-level 474 * "Public-field DTOs" section). Has no effect once {@link #objectWrapper(ObjectWrapper)} 475 * has been called with a non-{@code null} value — the explicit wrapper always wins. 476 * 477 * @param value The field-exposure flag. 478 * @return This object. 479 */ 480 public Builder exposeFields(boolean value) { 481 worker.exposeFields(value); 482 return this; 483 } 484 485 /** 486 * Sets a fully custom {@code ObjectWrapper} for the bridge-default configuration, 487 * overriding {@link #exposeFields(boolean)} entirely. 488 * 489 * <p> 490 * Escape hatch for consumers who need full control (e.g. a {@code BeansWrapper} with 491 * custom method/property exposure, or a third-party wrapper) without hand-rolling and 492 * registering a whole replacement {@code Configuration} bean. 493 * 494 * @param value The object wrapper. {@code null} reverts to the bridge-built 495 * {@link DefaultObjectWrapper} (per {@link #exposeFields(boolean)}). 496 * @return This object. 497 */ 498 public Builder objectWrapper(ObjectWrapper value) { 499 worker.objectWrapper(value); 500 return this; 501 } 502 503 /** 504 * Reads the current base path setting (test/inspection helper). 505 * 506 * @return The base path. Never {@code null}. 507 */ 508 public String getBasePath() { 509 return worker.getBasePath(); 510 } 511 512 /** 513 * Reads the current template-suffix setting (test/inspection helper). 514 * 515 * @return The template suffix. Never {@code null}. 516 */ 517 public String getTemplateSuffix() { 518 return worker.getTemplateSuffix(); 519 } 520 521 /** 522 * Reads the current cache-templates setting (test/inspection helper). 523 * 524 * @return The cache flag. 525 */ 526 public boolean isCacheTemplates() { 527 return worker.isCacheTemplates(); 528 } 529 530 /** 531 * Reads the current field-exposure setting (test/inspection helper). 532 * 533 * @return The field-exposure flag. 534 */ 535 public boolean isExposeFields() { 536 return worker.isExposeFields(); 537 } 538 539 /** 540 * Reads the current object-wrapper override (test/inspection helper). 541 * 542 * @return The object wrapper override, or {@code null} if none has been set. 543 */ 544 public ObjectWrapper getObjectWrapper() { 545 return worker.getObjectWrapper(); 546 } 547 548 /** 549 * Builds the {@link FreemarkerMixin}. 550 * 551 * @return A new {@link FreemarkerMixin} instance. 552 */ 553 public FreemarkerMixin build() { 554 return new FreemarkerMixin(this); 555 } 556 } 557}