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