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.widgets;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022import java.util.function.*;
023
024import org.apache.juneau.commons.svl.*;
025
026/**
027 * A {@link Widget}-marked declaration of named server-side scalar values.
028 *
029 * <p>
030 * This is a thin, typed facade over the existing {@link VarResolver} engine &mdash; <b>not</b> a second
031 * templating language.  An author declares named scalar value providers (for example {@code failedCount} or
032 * {@code lookbackReadable}); a widget emitter publishes them into a per-render registry and interpolates their
033 * resolved values into widget <i>chrome</i> text (titles / labels) as <js>"$FV{name}"</js> /
034 * <js>"$FV{name,default}"</js> at serve time.
035 *
036 * <p>
037 * <b>Not {@code $W}.</b>  {@code $FV} resolves declared scalar values as plain text (serializer-encoded); it is
038 * distinct from {@code $W} ({@code HtmlWidgetVar}), which emits raw widget HTML.  {@code $FV} never injects HTML.
039 *
040 * <p>
041 * Providers must return a scalar ({@link String} / {@link Number} / {@link Boolean}) or <jk>null</jk>; non-scalar
042 * results are rejected at resolve time (no {@code List.toString()} join &mdash; collection rendering is out of
043 * scope here).  Values are session-aware: each provider is a {@link Function} of the per-render
044 * {@link VarResolverSession}.
045 *
046 * <h5 class='section'>Example:</h5>
047 * <p class='bjava'>
048 *    ServerValues <jv>sv</jv> = ServerValues.<jsm>create</jsm>()
049 *       .value(<js>"failedCount"</js>, <jv>session</jv> -&gt; <jv>metrics</jv>.failed())
050 *       .value(<js>"env"</js>, <jv>session</jv> -&gt; <js>"prod"</js>);
051 *    <jc>// Chrome: column title "Failures ($FV{failedCount})" resolves at serve time.</jc>
052 * </p>
053 *
054 * @since 10.0.0
055 */
056@SuppressWarnings({
057   "java:S1845" // Fluent-builder setters intentionally mirror field names (Juneau DSL convention).
058})
059public class ServerValues implements Widget {
060
061   /** The frozen contract version for this widget. */
062   public static final String CONTRACT_VERSION = "1";
063
064   /**
065    * Ordered map of interpolation name &rarr; value declaration.
066    *
067    * <p>
068    * A {@link LinkedHashMap} for stable {@link #validate()} / emit order.  This field is <b>Java-only</b> &mdash;
069    * it is never placed on a {@code @BeanType} and never marshals to VIEW_META (lambda providers are not a wire
070    * type).
071    */
072   public Map<String,ServerValuesValue> values = new LinkedHashMap<>();
073
074   /**
075    * Creates an empty declaration.
076    *
077    * @return A new {@link ServerValues}.
078    */
079   public static ServerValues create() {
080      return new ServerValues();
081   }
082
083   /**
084    * Declares a named scalar value provider.
085    *
086    * @param name The interpolation name (the {@code x} in <js>"$FV{x}"</js>).  Must not be <jk>null</jk> or blank.
087    * @param provider The session-aware scalar provider.  Must not be <jk>null</jk>.
088    * @return This object.
089    */
090   public ServerValues value(String name, Function<VarResolverSession,?> provider) {
091      if (ib(name))
092         throw iaex("ServerValues name must not be null or blank.");
093      if (provider == null)
094         throw iaex("ServerValues provider for ''{0}'' must not be null.", name);
095      if (values.containsKey(name))
096         throw iaex("ServerValues duplicate name ''{0}''.", name);
097      values.put(name, ServerValuesValue.of(name, provider));
098      return this;
099   }
100
101   /**
102    * Sets the full ordered map of value declarations.
103    *
104    * @param value The declarations, keyed by interpolation name.  Must not be <jk>null</jk>.
105    * @return This object.
106    */
107   public ServerValues values(Map<String,ServerValuesValue> value) {
108      values = value;
109      return this;
110   }
111
112   @Override /* Widget */
113   public void validate() {
114      if (values == null)
115         return;
116      for (var e : values.entrySet()) {
117         var name = e.getKey();
118         if (ib(name))
119            throw iaex("ServerValues name must not be null or blank.");
120         var v = e.getValue();
121         if (v == null)
122            throw iaex("ServerValues value for ''{0}'' must not be null.", name);
123         if (v.provider == null)
124            throw iaex("ServerValues provider for ''{0}'' must not be null.", name);
125      }
126   }
127}