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>)-&gt;<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> -&gt; <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}