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