package gw.api.startup uses com.google.common.base.Stopwatch uses com.guidewire.pl.system.dependency.ServerDependencies uses com.guidewire.rating.flow.domain.util.CalcRoutineCodeGenerator uses com.guidewire.rating.flow.domain.util.CalcRoutineCodeGeneratorFactory uses com.guidewire.rating.flow.domain.util.CalcRoutineCodeGeneratorUtils uses gw.api.startable.IStartablePlugin uses gw.api.startable.StartablePluginCallbackHandler uses gw.api.startable.StartablePluginState uses gw.api.system.PCConfigParameters uses gw.api.system.PCDependenciesGateway uses gw.api.system.PCLoggerCategory uses gw.lang.reflect.TypeSystem uses gw.plugin.InitializablePlugin uses gw.plugin.Plugins uses gw.plugin.rateflow.RateBookPreloadPlugin uses org.slf4j.Logger uses java.io.File uses java.nio.file.Path uses java.nio.file.Paths uses java.util.concurrent.Callable uses java.util.concurrent.TimeUnit uses java.util.concurrent.atomic.AtomicInteger uses java.util.stream.Stream /** * A Startable plugin for experimental source code generation of CalcRoutines. *

* Parameters:
*

*/ @SuppressWarnings("HiddenPackageReference") class RateRoutineSourcesWriter implements IStartablePlugin, InitializablePlugin { static final var DIRECTORY_PARAMETER_KEY = "Directory" static final var COMPILE_PARAMETER_KEY = "Compile" static final var CODEGEN_STRATEGY_PARAMETER_KEY = "Strategy" static final var SOURCE_FILE_EXTENSION = ".gs" static final var CLASS_FILE_EXTENSION = ".class" static final var DEFAULT_DIRECTORY_PATH = CalcRoutineCodeGeneratorUtils.CompiledClassesDirectory static final var GOSU_CLASS_LOADER = TypeSystem.GosuClassLoader static final var _logger = PCLoggerCategory.RATEFLOW var _directory = DEFAULT_DIRECTORY_PATH var _policyLine : String = null var _compile = false var _state : StartablePluginState as readonly State = Stopped @SuppressWarnings("unused") var _routineCount: int as readonly RoutineCount = 0 // Used elsewhere via property name @SuppressWarnings("unused") var _processedCount: int as readonly ProcessedCount = 0 // Used elsewhere via property name construct() { } construct(policyLine : String, directory : String, compile : boolean) { _policyLine = policyLine _directory = directory _compile = compile } override function start(pluginCallbackHandler : StartablePluginCallbackHandler, serverStarting : boolean) { write(); } function write() { _state = Started writeSources() } function write(definitions: Collection) { _state = Started writeSources(definitions) } private function writeSources() { com.guidewire.pc.domain.rating.RateTableDefinitionCacheImpl.init() PCDependenciesGateway.RateTableDefinitionCache.start() var activeRateBooks = allRateBooksToExportFor(_policyLine) var definitions = new ArrayList() activeRateBooks.forEach(\book -> definitions.addAll(book.RateBookCalcRoutines*.CalcRoutineDefinition.toList())) writeSources(definitions) } private function writeSources(definitions: Collection) { try { _routineCount = definitions.size() var stopwatch = Stopwatch.createStarted() _logger.info("Writing rate routine generated sources to directory ${_directory} started") var routineCount = new AtomicInteger(0) var errorCount = new AtomicInteger(0) var filesWritten = new AtomicInteger(0) var stats = new RateRoutineStatistics() var definitionsStream = definitions.stream() if (PCConfigParameters.ParallelStreamsForRateRoutinesLoad.Value) { definitionsStream = definitionsStream.parallel() } definitionsStream.forEach(\definition -> { if (_state == Stopped) { return } routineCount.incrementAndGet() try { var sourceCodeWriter = new CalcRoutineSourceCodeWriterCallable(definition, _directory, _compile) filesWritten.addAndGet(sourceCodeWriter.call().size()) stats.update(definition, sourceCodeWriter.CodeGenerator) } catch (ex : Exception) { _logger.warn("Error writing and/or compiling rate routine ${definition.Code} v${definition.Version}", ex) errorCount.incrementAndGet() } var numRoutines = routineCount.get() _processedCount = numRoutines if ((numRoutines % 1000) == 0) { _logger.info("Processed ${numRoutines} routine definitions with ${errorCount.get()} errors. " + "Files = ${filesWritten.get()}") } }) if (_state == Stopped) { return } var duration = stopwatch.elapsed(TimeUnit.SECONDS) _logger.info("Writing rate routine generated sources to ${_directory} completed. " + "Processed ${routineCount.get()} routine definitions with ${errorCount} errors. #Files written = ${filesWritten}. Time = ${duration}seconds") if (errorCount.get() > 0) { throw new IllegalStateException("Code generation and/or compilation failed for ${errorCount.get()} rate routines.") } if (routineCount.get() > 0) { stats.log(_logger) } } finally { _state = Stopped } } override function stop(serverShuttingDown : boolean) { _logger.info("CalcRoutinesSourcesWriter: Stop requested.") _state = Stopped } override property set Parameters(params : Map) { for (key in params.Keys) { var strKey = key as String if (DIRECTORY_PARAMETER_KEY.equalsIgnoreCase(strKey)) { var directory = params.get(strKey) as String if (directory?.HasContent) { _directory = directory } } else if (COMPILE_PARAMETER_KEY.equalsIgnoreCase(strKey)) { var compile = params.get(strKey) as String if (compile?.HasContent) { _compile = Boolean.parseBoolean(compile) } } else if (CODEGEN_STRATEGY_PARAMETER_KEY.equalsIgnoreCase(strKey)) { var codegenStrategyStr = params.get(strKey) as String if (codegenStrategyStr?.HasContent) { codegenStrategyStr = codegenStrategyStr.trim().toUpperCase() ServerDependencies.SystemConfiguration.setParameterValue(PCConfigParameters.RateRoutineCodegenStrategy, codegenStrategyStr) } } } } private function allRateBooksToExportFor(policyLine : String): Iterable { var plugin = Plugins.get(RateBookPreloadPlugin) if (plugin == null) { return Collections.emptyList() } var rateBooksToExportToJar = plugin.getRateBooksToExportToJAR() return policyLine == null ? rateBooksToExportToJar : rateBooksToExportToJar.where(\elt -> policyLine == elt.PolicyLine) } private static class RateRoutineStatistics { var routineCount : long = 0 var minSteps = Integer.MAX_VALUE var maxSteps = Integer.MIN_VALUE var totalSteps : long = 0 var minStatements = Integer.MAX_VALUE var maxStatements = Integer.MIN_VALUE var totalStatements : long = 0 var minCodeSize = Integer.MAX_VALUE var maxCodeSize = Integer.MIN_VALUE var totalCodeSize : long = 0 var minLambdas = Integer.MAX_VALUE var maxLambdas = Integer.MIN_VALUE var totalLambdas : long = 0 function update(definition : CalcRoutineDefinition, codegen : CalcRoutineCodeGenerator) { routineCount++ var steps = definition.OrderedSteps.size() minSteps = Math.min(minSteps, steps) maxSteps = Math.max(maxSteps, steps) totalSteps += steps var statements = codegen.Statements var statementCount = statements.size() minStatements = Math.min(minStatements, statementCount) maxStatements = Math.max(maxStatements, statementCount) totalStatements += statementCount var codeSize = statements*.CodeSize.sum() minCodeSize = Math.min(minCodeSize, codeSize) maxCodeSize = Math.max(maxCodeSize, codeSize) totalCodeSize += codeSize var lambdaCount = codegen.Expression.countMatches("->") minLambdas = Math.min(minLambdas, lambdaCount) maxLambdas = Math.max(maxLambdas, lambdaCount) totalLambdas += lambdaCount } function log(logger : Logger) { if (routineCount == 0) { logger.info("No rate routines were written (and then loaded)") return } logger.info("Steps: Avg = {} Min = {} Max = {}", {totalSteps / routineCount, minSteps, maxSteps}) logger.info("Statements: Avg = {} Min = {} Max = {}", {totalStatements / routineCount, minStatements, maxStatements}) logger.info("Routine Code Size: Avg = {} Min = {} Max = {}", {totalCodeSize / routineCount, minCodeSize, maxCodeSize}) if (totalStatements > 0) { logger.info("Statement Code Size: Avg = {} Min = {} Max = {}", {totalCodeSize / totalStatements, (minCodeSize * routineCount) / totalStatements, (maxCodeSize * routineCount) / totalStatements}) } else { logger.info("There are no statements in loaded rate routines") } logger.info("Lambdas: Avg = {} Min = {} Max = {}", {totalLambdas / routineCount, minLambdas, maxLambdas}) } } private static class CalcRoutineSourceCodeWriterCallable implements Callable> { final var _definition : CalcRoutineDefinition as Definition final var _directory : String as Directory final var _compile : boolean as Compile var _codeGenerator: CalcRoutineCodeGenerator as readonly CodeGenerator construct(definition : CalcRoutineDefinition, directory : String, compile : boolean) { _definition = definition _directory = directory _compile = compile } override function call() : List { /** * Only create the source (and compile it) if the source doesn't exist or is older than the definition. If * compiling, if the top-level compiled class exists and all of its declared classes exist, and are all * more recent than the rate routine definition, then don't compile either. */ var preExistingPaths = SourceAndClassesPaths if (preExistingPaths != null) { return preExistingPaths } // Don't use _definition.getOrderedSteps() here as that will assume, because of the null validation context, that // we are executing and not generating code. _codeGenerator = CalcRoutineCodeGeneratorFactory.get(_definition, _definition.OrderedSteps, null, true /* always generate logging code as we're not validating */, false) return _codeGenerator.write(_directory, _compile).toList() } /** * Gets the path to the source file if it already exists and is more recent than the rate routine defintion. If * compiling, also includes the compiled class file and the class files for all its declared classed (recursive) exist. * * @return list of {@link Path}s of source file, and if compiling, of compiled class and compiled classes of all * declared classes of the base rate routine class, if these exist and are more recent than the rate routine * definition. null else. * @see Class#getDeclaredClasses() */ private property get SourceAndClassesPaths() : List { var packageName = CalcRoutineCodeGeneratorUtils.getPackageName(_definition) var routineName = CalcRoutineCodeGeneratorUtils.getRoutineName(_definition) var destDir = _directory + File.pathSeparator + packageName.replaceAll("\\.", File.pathSeparator) var sourceFilePath = Paths.get(destDir, {routineName + SOURCE_FILE_EXTENSION}) var sourceFile = sourceFilePath.toFile() var definitionUpdateTime = _definition.UpdateTime.Time if (sourceFile.exists() and sourceFile.lastModified() > definitionUpdateTime) { if (not _compile) { return Collections.singletonList(sourceFilePath) } var paths = new ArrayList() paths.add(sourceFilePath) var classFilePath = Paths.get(destDir, {routineName + CLASS_FILE_EXTENSION}) var classFile = classFilePath.toFile() if (classFile.exists() and classFile.lastModified() > definitionUpdateTime) { paths.add(classFilePath) } var declaredClassesPaths = getDeclaredClassesPaths(destDir) if (declaredClassesPaths != null) { paths.addAll(declaredClassesPaths) return paths } } return null } /** * Gets the paths to all declared classes (recursive) of the base rate rotuine class, if they all exists and are all * more recent than the rate routine definition. * * @param destDir directory under which to check for the classes * @return null if any of the declared classes do not exist or exist but is older than the rate routine * definition. Else list of paths to all declared classes of the base rate routine class. * @see Class#getDeclaredClasses() */ private function getDeclaredClassesPaths(destDir : String) : List { var clazz = GOSU_CLASS_LOADER.loadClass(CalcRoutineCodeGeneratorUtils.getClassName(_definition)) var declaredClassesIter = declaredClassesNames(clazz).iterator() var paths = new ArrayList() var notFoundOrOutOfDate = false while (not notFoundOrOutOfDate and declaredClassesIter.hasNext()) { var declaredClassName = declaredClassesIter.next() var declareClassFilePath = Paths.get(destDir, {declaredClassName + CLASS_FILE_EXTENSION}) var declaredClassFile = declareClassFilePath.toFile() if (declaredClassFile.exists() and declaredClassFile.lastModified() > _definition.UpdateTime.Time) { paths.add(declareClassFilePath) } else { notFoundOrOutOfDate = true } } return notFoundOrOutOfDate ? null : paths } /** * Gets the list of short names of all of the given classs's declared classes (recursive). * * @param clazz the class whose declared classes names are desired * @return list of short names of the given class's declared classes * @see Class#getDeclaredClasses() */ private function declaredClassesNames(clazz : Class) : List { var declaredClasses = new ArrayList() clazz.DeclaredClasses.each(\elt -> { declaredClasses.add(elt.SimpleName) declaredClasses.addAll(declaredClassesNames(elt)) }) return declaredClasses } } }