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.freemarker;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.io.*;
022import java.util.*;
023
024import org.apache.juneau.console.*;
025import org.apache.juneau.console.TopicDecl.*;
026
027import freemarker.core.*;
028import freemarker.template.*;
029
030/**
031 * {@code <@topic name="..." retain=true|false publisher="script|server|ribbon"/>}: declares a page-level custom topic
032 * in the contract's {@code topics} list.  The FTL twin of {@code PageSpec.topic(TopicDecl)}; the two union.
033 *
034 * <h5 class='section'>Example:</h5>
035 * <p class='bcode'>
036 *    &lt;@page&gt;
037 *      &lt;@topic name="app.region-picked" retain=true publisher="script"/&gt;
038 *      &lt;@card type="html" id="region" subscribes="app.region-picked"&gt;...&lt;/@card&gt;
039 *    &lt;/@page&gt;
040 * </p>
041 *
042 * @since 10.0.0
043 */
044@SuppressWarnings({
045   "java:S1192" // Repeated literals are contract wire keys; named constants would obscure the contract shape.
046})
047public final class TopicDirectiveModel implements TemplateDirectiveModel {
048
049   /** The shared-variable name this directive registers under. */
050   public static final String NAME = "topic";
051
052   static final Set<String> ATTRS = Set.of("name", "retain", "publisher");
053
054   TopicDirectiveModel() {}
055
056   @Override
057   @SuppressWarnings({
058      "unchecked" // FreeMarker's raw params Map is String-keyed by contract.
059   })
060   public void execute(Environment env, @SuppressWarnings("rawtypes") Map params, TemplateModel[] loopVars,
061         TemplateDirectiveBody body) throws TemplateException, IOException {
062      Map<String, TemplateModel> p = params;
063      var cap = PageCapture.get(env);
064      if (cap == null || ! (cap.consoleOpen || cap.pageOpen))
065         throw FtlAttrLists.reject("<@topic> must be inside <@console> or <@page>");
066      FtlAttrLists.rejectUnknown(p, NAME, ATTRS);
067      var name = FtlAttrLists.scalar(p, "name");
068      if (! p.containsKey("retain"))
069         throw FtlAttrLists.reject(f("<@topic name='%s'> needs retain=true or retain=false", name));
070      var retain = FtlAttrLists.strictBoolean(p, NAME, "retain", false);
071      var publisher = publisher(name, FtlAttrLists.scalar(p, "publisher"));
072      try {
073         var d = TopicDecl.of(name).retain(retain);
074         if (publisher != null)
075            d.publisher(publisher);
076         cap.addTopic(d.toMap(), "<@topic>");
077      } catch (IllegalArgumentException e) {
078         throw FtlAttrLists.reject(e.getMessage());
079      }
080   }
081
082   private static Publisher publisher(String name, String v) throws TemplateModelException {
083      if (v.isEmpty())
084         return null;  // TopicDecl.toMap() reports the missing publisher with the same text as Java.
085      return switch (v) {
086         case "script" -> Publisher.SCRIPT;
087         case "server" -> Publisher.SERVER;
088         case "ribbon" -> Publisher.RIBBON;
089         default -> throw FtlAttrLists.reject(f("<@topic name='%s'> publisher='%s' must be script, server or ribbon", name, v));
090      };
091   }
092}