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

dev.restate.sdk.gen.template.HandlebarsTemplateEngine Maven / Gradle / Ivy

The newest version!
// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH
//
// This file is part of the Restate Java SDK,
// which is released under the MIT license.
//
// You can find a copy of the license in file LICENSE in the root
// directory of this repository or package, or at
// https://github.com/restatedev/sdk-java/blob/main/LICENSE
package dev.restate.sdk.gen.template;

import com.github.jknack.handlebars.Context;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Template;
import com.github.jknack.handlebars.context.FieldValueResolver;
import com.github.jknack.handlebars.helper.StringHelpers;
import com.github.jknack.handlebars.internal.text.StringEscapeUtils;
import com.github.jknack.handlebars.io.TemplateLoader;
import dev.restate.sdk.common.ServiceType;
import dev.restate.sdk.common.function.ThrowingFunction;
import dev.restate.sdk.gen.model.Handler;
import dev.restate.sdk.gen.model.HandlerType;
import dev.restate.sdk.gen.model.Service;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class HandlebarsTemplateEngine {

  private final String baseTemplateName;
  private final Map templates;
  private final Set handlerNamesToPrefix;

  public HandlebarsTemplateEngine(
      String baseTemplateName,
      TemplateLoader templateLoader,
      Map templates,
      Set handlerNamesToPrefix) {
    this.baseTemplateName = baseTemplateName;
    this.handlerNamesToPrefix = handlerNamesToPrefix;

    Handlebars handlebars = new Handlebars(templateLoader);
    handlebars.registerHelpers(StringHelpers.class);
    handlebars.registerHelper(
        "targetExpr",
        (h, options) -> {
          return switch (h.serviceType) {
            case SERVICE ->
                String.format(
                    "Target.service(%s.SERVICE_NAME, \"%s\")", h.definitionsClass, h.name);
            case VIRTUAL_OBJECT ->
                String.format(
                    "Target.virtualObject(%s.SERVICE_NAME, %s, \"%s\")",
                    h.definitionsClass, options.param(0), h.name);
            case WORKFLOW ->
                String.format(
                    "Target.workflow(%s.SERVICE_NAME, %s, \"%s\")",
                    h.definitionsClass, options.param(0), h.name);
          };
        });
    handlebars.registerHelpers(StringEscapeUtils.class);

    this.templates =
        templates.entrySet().stream()
            .collect(
                Collectors.toMap(
                    Map.Entry::getKey,
                    e -> {
                      try {
                        return handlebars.compile(e.getValue());
                      } catch (IOException ex) {
                        throw new RuntimeException(
                            "Can't compile template for "
                                + e.getKey()
                                + " with base template name "
                                + baseTemplateName,
                            ex);
                      }
                    }));
  }

  public void generate(ThrowingFunction createFile, Service service)
      throws Throwable {
    String fileName = service.getGeneratedClassFqcnPrefix() + this.baseTemplateName;
    try (Writer out = createFile.apply(fileName)) {
      this.templates
          .get(service.getServiceType())
          .apply(
              Context.newBuilder(
                      new ServiceTemplateModel(
                          service, this.baseTemplateName, this.handlerNamesToPrefix))
                  .resolver(FieldValueResolver.INSTANCE)
                  .build(),
              out);
    }
  }

  // --- classes to interact with the handlebars template

  static class ServiceTemplateModel {
    public final String originalClassPkg;
    public final String originalClassFqcn;
    public final String generatedClassSimpleNamePrefix;
    public final String generatedClassSimpleName;
    public final String serviceName;
    public final String documentation;

    public final String serviceType;
    public final boolean isWorkflow;
    public final boolean isObject;
    public final boolean isService;
    public final boolean isKeyed;
    public final List handlers;

    private ServiceTemplateModel(
        Service inner, String baseTemplateName, Set handlerNamesToPrefix) {
      this.originalClassPkg = inner.getTargetPkg().toString();
      this.originalClassFqcn = inner.getTargetFqcn().toString();
      this.generatedClassSimpleNamePrefix = inner.getSimpleServiceName();
      this.generatedClassSimpleName = this.generatedClassSimpleNamePrefix + baseTemplateName;
      this.serviceName = inner.getFullyQualifiedServiceName();

      this.documentation = inner.getDocumentation();

      this.serviceType = inner.getServiceType().toString();
      this.isWorkflow = inner.getServiceType() == ServiceType.WORKFLOW;
      this.isObject = inner.getServiceType() == ServiceType.VIRTUAL_OBJECT;
      this.isService = inner.getServiceType() == ServiceType.SERVICE;
      this.isKeyed = this.isObject || this.isWorkflow;

      this.handlers =
          inner.getMethods().stream()
              .map(
                  h ->
                      new HandlerTemplateModel(
                          h,
                          inner.getServiceType(),
                          this.generatedClassSimpleNamePrefix + "Definitions",
                          handlerNamesToPrefix))
              .collect(Collectors.toList());
    }
  }

  static class HandlerTemplateModel {
    public final String name;
    public final String methodName;
    public final String handlerType;
    public final boolean isWorkflow;
    public final boolean isShared;
    public final boolean isStateless;
    public final boolean isExclusive;

    private final ServiceType serviceType;
    private final String definitionsClass;
    public final String documentation;

    public final boolean inputEmpty;
    public final String inputFqcn;
    public final String inputSerdeDecl;
    public final String boxedInputFqcn;
    public final String inputSerdeFieldName;
    public final String inputAcceptContentType;
    public final String inputSerdeRef;

    public final boolean outputEmpty;
    public final String outputFqcn;
    public final String outputSerdeDecl;
    public final String boxedOutputFqcn;
    public final String outputSerdeFieldName;
    public final String outputSerdeRef;

    private HandlerTemplateModel(
        Handler inner,
        ServiceType serviceType,
        String definitionsClass,
        Set handlerNamesToPrefix) {
      this.name = inner.name().toString();
      this.methodName = (handlerNamesToPrefix.contains(this.name) ? "_" : "") + this.name;
      this.handlerType = inner.handlerType().toString();
      this.isWorkflow = inner.handlerType() == HandlerType.WORKFLOW;
      this.isShared = inner.handlerType() == HandlerType.SHARED;
      this.isExclusive = inner.handlerType() == HandlerType.EXCLUSIVE;
      this.isStateless = inner.handlerType() == HandlerType.STATELESS;

      this.serviceType = serviceType;
      this.definitionsClass = definitionsClass;
      this.documentation = inner.documentation();

      this.inputEmpty = inner.inputType().isEmpty();
      this.inputFqcn = inner.inputType().name();
      this.inputSerdeDecl = inner.inputType().serdeDecl();
      this.boxedInputFqcn = inner.inputType().boxed();
      this.inputSerdeFieldName = this.name.toUpperCase() + "_INPUT";
      this.inputAcceptContentType = inner.inputAccept();
      this.inputSerdeRef = definitionsClass + ".Serde." + this.inputSerdeFieldName;

      this.outputEmpty = inner.outputType().isEmpty();
      this.outputFqcn = inner.outputType().name();
      this.outputSerdeDecl = inner.outputType().serdeDecl();
      this.boxedOutputFqcn = inner.outputType().boxed();
      this.outputSerdeFieldName = this.name.toUpperCase() + "_OUTPUT";
      this.outputSerdeRef = definitionsClass + ".Serde." + this.outputSerdeFieldName;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy