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

io.thestencil.staticontent.StaticContentProcessor Maven / Gradle / Ivy

The newest version!
package io.thestencil.staticontent;

/*-
 * #%L
 * quarkus-stencil-sc-deployment
 * %%
 * Copyright (C) 2021 Copyright 2021 ReSys OÜ
 * %%
 * 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.
 * #L%
 */

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.stream.Collectors;

import javax.inject.Inject;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanContainerListenerBuildItem;
import io.quarkus.bootstrap.model.AppArtifact;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.GeneratedResourceBuildItem;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.LiveReloadBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
import io.quarkus.deployment.configuration.ConfigurationError;
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
import io.quarkus.deployment.util.FileUtil;
import io.quarkus.deployment.util.WebJarUtil;
import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem;
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem;
import io.thestencil.client.api.Markdowns;
import io.thestencil.client.spi.StencilClientImpl;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;

public class StaticContentProcessor {
  
  private static final Logger LOGGER = LoggerFactory.getLogger(StaticContentProcessor.class);
  
  private static final String FINAL_DESTINATION = "META-INF/stencil-sc-files";
  public static final String FEATURE_BUILD_ITEM = "stencil-sc";
  
  @Inject
  private LaunchModeBuildItem launch;
  
  StaticContentConfig config;
  
  @BuildStep
  FeatureBuildItem feature() {
    return new FeatureBuildItem(FEATURE_BUILD_ITEM);
  }

  @BuildStep
  @Record(ExecutionTime.STATIC_INIT)
  void backendBeans(
      StaticContentBuildItem buildItem,
      StaticContentRecorder recorder,
      BuildProducer buildItems,
      BuildProducer beans) {

    
    if(config.siteJson.isPresent() && config.webjar.isPresent()) {
      throw new ConfigurationError("siteJson and webjar both can't be defined, define only one of them!"); 
    }

    if(config.siteJson.isEmpty() && config.webjar.isEmpty()) {
      throw new ConfigurationError("siteJson and webjar both are empty, define one of them!"); 
    }
    
    
    final var client = StencilClientImpl.builder().defaultObjectMapper().inmemory().build()
        .sites().source(buildItem.getContent())
        .imagePath(buildItem.getUiPath())
        .created(System.currentTimeMillis());
    final var content = client.build();
    final var contentValues = content.getSites().entrySet().stream()
        .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
    
    if(!contentValues.containsKey(config.defaultLocale)) {
      throw new ConfigurationError("Markdowns must have localization for default-locale: '" + config.defaultLocale + "'!");
    }
    
    if(LOGGER.isDebugEnabled()) {
      LOGGER.debug("Supported locales: '" + String.join(", ", contentValues.keySet()) + "'");
    }
    buildItems.produce(AdditionalBeanBuildItem.builder().setUnremovable().addBeanClass(StaticContentBeanFactory.class).build());
    beans.produce(new BeanContainerListenerBuildItem(recorder.listener(content, config.defaultLocale)));
  }
  
  @BuildStep
  @Record(ExecutionTime.RUNTIME_INIT)
  public void staticContentHandler(
    StaticContentRecorder recorder,
    HttpRootPathBuildItem httpRoot,
    BuildProducer routes,
    StaticContentConfig config) throws Exception {
    
    Handler handler = recorder.staticContentHandler();

    routes.produce(httpRoot.routeBuilder()
        .route(config.servicePath)
        .handler(handler)
        .build());
    routes.produce(httpRoot.routeBuilder()
        .route(config.servicePath + "/*")
        .handler(handler)
        .build());
  }
  
  @BuildStep
  @Record(ExecutionTime.STATIC_INIT)
  public void staticContent(
      StaticContentRecorder recorder,
      BuildProducer buildProducer,
      
      BuildProducer generatedResources,
      BuildProducer nativeImage,
      
      NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
      CurateOutcomeBuildItem curateOutcomeBuildItem,
      
      LiveReloadBuildItem liveReloadBuildItem,
      HttpRootPathBuildItem httpRootPathBuildItem,
      BuildProducer displayableEndpoints) throws Exception {
    
    

    if(this.config.siteJson.isPresent()) {
      staticJSONContent(recorder, buildProducer, generatedResources, nativeImage, nonApplicationRootPathBuildItem, curateOutcomeBuildItem, liveReloadBuildItem, httpRootPathBuildItem, displayableEndpoints);
      return;
    }
   
    if(this.config.webjar.isPresent()) {
      staticWebjarContent(recorder, buildProducer, generatedResources, nativeImage, nonApplicationRootPathBuildItem, curateOutcomeBuildItem, liveReloadBuildItem, httpRootPathBuildItem, displayableEndpoints);
      return;
    }
  }
  
  public void staticWebjarContent(
      StaticContentRecorder recorder,
      BuildProducer buildProducer,
      
      BuildProducer generatedResources,
      BuildProducer nativeImage,
      
      NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
      CurateOutcomeBuildItem curateOutcomeBuildItem,
      
      LiveReloadBuildItem liveReloadBuildItem,
      HttpRootPathBuildItem httpRootPathBuildItem,
      BuildProducer displayableEndpoints) throws Exception {

    
    displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(httpRootPathBuildItem.resolvePath(config.servicePath), "Zoe Static Content From Webjar"));
    
    final String[] fragments = config.webjar.get().split(":");
    final String webjarGroupId = fragments[0];
    final String webjarArtifactId = fragments[1];
    final String webjarPrefix = "META-INF/resources/webjars/" + webjarArtifactId + "/";
    
    // dev envir
    final var artifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, webjarGroupId, webjarArtifactId);    
    if (launch.getLaunchMode().isDevOrTest()) {
      
      Path tempPath = WebJarUtil
          .copyResourcesForDevOrTest(liveReloadBuildItem, curateOutcomeBuildItem, launch, artifact, webjarPrefix + artifact.getVersion(), false);
      String tempAbsolutePath = tempPath.toAbsolutePath().toString();
      
      final var builder = StencilClientImpl.builder().defaultObjectMapper().inmemory().build().markdown();
      Files.walk(tempPath).filter(Files::isRegularFile).forEach(file -> {
        try {
          String absolutePath = file.toAbsolutePath().toString();
          String path = absolutePath.substring(tempAbsolutePath.length() + 1);
          byte[] bytes = FileUtil.readFileContents(new FileInputStream(file.toFile()));
          builder.md(path, bytes);
        } catch(IOException e) {
          throw new ConfigurationError("Failed to read file: '" + file + "'!");
        }
      });

      final String frontendPath = httpRootPathBuildItem.resolvePath(config.imagePath);
      buildProducer.produce(new StaticContentBuildItem(tempPath.toAbsolutePath().toString(), frontendPath, builder.build()));
      displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(httpRootPathBuildItem.resolvePath(frontendPath + "/"), "Zoe Static Content From Webjar"));

      // Handle live reload of branding files
      if (liveReloadBuildItem.isLiveReload() && !liveReloadBuildItem.getChangedResources().isEmpty()) {
          WebJarUtil.hotReloadBrandingChanges(
              curateOutcomeBuildItem, launch, artifact,
                  liveReloadBuildItem.getChangedResources());
      }
      return;
    } 
      
    
    // native image
    final String frontendPath = httpRootPathBuildItem.resolvePath(config.imagePath);
    final Map files = WebJarUtil.copyResourcesForProduction(curateOutcomeBuildItem, artifact, webjarPrefix + artifact.getVersion());
    final var builder = StencilClientImpl.builder().defaultObjectMapper().inmemory().build().markdown();
    
    for (Map.Entry file : files.entrySet()) {
      String fileName = file.getKey();
      byte[] content = file.getValue();
      fileName = FINAL_DESTINATION + "/" + fileName;
      final String cleanFileName = fileName.toLowerCase();
      
      // copy images
      if(cleanFileName.startsWith("images/")) {
        generatedResources.produce(new GeneratedResourceBuildItem(fileName, content));
        nativeImage.produce(new NativeImageResourceBuildItem(fileName));
      }
      builder.md(file.getKey(), file.getValue());
    }      
    buildProducer.produce(new StaticContentBuildItem(FINAL_DESTINATION, frontendPath, builder.build()));
    
  }
  
  public void staticJSONContent(
      StaticContentRecorder recorder,
      BuildProducer buildProducer,
      
      BuildProducer generatedResources,
      BuildProducer nativeImage,
      
      NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
      CurateOutcomeBuildItem curateOutcomeBuildItem,
      
      LiveReloadBuildItem liveReloadBuildItem,
      HttpRootPathBuildItem httpRootPathBuildItem,
      BuildProducer displayableEndpoints) throws Exception {

    
    displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(httpRootPathBuildItem.resolvePath(config.servicePath), "Zoe Static Content From JSON"));
    Path tempPath = this.config.siteJson.get();
    
    // dev envir    
    if (launch.getLaunchMode().isDevOrTest()) {
      String site;
      try {
        final var stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(tempPath.toString());
        site = IOUtils.toString(stream, StandardCharsets.UTF_8);        
      } catch(IOException e) {
        throw new ConfigurationError("Failed to read file: '" + tempPath + "'!");
      }
      
      final Markdowns md = StencilClientImpl.builder().defaultObjectMapper().inmemory().build().markdown().json(site, false).build();
      final String frontendPath = httpRootPathBuildItem.resolvePath(config.imagePath);
      buildProducer.produce(new StaticContentBuildItem(tempPath.toAbsolutePath().toString(), frontendPath, md));
      displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(httpRootPathBuildItem.resolvePath(frontendPath + "/"), "Zoe Static Content"));
      return;
    } 
    
    
    // native image
    final String frontendPath = httpRootPathBuildItem.resolvePath(config.imagePath);
    final String site;
    
    try {
      final var stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(tempPath.toString());
      site = IOUtils.toString(stream, StandardCharsets.UTF_8);
    } catch(IOException e) {
      throw new ConfigurationError("Failed to read file: '" + tempPath + "'!");
    }

    String fileName = tempPath.toFile().getName().toString();
    fileName = FINAL_DESTINATION + "/" + fileName;
    final Markdowns md = StencilClientImpl.builder().defaultObjectMapper().inmemory().build().markdown().json(site, false).build();
    buildProducer.produce(new StaticContentBuildItem(FINAL_DESTINATION, frontendPath, md));
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy