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

org.rosaenlg.lib.RosaeContext Maven / Gradle / Ivy

Go to download

Java wrapper around RosaeNLG (JavaScript Natural Language Generation library), using GraalVM

There is a newer version: 3.2.2
Show newest version
package org.rosaenlg.lib;

/*-
 * #%L
 * org.rosaenlg:java-wrapper
 * %%
 * Copyright (C) 2019 RosaeNLG.org, Ludan Stoecklé
 * %%
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * #L%
 */

import java.io.File;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.file.FileSystems;
import java.nio.file.Paths;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;

import org.json.JSONObject;

import org.rosaenlg.lib.Autotest;
import org.rosaenlg.lib.JsonPackage;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Wrapper around RosaeNLG.
 * 
 * @see RosaeNLG documentation
 * @author Ludan Stoecklé [email protected]
 */
public class RosaeContext {

  private static final Logger logger = LoggerFactory.getLogger(RosaeContext.class);

  private static Source sourceWrapper;
  private static final String exceptionMarker = "EXCEPTION ";
  private static Map sourcesRosaeNLG = new HashMap();

  private String language;
  private String templateId;
  private String entryTemplate;
  private CompileOptions compileInfo;
  private Map templates = new HashMap();
  private Autotest autotest;

  private Context context;

  private String originalJsonPackage;
  private Value compiledTemplateFct;

  static {
    logger.info("Entering static constructor...");

    try {
      sourceWrapper = Source.newBuilder("js",
          new InputStreamReader(
            RosaeContext.class.getResourceAsStream("/wrapper.js"), "UTF-8"), 
            "wrapper.js").build();
      try {
        // System.out.println("We are in a fat jar...");
        URL res = RosaeContext.class.getClassLoader().getResource("/META-INF/truffle/language");
        // initialize the file system for the language file
        FileSystems.newFileSystem(res.toURI(), new HashMap<>());
        logger.info("Fat jar FileSystem ok");
      } catch (Throwable ignored) {
        // in case of starting without fat jar
      }

    } catch (Exception e) {
      throw new RuntimeException("cannot find wrapper.js: " + e.toString());
    }

    logger.info("Constructor static done.");
  }

  /** Constructor, for very simple templates.
   * 
   * 

* Will create a new GraalVM context for this template, compile the template, * and be ready to render multiple times. *

* * @param template the content of the template * @param compileInfo how to compile the template * @throws Exception if compiling fails */ public RosaeContext( String template, CompileOptions compileInfo) throws Exception { this.entryTemplate = "main"; this.compileInfo = compileInfo; templates.put("main", template); this.init(); } /** Constructor, based on traditional Pug templates. * *

* Will create a new GraalVM context for this template, compile the template, * and be ready to render multiple times. *

* * @param entryTemplate the name of the template to compile * @param includesPath the path where the templates to be included are located * (including the entry template) * @param compileInfo how to compile the template * @throws Exception if compiling fails */ public RosaeContext( String entryTemplate, File includesPath, CompileOptions compileInfo) throws Exception { this.entryTemplate = entryTemplate; this.compileInfo = compileInfo; Collection files = FileUtils.listFiles( includesPath, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE); for (File file : files) { String content = FileUtils.readFileToString(file, "utf-8"); templates.put( Paths.get(includesPath.toURI()) .relativize(Paths.get(file.toURI())) .toString() .replace("\\", "/"), // must fit pug convention content); } // logger.debug(templates.toString()); this.init(); } /** Constructor, based on a String containing all the information on a template. * *

* Will create a new GraalVM context for this template, compile the template, * test if autotest is activated, and be ready to render multiple times. * See * https://rosaenlg.org/rosaenlg/1.5.0/integration/gulp.html#_packagetemplatejson * to create such a package. *

* * @param jsonPackageAsString contains a template and its parameters, JSON format * @throws Exception when the JSON package is not well formated, * or if the autotest was activated and failed. */ public RosaeContext(String jsonPackageAsString) throws Exception { JsonPackage jsonPackage = new JsonPackage(jsonPackageAsString); this.originalJsonPackage = jsonPackageAsString; this.templateId = jsonPackage.getTemplateId(); this.entryTemplate = jsonPackage.getEntryTemplate(); this.compileInfo = jsonPackage.getCompileInfo(); this.templates = jsonPackage.getTemplates(); this.autotest = jsonPackage.getAutotest(); this.init(); } private void init() throws Exception { logger.info("Constructor RosaeContext"); this.language = this.compileInfo.getLanguage(); if (this.language == null) { throw new Exception("language must be set!"); } this.initGraalContext(); this.compile(); this.autotest(); logger.info("Constructor RosaeContext done."); } private void initGraalContext() throws Exception { context = Context.newBuilder("js").allowAllAccess(false).build(); context.eval(getSourcesForLanguage(this.language)); context.eval(sourceWrapper); } private void compile() throws Exception { // logger.info("templates: {}", (new JSONObject(jsonPackage.getTemplates())).toString()); // logger.info("compileInfo: {}", jsonPackage.getCompileInfo().toJson()); Value compileFileFct = context.eval("js", "compileFile"); assert compileFileFct.canExecute(); compiledTemplateFct = compileFileFct.execute( entryTemplate, this.language, (new JSONObject(templates)).toString(), compileInfo.toJson(), exceptionMarker); if (compiledTemplateFct.toString().startsWith(exceptionMarker)) { throw new Exception(compiledTemplateFct.toString().replace(exceptionMarker, "")); } } private void autotest() throws Exception { if (autotest != null && autotest.getActivate()) { logger.info("auto test is activated"); String rendered = this.render(autotest.getJsonInput()); for (int i = 0; i < autotest.getExpected().size(); i++) { if (!rendered.contains(autotest.getExpected().get(i))) { throw new Exception( templateId + " autotest fail on " + autotest.getExpected().get(i) + " was " + rendered); } } } } private Source getSourcesForLanguage(String language) throws Exception { if (sourcesRosaeNLG.get(language) == null) { logger.info("will now load rosaenlg js for {}", language); final Properties properties = new Properties(); properties.load(RosaeContext.class.getResourceAsStream("/project.properties")); String version = properties.getProperty("rosaenlg.version"); String rosaejsFileName = "rosaenlg_tiny_" + language + "_" + version + "_comp.js"; logger.info("using rosaenlg file: {}", rosaejsFileName); Source sourceRosaeNlg = Source.newBuilder( "js", new InputStreamReader( RosaeContext.class.getResourceAsStream("/" + rosaejsFileName), "UTF-8"), rosaejsFileName) .build(); sourcesRosaeNLG.put(language, sourceRosaeNlg); logger.info("RosaeNLG js lib read, lines {}", sourceRosaeNlg.getLineCount()); } else { logger.info("rosaenlg js for {} is already loaded", language); } return sourcesRosaeNLG.get(language); } /** Render the template with input data. * * @param jsonOptionsAsString JSON string containing all the input data to render the template. * @return String the rendered result * @throws Exception if an error occurs during rendering */ public synchronized String render(String jsonOptionsAsString) throws Exception { // inject NlgLib into the options JSONObject jsonOptions = new JSONObject(jsonOptionsAsString); RenderOptions runtimeOptions = new RenderOptions(jsonOptions); // we keep original but add 'util' jsonOptions.put("util", "NLGLIB_PLACEHOLDER"); String finalOptions = jsonOptions.toString().replace("\"NLGLIB_PLACEHOLDER\"", "new rosaenlg_" + this.language + ".NlgLib(JSON.parse('" + runtimeOptions.toJson() + "'))"); String paramForge = "() => { return " + finalOptions + ";}"; Value realParam = context.eval("js", paramForge).execute(); String rendered = compiledTemplateFct.execute(realParam).asString(); if (rendered.startsWith(exceptionMarker)) { throw new Exception(rendered.replace(exceptionMarker, "")); } return rendered; } /** * Create a compiled file for client side rendering. *

* This is useful to compile a templates, bundle everything for later rendering, * typically client side in a browser. *

* * @return String the compiled template (which is JavaScript code) * @throws Exception if a problem occurs */ public String getCompiledClient() throws Exception { Value compileFileClientFct = context.eval("js", "compileFileClient"); assert compileFileClientFct.canExecute(); CompileOptions newCompileOpts = (CompileOptions)this.compileInfo.clone(); newCompileOpts.setEmbedResources(true); String compiled = compileFileClientFct.execute( entryTemplate, language, (new JSONObject(templates)).toString(), newCompileOpts.toJson(), exceptionMarker) .asString(); if (compiled.startsWith(exceptionMarker)) { throw new Exception(compiled.replace(exceptionMarker, "")); } return compiled; } /** Getter on template ID. * * @return String the template ID */ public String getTemplateId() { return this.templateId; } /** Getter on the original JSON package containing the template. * * @return String the JSON package containing the template. */ public String getJsonPackageAsString() { return this.originalJsonPackage; } /** Destroys the object by closing its context. * *

* It is recommended to use 'destroy' so that the GraalVM is explicitely closed. *

* * @throws Exception if closing the context failed */ public synchronized void destroy() throws Exception { context.close(false); // no cancel if still executing } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy