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.rest.debug; 018 019import static org.apache.juneau.Enablement.*; 020import static org.apache.juneau.commons.utils.CollectionUtils.*; 021import static org.apache.juneau.commons.utils.Utils.*; 022 023import java.util.function.*; 024 025import org.apache.juneau.*; 026import org.apache.juneau.commons.collections.*; 027import org.apache.juneau.commons.reflect.*; 028import org.apache.juneau.cp.*; 029import org.apache.juneau.http.response.*; 030import org.apache.juneau.rest.*; 031import org.apache.juneau.rest.annotation.*; 032 033import jakarta.servlet.http.*; 034 035/** 036 * Interface used for selectively turning on debug per request. 037 * 038 * <h5 class='section'>See Also:</h5><ul> 039 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerLoggingAndDebugging">Logging / Debugging</a> 040 * </ul> 041 */ 042public abstract class DebugEnablement { 043 /** 044 * Builder class. 045 */ 046 public static class Builder { 047 048 ReflectionMap.Builder<Enablement> mapBuilder; 049 Enablement defaultEnablement = NEVER; 050 Predicate<HttpServletRequest> conditional; 051 BeanCreator<DebugEnablement> creator; 052 053 /** 054 * Constructor. 055 * 056 * @param beanStore The bean store to use for creating beans. 057 */ 058 protected Builder(BeanStore beanStore) { 059 mapBuilder = ReflectionMap.create(Enablement.class); 060 defaultEnablement = NEVER; 061 conditional = x -> eqic("true", x.getHeader("Debug")); 062 creator = beanStore.createBean(DebugEnablement.class).type(BasicDebugEnablement.class).builder(Builder.class, this); 063 } 064 065 /** 066 * Creates a new {@link DebugEnablement} object from this builder. 067 *s 068 * <p> 069 * Instantiates an instance of the {@link #type(Class) implementation class} or 070 * else {@link BasicDebugEnablement} if implementation class was not specified. 071 * 072 * @return A new {@link DebugEnablement} object. 073 */ 074 public DebugEnablement build() { 075 try { 076 return creator.run(); 077 } catch (Exception e) { 078 throw new InternalServerError(e); 079 } 080 } 081 082 /** 083 * Specifies the predicate to use for conditional debug enablement. 084 * 085 * <p> 086 * Specifies the predicate to use to determine whether debug is enabled when the resolved enablement value 087 * is {@link Enablement#CONDITIONAL CONDITIONAL}. 088 * 089 * <p> 090 * The default value for this setting is <c>(<jv>x</jv>)-><js>"true"</js>.equalsIgnoreCase(<jv>x</jv>.getHeader(<js>"Debug"</js>))</c>. 091 * 092 * @param value The predicate. 093 * @return This object. 094 */ 095 public Builder conditional(Predicate<HttpServletRequest> value) { 096 conditional = value; 097 return this; 098 } 099 100 /** 101 * Specifies the default debug enablement setting if not overridden per class/method. 102 * 103 * <p> 104 * The default value for this setting is {@link Enablement#NEVER NEVER}. 105 * 106 * @param value The default debug enablement setting if not overridden per class/method. 107 * @return This object. 108 */ 109 public Builder defaultEnable(Enablement value) { 110 defaultEnablement = value; 111 return this; 112 } 113 114 /** 115 * Enables or disables debug on the specified classes. 116 * 117 * <p> 118 * Identical to {@link #enable(Enablement, String...)} but allows you to specify specific classes. 119 * 120 * @param enablement 121 * The debug enablement setting to set on the specified classes/methods. 122 * <br>Can be any of the following: 123 * <ul> 124 * <li>{@link Enablement#ALWAYS ALWAYS} - Debug is always enabled. 125 * <li>{@link Enablement#NEVER NEVER} - Debug is always disabled. 126 * <li>{@link Enablement#CONDITIONAL CONDITIONAL} - Debug is enabled when the {@link #conditional(Predicate)} conditional predicate test} passes. 127 * </ul> 128 * @param classes 129 * The classes to set the debug enablement setting on. 130 * @return This object. 131 */ 132 public Builder enable(Enablement enablement, Class<?>...classes) { 133 for (var c : classes) 134 mapBuilder.append(c.getName(), enablement); 135 return this; 136 } 137 138 /** 139 * Enables or disables debug on the specified classes and/or methods. 140 * 141 * <p> 142 * Allows you to target specified debug enablement on specified classes and/or methods. 143 * 144 * @param enablement 145 * The debug enablement setting to set on the specified classes/methods. 146 * <br>Can be any of the following: 147 * <ul> 148 * <li>{@link Enablement#ALWAYS ALWAYS} - Debug is always enabled. 149 * <li>{@link Enablement#NEVER NEVER} - Debug is always disabled. 150 * <li>{@link Enablement#CONDITIONAL CONDITIONAL} - Debug is enabled when the {@link #conditional(Predicate)} conditional predicate test} passes. 151 * </ul> 152 * @param keys 153 * The mapping keys. 154 * <br>Can be any of the following: 155 * <ul> 156 * <li>Full class name (e.g. <js>"com.foo.MyClass"</js>). 157 * <li>Simple class name (e.g. <js>"MyClass"</js>). 158 * <li>All classes (e.g. <js>"*"</js>). 159 * <li>Full method name (e.g. <js>"com.foo.MyClass.myMethod"</js>). 160 * <li>Simple method name (e.g. <js>"MyClass.myMethod"</js>). 161 * <li>A comma-delimited list of anything on this list. 162 * </ul> 163 * @return This object. 164 */ 165 public Builder enable(Enablement enablement, String...keys) { 166 for (var k : keys) 167 mapBuilder.append(k, enablement); 168 return this; 169 } 170 171 /** 172 * Specifies an already-instantiated bean for the {@link #build()} method to return. 173 * 174 * @param value The setting value. 175 * @return This object. 176 */ 177 public Builder impl(DebugEnablement value) { 178 creator.impl(value); 179 return this; 180 } 181 182 /** 183 * Specifies a subclass of {@link DebugEnablement} to create when the {@link #build()} method is called. 184 * 185 * @param value The new value for this setting. 186 * @return This object. 187 */ 188 public Builder type(Class<? extends DebugEnablement> value) { 189 creator.type(value == null ? BasicDebugEnablement.class : value); 190 return this; 191 } 192 } 193 194 /** 195 * Represents no DebugEnablement. 196 */ 197 public abstract class Void extends DebugEnablement { 198 Void(BeanStore beanStore) { 199 super(beanStore); 200 } 201 } 202 203 /** 204 * Static creator. 205 * 206 * @param beanStore The bean store to use for creating beans. 207 * @return A new builder for this object. 208 */ 209 public static Builder create(BeanStore beanStore) { 210 return new Builder(beanStore); 211 } 212 213 private final Enablement defaultEnablement; 214 private final ReflectionMap<Enablement> enablementMap; 215 private final Predicate<HttpServletRequest> conditionalPredicate; 216 217 /** 218 * Constructor. 219 * <p> 220 * Subclasses typically override the {@link #init(BeanStore)} method when using this constructor. 221 * 222 * @param beanStore The bean store containing injectable beans for this enablement. 223 */ 224 public DebugEnablement(BeanStore beanStore) { 225 var builder = init(beanStore); 226 this.defaultEnablement = firstNonNull(builder.defaultEnablement, NEVER); 227 this.enablementMap = builder.mapBuilder.build(); 228 this.conditionalPredicate = firstNonNull(builder.conditional, x -> eqic("true", x.getHeader("Debug"))); 229 } 230 231 /** 232 * Constructor. 233 * 234 * @param builder The builder for this enablement. 235 */ 236 public DebugEnablement(Builder builder) { 237 this.defaultEnablement = firstNonNull(builder.defaultEnablement, NEVER); 238 this.enablementMap = builder.mapBuilder.build(); 239 this.conditionalPredicate = firstNonNull(builder.conditional, x -> eqic("true", x.getHeader("Debug"))); 240 241 } 242 243 /** 244 * Returns <jk>true</jk> if debug is enabled on the specified class and request. 245 * 246 * <p> 247 * This enables debug mode on requests once the matched class is found and before the 248 * Java method is found. 249 * 250 * @param context The context of the {@link Rest}-annotated class. 251 * @param req The HTTP request. 252 * @return <jk>true</jk> if debug is enabled on the specified method and request. 253 */ 254 public boolean isDebug(RestContext context, HttpServletRequest req) { 255 var c = context.getResourceClass(); 256 var e = enablementMap.find(c).findFirst().orElse(defaultEnablement); 257 return e == ALWAYS || (e == CONDITIONAL && isConditionallyEnabled(req)); 258 } 259 260 /** 261 * Returns <jk>true</jk> if debug is enabled on the specified method and request. 262 * 263 * <p> 264 * This enables debug mode after the Java method is found and allows you to enable 265 * debug on individual Java methods instead of the entire class. 266 * 267 * @param context The context of the {@link RestOp}-annotated method. 268 * @param req The HTTP request. 269 * @return <jk>true</jk> if debug is enabled on the specified method and request. 270 */ 271 public boolean isDebug(RestOpContext context, HttpServletRequest req) { 272 var m = context.getJavaMethod(); 273 var e = enablementMap.find(m).findFirst().orElseGet(() -> enablementMap.find(m.getDeclaringClass()).findFirst().orElse(defaultEnablement)); 274 return e == ALWAYS || (e == CONDITIONAL && isConditionallyEnabled(req)); 275 } 276 277 protected FluentMap<String,Object> properties() { 278 // @formatter:off 279 return filteredBeanPropertyMap() 280 .a("conditionalPredicate", conditionalPredicate) 281 .a("defaultEnablement", defaultEnablement) 282 .a("enablementMap", enablementMap); 283 // @formatter:on 284 } 285 286 @Override /* Overridden from Object */ 287 public String toString() { 288 return r(properties()); 289 } 290 291 /** 292 * Initializer. 293 * <p> 294 * Subclasses should override this method to make modifications to the builder used to create this logger. 295 * 296 * @param beanStore The bean store containing injectable beans for this logger. 297 * @return A new builder object. 298 */ 299 protected Builder init(BeanStore beanStore) { 300 return new Builder(beanStore); 301 } 302 303 /** 304 * Returns <jk>true</jk> if debugging is conditionally enabled on the specified request. 305 * 306 * <p> 307 * This method only gets called when the enablement value resolves to {@link Enablement#CONDITIONAL CONDITIONAL}. 308 * 309 * <p> 310 * Subclasses can override this method to provide their own implementation. 311 * The default implementation is provided by {@link DebugEnablement.Builder#conditional(Predicate)} 312 * which has a default predicate of <c><jv>x</jv> -> <js>"true"</js>.equalsIgnoreCase(<jv>x</jv>.getHeader(<js>"Debug"</js>)</c>. 313 * 314 * @param req The incoming HTTP request. 315 * @return <jk>true</jk> if debugging is conditionally enabled on the specified request. 316 */ 317 protected boolean isConditionallyEnabled(HttpServletRequest req) { 318 return conditionalPredicate.test(req); 319 } 320}