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 &mdash; 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 &mdash; 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>// &lt;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"&gt;&lt;/script&gt;</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} &mdash; 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} &mdash; 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} &mdash;
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} &mdash; topic declare/claim, publish/subscribe with optional
203    * retention, and the frame and source bridges &mdash; 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>// &lt;script src="${bus}"&gt;&lt;/script&gt;&lt;script src="${views}"&gt;&lt;/script&gt;</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>// &lt;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"&gt;&lt;/script&gt;</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>// &lt;script src="/views/juneau-icons.js" data-juneau-icon-pack="material"&gt;&lt;/script&gt;</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>&#64;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 &mdash; 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] &mdash; 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] &mdash; 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] &mdash; 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] &mdash; 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] &mdash; 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] &mdash; 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] &mdash; 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] &mdash; 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] &mdash; 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] &mdash; 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] &mdash; 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] &mdash; 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] &mdash; 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] &mdash; 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] &mdash; 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] &mdash; 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] &mdash; 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 &mdash; 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 &mdash; via
788    * {@link RestRequest#getUriResolver()} &mdash; 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] &mdash; 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] &mdash; 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] &mdash; 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}