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 freemarker.core.*; 025import freemarker.template.*; 026 027/** 028 * The {@code <@navigation>} console FreeMarker directive: opens the nav tree on the {@link PageCapture}. 029 * 030 * <p> 031 * Nested {@code <@node>}s add {@code nav[]} entries of any depth; the shell renders the nav and sets 032 * {@code aria-current}. An omitted {@code layout=} leaves {@code navLayout} out of the contract (the shell defaults 033 * to horizontal). {@code format=}, any unknown attribute, and nesting inside another {@code <@navigation>} or outside 034 * {@code <@console>} are all rejected (fail closed). 035 * 036 * @since 10.0.0 037 */ 038public final class NavigationDirectiveModel implements TemplateDirectiveModel { 039 040 /** The shared-variable name this directive registers under. */ 041 public static final String NAME = "navigation"; 042 043 static final Set<String> ATTRS = Set.of("layout"); 044 045 NavigationDirectiveModel() {} 046 047 @Override 048 @SuppressWarnings({ 049 "unchecked" // FreeMarker's raw params Map is String-keyed by contract. 050 }) 051 public void execute(Environment env, @SuppressWarnings("rawtypes") Map params, TemplateModel[] loopVars, 052 TemplateDirectiveBody body) throws TemplateException, IOException { 053 Map<String, TemplateModel> p = params; 054 if (p.containsKey("format")) 055 throw FtlAttrLists.reject("<@navigation> has no format= attribute."); 056 FtlAttrLists.rejectUnknown(p, NAME, ATTRS); 057 058 var cap = PageCapture.get(env); 059 if (n(cap) || ! cap.consoleOpen) 060 throw FtlAttrLists.reject(f("<@%s> must be nested inside <@console>.", NAME)); 061 if (cap.navOpen) 062 throw FtlAttrLists.reject("<@navigation> cannot be nested inside another <@navigation>."); 063 064 var layout = FtlAttrLists.scalar(p, "layout"); 065 if (! layout.isEmpty()) { 066 if (! (eqa(layout, "horizontal", "vertical"))) 067 throw FtlAttrLists.reject(f( 068 "<@navigation> layout= must be horizontal or vertical; got '%s'.", layout)); 069 cap.navLayout(layout); 070 } 071 072 cap.navOpen = true; 073 cap.navCursor.push(cap.navRoot()); 074 try (var sink = new StringWriter()) { 075 if (nn(body)) 076 body.render(sink); 077 } finally { 078 cap.navCursor.pop(); 079 cap.navOpen = false; 080 } 081 } 082}