All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nstream.adapter.common.amenity.SummaryAmenity Maven / Gradle / Ivy

There is a newer version: 4.15.23
Show newest version
// Copyright 2015-2024 Nstream, inc.
//
// Licensed under the Redis Source Available License 2.0 (RSALv2) Agreement;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://redis.com/legal/rsalv2-agreement/
//
// 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 nstream.adapter.common.amenity;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import swim.api.Lane;
import swim.api.agent.AbstractAgent;
import swim.api.agent.Agent;
import swim.api.lane.MapLane;
import swim.api.lane.ValueLane;
import swim.codec.Decoder;
import swim.codec.Utf8;
import swim.http.HttpHeader;
import swim.http.HttpMethod;
import swim.http.HttpRequest;
import swim.http.HttpResponse;
import swim.http.HttpStatus;
import swim.http.MediaRange;
import swim.http.MediaType;
import swim.http.header.AcceptHeader;
import swim.json.Json;
import swim.recon.Recon;
import swim.structure.Form;
import swim.structure.Record;
import swim.structure.Value;
import swim.system.LaneBinding;
import swim.system.agent.AgentView;
import swim.uri.Uri;

public class SummaryAmenity {

  public static final String SUMMARY_LANE_URI = "api/laneSummary";

  private final AbstractAgent runtime;

  public SummaryAmenity(AbstractAgent runtime) {
    this.runtime = runtime;
  }

  @SuppressWarnings("unchecked")
  public Decoder apiLaneSummaryDecodeRequest(HttpRequest request) {
    // FIXME: more request content types
    return (Decoder) (Decoder) Utf8.decodedParser(Json.structureParser().documentParser());
  }

  public HttpResponse apiLaneSummaryDoRespond(HttpRequest request) {
    if (!HttpMethod.GET.equals(request.method())) {
      return HttpResponse.create(HttpStatus.METHOD_NOT_ALLOWED);
    }
    final Value snapshot = laneSummaryStructure();
    return buildResponse(snapshot, request);
  }

  protected Record laneSummaryStructure() {
    Record result = Record.empty();
    final List agents = this.runtime.agentContext().agents();
    for (Agent agent : agents) {
      result = appendLaneSummaryForAgent((AgentView) agent.agentContext(), result);
    }
    return result;
  }

  private Record appendLaneSummaryForAgent(AgentView view, Record result) {
    final Map map = view.lanes();
    if (map != null && !map.isEmpty()) {
      for (Map.Entry entry : map.entrySet()) {
        final Lane lane = entry.getValue().getLaneView(view);
        if (lane instanceof MapLane) {
          result = appendMapLaneSummary((MapLane) lane, entry.getKey().toString(), result);
        } else if (lane instanceof ValueLane) {
          result = appendValueLaneSummary((ValueLane) lane, entry.getKey().toString(), result);
        }
      }
    }
    return result;
  }

  @SuppressWarnings({"rawtypes", "unchecked"})
  private static Record appendMapLaneSummary(MapLane lane, String name, Record result) {
    final Form uncheckedKeyForm = lane.keyForm(),
        uncheckedValueForm = lane.valueForm();
    Record inner = Record.create(lane.size());
    for (Map.Entry laneEntry : lane.entrySet()) {
      inner = inner.item(Record.create(2).slot("key", uncheckedKeyForm.mold(laneEntry.getKey()).toValue())
          .updated("value", uncheckedValueForm.mold(laneEntry.getValue()).toValue()));
      // inner = inner.slot(uncheckedKeyForm.mold(laneEntry.getKey()).toValue(),
      //     uncheckedValueForm.mold(laneEntry.getValue()).toValue());
    }
    return result.updated(name, Record.create(1).slot("data", inner));
    // return result.slot(name, inner.isEmpty() ? Value.extant() : inner);
  }

  @SuppressWarnings({"rawtypes", "unchecked"})
  private static Record appendValueLaneSummary(ValueLane lane, String name, Record result) {
    final Form uncheckedForm = lane.valueForm();
    return result.updated(name, uncheckedForm.mold(lane.get()).toValue());
  }

  protected final HttpResponse buildResponse(Value structure, HttpRequest request) {
    // TODO: negotiate content encoding
    final ContentTypeNegotiation negotiation = ContentTypeNegotiation.fromRequest(request);
    return HttpResponse.create(HttpStatus.OK)
          .body(negotiation.serializer.apply(structure), negotiation.mediaType);
  }

  private enum ContentTypeNegotiation {
    // FIXME: XML
    JSON(Json::toString, MediaType.applicationJson(), 0),
    RECON(Recon::toString, MediaType.applicationXRecon(), 1),
    EMPTY(Value::toString, MediaType.textPlain(), Integer.MAX_VALUE);

    private final Function serializer;
    private final MediaType mediaType;
    private final int precedence;

    ContentTypeNegotiation(Function serializer, MediaType mediaType,
                           int precedence) {
      this.serializer = serializer;
      this.mediaType = mediaType;
      this.precedence = precedence;
    }

    private static ContentTypeNegotiation fromMediaRanges(Collection ranges) {
      ContentTypeNegotiation result = EMPTY;
      float bestQuality = 0f;
      for (MediaRange range : ranges) {
        final float candidateQuality = range.weight();
        if (candidateQuality < bestQuality) {
          continue;
        }
        final ContentTypeNegotiation candidate;
        switch (range.subtype()) {
          case "json":
            candidate = JSON;
            break;
          case "recon":
          case "x-recon":
            candidate = RECON;
            break;
          default:
            candidate = EMPTY;
            break;
        }
        final boolean candidateBreaksTie = (candidateQuality == bestQuality) && (candidate.precedence < result.precedence);
        if (candidate != EMPTY && (candidateQuality > bestQuality || candidateBreaksTie)) {
          bestQuality = candidateQuality;
          result = candidate;
        }
      }
      return result;
    }

    private static ContentTypeNegotiation fromRequest(HttpRequest request) {
      ContentTypeNegotiation result = EMPTY;
      for (HttpHeader header : request.headers()) {
        if (header instanceof AcceptHeader) {
          result = fromMediaRanges(((AcceptHeader) header).mediaRanges());
          break;
        }
      }
      return result == EMPTY ? JSON : result;
    }

  }

}