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

cz.o2.proxima.direct.http.WebsocketReader Maven / Gradle / Ivy

/**
 * Copyright 2017-2020 O2 Czech Republic, a.s.
 *
 * 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 cz.o2.proxima.direct.http;

import static cz.o2.proxima.direct.commitlog.ObserverUtils.asOnNextContext;

import com.google.common.collect.Iterables;
import cz.o2.proxima.direct.commitlog.CommitLogReader;
import cz.o2.proxima.direct.commitlog.LogObserver;
import cz.o2.proxima.direct.commitlog.LogObserver.OnNextContext;
import cz.o2.proxima.direct.commitlog.ObserveHandle;
import cz.o2.proxima.direct.commitlog.Offset;
import cz.o2.proxima.direct.core.Partition;
import cz.o2.proxima.functional.UnaryFunction;
import cz.o2.proxima.repository.AttributeDescriptor;
import cz.o2.proxima.repository.EntityDescriptor;
import cz.o2.proxima.storage.AbstractStorage;
import cz.o2.proxima.storage.StreamElement;
import cz.o2.proxima.storage.commitlog.Position;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

/** Reader of data from websocket (ws, or wss). */
public class WebsocketReader extends AbstractStorage implements CommitLogReader {

  private static final Partition PARTITION = () -> 0;
  private static final Offset OFFSET =
      new Offset() {

        private static final long serialVersionUID = 1L;

        @Override
        public Partition getPartition() {
          return PARTITION;
        }

        @Override
        public long getWatermark() {
          return System.currentTimeMillis();
        }
      };

  private final AttributeDescriptor attr;
  private final UnaryFunction keyExtractor;
  private final String hello;

  public WebsocketReader(EntityDescriptor entityDescriptor, URI uri, Map cfg) {

    super(entityDescriptor, uri);
    @SuppressWarnings("unchecked")
    List attributes = (List) cfg.get("attributes");
    if (attributes.size() > 1) {
      throw new IllegalArgumentException(
          "Can read only single attribute from websocket, got " + attributes);
    }
    String name = Iterables.getOnlyElement(attributes);
    if (name.equals("*")) {
      if (entityDescriptor.getAllAttributes().size() != 1) {
        throw new IllegalArgumentException(
            "When specifying wildcard attribute, entity has to have "
                + "only single attribute, got "
                + entityDescriptor.getAllAttributes());
      }
      name = Iterables.getOnlyElement(entityDescriptor.getAllAttributes()).getName();
    }
    String attrName = name;
    attr =
        entityDescriptor
            .findAttribute(attrName)
            .orElseThrow(
                () ->
                    new IllegalStateException(
                        "Attribute " + attrName + " should be present in " + entityDescriptor));
    // FIXME: keyExtractor
    keyExtractor = m -> UUID.randomUUID().toString();
    hello =
        Optional.ofNullable(cfg.get("hello"))
            .map(Object::toString)
            .orElseThrow(() -> new IllegalArgumentException("Missing 'hello' message"));
  }

  @Override
  public List getPartitions() {
    // single partition
    return Arrays.asList(PARTITION);
  }

  @Override
  public ObserveHandle observe(String name, Position position, LogObserver observer) {

    checkSupportedPosition(position);
    return observe(element -> observer.onNext(element, nullContext()), observer::onError);
  }

  private ObserveHandle observe(Consumer onNext, Consumer onError) {

    WebSocketClient client =
        new WebSocketClient(getUri()) {

          @Override
          public void onOpen(ServerHandshake sh) {
            send(hello);
          }

          @Override
          public void onMessage(String m) {
            StreamElement elem =
                StreamElement.upsert(
                    getEntityDescriptor(),
                    attr,
                    UUID.randomUUID().toString(),
                    keyExtractor.apply(m),
                    attr.getName(),
                    System.currentTimeMillis(),
                    m.getBytes(StandardCharsets.UTF_8));
            onNext.accept(elem);
          }

          @Override
          public void onClose(int code, String reason, boolean remote) {
            if (remote) {
              onError.accept(new RuntimeException("Server error: " + code + ": " + reason));
            }
          }

          @Override
          public void onError(Exception excptn) {
            onError.accept(excptn);
          }
        };
    client.connect();
    return new ObserveHandle() {
      @Override
      public void close() {
        client.close();
      }

      @Override
      public List getCommittedOffsets() {
        return Arrays.asList(OFFSET);
      }

      @Override
      public void resetOffsets(List offsets) {
        // nop
      }

      @Override
      public List getCurrentOffsets() {
        return getCommittedOffsets();
      }

      @Override
      public void waitUntilReady() throws InterruptedException {
        // nop
      }
    };
  }

  @Override
  public ObserveHandle observePartitions(
      String name,
      Collection partitions,
      Position position,
      boolean stopAtCurrent,
      LogObserver observer) {

    checkSupportedPosition(position);
    return observe(element -> observer.onNext(element, nullContext()), observer::onError);
  }

  @Override
  public ObserveHandle observeBulk(
      String name, Position position, boolean stopAtCurrent, LogObserver observer) {

    checkSupportedPosition(position);
    return observe(element -> observer.onNext(element, nullContext()), observer::onError);
  }

  @Override
  public ObserveHandle observeBulkPartitions(
      String name,
      Collection partitions,
      Position position,
      boolean stopAtCurrent,
      LogObserver observer) {

    return observeBulk(name, position, observer);
  }

  @Override
  public ObserveHandle observeBulkOffsets(Collection offsets, LogObserver observer) {

    return observeBulk(null, Position.NEWEST, observer);
  }

  private OnNextContext nullContext() {
    return asOnNextContext((succ, err) -> {}, OFFSET);
  }

  private void checkSupportedPosition(Position position) {
    if (position == Position.OLDEST) {
      throw new UnsupportedOperationException("Cannot read OLDEST data from websocket");
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy