content: /**
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.aurora.scheduler.http;
  
  import java.util.Optional;
  
  import javax.inject.Inject;
  import javax.ws.rs.GET;
  import javax.ws.rs.Path;
  import javax.ws.rs.PathParam;
  import javax.ws.rs.Produces;
  import javax.ws.rs.core.MediaType;
  import javax.ws.rs.core.Response;
  import javax.ws.rs.core.Response.Status;
  
  import com.google.gson.ExclusionStrategy;
  import com.google.gson.FieldAttributes;
  import com.google.gson.Gson;
  import com.google.gson.GsonBuilder;
  
  import org.apache.aurora.scheduler.base.JobKeys;
  import org.apache.aurora.scheduler.storage.Storage;
  import org.apache.aurora.scheduler.storage.Storage.Work.Quiet;
  import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
  import org.apache.aurora.scheduler.storage.entities.IJobKey;
  import org.apache.aurora.scheduler.storage.entities.IScheduledTask;
  import org.apache.thrift.TBase;
  
  import static java.util.Objects.requireNonNull;
  
  /**
   * Servlet that prints out the raw configuration for a specified struct.
   */
  @Path("/structdump")
  public class StructDump extends JerseyTemplateServlet {
  
    private final Storage storage;
  
    @Inject
    public StructDump(Storage storage) {
      super("structdump");
      this.storage = requireNonNull(storage);
    }
  
    private static final String USAGE =
        "<html>Print the internal thrift structure of task or cronjob. "
        + "<p>Usage: /structdump/task/{task_id} or /structdump/cron/{role}/{env}/{job}</p></html>";
  
    @GET
    @Produces(MediaType.TEXT_HTML)
    public Response getUsage() {
      return Response
          .status(Status.BAD_REQUEST)
          .entity(USAGE)
          .build();
    }
  
    /**
     * Dumps a task struct.
     *
     * @return HTTP response.
     */
    @GET
    @Path("/task/{task}")
    @Produces(MediaType.TEXT_HTML)
    public Response dumpJob(
        @PathParam("task") final String taskId) {
  
      return dumpEntity(
          "Task " + taskId,
          storeProvider ->
              storeProvider.getTaskStore().fetchTask(taskId).map(IScheduledTask::newBuilder));
    }
  
    /**
     * Dumps a cron job struct.
     *
     * @return HTTP response.
     */
    @GET
    @Path("/cron/{role}/{environment}/{job}")
    @Produces(MediaType.TEXT_HTML)
    public Response dump(
        @PathParam("role") final String role,
        @PathParam("environment") final String environment,
        @PathParam("job") final String job) {
  
      final IJobKey jobKey = JobKeys.from(role, environment, job);
      return dumpEntity("Cron job " + JobKeys.canonicalString(jobKey),
          storeProvider -> storeProvider.getCronJobStore().fetchJob(jobKey)
              .map(IJobConfiguration::newBuilder));
    }
  
    private static final Gson GSON = new GsonBuilder()
        .setPrettyPrinting()
        .setExclusionStrategies(new ExclusionStrategy() {
          @Override
          public boolean shouldSkipField(FieldAttributes f) {
            if (f.getName().startsWith("_")) {
              return true;
            }
            return f.getDeclaredClass().getName().contains("$_Fields");
          }
  
          @Override
          public boolean shouldSkipClass(Class<?> clazz) {
            return false;
          }
        })
        .setFieldNamingStrategy(f -> {
          switch (f.getName()) {
            case "setField_": return "key";
            case "value_": return "value";
            default: return f.getName();
          }
        })
        .create();
  
    private Response dumpEntity(String id, Quiet<Optional<? extends TBase<?, ?>>> work) {
      return fillTemplate(template -> {
        template.setAttribute("id", id);
        Optional<? extends TBase<?, ?>> struct = storage.read(work);
        if (struct.isPresent()) {
          template.setAttribute("structPretty", GSON.toJson(struct.get()));
          template.setAttribute("exception", null);
        } else {
          template.setAttribute("exception", "Entity not found");
        }
      });
    }
  }
  