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.convention; 018 019import org.apache.juneau.http.*; 020import org.apache.juneau.rest.server.*; 021import org.apache.juneau.rest.server.servlet.*; 022 023/** 024 * Mixin that serves a {@code favicon.ico} icon at {@code /favicon.ico}. 025 * 026 * <p> 027 * Sibling of {@link SeoMixin} (robots.txt / sitemap.xml), {@link VersionMixin} 028 * ({@code /version}), and {@link WellKnownMixin} 029 * ({@code /.well-known/*}). All four classes live in the {@code org.apache.juneau.rest.server.convention} 030 * convention-endpoints mixin pack. 031 * 032 * <p> 033 * Compose into a host resource via {@link Rest#mixins() @Rest(mixins=FaviconMixin.class)}; 034 * the {@code /favicon.ico} URL becomes available alongside the host's own endpoints with no further 035 * wiring. 036 * 037 * <h5 class='section'>Hardcoded mount path:</h5> 038 * 039 * <p> 040 * Unlike the sibling api-docs and ops mixins (see {@link org.apache.juneau.rest.server.docs.SwaggerMixin}, 041 * {@link VersionMixin}, etc.) the mount path here is <b>not</b> SVL-configurable 042 * — {@code /favicon.ico} is fixed by browser convention and the {@code link rel="icon"} 043 * default the HTML spec inherits from <a href="https://html.spec.whatwg.org/multipage/links.html#rel-icon">WHATWG HTML</a>. 044 * Browsers fetch {@code /favicon.ico} from the site root regardless of any application-level 045 * routing rewrites, so a runtime mount-path override here would have no practical effect. 046 * 047 * <h5 class='section'>Mixin-only deployment:</h5> 048 * 049 * <p> 050 * This resource is designed for composition via {@code @Rest(mixins=...)}. The mount path is 051 * pinned at the op level by {@link RestGet @RestGet(path="/favicon.ico")} on {@link #getFavicon}; 052 * a class-level {@code @Rest(paths=...)} declaration would be silently ignored under the mixin 053 * pattern (see {@link Rest#paths() @Rest(paths)} Javadoc). 054 * 055 * <h5 class='figure'>Composition example:</h5> 056 * 057 * <p class='bjava'> 058 * <ja>@Rest</ja>(path=<js>"/api"</js>, mixins=FaviconMixin.<jk>class</jk>) 059 * <jk>public class</jk> ApiResource <jk>extends</jk> RestServlet { 060 * <jc>// Use the default Juneau-branded favicon.</jc> 061 * } 062 * 063 * <jc>// Or override the icon bytes via a @Bean factory:</jc> 064 * <ja>@Bean FaviconMixin favicon()</ja> { 065 * <jk>return</jk> FaviconMixin.<jsm>create</jsm>().bytes(myLogoBytes).build(); 066 * } 067 * </p> 068 * 069 * <h5 class='section'>Behavior:</h5> 070 * 071 * <ul class='spaced-list'> 072 * <li>{@code GET /favicon.ico} returns the configured icon bytes with 073 * {@code Content-Type: image/x-icon} and {@code Cache-Control: max-age=2592000, public} 074 * (30 days — favicons rarely change and browsers re-fetch frequently when uncached). 075 * <li>The default icon ({@code juneau-favicon.ico} on the framework classpath) is a 076 * 16×16 Juneau-branded ICO; users replace it by registering an alternate 077 * {@code @Bean FaviconMixin} whose builder supplies different bytes. 078 * <li>The handler is excluded from generated Swagger / OpenAPI specs via 079 * {@link OpSwagger#ignore() @OpSwagger(ignore=true)} — favicons are not 080 * API-meaningful. 081 * </ul> 082 * 083 * <h5 class='section'>Builder API:</h5> 084 * 085 * <ul class='spaced-list'> 086 * <li>{@link #create() create()} — entry point for configuring an instance. 087 * <li>{@link Builder#bytes(byte[]) bytes(byte[])} — raw favicon bytes. 088 * <li>{@link Builder#classpath(String) classpath(String)} — load icon bytes from a 089 * classpath resource (resolved against the {@code FaviconMixin} classloader). 090 * <li>{@link Builder#cacheControl(String) cacheControl(String)} — override the default 091 * 30-day {@code Cache-Control} header. 092 * </ul> 093 * 094 * <h5 class='section'>See Also:</h5><ul> 095 * <li class='jc'>{@link SeoMixin} 096 * <li class='jc'>{@link VersionMixin} 097 * <li class='jc'>{@link WellKnownMixin} 098 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server — Mixins and Multi-Mount Paths</a> 099 * </ul> 100 * 101 * @since 10.0.0 102 */ 103// @formatter:off 104@Rest 105public class FaviconMixin extends RestMixin { 106 107 /** Default {@code Cache-Control} header value: {@code max-age=2592000, public} (30 days). */ 108 public static final String DEFAULT_CACHE_CONTROL = FaviconProvider.DEFAULT_CACHE_CONTROL; 109 110 /** 111 * Creates a new builder for configuring a {@link FaviconMixin}. 112 * 113 * @return A new builder. 114 */ 115 public static Builder create() { 116 return new Builder(); 117 } 118 119 private final FaviconProvider worker; 120 121 /** 122 * No-arg constructor — used when a host registers the mixin without supplying a 123 * builder-configured {@code @Bean FaviconMixin}. Delegates to a default {@link FaviconProvider} worker 124 * (the framework-shipped Juneau-branded favicon). 125 */ 126 public FaviconMixin() { 127 this(new FaviconProvider()); 128 } 129 130 /** 131 * Worker constructor. 132 * 133 * @param worker The shared {@link FaviconProvider} worker this flavor delegates to. Must not be 134 * <jk>null</jk>. 135 */ 136 protected FaviconMixin(FaviconProvider worker) { 137 this.worker = worker; 138 } 139 140 /** 141 * Worker + builder constructor. 142 * 143 * <p> 144 * Used by {@link Builder#build()} to both delegate to the shared {@link FaviconProvider} worker and stash the 145 * programmatic {@link RestBuilder}(carrying any {@code @Rest}-level overrides such as {@code path}) so those 146 * values take precedence over the mixin class's own {@link Rest @Rest} annotation. 147 * 148 * <h5 class='section'>Why worker + builder, not the flavor Builder:</h5> 149 * <ul class='spaced-list'> 150 * <li>Takes the already-built <b>worker bean</b> (not the flavor {@link Builder}) so the flavor can be 151 * constructed from ANY independently-supplied worker — e.g. a user's own {@code @Bean FaviconProvider} 152 * or BeanStore bean, per the delegate-bean model — not only via this flavor's own builder. 153 * <li>Takes the generic {@link RestBuilder}(here {@code this} from {@link Builder#build()}) so it honors the 154 * uniform §2.4 {@code Foo(RestBuilder<?>)} injection contract the base class and DI resolution key on; the 155 * base knows nothing about the concrete flavor builder or the worker type. 156 * <li>Holds the finished worker product (the {@code final} {@link FaviconProvider} field), not a transient 157 * builder; the worker is materialized exactly once at {@link Builder#build()} time. 158 * <li>Keeps the capability worker and the REST-level config as two distinct inputs. 159 * </ul> 160 * 161 * @param worker The shared {@link FaviconProvider} worker this flavor delegates to. Must not be <jk>null</jk>. 162 * @param builder The programmatic configuration builder. May be <jk>null</jk>. 163 */ 164 protected FaviconMixin(FaviconProvider worker, RestBuilder<?> builder) { 165 super(builder); 166 this.worker = worker; 167 } 168 169 /** 170 * [GET /favicon.ico] — serve the configured favicon bytes. 171 * 172 * @return The favicon as an {@link HttpResource} with proper headers. 173 */ 174 @RestGet( 175 path="/favicon.ico", 176 summary="Favorites icon", 177 description="Browser favorites icon (favicon.ico).", 178 swagger=@OpSwagger(ignore=true) 179 ) 180 public HttpResource getFavicon() { 181 return worker.serve(); 182 } 183 184 /** 185 * Builder for {@link FaviconMixin} instances. 186 * 187 * <p> 188 * Mirrors {@link FaviconProvider.Builder}'s configuration methods on the mixin's own surface and 189 * forwards each call to an underlying {@link FaviconProvider.Builder}, which builds the shared worker 190 * the mixin delegates to. 191 * 192 * <p> 193 * Extends {@link org.apache.juneau.rest.server.servlet.RestMixin.Builder RestMixin.Builder} so the mixin's bespoke worker-config setters 194 * ({@link #bytes(byte[])}, {@link #classpath(String)}, {@link #cacheControl(String)}) chain with true 195 * covariant returns alongside the inherited {@link RestBuilder}surface (e.g. {@code path}, {@code roleGuard}). 196 * This is how a multi-flavor capability avoids triplicating its REST-level config: the worker config is 197 * forwarded once into {@link FaviconProvider.Builder}, and the REST config is inherited once from 198 * {@link AbstractRestBuilder}. 199 */ 200 public static class Builder extends RestMixin.Builder<FaviconMixin,Builder> { 201 202 private final FaviconProvider.Builder worker = FaviconProvider.create(); 203 204 /** Constructor — package access for {@link FaviconMixin#create()}. */ 205 protected Builder() { 206 super(FaviconMixin.class); 207 } 208 209 /** 210 * Sets the raw favicon bytes. 211 * 212 * <p> 213 * Mutually exclusive with {@link #classpath(String)} — whichever is set last wins 214 * at {@link #build()} time. 215 * 216 * @param value The favicon bytes (typically an {@code .ico} or {@code .png} payload). 217 * Must not be <jk>null</jk>. 218 * @return This object. 219 */ 220 public Builder bytes(byte[] value) { 221 worker.bytes(value); 222 return this; 223 } 224 225 /** 226 * Sets the classpath resource path from which to load the favicon bytes. 227 * 228 * <p> 229 * A resolved-to-{@code null} stream falls back to the framework's default favicon. 230 * Mutually exclusive with {@link #bytes(byte[])}. 231 * 232 * @param value The classpath resource path (e.g. {@code "/myapp/icon.ico"}). 233 * Must not be <jk>null</jk> or blank. 234 * @return This object. 235 */ 236 public Builder classpath(String value) { 237 worker.classpath(value); 238 return this; 239 } 240 241 /** 242 * Sets the {@code Cache-Control} header value emitted with the favicon response. 243 * 244 * <p> 245 * Defaults to {@value #DEFAULT_CACHE_CONTROL} (30 days). 246 * 247 * @param value The new {@code Cache-Control} value. Must not be <jk>null</jk> or blank. 248 * @return This object. 249 */ 250 public Builder cacheControl(String value) { 251 worker.cacheControl(value); 252 return this; 253 } 254 255 /** 256 * Builds a {@link FaviconMixin} instance, delegating to the shared {@link FaviconProvider} worker and 257 * stashing this builder so its {@code @Rest}-level overrides take precedence over the annotation. 258 * 259 * @return A configured instance. 260 */ 261 @Override /* AbstractRestBuilder */ 262 public FaviconMixin build() { 263 return new FaviconMixin(worker.build(), this); 264 } 265 } 266}