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