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.*;
022
023import org.apache.juneau.commons.svl.*;
024
025/**
026 * An internal, per-render resolver for a single {@link ServerValues} declaration.
027 *
028 * <p>
029 * A registry is added to a <b>sibling</b> {@link VarResolverSession} bean store so provider values never leak
030 * across requests.  {@code ServerValuesVar} ({@code $FV}) looks this bean up in the session and delegates each
031 * <js>"$FV{name}"</js> resolution to {@link #resolve(VarResolverSession, String)}.
032 *
033 * <p>
034 * Resolution rejects non-scalar provider results (no {@code List.toString()} join) and memoizes only entries
035 * flagged {@link ServerValuesValue#cacheable}.  A missing name returns <jk>null</jk> so the caller's
036 * {@code DefaultingVar} default applies; a provider that throws is allowed to propagate (fail-closed).
037 *
038 * @since 10.0.0
039 */
040public class ServerValuesRegistry {
041
042   private final Map<String,ServerValuesValue> values;
043   private final Map<String,String> memo = new HashMap<>();
044
045   /**
046    * Constructor.
047    *
048    * @param values The value declarations, keyed by interpolation name.
049    */
050   private ServerValuesRegistry(Map<String,ServerValuesValue> values) {
051      this.values = values == null ? Collections.emptyMap() : values;
052   }
053
054   /**
055    * Creates a per-render registry over the specified declaration.
056    *
057    * @param serverValues The declaration.  Must not be <jk>null</jk>.
058    * @return A new {@link ServerValuesRegistry}.
059    */
060   public static ServerValuesRegistry of(ServerValues serverValues) {
061      return new ServerValuesRegistry(serverValues.values);
062   }
063
064   /**
065    * Resolves a single named scalar value.
066    *
067    * @param session The per-render session passed to the provider.
068    * @param name The interpolation name (first {@code DefaultingVar} field only).
069    * @return The resolved scalar as a string, or <jk>null</jk> if the name is not declared or the provider
070    *    returned <jk>null</jk> (so a {@code DefaultingVar} default applies).
071    */
072   public String resolve(VarResolverSession session, String name) {
073      var v = values.get(name);
074      if (v == null)
075         return null;
076      if (v.cacheable && memo.containsKey(name))
077         return memo.get(name);
078      var o = v.provider.apply(session);
079      if (o == null)
080         return null;
081      if (!(o instanceof String || o instanceof Number || o instanceof Boolean))
082         throw iaex("ServerValues provider ''{0}'' returned non-scalar type {1}.", name, o.getClass().getName());
083      var s = o.toString();
084      if (v.cacheable)
085         memo.put(name, s);
086      return s;
087   }
088}