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

io.atomix.rest.impl.VertxRestService Maven / Gradle / Ivy

/*
 * Copyright 2017-present Open Networking Foundation
 *
 * 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 io.atomix.rest.impl;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import io.atomix.cluster.ClusterMembershipService;
import io.atomix.cluster.messaging.ClusterCommunicationService;
import io.atomix.cluster.messaging.ClusterEventService;
import io.atomix.core.Atomix;
import io.atomix.core.AtomixRegistry;
import io.atomix.core.PrimitivesService;
import io.atomix.primitive.PrimitiveFactory;
import io.atomix.primitive.config.PrimitiveConfig;
import io.atomix.primitive.partition.PartitionGroupConfig;
import io.atomix.primitive.protocol.PrimitiveProtocolConfig;
import io.atomix.rest.AtomixResource;
import io.atomix.rest.ManagedRestService;
import io.atomix.rest.RestService;
import io.atomix.utils.misc.StringUtils;
import io.atomix.utils.net.Address;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ScanResult;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServer;
import org.jboss.resteasy.plugins.server.vertx.VertxRequestHandler;
import org.jboss.resteasy.plugins.server.vertx.VertxResteasyDeployment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Vert.x REST service.
 */
public class VertxRestService implements ManagedRestService {
  private static final Logger LOGGER = LoggerFactory.getLogger(VertxRestService.class);

  private final Atomix atomix;
  private final Address address;
  private final Vertx vertx;
  private HttpServer server;
  private VertxResteasyDeployment deployment;
  private final AtomicBoolean open = new AtomicBoolean();

  public VertxRestService(Atomix atomix, Address address) {
    this.atomix = checkNotNull(atomix, "atomix cannot be null");
    this.address = checkNotNull(address, "address cannot be null");
    this.vertx = Vertx.vertx();
  }

  @Override
  public Address address() {
    return address;
  }

  @Override
  public CompletableFuture start() {
    server = vertx.createHttpServer();
    deployment = new VertxResteasyDeployment();
    deployment.start();

    deployment.getDispatcher().getDefaultContextObjects()
        .put(ClusterMembershipService.class, atomix.getMembershipService());
    deployment.getDispatcher().getDefaultContextObjects()
        .put(ClusterCommunicationService.class, atomix.getCommunicationService());
    deployment.getDispatcher().getDefaultContextObjects()
        .put(ClusterEventService.class, atomix.getEventService());
    deployment.getDispatcher().getDefaultContextObjects()
        .put(PrimitiveFactory.class, atomix.getPrimitivesService());
    deployment.getDispatcher().getDefaultContextObjects()
        .put(PrimitivesService.class, atomix.getPrimitivesService());
    deployment.getDispatcher().getDefaultContextObjects()
        .put(EventManager.class, new EventManager());
    deployment.getDispatcher().getDefaultContextObjects()
        .put(AtomixRegistry.class, atomix.getRegistry());

    final ClassLoader classLoader = atomix.getClass().getClassLoader();
    final String[] whitelistPackages = StringUtils.split(System.getProperty("io.atomix.whitelistPackages"), ",");
    final ClassGraph classGraph = whitelistPackages != null
        ? new ClassGraph().enableAnnotationInfo().whitelistPackages(whitelistPackages).addClassLoader(classLoader)
        : new ClassGraph().enableAnnotationInfo().addClassLoader(classLoader);

    try (final ScanResult scanResult = classGraph.scan()) {
      scanResult.getClassesWithAnnotation(AtomixResource.class.getName()).forEach(classInfo -> {
        deployment.getRegistry().addPerInstanceResource(classInfo.loadClass(), "/v1");
      });
    }

    deployment.getDispatcher().getProviderFactory().register(new JacksonProvider(createObjectMapper()));

    server.requestHandler(new VertxRequestHandler(vertx, deployment));

    CompletableFuture future = new CompletableFuture<>();
    server.listen(address.port(), address.address(true).getHostAddress(), result -> {
      if (result.succeeded()) {
        open.set(true);
        LOGGER.info("Started");
        future.complete(this);
      } else {
        future.completeExceptionally(result.cause());
      }
    });
    return future;
  }

  @Override
  public boolean isRunning() {
    return open.get();
  }

  @Override
  public CompletableFuture stop() {
    if (server != null) {
      CompletableFuture future = new CompletableFuture<>();
      server.close(result -> {
        LOGGER.info("Stopped");
        future.complete(null);
      });
      deployment.stop();
      return future;
    }
    open.set(false);
    return CompletableFuture.completedFuture(null);
  }

  protected ObjectMapper createObjectMapper() {
    ObjectMapper mapper = new ObjectMapper();

    mapper.setPropertyNamingStrategy(new ConfigPropertyNamingStrategy());
    mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
    mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
    mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
    mapper.configure(JsonParser.Feature.ALLOW_YAML_COMMENTS, true);

    SimpleModule module = new SimpleModule("PolymorphicTypes");
    module.addDeserializer(PartitionGroupConfig.class, new PartitionGroupDeserializer(atomix.getRegistry()));
    module.addDeserializer(PrimitiveProtocolConfig.class, new PrimitiveProtocolDeserializer(atomix.getRegistry()));
    module.addDeserializer(PrimitiveConfig.class, new PrimitiveConfigDeserializer(atomix.getRegistry()));
    mapper.registerModule(module);

    return mapper;
  }

  /**
   * Vert.x REST service builder.
   */
  public static class Builder extends RestService.Builder {
    private static final String DEFAULT_HOST = "0.0.0.0";
    private static final int DEFAULT_PORT = 5678;

    @Override
    public ManagedRestService build() {
      if (address == null) {
        address = Address.from(DEFAULT_HOST, DEFAULT_PORT);
      }
      return new VertxRestService(atomix, address);
    }
  }

  @Provider
  @Consumes(MediaType.APPLICATION_JSON)
  @Produces(MediaType.APPLICATION_JSON)
  private static class JacksonProvider implements ContextResolver {
    private final ObjectMapper mapper;

    JacksonProvider(ObjectMapper mapper) {
      this.mapper = mapper;
    }

    @Override
    public ObjectMapper getContext(Class type) {
      return mapper;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy