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.console.views; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import org.apache.juneau.http.*; 022import org.apache.juneau.rest.server.*; 023import org.apache.juneau.commons.beanquery.*; 024import org.apache.juneau.console.*; 025import org.apache.juneau.console.datatables.*; 026import org.apache.juneau.rest.server.util.*; 027import org.apache.juneau.console.widgets.*; 028 029/** 030 * Mixin that serves the first-party rich-view runtime assets — {@code juneau-views.js}, 031 * {@code juneau-ribbon.js}, {@code juneau-renders.js}, {@code juneau-views.css}, the opt-in 032 * {@code juneau-regions.js} region-populate runtime, the opt-in {@code juneau-config.js}/{@code juneau-config.css} 033 * column-chooser runtime — each at its stable path, plus deprecated compatibility mounts 034 * for the two assets that have since moved to the widget module. 035 * 036 * <p> 037 * Compose into a host resource via {@link Rest#mixins() @Rest(mixins=ViewsMixin.class)}; the asset URLs then become 038 * available alongside the host's own endpoints, so browser pages can load the runtime without the application hosting 039 * it itself. This mirrors the sibling {@code DataTablesMixin}/{@code ConsoleChromeMixin} serving pattern (those 040 * modules are not dependencies of this one, so they are not linked here). 041 * 042 * <h5 class='section'>What this ships (and what it deliberately does not):</h5> 043 * <p> 044 * Juneau's jars don't copy the <a class="doclink" href="https://datatables.net">DataTables</a> library or jQuery. Only 045 * the thin, first-party runtime is served here; the DataTables, jQuery and Buttons files come from the WebJars that 046 * {@code juneau-console-datatables} declares, served by {@code WebJarsMixin}. The 047 * base {@code juneau-views.css} {@code .tag} chip is dependency-free (neutral, no colors); {@code console-ui}'s 048 * {@code chrome.css} themes the same {@code .tag.<domain>.<value>} classes when present, but this module takes 049 * <b>no</b> dependency on it. 050 * 051 * <h5 class='section'>Relocated: two assets now ship in the widget module</h5> 052 * <p> 053 * {@code juneau-calendar.js} and {@code juneau-calendar.css} are widget runtimes, not 054 * table runtimes, and their bytes now live in {@code juneau-console-widgets} beside the bean contracts that 055 * drive them. {@link WidgetsMixin} is where a new application gets them. 056 * 057 * <p> 058 * This mixin keeps a <b>deprecated</b> mount and path constant for each of the two so that an existing 059 * {@code @Rest(mixins=ViewsMixin.class)} application keeps working with no change: the mount reads the widget 060 * module's bytes off the classpath (that module is a compile-scope dependency of this one) rather than holding a 061 * second copy, so the body and the cache-buster are identical to what {@link WidgetsMixin} serves. Composing both 062 * mixins is therefore harmless. 063 * 064 * <p> 065 * The relocation does <b>not</b> make that script standalone: it still resolves glyphs through this module's 066 * {@code juneau-icons.js} and pushes its popovers onto the ONE shared layer stack {@code juneau-views.js} publishes. 067 * A page loading the widget calendar runtime must still load this module's {@code juneau-icons.js} and 068 * {@code juneau-views.js} first. 069 * 070 * <h5 class='section'>Cache-busting + versioned URLs:</h5> 071 * <p> 072 * Each asset is served with a one-day {@code Cache-Control} and referenced from a page's {@code head=} block with a 073 * {@code ?v=<buildVersion>-<hash8>} cache-buster (see {@link #viewAssetUrl(String)}, or 074 * {@link #viewAssetUrl(RestRequest, String)} for a template-rendered consumer that needs the URL already resolved 075 * to an absolute, browser-fetchable form), where {@code hash8} is an 076 * 8-hex-char CRC32 of that asset's own served bytes, computed once and cached (classpath resources never change 077 * within a running JVM). Keying the buster off content rather than {@code buildVersion} alone matters for 078 * {@code -SNAPSHOT} builds: that version string is stable across dev rebuilds, so a version-only buster would keep 079 * serving a browser's stale cached copy after every rebuild - the content hash changes the instant the bytes do, 080 * with no SNAPSHOT special-casing needed. {@code buildVersion} is the framework's 081 * {@link Package#getImplementationVersion() implementation version}, falling back to {@code "dev"} when running from an 082 * unpackaged (IDE/test) classpath. The served endpoint ignores the query string, so any (or no) {@code ?v=} still 083 * resolves to the same asset. 084 * 085 * <h5 class='section'>Contract-version handshake:</h5> 086 * <p> 087 * {@link #CONTRACT_VERSION} is the single wire-contract discriminator for a {@code VIEW_META} sidecar. 088 * {@code juneau-views.js} bakes in the same value so the client can fail loud when a served sidecar's 089 * {@code contractVersion} differs from the runtime's. 090 * 091 * <h5 class='section'>Mixin-only deployment:</h5> 092 * <p> 093 * The mount paths are pinned at the op level by {@code @RestGet(path=...)} on the serving methods; a class-level 094 * {@code @Rest(paths=...)} would be silently ignored under the mixin pattern. 095 * 096 * <h5 class='section'>See Also:</h5> 097 * <ul> 098 * <li class='jc'>{@link ViewTable} 099 * </ul> 100 * 101 * @since 10.0.0 102 */ 103// @formatter:off 104@Rest 105@SuppressWarnings({ 106 "java:S1133" // Still referenced externally (compatibility mount, forRemoval=false); removal is a separate deprecation-cycle decision. 107}) 108public class ViewsMixin { 109 110 /** 111 * The URL path at which the client initializer is served (relative to the host mount). A page with any 112 * datatables card must load {@code juneau-datatables.js} ({@link #DATATABLES_JS_PATH}) <b>before</b> this script, 113 * so {@code window.JuneauDataTables} is already present when {@code buildOptions} wires up that card's ajax. 114 * This mixin does not serve {@code juneau-datatables.js} itself — the resource 115 * mixes in {@code DataTablesMixin} for that, the same way it already mixes in {@code ViewsMixin} for this 116 * script; a duplicate {@code @RestGet} for the same path on two mixed-in classes would fail route 117 * registration. A missing {@code window.JuneauDataTables} when a server-side table initializes fails loudly (console warning, 118 * no silent GET fallback) rather than silently misbehaving. A page with any ribbon options should likewise load 119 * {@code juneau-ribbon.js} before this script, so {@code window.JuneauViews.ribbon} is present when 120 * {@code buildOptions} merges that view's active ribbon state into the request; unlike the 121 * {@code JuneauDataTables} case this one degrades rather than fails — a missing 122 * {@code window.JuneauViews.ribbon} warns and simply contributes no ribbon filters, since a view with no ribbon 123 * configured never needed {@code juneau-ribbon.js} loaded at all. 124 */ 125 public static final String VIEWS_JS_PATH = "/juneau-views.js"; 126 127 /** 128 * The URL path of the DataTables glue script, {@code juneau-datatables.js} (the same path as 129 * {@link DataTablesMixin#GLUE_PATH}). 130 * 131 * <p> 132 * Named here so {@link #viewAssetUrl(RestRequest, String)} can build its cache-busted URL, and so the console's 133 * {@code "datatables-glue"} toolkit pack can name the path without importing the datatables package 134 * ({@code juneau-console-freemarker} forbids that import; see its {@code ModuleGraph_ImportScan_Test}). This mixin does 135 * <b>not</b> serve it (see {@link #VIEWS_JS_PATH} for why); {@link DataTablesMixin} does. On a console page the 136 * {@code "datatables-glue"} toolkit pack emits it for every {@code type="datatables"} card, client or server mode, 137 * after jQuery and DataTables and before {@code juneau-views.js}. A host with datatables cards composes 138 * {@link DataTablesMixin} and {@code WebJarsMixin}. 139 * 140 * <h5 class='section'>Example:</h5> 141 * <p class='bjava'> 142 * <ja>@Rest</ja>(mixins={ViewsMixin.<jk>class</jk>, DataTablesMixin.<jk>class</jk>, WebJarsMixin.<jk>class</jk>}) 143 * <jk>public class</jk> MyResource <jk>extends</jk> BasicRestServlet { 144 * <jc>// Emitted by the "datatables-glue" pack before juneau-views.js:</jc> 145 * <jc>// ViewsMixin.viewAssetUrl(req, ViewsMixin.DATATABLES_JS_PATH)</jc> 146 * } 147 * </p> 148 */ 149 public static final String DATATABLES_JS_PATH = DataTablesMixin.GLUE_PATH; 150 151 /** The URL path at which the ribbon runtime is served (relative to the host mount). */ 152 public static final String RIBBON_JS_PATH = "/juneau-ribbon.js"; 153 154 /** The URL path at which the renderer registry is served (relative to the host mount). */ 155 public static final String RENDERS_JS_PATH = "/juneau-renders.js"; 156 157 /** The URL path at which the base view stylesheet is served (relative to the host mount). */ 158 public static final String VIEWS_CSS_PATH = "/juneau-views.css"; 159 160 /** 161 * The URL path at which the icon registry is served (relative to the host mount). 162 * 163 * <h5 class='section'>Example:</h5> 164 * <p class='bjava'> 165 * <jc>// Page-level sprite layers, read once before the chrome draws:</jc> 166 * <jc>// <script src="/views/juneau-icons.js"</jc> 167 * <jc>// data-juneau-icon-replacement="/my/set.svg"</jc> 168 * <jc>// data-juneau-icon-override="/my/overrides.svg"></script></jc> 169 * </p> 170 */ 171 public static final String ICONS_JS_PATH = "/juneau-icons.js"; 172 173 /** 174 * The URL path at which the column-search engine is served (relative to the host mount). This 175 * dependency-free {@code <script>} loads before {@code juneau-views.js} (like the icon registry): it publishes 176 * {@code JuneauViews.search} — the client mirror of the server-side {@link SearchExpressionParser}/ 177 * {@link SearchOperatorSet}/{@link InMemoryBeanQueryContext} context that the header search popup and a client-mode 178 * grid use to parse and evaluate each column's raw search expression. 179 */ 180 public static final String SEARCH_JS_PATH = "/juneau-search.js"; 181 182 /** 183 * The URL path at which the general page-state store is served (relative to the host mount). This 184 * dependency-free {@code <script>} loads before {@code juneau-views.js} and {@code juneau-config.js} (like the 185 * icon registry and column-search engine): it publishes {@code JuneauViews.pageState} — a per-table / 186 * per-page key/value store, backed by {@code localStorage} by default and degrading to a silent no-op when 187 * browser storage is blocked. The datatable View Settings dialog and any other page preference share it. 188 */ 189 public static final String PAGESTATE_JS_PATH = "/juneau-pagestate.js"; 190 191 /** 192 * The URL path at which the shareable "Copy link" URL-state codec is served (relative to the host mount). This 193 * dependency-free {@code <script>} loads before {@code juneau-views.js} and {@code juneau-config.js} (like the 194 * icon registry, column-search engine, and page-state store): it publishes {@code JuneauViews.urlState} — 195 * the single codec for the one {@code ?state=} query parameter that carries a shareable link's live tab, primary- 196 * table filters, and primary-table sort (never the browser-local View Settings, and never nested tables). 197 */ 198 public static final String URLSTATE_JS_PATH = "/juneau-urlstate.js"; 199 200 /** 201 * The URL path at which the in-page message bus is served (relative to the host mount). This dependency-free 202 * {@code <script>} publishes {@code JuneauViews.bus} — topic declare/claim, publish/subscribe with optional 203 * retention, and the frame and source bridges — and it <b>must</b> load before {@code juneau-views.js}, 204 * {@code juneau-regions.js} and {@code juneau-console.js}, all of which read it. The {@code "views"} toolkit pack 205 * lists it first. A page that wires topics without it fails loud with {@code E-JS-46}. 206 * 207 * <h5 class='section'>Example:</h5> 208 * <p class='bjava'> 209 * <jc>// A hand-written page head that does not use the "views" toolkit pack: the bus goes first.</jc> 210 * <jk>var</jk> <jv>bus</jv> = ViewsMixin.<jsm>viewAssetUrl</jsm>(<jv>req</jv>, ViewsMixin.<jsf>BUS_JS_PATH</jsf>); 211 * <jk>var</jk> <jv>views</jv> = ViewsMixin.<jsm>viewAssetUrl</jsm>(<jv>req</jv>, ViewsMixin.<jsf>VIEWS_JS_PATH</jsf>); 212 * <jc>// <script src="${bus}"></script><script src="${views}"></script></jc> 213 * </p> 214 */ 215 public static final String BUS_JS_PATH = "/juneau-bus.js"; 216 217 /** 218 * The URL path at which the shared SVG symbol sprite is served (relative to the host mount). 219 * {@code juneau-icons.js} fetches this next to itself by default (pack {@code original}); the key/legend 220 * file is not served to browsers. An app replaces part or all of this shipped set per page with a replacement 221 * and/or override sprite that uses the same {@code juneau-sym-{stem}} symbol ids. 222 * 223 * <h5 class='section'>Example:</h5> 224 * <p class='bjava'> 225 * <jc>// Page-level sprite layers, read once before the chrome draws:</jc> 226 * <jc>// <script src="/views/juneau-icons.js"</jc> 227 * <jc>// data-juneau-icon-replacement="/my/set.svg"</jc> 228 * <jc>// data-juneau-icon-override="/my/overrides.svg"></script></jc> 229 * </p> 230 */ 231 public static final String SYMBOLS_SVG_PATH = "/juneau-symbols.svg"; 232 233 /** 234 * The URL path at which the opt-in Material Symbols Outlined sprite is served (relative to the host mount). 235 * Selected as the legacy replacement layer by {@code JuneauViews.icons.pack("material")} / 236 * {@code data-juneau-icon-pack="material"}; new apps point {@code data-juneau-icon-replacement} / 237 * {@code JuneauViews.icons.sprites({replacementUrl})} at the set they want instead. 238 * Default remains {@link #SYMBOLS_SVG_PATH} (Juneau's own shipped set). The Material pack loads as the replacement 239 * layer on top of that shipped sprite, so stems the Material file does not carry fall back to the shipped glyph. 240 * 241 * <h5 class='section'>Example:</h5> 242 * <p class='bjava'> 243 * <jc>// Opt in to the Material Symbols sprite as the replacement layer over the shipped set, before the chrome draws:</jc> 244 * <jc>// <script src="/views/juneau-icons.js" data-juneau-icon-pack="material"></script></jc> 245 * <jc>// or, from script before first paint:</jc> 246 * <jc>// JuneauViews.icons.pack("material");</jc> 247 * </p> 248 */ 249 public static final String SYMBOLS_MATERIAL_SVG_PATH = "/juneau-symbols-material.svg"; 250 251 /** 252 * The URL path at which the opt-in region-populate runtime is served (relative to the host mount). A page with 253 * no {@code data-juneau-region} element never needs to load it. 254 * 255 * <h5 class='section'>Load order is a contract, not a preference:</h5> 256 * <p> 257 * This {@code <script>} MUST come after {@code juneau-views.js}. The region runtime publishes onto the same 258 * {@code window.JuneauViews} namespace that {@code juneau-views.js} creates, and reuses that runtime's 259 * {@code renderAsyncStatus} status renderer rather than defining one of its own. 260 */ 261 public static final String REGIONS_JS_PATH = "/juneau-regions.js"; 262 263 /** 264 * The URL path at which the opt-in region-populate paint library is served (relative to the host mount). A 265 * page of purely custom regions (every populate hand-writes its own DOM) never needs to load it. 266 * 267 * <h5 class='section'>Load order is a contract, not a preference:</h5> 268 * <p> 269 * This {@code <script>} MUST come after {@code juneau-regions.js} (itself after {@code juneau-views.js}). 270 * {@code fieldGrid}'s renderer dispatch reads the renderer registry {@code juneau-views.js} publishes and its 271 * markdown/sanitized-HTML paths reuse that runtime's allowlist copiers, and {@code ctx.helpers} 272 * (a bare pass-through of this asset's exports) is a field of the {@code ctx} {@code juneau-regions.js} freezes. 273 */ 274 public static final String HELPERS_JS_PATH = "/juneau-helpers.js"; 275 276 /** 277 * [GET] path of the DataRegistry runtime ({@code JuneauViews.data}): the frames behind {@code data:} bus topics. 278 * Loaded after {@link #BUS_JS_PATH}; the {@code "data"} toolkit pack serves it. 279 */ 280 public static final String DATA_JS_PATH = "/juneau-data.js"; 281 282 /** 283 * The URL path at which the opt-in terminal-view module is served (relative to the host mount). 284 * 285 * <h5 class='section'>Load order is a contract, not a preference:</h5> 286 * <p> 287 * This {@code <script>} MUST come after {@code juneau-regions.js} (itself after {@code juneau-views.js}) and 288 * before {@code juneau-helpers.js}. It registers its populator with {@code juneau-regions.js} at load time; 289 * icons are built through {@code JuneauViews.helpers.icon}, which is resolved at call time, so loading before 290 * {@code juneau-helpers.js} is safe. 291 * 292 * @since 10.0.0 293 */ 294 public static final String RUN_VIEW_JS_PATH = "/juneau-terminal-view.js"; 295 296 /** 297 * The URL path at which the opt-in column-chooser runtime is served (relative to the host mount). A 298 * consumer adds this {@code <script>} after {@code juneau-views.js}; a non-configurable table never loads it. 299 */ 300 public static final String CONFIG_JS_PATH = "/juneau-config.js"; 301 302 /** 303 * The URL path at which the opt-in column-chooser stylesheet is served (relative to the host mount). 304 */ 305 public static final String CONFIG_CSS_PATH = "/juneau-config.css"; 306 307 /** 308 * The URL path at which the opt-in reusable-calendar runtime is served (relative to the host mount). A page 309 * with no calendar never loads it. 310 * 311 * <h5 class='section'>Load order is a contract, not a preference:</h5> 312 * <p> 313 * This {@code <script>} MUST come after {@code juneau-views.js}. The calendar's {@code "+N more"} popover is 314 * pushed onto the ONE shared layer stack that {@code juneau-views.js} publishes, so that Escape, focus return 315 * and z-order are the same as every other layer in the page. The calendar deliberately defines no second 316 * stack of its own: if the shared one is absent it fails loud (console error plus a visible in-cell refusal) 317 * rather than quietly opening a popover nothing can dismiss. 318 * 319 * @deprecated The calendar runtime now ships in the widget module; compose 320 * {@link WidgetsMixin} and use its constant of the same name. This one 321 * remains only so that an application composing this mixin alone keeps serving the asset at the same URL. 322 */ 323 @Deprecated(since = "10.0.0", forRemoval = false) 324 public static final String CALENDAR_JS_PATH = "/juneau-calendar.js"; 325 326 /** 327 * The URL path at which the opt-in reusable-calendar stylesheet is served (relative to the host mount). 328 * 329 * @deprecated The calendar stylesheet now ships in the widget module; compose 330 * {@link WidgetsMixin} and use its constant of the same name. This one 331 * remains only so that an application composing this mixin alone keeps serving the asset at the same URL. 332 */ 333 @Deprecated(since = "10.0.0", forRemoval = false) 334 public static final String CALENDAR_CSS_PATH = "/juneau-calendar.css"; 335 336 /** 337 * The URL path at which the console shell runtime is served (relative to the host mount). 338 * 339 * <p> 340 * {@code juneau-console.js} mounts the C1 page contract (header, nav, cards, footer) from a page's 341 * {@code #juneau-page} script island. This module takes a compile-scope dependency on 342 * {@code juneau-console} so that a {@code views}-only application can serve the console shell 343 * from the same mixin it already composes, without separately adding {@link ConsoleChromeMixin}. The toolkit 344 * pack ({@code juneau-views.js} and siblings) deliberately does <b>not</b> bundle this script: a console page's 345 * shell loads it once via {@code PageCapture.writeBody}, and a second copy from the toolkit pack would 346 * double-load the runtime. 347 * 348 * <h5 class='section'>Example:</h5> 349 * <p class='bjava'> 350 * <jc>// A views-only application serves the console shell at the same stable (unprefixed) path ConsoleChromeMixin serves it at.</jc> 351 * <ja>@Rest</ja>(mixins=ViewsMixin.<jk>class</jk>) 352 * <jk>public class</jk> MyResource {} 353 * 354 * <jc>// GET /juneau-console.js now returns the shell runtime.</jc> 355 * </p> 356 */ 357 public static final String CONSOLE_JS_PATH = ConsoleChromeMixin.CONSOLE_JS_PATH_UNPREFIXED; 358 359 /** 360 * The frozen {@code VIEW_META} contract-version handshake constant. FTL {@code <@card type="datatables">} 361 * catalogs emit this on the lifted {@code view} object. 362 * 363 * <p> 364 * Templates never author {@code view.contractVersion}: the framework stamps this value when it lifts the catalog, 365 * so a contract bump never touches adopter templates. A pre-built envelope body is rejected at render time, and 366 * {@code ConsoleTemplateValidator}'s {@code view-contract-version} rule flags a literal pin statically. 367 */ 368 public static final String CONTRACT_VERSION = "5"; 369 370 /** 371 * The app-header refresh-envelope contract-version handshake constant that the console shell bakes in. 372 * Deliberately distinct from {@link #CONTRACT_VERSION} and {@link #BAR_CONTRACT_VERSION}: a header-envelope 373 * revision must never force a view-sidecar or bar-sidecar bump, or vice-versa. 374 */ 375 public static final String HEADER_CONTRACT_VERSION = "1"; 376 377 /** 378 * The bar-slot refresh-envelope contract-version handshake constant that the console shell bakes in. 379 * Deliberately a distinct constant from {@link #HEADER_CONTRACT_VERSION} (see that constant). 380 */ 381 public static final String BAR_CONTRACT_VERSION = "1"; 382 383 /** 384 * The slot-envelope ({@code SLOT_META}) contract-version handshake constant that {@code juneau-views.js} 385 * bakes in. Deliberately a distinct constant from {@link #CONTRACT_VERSION} (the 386 * inner {@code VIEW_META} object): a slot-envelope revision must never force a view-sidecar bump, or vice-versa. 387 */ 388 public static final String SLOT_CONTRACT_VERSION = "1"; 389 390 /** Classpath location of the shipped initializer. */ 391 static final String VIEWS_JS_RESOURCE = "/org/apache/juneau/views/juneau-views.js"; 392 393 /** Classpath location of the DataTables glue script, shipped by {@code juneau-console-datatables}. */ 394 static final String DATATABLES_JS_RESOURCE = "/org/apache/juneau/console/datatables/juneau-datatables.js"; 395 396 /** Classpath location of the shipped ribbon runtime. */ 397 static final String RIBBON_JS_RESOURCE = "/org/apache/juneau/views/juneau-ribbon.js"; 398 399 /** Classpath location of the shipped renderer registry. */ 400 static final String RENDERS_JS_RESOURCE = "/org/apache/juneau/views/juneau-renders.js"; 401 402 /** Classpath location of the shipped base stylesheet. */ 403 static final String VIEWS_CSS_RESOURCE = "/org/apache/juneau/views/juneau-views.css"; 404 405 /** Classpath location of the shipped icon registry. */ 406 static final String ICONS_JS_RESOURCE = "/org/apache/juneau/views/juneau-icons.js"; 407 408 /** Classpath location of the shipped column-search engine (client mirror of the Java column-search engine). */ 409 static final String SEARCH_JS_RESOURCE = "/org/apache/juneau/views/juneau-search.js"; 410 411 /** Classpath location of the shipped in-page message bus ({@code JuneauViews.bus}). */ 412 static final String BUS_JS_RESOURCE = "/org/apache/juneau/views/juneau-bus.js"; 413 414 /** Classpath location of the shipped general page-state store. */ 415 static final String PAGESTATE_JS_RESOURCE = "/org/apache/juneau/views/juneau-pagestate.js"; 416 417 /** Classpath location of the shipped shareable-link URL-state codec. */ 418 static final String URLSTATE_JS_RESOURCE = "/org/apache/juneau/views/juneau-urlstate.js"; 419 420 /** Classpath location of the shipped SVG symbol sprite (Juneau's own set, default pack). */ 421 static final String SYMBOLS_SVG_RESOURCE = "/org/apache/juneau/views/juneau-symbols.svg"; 422 423 /** Classpath location of the opt-in Material Symbols Outlined sprite. */ 424 static final String SYMBOLS_MATERIAL_SVG_RESOURCE = "/org/apache/juneau/views/juneau-symbols-material.svg"; 425 426 /** Classpath location of the shipped region-populate runtime. */ 427 static final String REGIONS_JS_RESOURCE = "/org/apache/juneau/views/juneau-regions.js"; 428 429 /** Classpath location of the shipped region-populate paint library. */ 430 static final String HELPERS_JS_RESOURCE = "/org/apache/juneau/views/juneau-helpers.js"; 431 432 /** Classpath location of the shipped DataRegistry runtime. */ 433 static final String DATA_JS_RESOURCE = "/org/apache/juneau/views/juneau-data.js"; 434 435 /** Classpath location of the shipped terminal-view module. */ 436 static final String RUN_VIEW_JS_RESOURCE = "/org/apache/juneau/views/juneau-terminal-view.js"; 437 438 /** Classpath location of the shipped column-chooser runtime. */ 439 static final String CONFIG_JS_RESOURCE = "/org/apache/juneau/views/juneau-config.js"; 440 441 /** Classpath location of the shipped column-chooser stylesheet. */ 442 static final String CONFIG_CSS_RESOURCE = "/org/apache/juneau/views/juneau-config.css"; 443 444 /** 445 * Classpath location of the reusable-calendar runtime, which the <b>widget</b> module now ships. 446 * 447 * <p> 448 * The two widget-owned assets below are read out of the widget module's classpath (a compile-scope dependency of 449 * this one), never copied into this module's resources. Reading rather than copying is what makes this mixin's 450 * deprecated accessor serve the same bytes the widget mixin serves, and it is why the absent-from-this-module 451 * guard in the serving test can assert a move rather than a duplication. 452 */ 453 static final String CALENDAR_JS_RESOURCE = "/org/apache/juneau/widgets/juneau-calendar.js"; 454 455 /** Classpath location of the reusable-calendar stylesheet, which the widget module now ships (see {@link #CALENDAR_JS_RESOURCE}). */ 456 static final String CALENDAR_CSS_RESOURCE = "/org/apache/juneau/widgets/juneau-calendar.css"; 457 458 /** Content type emitted for the JavaScript assets. */ 459 static final String JS_CONTENT_TYPE = "text/javascript;charset=utf-8"; 460 461 /** Content type emitted for the stylesheet asset. */ 462 static final String CSS_CONTENT_TYPE = "text/css;charset=utf-8"; 463 464 /** Content type emitted for the SVG symbol sprite. */ 465 static final String SVG_CONTENT_TYPE = "image/svg+xml"; 466 467 /** {@code Cache-Control} header emitted for every asset (1 day). */ 468 static final String CACHE_CONTROL = "max-age=86400, public"; 469 470 /** 471 * Read+cache+hash+serve helper for this mixin's shipped assets, anchored on this class so 472 * {@link ClasspathAssetCache#buildVersion()} resolves this module's own implementation version (see that 473 * class's javadoc's version-anchor section). 474 */ 475 private static final ClasspathAssetCache ASSET_CACHE = new ClasspathAssetCache(ViewsMixin.class); 476 477 /** 478 * The same helper, but anchored on the <b>widget</b> mixin, for the two assets that module now ships and this 479 * one only keeps deprecated mounts for. 480 * 481 * <p> 482 * The anchor governs two things: which classpath the bytes are read from (irrelevant here — the widget 483 * module is a compile-scope dependency, so either anchor finds the same bytes) and which module's 484 * implementation version the {@code ?v=<buildVersion>-<hash8>} cache-buster carries. Anchoring the relocated 485 * two on the widget mixin makes this mixin's deprecated URL for an asset <b>byte-identical</b> to the widget 486 * mixin's URL for it, buster included, rather than merely serving the same body behind two differently-versioned 487 * URLs. A page that mixes both mixins therefore cannot end up caching the same script twice. 488 */ 489 private static final ClasspathAssetCache WIDGET_ASSET_CACHE = new ClasspathAssetCache(WidgetsMixin.class); 490 491 /** 492 * The same helper, but anchored on the <b>console-ui</b> mixin, for the console shell script that module ships 493 * and this one serves at {@link #CONSOLE_JS_PATH}. Anchoring on {@link ConsoleChromeMixin} (rather than this 494 * class) makes this mount's {@code ?v=<buildVersion>-<hash8>} cache-buster identical to 495 * {@code ConsoleChromeMixin}'s own mount of the same bytes, so a page that composes both mixins cannot end up 496 * caching the same script twice under two different busters. 497 */ 498 private static final ClasspathAssetCache CONSOLE_ASSET_CACHE = new ClasspathAssetCache(ConsoleChromeMixin.class); 499 500 /** 501 * The same helper, but anchored on {@link DataTablesMixin}, for {@link #DATATABLES_JS_PATH}: the cache-buster 502 * carries the datatables module's implementation version, since that module ships (and serves) the bytes. 503 */ 504 private static final ClasspathAssetCache DATATABLES_ASSET_CACHE = new ClasspathAssetCache(DataTablesMixin.class); 505 506 /** 507 * [GET /juneau-views.js] — serve the client initializer. 508 * 509 * @return The initializer as a JavaScript {@link HttpResource}. 510 */ 511 @RestGet( 512 path=VIEWS_JS_PATH, 513 summary="Juneau rich-view client initializer", 514 description="First-party JavaScript that auto-initializes <table data-juneau-view> elements from their VIEW_META sidecar.", 515 swagger=@OpSwagger(ignore=true) 516 ) 517 public HttpResource getViewsScript() { 518 return serve(VIEWS_JS_RESOURCE, JS_CONTENT_TYPE); 519 } 520 521 /** 522 * [GET /juneau-ribbon.js] — serve the ribbon/toolbar runtime. 523 * 524 * @return The ribbon runtime as a JavaScript {@link HttpResource}. 525 */ 526 @RestGet( 527 path=RIBBON_JS_PATH, 528 summary="Juneau rich-view ribbon runtime", 529 description="First-party JavaScript that builds the ribbon/toolbar from a view's VIEW_META ribbon actions.", 530 swagger=@OpSwagger(ignore=true) 531 ) 532 public HttpResource getRibbonScript() { 533 return serve(RIBBON_JS_RESOURCE, JS_CONTENT_TYPE); 534 } 535 536 /** 537 * [GET /juneau-renders.js] — serve the renderer registry. 538 * 539 * @return The renderer registry as a JavaScript {@link HttpResource}. 540 */ 541 @RestGet( 542 path=RENDERS_JS_PATH, 543 summary="Juneau rich-view renderer registry", 544 description="First-party, dependency-free JavaScript cell-renderer registry consumed by the view initializer.", 545 swagger=@OpSwagger(ignore=true) 546 ) 547 public HttpResource getRendersScript() { 548 return serve(RENDERS_JS_RESOURCE, JS_CONTENT_TYPE); 549 } 550 551 /** 552 * [GET /juneau-views.css] — serve the base view stylesheet. 553 * 554 * @return The base stylesheet as a CSS {@link HttpResource}. 555 */ 556 @RestGet( 557 path=VIEWS_CSS_PATH, 558 summary="Juneau rich-view base stylesheet", 559 description="First-party base '.tag' chip stylesheet (neutral, no colors); console-ui themes the same classes when present.", 560 swagger=@OpSwagger(ignore=true) 561 ) 562 public HttpResource getViewsStylesheet() { 563 return serve(VIEWS_CSS_RESOURCE, CSS_CONTENT_TYPE); 564 } 565 566 /** 567 * [GET /juneau-icons.js] — serve the icon registry. 568 * 569 * @return The icon registry as a JavaScript {@link HttpResource}. 570 */ 571 @RestGet( 572 path=ICONS_JS_PATH, 573 summary="Juneau rich-view icon registry", 574 description="First-party, dependency-free JavaScript icon registry (name -> inline-SVG markup) consumed by the ribbon/paging-pill runtime.", 575 swagger=@OpSwagger(ignore=true) 576 ) 577 public HttpResource getIconsScript() { 578 return serve(ICONS_JS_RESOURCE, JS_CONTENT_TYPE); 579 } 580 581 /** 582 * [GET /juneau-search.js] — serve the column-search engine. 583 * 584 * @return The column-search engine as a JavaScript {@link HttpResource}. 585 */ 586 @RestGet( 587 path=SEARCH_JS_PATH, 588 summary="Juneau rich-view column-search engine", 589 description="First-party, dependency-free JavaScript mirror of the server-side column-search engine (parser, operator metadata, and per-type leaf semantics) published as JuneauViews.search.", 590 swagger=@OpSwagger(ignore=true) 591 ) 592 public HttpResource getSearchScript() { 593 return serve(SEARCH_JS_RESOURCE, JS_CONTENT_TYPE); 594 } 595 596 /** 597 * [GET /juneau-pagestate.js] — serve the general page-state store. 598 * 599 * @return The page-state store as a JavaScript {@link HttpResource}. 600 */ 601 @RestGet( 602 path=PAGESTATE_JS_PATH, 603 summary="Juneau rich-view page-state store", 604 description="First-party, dependency-free JavaScript key/value store (per-table and per-page namespaces, localStorage-backed with a blocked-storage no-op fallback) published as JuneauViews.pageState.", 605 swagger=@OpSwagger(ignore=true) 606 ) 607 public HttpResource getPageStateScript() { 608 return serve(PAGESTATE_JS_RESOURCE, JS_CONTENT_TYPE); 609 } 610 611 /** 612 * [GET /juneau-urlstate.js] — serve the shareable-link URL-state codec. 613 * 614 * @return The URL-state codec as a JavaScript {@link HttpResource}. 615 */ 616 @RestGet( 617 path=URLSTATE_JS_PATH, 618 summary="Juneau rich-view shareable-link URL-state codec", 619 description="First-party, dependency-free JavaScript codec for the one ?state= query parameter (tab/filter/sort of the primary table only, never View Settings or nested tables) published as JuneauViews.urlState.", 620 swagger=@OpSwagger(ignore=true) 621 ) 622 public HttpResource getUrlStateScript() { 623 return serve(URLSTATE_JS_RESOURCE, JS_CONTENT_TYPE); 624 } 625 626 /** 627 * [GET /juneau-bus.js] — serve the in-page message bus. 628 * 629 * @return The message bus as a JavaScript {@link HttpResource}. 630 */ 631 @RestGet( 632 path=BUS_JS_PATH, 633 summary="Juneau in-page message bus", 634 description="Dependency-free topic bus (JuneauViews.bus): declare/claim, publish/subscribe with retention, frame and source bridges.", 635 swagger=@OpSwagger(ignore=true) 636 ) 637 public HttpResource getBusScript() { 638 return serve(BUS_JS_RESOURCE, JS_CONTENT_TYPE); 639 } 640 641 /** 642 * [GET /juneau-symbols.svg] — serve the shared SVG symbol sprite. 643 * 644 * @return The sprite as an SVG {@link HttpResource}. 645 */ 646 @RestGet( 647 path=SYMBOLS_SVG_PATH, 648 summary="Juneau rich-view SVG symbol sprite", 649 description="Shared SVG <symbol> sprite referenced by juneau-icons.js <use href> hosts. The key/legend file is not served.", 650 swagger=@OpSwagger(ignore=true) 651 ) 652 public HttpResource getSymbolsSvg() { 653 return serve(SYMBOLS_SVG_RESOURCE, SVG_CONTENT_TYPE); 654 } 655 656 /** 657 * [GET /juneau-symbols-material.svg] — serve the opt-in Material Symbols Outlined sprite. 658 * 659 * @return The Material sprite as an SVG {@link HttpResource}. 660 */ 661 @RestGet( 662 path=SYMBOLS_MATERIAL_SVG_PATH, 663 summary="Juneau rich-view Material Symbols SVG sprite (opt-in)", 664 description="NOTICE-attributed Material Symbols Outlined sprite. Apps normally supply their own set via the icon replacementUrl (data-juneau-icon-replacement); JuneauViews.icons.pack(\"material\") remains the legacy base selector. Default remains juneau-symbols.svg.", 665 swagger=@OpSwagger(ignore=true) 666 ) 667 public HttpResource getSymbolsMaterialSvg() { 668 return serve(SYMBOLS_MATERIAL_SVG_RESOURCE, SVG_CONTENT_TYPE); 669 } 670 671 /** 672 * [GET /juneau-regions.js] — serve the opt-in region-populate runtime. 673 * 674 * @return The region-populate runtime as a JavaScript {@link HttpResource}. 675 */ 676 @RestGet( 677 path=REGIONS_JS_PATH, 678 summary="Juneau rich-view region-populate runtime", 679 description="First-party, opt-in JavaScript that resolves a name-keyed populator against a data-juneau-region container.", 680 swagger=@OpSwagger(ignore=true) 681 ) 682 public HttpResource getRegionsScript() { 683 return serve(REGIONS_JS_RESOURCE, JS_CONTENT_TYPE); 684 } 685 686 /** 687 * [GET /juneau-helpers.js] — serve the opt-in region-populate paint library. 688 * 689 * @return The paint library as a JavaScript {@link HttpResource}. 690 */ 691 @RestGet( 692 path=HELPERS_JS_PATH, 693 summary="Juneau rich-view region-populate paint library", 694 description="First-party, opt-in JavaScript helper library (fieldGrid, kvTable, tabStrip, dataPane, recordTable, and the rest of JuneauViews.helpers) for a data-juneau-region populator.", 695 swagger=@OpSwagger(ignore=true) 696 ) 697 public HttpResource getHelpersScript() { 698 return serve(HELPERS_JS_RESOURCE, JS_CONTENT_TYPE); 699 } 700 701 /** 702 * [GET /juneau-data.js] — serve the DataRegistry runtime. 703 * 704 * @return The runtime as a JavaScript {@link HttpResource}. 705 */ 706 @RestGet( 707 path=DATA_JS_PATH, 708 summary="Juneau DataRegistry runtime", 709 description="First-party JavaScript holding the columnar frames behind data: bus topics (JuneauViews.data).", 710 swagger=@OpSwagger(ignore=true) 711 ) 712 public HttpResource getDataScript() { 713 return serve(DATA_JS_RESOURCE, JS_CONTENT_TYPE); 714 } 715 716 /** 717 * [GET /juneau-terminal-view.js] — serve the opt-in terminal-view module. 718 * 719 * @return The terminal-view module as a JavaScript {@link HttpResource}. 720 * @since 10.0.0 721 */ 722 @RestGet( 723 path=RUN_VIEW_JS_PATH, 724 summary="Juneau rich-view terminal-view module", 725 description="First-party, opt-in JavaScript for the terminal-view region: a live test and build run summary with suites, failures and notes.", 726 swagger=@OpSwagger(ignore=true) 727 ) 728 public HttpResource getTerminalViewScript() { 729 return serve(RUN_VIEW_JS_RESOURCE, JS_CONTENT_TYPE); 730 } 731 732 /** 733 * [GET /juneau-config.js] — serve the opt-in column-chooser runtime (column configuration is localStorage-persisted). 734 * 735 * @return The column-chooser runtime as a JavaScript {@link HttpResource}. 736 */ 737 @RestGet( 738 path=CONFIG_JS_PATH, 739 summary="Juneau rich-view column-chooser runtime", 740 description="First-party, opt-in JavaScript that renders the View-tab column chooser and column configuration (localStorage-persisted) for a columnConfig view.", 741 swagger=@OpSwagger(ignore=true) 742 ) 743 public HttpResource getConfigScript() { 744 return serve(CONFIG_JS_RESOURCE, JS_CONTENT_TYPE); 745 } 746 747 /** 748 * [GET /juneau-config.css] — serve the opt-in column-chooser stylesheet. 749 * 750 * @return The column-chooser stylesheet as a CSS {@link HttpResource}. 751 */ 752 @RestGet( 753 path=CONFIG_CSS_PATH, 754 summary="Juneau rich-view column-chooser stylesheet", 755 description="First-party, opt-in CSS for the View-tab column chooser dialog.", 756 swagger=@OpSwagger(ignore=true) 757 ) 758 public HttpResource getConfigStylesheet() { 759 return serve(CONFIG_CSS_RESOURCE, CSS_CONTENT_TYPE); 760 } 761 762 /** 763 * Returns the servlet-relative URL for a served asset, carrying a {@code ?v=<buildVersion>-<hash8>} content- 764 * sensitive cache-buster suitable for a page's {@code head=} block (see the class Javadoc's cache-busting 765 * section for why the buster is content- rather than purely version-keyed). 766 * 767 * @param path One of the asset path constants ({@link #VIEWS_JS_PATH}, {@link #RIBBON_JS_PATH}, 768 * {@link #BUS_JS_PATH}, {@link #RENDERS_JS_PATH}, {@link #VIEWS_CSS_PATH}, {@link #ICONS_JS_PATH}, {@link #SYMBOLS_SVG_PATH}, 769 * {@link #SYMBOLS_MATERIAL_SVG_PATH}, {@link #REGIONS_JS_PATH}, {@link #HELPERS_JS_PATH}, {@link #DATA_JS_PATH}, {@link #RUN_VIEW_JS_PATH}, {@link #CONFIG_JS_PATH}, {@link #CONFIG_CSS_PATH}, 770 * {@link #CALENDAR_JS_PATH}, {@link #CALENDAR_CSS_PATH}, {@link #DATATABLES_JS_PATH}). 771 * @return The servlet-relative asset URL with the version+content-hash cache-buster appended. 772 */ 773 public static String viewAssetUrl(String path) { 774 return "servlet:" + path + cacheFor(path).cacheBuster(resourceFor(path)); 775 } 776 777 /** 778 * Returns a real, browser-fetchable <b>absolute</b> URL for a served asset, resolved against the given 779 * request's context path and mount — carrying the same {@code ?v=<buildVersion>-<hash8>} content- 780 * sensitive cache-buster as {@link #viewAssetUrl(String)} (see the class Javadoc's cache-busting section). 781 * 782 * <p> 783 * {@link #viewAssetUrl(String)} returns a {@code servlet:}-prefixed URL that only Juneau's own HTML serializer 784 * resolves (it rewrites {@code servlet:} against the request at render time). A template-rendering consumer 785 * (e.g. {@code juneau-rest-server-view-freemarker}) sits downstream of that serializer, so it never sees the 786 * rewrite and would otherwise receive the literal, unfetchable string. This overload resolves the URL itself, 787 * per-request, the same way {@code ConsoleChromeMixin.assetUrl(RestRequest, ...)} does — via 788 * {@link RestRequest#getUriResolver()} — so it is usable from any consumer, template-rendered or not. 789 * 790 * @param req The current request, supplying the context path/mount to resolve against. 791 * @param path One of the asset path constants ({@link #VIEWS_JS_PATH}, {@link #RIBBON_JS_PATH}, 792 * {@link #BUS_JS_PATH}, {@link #RENDERS_JS_PATH}, {@link #VIEWS_CSS_PATH}, {@link #ICONS_JS_PATH}, {@link #SYMBOLS_SVG_PATH}, 793 * {@link #SYMBOLS_MATERIAL_SVG_PATH}, {@link #REGIONS_JS_PATH}, {@link #HELPERS_JS_PATH}, {@link #DATA_JS_PATH}, {@link #RUN_VIEW_JS_PATH}, {@link #CONFIG_JS_PATH}, {@link #CONFIG_CSS_PATH}, 794 * {@link #CALENDAR_JS_PATH}, {@link #CALENDAR_CSS_PATH}, {@link #DATATABLES_JS_PATH}). 795 * @return The absolute asset URL with the version+content-hash cache-buster appended. 796 */ 797 public static String viewAssetUrl(RestRequest req, String path) { 798 return req.getUriResolver().resolve("servlet:" + path) + cacheFor(path).cacheBuster(resourceFor(path)); 799 } 800 801 /** 802 * [GET /juneau-calendar.js] — serve the opt-in reusable-calendar runtime from the widget module's classpath. 803 * 804 * @return The calendar runtime as a JavaScript {@link HttpResource}. 805 * @deprecated Compose {@link WidgetsMixin} instead, which ships these bytes. This mount stays so that an 806 * application composing only this mixin keeps serving the asset at the same URL with the same body. 807 */ 808 @Deprecated(since = "10.0.0", forRemoval = false) 809 @RestGet( 810 path=CALENDAR_JS_PATH, 811 summary="Juneau reusable-calendar runtime (relocated)", 812 description="Deprecated compatibility mount. The calendar runtime now ships in juneau-console-widgets; these bytes are read from that module and are identical to the ones WidgetsMixin serves.", 813 swagger=@OpSwagger(ignore=true) 814 ) 815 public HttpResource getCalendarScript() { 816 return WIDGET_ASSET_CACHE.serve(CALENDAR_JS_RESOURCE, JS_CONTENT_TYPE, CACHE_CONTROL); 817 } 818 819 /** 820 * [GET /juneau-calendar.css] — serve the opt-in reusable-calendar stylesheet from the widget module's classpath. 821 * 822 * @return The calendar stylesheet as a CSS {@link HttpResource}. 823 * @deprecated Compose {@link WidgetsMixin} instead, which ships these bytes. This mount stays so that an 824 * application composing only this mixin keeps serving the asset at the same URL with the same body. 825 */ 826 @Deprecated(since = "10.0.0", forRemoval = false) 827 @RestGet( 828 path=CALENDAR_CSS_PATH, 829 summary="Juneau reusable-calendar stylesheet (relocated)", 830 description="Deprecated compatibility mount. The calendar stylesheet now ships in juneau-console-widgets; these bytes are read from that module and are identical to the ones WidgetsMixin serves.", 831 swagger=@OpSwagger(ignore=true) 832 ) 833 public HttpResource getCalendarStylesheet() { 834 return WIDGET_ASSET_CACHE.serve(CALENDAR_CSS_RESOURCE, CSS_CONTENT_TYPE, CACHE_CONTROL); 835 } 836 837 /** 838 * [GET /juneau-console.js] — serve the console shell runtime from the console-ui module's classpath. 839 * 840 * @return The console shell runtime as a JavaScript {@link HttpResource}. 841 */ 842 @RestGet( 843 path=CONSOLE_JS_PATH, 844 summary="Juneau console shell runtime", 845 description="Renders the #juneau-page contract into header, nav, cards and footer; served from the console-ui module's classpath.", 846 swagger=@OpSwagger(ignore=true) 847 ) 848 public HttpResource getConsoleJs() { 849 return CONSOLE_ASSET_CACHE.serve(ConsoleChromeMixin.CONSOLE_JS_RESOURCE, JS_CONTENT_TYPE, CACHE_CONTROL); 850 } 851 852 /** Reads (and caches) the classpath asset and wraps it as a cacheable {@link HttpResource}. */ 853 private static HttpResource serve(String resource, String contentType) { 854 return ASSET_CACHE.serve(resource, contentType, CACHE_CONTROL); 855 } 856 857 /** 858 * Selects the cache that owns the given asset path: the two relocated widget assets hash and version through 859 * the widget module's cache (see {@link #WIDGET_ASSET_CACHE}), the DataTables glue through the datatables 860 * module's (see {@link #DATATABLES_ASSET_CACHE}), everything else through this module's own. 861 */ 862 private static ClasspathAssetCache cacheFor(String path) { 863 if (eqa(path, CALENDAR_JS_PATH, CALENDAR_CSS_PATH)) 864 return WIDGET_ASSET_CACHE; 865 if (eq(path, DATATABLES_JS_PATH)) 866 return DATATABLES_ASSET_CACHE; 867 return ASSET_CACHE; 868 } 869 870 /** Maps a public asset path constant to its classpath resource constant (content-hashing only; routing itself is by {@code @RestGet(path=...)}). */ 871 @SuppressWarnings({ 872 "java:S3776" // Flat path-to-resource lookup table; one branch per asset, no nesting. 873 }) 874 private static String resourceFor(String path) { 875 if (eq(path, VIEWS_JS_PATH)) return VIEWS_JS_RESOURCE; 876 if (eq(path, RIBBON_JS_PATH)) return RIBBON_JS_RESOURCE; 877 if (eq(path, RENDERS_JS_PATH)) return RENDERS_JS_RESOURCE; 878 if (eq(path, VIEWS_CSS_PATH)) return VIEWS_CSS_RESOURCE; 879 if (eq(path, ICONS_JS_PATH)) return ICONS_JS_RESOURCE; 880 if (eq(path, SEARCH_JS_PATH)) return SEARCH_JS_RESOURCE; 881 if (eq(path, PAGESTATE_JS_PATH)) return PAGESTATE_JS_RESOURCE; 882 if (eq(path, URLSTATE_JS_PATH)) return URLSTATE_JS_RESOURCE; 883 if (eq(path, BUS_JS_PATH)) return BUS_JS_RESOURCE; 884 if (eq(path, SYMBOLS_SVG_PATH)) return SYMBOLS_SVG_RESOURCE; 885 if (eq(path, SYMBOLS_MATERIAL_SVG_PATH)) return SYMBOLS_MATERIAL_SVG_RESOURCE; 886 if (eq(path, REGIONS_JS_PATH)) return REGIONS_JS_RESOURCE; 887 if (eq(path, HELPERS_JS_PATH)) return HELPERS_JS_RESOURCE; 888 if (eq(path, DATA_JS_PATH)) return DATA_JS_RESOURCE; 889 if (eq(path, RUN_VIEW_JS_PATH)) return RUN_VIEW_JS_RESOURCE; 890 if (eq(path, CONFIG_JS_PATH)) return CONFIG_JS_RESOURCE; 891 if (eq(path, CONFIG_CSS_PATH)) return CONFIG_CSS_RESOURCE; 892 if (eq(path, CALENDAR_JS_PATH)) return CALENDAR_JS_RESOURCE; 893 if (eq(path, CALENDAR_CSS_PATH)) return CALENDAR_CSS_RESOURCE; 894 if (eq(path, DATATABLES_JS_PATH)) return DATATABLES_JS_RESOURCE; 895 throw new IllegalArgumentException("Unknown asset path: " + path); 896 } 897}