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.microservice.examples;
018
019import java.io.*;
020
021import org.apache.juneau.http.classic.response.*;
022import org.apache.juneau.marshall.html.*;
023import org.apache.juneau.microservice.*;
024import org.apache.juneau.microservice.jetty.*;
025import org.apache.juneau.rest.server.*;
026import org.apache.juneau.rest.server.beans.*;
027import org.apache.juneau.rest.server.servlet.*;
028
029/**
030 * Microservice debug utilities.
031 *
032 * <p>
033 * The {@code jetty-thread-dump} navlink is a plain {@code GET /jetty/dump}, which shows the dump.  Writing the dump to
034 * <l>jetty-thread-dump.log</l> needs a real {@code POST /jetty/dump}; a link can't reach it, because the router refuses
035 * any override of a {@code GET} to {@code POST}.  Put the resource behind a CSRF boundary before mounting it anywhere a
036 * browser can reach.
037 *
038 * <h5 class='section'>See Also:</h5><ul>
039 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceJetty">juneau-microservice-jetty</a>
040
041 * </ul>
042 *
043 * @serial exclude
044 */
045// @formatter:off
046@Rest(
047   path="/debug",
048   title="Debug",
049   description="Debug Utilities.",
050   allowedMethodParams="OPTIONS"
051)
052@HtmlDocConfig(
053   navlinks={
054      "up: request:/..",
055      "jetty-thread-dump: servlet:/jetty/dump",
056      "api: servlet:/api",
057      "stats: servlet:/stats"
058   }
059)
060// @formatter:on
061public class DebugResource extends BasicRestServlet {
062   private static final long serialVersionUID = 1L;
063
064   /**
065    * [POST /jetty/dump] - Generates and saves the jetty thread dump file to jetty-thread-dump.log.
066    *
067    * @param req The request.
068    * @param res The response.
069    * @throws Exception Gets converted to 500 response.
070    * @return The thread dump contents.
071    */
072   @RestPost(path = "/jetty/dump", description = "Generates and saves the jetty thread dump file to jetty-thread-dump.log.")
073   @SuppressWarnings({
074      "java:S112" // throws Exception intentional - callback/lifecycle method
075   })
076   public Ok createJettyDump(RestRequest req, RestResponse res) throws Exception {
077      var dump = jettyServerComponent().getServer().dump();
078      try (var fw = new FileWriter(req.getConfig().get("Logging/logDir").orElse("") + "/jetty-thread-dump.log")) {
079         fw.write(dump);
080      }
081      return Ok.OK;
082   }
083
084   /**
085    * [GET /] - Shows child utilities.
086    *
087    * @return Child utility links.
088    */
089   @RestGet(path="/", description="Show contents of config file.")
090   public ResourceDescriptions getChildren() {
091      // @formatter:off
092      return ResourceDescriptions
093         .create()
094         .append("jetty/dump", "Jetty thread dump");
095      // @formatter:on
096   }
097
098   /**
099    * [GET /jetty/dump] - Generates and retrieves the jetty thread dump.
100    *
101    * @param req The request.
102    * @param res The response.
103    * @return The thread dump contents.
104    */
105   @RestGet(path = "/jetty/dump", description = "Generates and retrieves the jetty thread dump.")
106   public Reader getJettyDump(RestRequest req, RestResponse res) {
107      res.setContentType("text/plain");
108      return new StringReader(jettyServerComponent().getServer().dump());
109   }
110
111   @SuppressWarnings({
112      "resource" // BeanStore returned by Microservice.getBeanStore() is owned by the microservice lifecycle; do not close here.
113   })
114   private static JettyServerComponent jettyServerComponent() {
115      return Microservice.getInstance().getBeanStore().getBean(JettyServerComponent.class)
116         .orElseThrow(() -> new IllegalStateException("JettyServerComponent bean not found.  Did you forget to add JettyConfiguration.class to Microservice.Builder.configurations(...)?"));
117   }
118}