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

eu.cloudnetservice.node.provider.NodeServiceTaskProvider Maven / Gradle / Ivy

Go to download

A modern application that can dynamically and easily deliver Minecraft oriented software

The newest version!
/*
 * Copyright 2019-2024 CloudNetService team & contributors
 *
 * 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 eu.cloudnetservice.node.provider;

import dev.derklaro.aerogel.PostConstruct;
import dev.derklaro.aerogel.auto.Provides;
import eu.cloudnetservice.common.Named;
import eu.cloudnetservice.common.io.FileUtil;
import eu.cloudnetservice.common.jvm.JavaVersion;
import eu.cloudnetservice.common.language.I18n;
import eu.cloudnetservice.driver.channel.ChannelMessage;
import eu.cloudnetservice.driver.document.Document;
import eu.cloudnetservice.driver.document.DocumentFactory;
import eu.cloudnetservice.driver.event.EventManager;
import eu.cloudnetservice.driver.network.buffer.DataBuf;
import eu.cloudnetservice.driver.network.def.NetworkConstants;
import eu.cloudnetservice.driver.network.rpc.factory.RPCFactory;
import eu.cloudnetservice.driver.network.rpc.handler.RPCHandlerRegistry;
import eu.cloudnetservice.driver.provider.ServiceTaskProvider;
import eu.cloudnetservice.driver.service.ServiceRemoteInclusion;
import eu.cloudnetservice.driver.service.ServiceTask;
import eu.cloudnetservice.node.cluster.sync.DataSyncHandler;
import eu.cloudnetservice.node.cluster.sync.DataSyncRegistry;
import eu.cloudnetservice.node.event.task.LocalServiceTaskAddEvent;
import eu.cloudnetservice.node.event.task.LocalServiceTaskRemoveEvent;
import eu.cloudnetservice.node.network.listener.message.TaskChannelMessageListener;
import eu.cloudnetservice.node.setup.DefaultInstallation;
import eu.cloudnetservice.node.setup.DefaultTaskSetup;
import eu.cloudnetservice.node.util.JavaVersionResolver;
import io.leangen.geantyref.TypeFactory;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import lombok.NonNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Singleton
@Provides(ServiceTaskProvider.class)
public class NodeServiceTaskProvider implements ServiceTaskProvider {

  private static final Path TASKS_DIRECTORY = Path.of(
    System.getProperty("cloudnet.config.tasks.directory.path", "local/tasks"));
  private static final Type LIST_DOCUMENT_TYPE = TypeFactory.parameterizedClass(List.class, Document.class);

  private static final Logger LOGGER = LoggerFactory.getLogger(NodeServiceTaskProvider.class);

  private final EventManager eventManager;
  private final Map serviceTasks = new ConcurrentHashMap<>();

  @Inject
  public NodeServiceTaskProvider(
    @NonNull EventManager eventManager,
    @NonNull RPCFactory rpcFactory,
    @NonNull DataSyncRegistry syncRegistry,
    @NonNull RPCHandlerRegistry handlerRegistry
  ) {
    this.eventManager = eventManager;

    // rpc
    var rpcHandler = rpcFactory.newRPCHandlerBuilder(ServiceTaskProvider.class).targetInstance(this).build();
    handlerRegistry.registerHandler(rpcHandler);

    // cluster data sync
    syncRegistry.registerHandler(
      DataSyncHandler.builder()
        .key("task")
        .nameExtractor(Named::name)
        .convertObject(ServiceTask.class)
        .writer(this::addPermanentServiceTaskSilently)
        .dataCollector(this::serviceTasks)
        .currentGetter(task -> this.serviceTask(task.name()))
        .build());
  }

  @Inject
  private void loadTasks(@NonNull DefaultInstallation installation) {
    if (Files.exists(TASKS_DIRECTORY)) {
      this.loadServiceTasks();
    } else {
      FileUtil.createDirectory(TASKS_DIRECTORY);
      installation.registerSetup(DefaultTaskSetup.class);
    }
  }

  @PostConstruct
  private void registerTaskChannelListener() {
    this.eventManager.registerListener(TaskChannelMessageListener.class);
  }

  @Override
  public void reload() {
    // clear the cache
    this.serviceTasks.clear();
    // load all service tasks
    this.loadServiceTasks();
  }

  @Override
  public @NonNull Collection serviceTasks() {
    return Collections.unmodifiableCollection(this.serviceTasks.values());
  }

  @Override
  public @Nullable ServiceTask serviceTask(@NonNull String name) {
    return this.serviceTasks.get(name);
  }

  @Override
  public boolean addServiceTask(@NonNull ServiceTask serviceTask) {
    // register the task locally and notify all event listeners
    var serviceTaskAddEvent = this.eventManager.callEvent(new LocalServiceTaskAddEvent(serviceTask));
    this.addPermanentServiceTaskSilently(serviceTaskAddEvent.task());

    // notify the cluster
    ChannelMessage.builder()
      .targetAll()
      .message("add_service_task")
      .channel(NetworkConstants.INTERNAL_MSG_CHANNEL)
      .buffer(DataBuf.empty().writeObject(serviceTaskAddEvent.task()))
      .build()
      .send();
    return true;
  }

  @Override
  public void removeServiceTaskByName(@NonNull String name) {
    var task = this.serviceTask(name);
    if (task != null) {
      this.removeServiceTask(task);
    }
  }

  @Override
  public void removeServiceTask(@NonNull ServiceTask serviceTask) {
    // remove the task locally and notify all event listeners
    this.removePermanentServiceTaskSilently(serviceTask);
    this.eventManager.callEvent(new LocalServiceTaskRemoveEvent(serviceTask));

    // notify the cluster
    ChannelMessage.builder()
      .targetAll()
      .message("remove_service_task")
      .channel(NetworkConstants.INTERNAL_MSG_CHANNEL)
      .buffer(DataBuf.empty().writeObject(serviceTask))
      .build()
      .send();
  }

  public void addPermanentServiceTaskSilently(@NonNull ServiceTask serviceTask) {
    // cache locally
    this.serviceTasks.put(serviceTask.name(), serviceTask);
    this.writeServiceTask(serviceTask);
  }

  public void removePermanentServiceTaskSilently(@NonNull ServiceTask serviceTask) {
    // remove from cache
    this.serviceTasks.remove(serviceTask.name());
    // remove the local file if it exists
    FileUtil.delete(this.taskFile(serviceTask));
  }

  protected @NonNull Path taskFile(@NonNull ServiceTask task) {
    return TASKS_DIRECTORY.resolve(task.name() + ".json");
  }

  protected void writeServiceTask(@NonNull ServiceTask serviceTask) {
    Document.newJsonDocument().appendTree(serviceTask).writeTo(this.taskFile(serviceTask));
  }

  protected void loadServiceTasks() {
    FileUtil.walkFileTree(TASKS_DIRECTORY, ($, file) -> {
      var document = DocumentFactory.json().parse(file);

      // TODO: remove in 4.1
      // check if the task has a name splitter
      if (!document.containsNonNull("nameSplitter")) {
        document.append("nameSplitter", "-");
      }

      // check if the task has environment variables
      var processConfiguration = document.readMutableDocument("processConfiguration");
      if (!processConfiguration.containsNonNull("environmentVariables")) {
        processConfiguration.append("environmentVariables", new HashMap<>());
        document.append("processConfiguration", processConfiguration);
      }

      // inclusions now feature a mandatory cache strategy field.
      // Convert old inclusions that do not have a non-null value already set.
      List remoteInclusions = document.readObject("includes", LIST_DOCUMENT_TYPE);
      var migratedInclusions = remoteInclusions.stream().map(remoteInclusion -> {
        if (remoteInclusion.containsNonNull("cacheStrategy")) {
          return remoteInclusion;
        }

        return remoteInclusion.mutableCopy().append("cacheStrategy", ServiceRemoteInclusion.NO_CACHE_STRATEGY);
      }).toList();
      document.append("includes", migratedInclusions);

      // load the service task
      var task = document.toInstanceOf(ServiceTask.class);
      // check if the file name is still up-to-date
      var taskName = file.getFileName().toString().replace(".json", "");
      if (!taskName.equals(task.name())) {
        // rename the file
        FileUtil.move(file, this.taskFile(task), StandardCopyOption.REPLACE_EXISTING);
      }

      // Wrap the command to a path and unwrap it again to ensure that the command is os specific
      // This allows for example Windows users to use '/' in the task file which is way easier as there
      // is no need to escape. ('\' must be escaped)
      var javaCommand = task.javaCommand();
      if (javaCommand != null && !javaCommand.equals("java")) {
        var command = Path.of(javaCommand).toAbsolutePath().normalize().toString();
        // validate if the task java command needs an update
        if (!javaCommand.equals(command)) {
          task = ServiceTask.builder(task).javaCommand(command).build();
        }

        // remove all custom java paths that do not support Java 23
        var javaVersion = JavaVersionResolver.resolveFromJavaExecutable(task.javaCommand());
        if (javaVersion != JavaVersion.JAVA_23) {
          task = ServiceTask.builder(task).javaCommand(null).build();
          LOGGER.warn(I18n.trans("cloudnet-load-task-unsupported-java-version", taskName));
        }
      }

      // cache the task
      this.addServiceTask(task);
    }, false, "*.json");
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy