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.console.views; 018 019import java.io.*; 020import java.util.*; 021 022import org.apache.juneau.http.response.BadRequest; 023import org.apache.juneau.http.response.Gone; 024import org.apache.juneau.http.response.NotFound; 025import org.apache.juneau.marshall.json.*; 026import org.apache.juneau.rest.server.*; 027import org.apache.juneau.terminal.report.*; 028 029/** 030 * Static helpers that implement the terminal-view events endpoint. 031 * 032 * <p> 033 * {@link TerminalViewMixin} delegates to these; an application that wants its own path writes one {@code @RestGet} 034 * method that calls {@link #events(Optional, RestRequest, RestResponse)} and gets the same validation, cap and 035 * headers. 036 */ 037public final class TerminalViewEndpoints { 038 039 /** The page size cap of the bundled endpoint. */ 040 public static final int MAX_ENDPOINT_PAGE_EVENTS = 2000; 041 042 private TerminalViewEndpoints() {} 043 044 /** 045 * Checks a mixin {@code runId} against {@code ^[A-Za-z0-9_-]{1,128}$}. 046 * 047 * @param runId The run id from the URL path. 048 * @return The same id. 049 * @throws BadRequest If the id does not match. 050 */ 051 public static String checkRunId(String runId) { 052 if (! TerminalViewChecks.isRunId(runId)) 053 throw new BadRequest("runId must match [A-Za-z0-9_-]{1,128}"); 054 return runId; 055 } 056 057 /** 058 * Serves one page of the events endpoint ({@code ?after=}) as JSON. 059 * 060 * <p> 061 * Answers {@code 404} for an empty source, {@code 400} for a malformed {@code after}, {@code 410 Gone} for an 062 * unknown or stale token, and otherwise {@code 200} with {@code Cache-Control: no-store}. 063 * 064 * @param source The resolved source; empty answers {@code 404}. 065 * @param req The request. 066 * @param res The response. 067 * @throws IOException If the response could not be written. 068 */ 069 public static void events(Optional<TerminalViewSource> source, RestRequest req, RestResponse res) throws IOException { 070 var src = source.orElseThrow(NotFound::new); 071 var after = req.getQueryParam("after").asString().orElse(null); 072 if (after != null && ! TerminalViewChecks.isToken(after)) 073 throw new BadRequest("after is not a valid token"); 074 TerminalViewPage page; 075 try { 076 page = src.page(after, MAX_ENDPOINT_PAGE_EVENTS); 077 } catch (TerminalViewSource.UnknownTokenException e) { 078 throw new Gone("unknown or stale terminal-view token"); 079 } 080 page.validate(); 081 res.setHeader("Cache-Control", "no-store"); 082 try (var w = res.getDirectWriter("application/json")) { 083 JsonSerializer.DEFAULT.write(page.toContractMap(), w); 084 } 085 } 086}