gt:             throw new BadRequestException(MSG_NO_ID);
context: package org.yamcs.http.api;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  import java.util.UUID;
  import java.util.stream.Collectors;
  
  import org.yamcs.YamcsServer;
  import org.yamcs.api.Observer;
  import org.yamcs.http.BadRequestException;
  import org.yamcs.http.Context;
  import org.yamcs.http.InternalServerErrorException;
  import org.yamcs.http.NotFoundException;
  import org.yamcs.logging.Log;
  import org.yamcs.protobuf.AbstractTimelineApi;
  import org.yamcs.protobuf.AddBandRequest;
  import org.yamcs.protobuf.AddViewRequest;
  import org.yamcs.protobuf.CreateItemRequest;
  import org.yamcs.protobuf.DeleteBandRequest;
  import org.yamcs.protobuf.DeleteItemRequest;
  import org.yamcs.protobuf.DeleteTimelineGroupRequest;
  import org.yamcs.protobuf.DeleteViewRequest;
  import org.yamcs.protobuf.GetBandRequest;
  import org.yamcs.protobuf.GetItemRequest;
  import org.yamcs.protobuf.GetViewRequest;
  import org.yamcs.protobuf.ListBandsRequest;
  import org.yamcs.protobuf.ListBandsResponse;
  import org.yamcs.protobuf.ListItemsRequest;
  import org.yamcs.protobuf.ListItemsResponse;
  import org.yamcs.protobuf.ListSourcesRequest;
  import org.yamcs.protobuf.ListSourcesResponse;
  import org.yamcs.protobuf.ListTimelineTagsRequest;
  import org.yamcs.protobuf.ListTimelineTagsResponse;
  import org.yamcs.protobuf.ListViewsRequest;
  import org.yamcs.protobuf.ListViewsResponse;
  import org.yamcs.protobuf.RelativeTime;
  import org.yamcs.protobuf.TimelineBand;
  import org.yamcs.protobuf.TimelineItem;
  import org.yamcs.protobuf.TimelineItemType;
  import org.yamcs.protobuf.TimelineView;
  import org.yamcs.protobuf.UpdateBandRequest;
  import org.yamcs.protobuf.UpdateItemRequest;
  import org.yamcs.protobuf.UpdateViewRequest;
  import org.yamcs.security.SystemPrivilege;
  import org.yamcs.timeline.ActivityGroup;
  import org.yamcs.timeline.AutomatedActivity;
  import org.yamcs.timeline.BandListener;
  import org.yamcs.timeline.ItemFilter;
  import org.yamcs.timeline.ItemGroup;
  import org.yamcs.timeline.ItemListener;
  import org.yamcs.timeline.ItemProvider;
  import org.yamcs.timeline.ManualActivity;
  import org.yamcs.timeline.TimelineBandDb;
  import org.yamcs.timeline.TimelineEvent;
  import org.yamcs.timeline.TimelineService;
  import org.yamcs.timeline.TimelineViewDb;
  import org.yamcs.timeline.ViewListener;
  import org.yamcs.utils.InvalidRequestException;
  import org.yamcs.utils.TimeEncoding;
  import org.yamcs.utils.TimeInterval;
  
  import com.google.protobuf.util.Durations;
  
  public class TimelineApi extends AbstractTimelineApi<Context> {
      static final String MSG_NO_ID = "No id specified";
  
  
      private static final Log log = new Log(TimelineApi.class);
  
      @Override
      public void createItem(Context ctx, CreateItemRequest request, Observer<TimelineItem> observer) {
          ctx.checkSystemPrivilege(SystemPrivilege.ControlTimeline);
          TimelineService timelineService = verifyService(request.getInstance());
          ItemProvider timelineSource = verifySource(timelineService,
                  request.hasSource() ? request.getSource() : TimelineService.RDB_TIMELINE_SOURCE);
  
          org.yamcs.timeline.TimelineItem item = req2Item(request);
          try {
              item = timelineSource.addItem(item);
              observer.complete(item.toProtoBuf());
          } catch (InvalidRequestException e) {
              throw new BadRequestException(e.getMessage());
          }
      }
  
      @Override
      public void getItem(Context ctx, GetItemRequest request, Observer<TimelineItem> observer) {
          ctx.checkSystemPrivilege(SystemPrivilege.ReadTimeline);
          TimelineService timelineService = verifyService(request.getInstance());
          ItemProvider timelineSource = verifySource(timelineService,
                  request.hasSource() ? request.getSource() : TimelineService.RDB_TIMELINE_SOURCE);
  
          if (!request.hasId()) {
              throw new BadRequestException(MSG_NO_ID);
          }
          String id = request.getId();
          org.yamcs.timeline.TimelineItem item = timelineSource.getItem(id);
          if (item == null) {
              throw new NotFoundException(MSG_ITEM_NOT_FOUND(id));
          } else {
              observer.complete(item.toProtoBuf());
          }
      }
  
      @Override
      public void updateItem(Context ctx, UpdateItemRequest request, Observer<TimelineItem> observer) {
          ctx.checkSystemPrivilege(SystemPrivilege.ControlTimeline);
          TimelineService timelineService = verifyService(request.getInstance());
          ItemProvider timelineSource = verifySource(timelineService,
                  request.hasSource() ? request.getSource() : TimelineService.RDB_TIMELINE_SOURCE);
  
          if (!request.hasId()) {
              throw new BadRequestException(MSG_NO_ID);
          }
  
          String id = request.getId();
          org.yamcs.timeline.TimelineItem item = timelineSource.getItem(id);
          if (item == null) {
              throw new NotFoundException(MSG_ITEM_NOT_FOUND(id));
          }
  
          if (request.hasName()) {
              item.setName(request.getName());
          }
  
          if (request.hasStart()) {
              if (request.hasRelativeTime()) {
                  throw new BadRequestException("Cannot specify both start and relative time");
              }
              item.setStart(TimeEncoding.fromProtobufTimestamp(request.getStart()));
  
          } else if (request.hasRelativeTime()) {
              RelativeTime relt = request.getRelativeTime();
              if (!relt.hasRelto()) {
                  throw new BadRequestException("relto item is required with relative time");
              }
              if (!relt.hasRelativeStart()) {
                  throw new BadRequestException("relative start is required in the relative time");
              }
              item.setRelativeItemUuid(parseUuid(relt.getRelto()));
              item.setRelativeStart(Durations.toMillis(relt.getRelativeStart()));
          } else {
              throw new BadRequestException("One of start or relativeTime has to be specified");
          }
          if (request.hasDuration()) {
              item.setDuration(Durations.toMillis(request.getDuration()));
          }
  
          item.setGroupUuid(request.hasGroupId() ? parseUuid(request.getGroupId()) : null);
          item.setTags(request.getTagsList());
  
          try {
              item = timelineSource.updateItem(item);
              observer.complete(item.toProtoBuf());
          } catch (InvalidRequestException e) {
              throw new BadRequestException(e.getMessage());
          }
      }
  
      @Override
      public void listItems(Context ctx, ListItemsRequest request, Observer<ListItemsResponse> observer) {
          ctx.checkSystemPrivilege(SystemPrivilege.ReadTimeline);
          TimelineService timelineService = verifyService(request.getInstance());
          ItemProvider timelineSource = verifySource(timelineService,
                  request.hasSource() ? request.getSource() : TimelineService.RDB_TIMELINE_SOURCE);
  
          String next = request.hasNext() ? request.getNext() : null;
          int limit = request.hasLimit() ? request.getLimit() : 500;
          TimeInterval interval = new TimeInterval();
          if (request.hasStart()) {
              interval.setStart(TimeEncoding.fromProtobufTimestamp(request.getStart()));
          }
          if (request.hasStop()) {
              interval.setEnd(TimeEncoding.fromProtobufTimestamp(request.getStop()));
          }
          ItemFilter filter = new ItemFilter(interval);
          if (request.hasBand()) {
              UUID bandId = UUID.fromString(request.getBand());
              org.yamcs.timeline.TimelineBand band = timelineService.getTimelineBandDb().getBand(bandId);
              filter.setTags(band.getTags());
          }
  
          ListItemsResponse.Builder resp = ListItemsResponse.newBuilder();
  
          timelineSource.getItems(limit, next, filter, new ItemListener() {
  
              @Override
              public void next(org.yamcs.timeline.TimelineItem item) {
                  resp.addItems(item.toProtoBuf());
              }
  
              @Override
              public void completeExceptionally(Throwable t) {
                  log.warn("Error retrieving timeline items", t);
                  observer.completeExceptionally(t);
              }
  
              @Override
              public void complete(String token) {
                  if (token != null) {
                      resp.setContinuationToken(token);
                  }
                  observer.complete(resp.build());
              }
          });
  
      }
  
      @Override
      public void listSources(Context ctx, ListSourcesRequest request, Observer<ListSourcesResponse> observer) {
          ctx.checkSystemPrivilege(SystemPrivilege.ReadTimeline);
          TimelineService timelineService = verifyService(request.getInstance());
          ListSourcesResponse.Builder lsrb = ListSourcesResponse.newBuilder().putAllSources(timelineService.getSources());
          observer.complete(lsrb.build());
      }
  
      @Override
      public void listTags(Context ctx, ListTimelineTagsRequest request, Observer<ListTimelineTagsResponse> observer) {
          ctx.checkSystemPrivilege(SystemPrivilege.ReadTimeline);
          TimelineService timelineService = verifyService(request.getInstance());
          ItemProvider timelineSource = verifySource(timelineService,
                  request.hasSource() ? request.getSource() : TimelineService.RDB_TIMELINE_SOURCE);
          ListTimelineTagsResponse.Builder responseb = ListTimelineTagsResponse.newBuilder()
                  .addAllTags(timelineSource.getTags());
          observer.complete(responseb.build());
  
      }
  
      @Override
      public void addBand(Context ctx, AddBandRequest request, Observer<TimelineBand> observer) {
          ctx.checkSystemPrivilege(SystemPrivilege.ControlTimeline);
          TimelineService timelineService = verifyService(request.getInstance());
          TimelineBandDb timelineBandDb = timelineService.getTimelineBandDb();
          org.yamcs.timeline.TimelineBand band = req2Band(request, ctx.user.getName());
          try {
              band = timelineBandDb.addBand(band);
              observer.complete(band.toProtobuf());
          } catch (InvalidRequestException e) {
              throw new BadRequestException(e.getMessage());
          }
      }
  
      @Override
      public void getBand(Context ctx, GetBandRequest request, Observer<TimelineBand> observer) {
          ctx.checkSystemPrivilege(SystemPrivilege.ReadTimeline);
          TimelineService timelineService = verifyService(request.getInstance());
          TimelineBandDb timelineBandDb = timelineService.getTimelineBandDb();
          if (!request.hasId()) {
              throw new BadRequestException(MSG_NO_ID);
          }
          UUID uuid = parseUuid(request.getId());
          org.yamcs.timeline.TimelineBand band = timelineBandDb.getBand(uuid);
          if (band == null) {
              throw new NotFoundException(MSG_ITEM_NOT_FOUND(request.getId()));
          } else {
              observer.complete(band.toProtobuf());
          }
      }
  
      @Override
      public void listBands(Context ctx, ListBandsRequest request, Observer<ListBandsResponse> observer) {
          ctx.checkSystemPrivilege(SystemPrivilege.ReadTimeline);
          TimelineService timelineService = verifyService(request.getInstance());
          TimelineBandDb timelineBandDb = timelineService.getTimelineBandDb();
  
          List<TimelineBand> bands = new ArrayList<>();
          timelineBandDb.listBands(ctx.user.getName(), new BandListener() {
  
              @Override
              public void next(org.yamcs.timeline.TimelineBand band) {
                  bands.add(band.toProtobuf());
              }
  
              @Override
              public void completeExceptionally(Throwable t) {
                  log.warn("Error retrieving timeline bands", t);
                  observer.completeExceptionally(t);
              }
  
              @Override
              public void complete(String token) {
                  Collections.sort(bands, (b1, b2) -> b1.getName().compareToIgnoreCase(b2.getName()));
                  ListBandsResponse.Builder resp = ListBandsResponse.newBuilder()
                          .addAllBands(bands);
                  observer.complete(resp.build());
              }
          });
      }
  
      @Override
      public void updateBand(Context ctx, UpdateBandRequest request, Observer<TimelineBand> observer) {
          ctx.checkSystemPrivilege(SystemPrivilege.ControlTimeline);
          TimelineService timelineService = verifyService(request.getInstance());
          TimelineBandDb timelineBandDb = timelineService.getTimelineBandDb();
  
          if (!request.hasId()) {
              throw new BadRequestException(MSG_NO_ID);
          }
          UUID uuid = parseUuid(request.getId());
  
          org.yamcs.timeline.TimelineBand band = timelineBandDb.getBand(uuid);
          if (band == null) {
              throw new NotFoundException(MSG_BAND_NOT_FOUND(request.getId()));
          }
  
          if (request.hasName()) {
              band.setName(request.getName());
          }
          if (request.hasDescription()) {
              band.setDescription(request.getDescription());
          }
          band.setTags(request.getTagsList());
          band.setProperties(request.getPropertiesMap());
  
          try {
              band = timelineBandDb.updateBand(band);
              observer.complete(band.toProtobuf());
          } catch (InvalidRequestException e) {
              throw new BadRequestException(e.getMessage());
          }
      }
  
      @Override
      public void deleteBand(Context ctx, DeleteBandRequest request, Observer<TimelineBand> observer) {
          ctx.checkSystemPrivilege(SystemPrivilege.ControlTimeline);
          TimelineService timelineService = verifyService(request.getInstance());
          TimelineBandDb timelineBandDb = timelineService.getTimelineBandDb();
  
          if (!request.hasId()) {
  