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