001package io.jstach.jstachio.spi; 002 003import java.lang.System.Logger; 004import java.util.List; 005import java.util.ResourceBundle; 006 007import org.eclipse.jdt.annotation.NonNull; 008import org.eclipse.jdt.annotation.Nullable; 009 010import io.jstach.jstache.JStacheConfig; 011 012/** 013 * Runtime Config Service. 014 * <p> 015 * While a majority of jstachio config is static and done at compile time some config like 016 * logging and disabling extensions is needed at runtime. Config and DI agnostic 017 * extensions should use this facility for simple key value based config. 018 * <p> 019 * The default resolved config uses System properties but can be replaced by implementing 020 * this extension. 021 * <p> 022 * Core runtime configuration properties for the {@link JStachioFactory#defaultJStachio() 023 * default JStachio} are: 024 * <ul id="_jstachio_config_properties"> 025 * <li>{@link #REFLECTION_TEMPLATE_DISABLE}</li> 026 * <li>{@link #SERVICELOADER_TEMPLATE_DISABLE}</li> 027 * <li>{@link #LOGGING_DISABLE}</li> 028 * </ul> 029 * <strong>This configuration is for runtime only and NOT {@linkplain JStacheConfig static 030 * configuration} needed for code generation.</strong> 031 * 032 * @see JStachioExtension 033 * @author agentgt 034 */ 035public non-sealed interface JStachioConfig extends JStachioExtension { 036 037 /** 038 * Config key to disable non service loader reflection based lookup of templates. If a 039 * custom JStachio is being used this configuration property maybe irrelevant. 040 * <p> 041 * Valid values are <code>true</code> or <code>false</code>. The default is 042 * <code>false</code>. 043 */ 044 public static String REFLECTION_TEMPLATE_DISABLE = "jstachio.reflection.template.disable"; 045 046 /** 047 * Config key to disable service loader based lookup of templates. If a custom 048 * JStachio is being used this configuration property maybe irrelevant. 049 * <p> 050 * Valid values are <code>true</code> or <code>false</code>. The default is 051 * <code>false</code>. 052 */ 053 public static String SERVICELOADER_TEMPLATE_DISABLE = "jstachio.serviceloader.template.disable"; 054 055 /** 056 * Config key to disable logging. By default logging is enabled and will use the 057 * {@link System.Logger}. If a custom {@link JStachioConfig} is being used this 058 * configuration property maybe irrelevant. 059 * <p> 060 * Valid values are <code>true</code> or <code>false</code>. The default is 061 * <code>false</code>. 062 */ 063 public static String LOGGING_DISABLE = "jstachio.logging.disable"; 064 065 /** 066 * Gets a property from some config implementation. 067 * @param key the key to use to lookup 068 * @return if not found <code>null</code>. 069 */ 070 public @Nullable String getProperty(String key); 071 072 /** 073 * See {@link Boolean#getBoolean(String)}. 074 * @param key the property key 075 * @return only true if string is "true" 076 */ 077 default boolean getBoolean(String key) { 078 String prop = getProperty(key); 079 return Boolean.parseBoolean(prop); 080 } 081 082 /** 083 * Gets the property as a boolean and if no property value is found the fallback is 084 * used. 085 * @param key property key 086 * @param fallback if property has no value this value is used. 087 * @return the parsed boolean or the fallback 088 */ 089 default boolean getBoolean(String key, boolean fallback) { 090 String prop = getProperty(key); 091 if (prop == null) { 092 return fallback; 093 } 094 return Boolean.parseBoolean(prop); 095 } 096 097 /** 098 * A NonNull friendly analog of {@link System#getProperty(String, String)} that will 099 * never return null unlike System.getProperty which is PolyNull. 100 * @param key checked if null and will NPE immediatly if it is 101 * @param fallback used if the retrieved property is null 102 * @return property or fallback if property is not found (<code>null</code>). 103 * @throws NullPointerException if the fallback is null or if the key is null. 104 */ 105 default String requireProperty(String key, String fallback) { 106 if (isNull(key)) { 107 throw new NullPointerException("key is null"); 108 } 109 String v = getProperty(key); 110 if (v == null) { 111 v = fallback; 112 } 113 if (isNull(v)) { 114 throw new NullPointerException("fallback is null. key: " + key); 115 } 116 return v; 117 } 118 119 private static boolean isNull(Object o) { 120 return o == null; 121 } 122 123 /** 124 * Gets a system logger if the property {@link #LOGGING_DISABLE} is 125 * <code>false</code>. If the property is set to a <code>true</code> value a NOOP 126 * Logger <em>that will not trigger initialization of the System {@link Logger} 127 * facilities</em> will be returned. The NOOP logger is always disabled at every level 128 * and will not produce any output. 129 * @param name the name of the logger usually the class. 130 * @return the System logger. 131 * @see #noopLogger() 132 */ 133 default Logger getLogger(String name) { 134 if (!getBoolean(LOGGING_DISABLE)) { 135 return System.getLogger(name); 136 } 137 return noopLogger(); 138 } 139 140 /** 141 * NOOP Logger <em>that will not trigger initialization of the System {@link Logger} 142 * facilities</em>. The NOOP logger is always disabled at every level and will not 143 * produce any output. 144 * <p> 145 * Extensions might find this useful to set a nonnull Logger field like: 146 * <pre><code class="language-java"> 147 * private Logger logger = JStacheConfig.noopLogger(); 148 * public void init(JStacheConfig config) { 149 * logger = config.getLogger(getClass().getName()); 150 * } 151 * </code> </pre> 152 * @return singleton instance of noop logger 153 */ 154 public static Logger noopLogger() { 155 return NOOPLogger.INSTANCE; 156 } 157 158} 159 160enum NOOPLogger implements Logger { 161 162 INSTANCE; 163 164 @Override 165 public @NonNull String getName() { 166 return "NOOPLogger"; 167 } 168 169 @Override 170 public boolean isLoggable(Level level) { 171 return false; 172 } 173 174 @Override 175 public void log(Level level, @Nullable ResourceBundle bundle, @Nullable String msg, @Nullable Throwable thrown) { 176 // Do nothing 177 } 178 179 @Override 180 public void log(Level level, @Nullable ResourceBundle bundle, @Nullable String format, 181 @Nullable Object @NonNull... params) { 182 // Do nothing 183 } 184 185} 186 187enum SystemPropertyConfig implements JStachioConfig { 188 189 INSTANCE; 190 191 @Override 192 public @Nullable String getProperty(String key) { 193 return System.getProperty(key); 194 } 195 196} 197 198class CompositeConfig implements JStachioConfig { 199 200 private final List<JStachioConfig> configs; 201 202 static JStachioConfig of(List<JStachioConfig> configs) { 203 if (configs.isEmpty()) { 204 return SystemPropertyConfig.INSTANCE; 205 } 206 if (configs.size() == 1) { 207 return configs.get(0); 208 } 209 return new CompositeConfig(configs); 210 211 } 212 213 private CompositeConfig(List<JStachioConfig> configs) { 214 super(); 215 this.configs = configs; 216 } 217 218 @Override 219 public @Nullable String getProperty(String key) { 220 for (var c : configs) { 221 String v = c.getProperty(key); 222 if (v != null) { 223 return v; 224 } 225 } 226 return null; 227 } 228 229 @Override 230 public Logger getLogger(String name) { 231 return configs.get(0).getLogger(name); 232 } 233 234}