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

com.virtusa.gto.nyql.engine.repo.Caching.groovy Maven / Gradle / Ivy

package com.virtusa.gto.nyql.engine.repo

import com.virtusa.gto.nyql.configs.Configurations
import com.virtusa.gto.nyql.exceptions.NyException
import com.virtusa.gto.nyql.model.*
import groovy.transform.CompileStatic
import org.codehaus.groovy.control.CompilationFailedException
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ImportCustomizer
import org.slf4j.Logger
import org.slf4j.LoggerFactory

import java.util.concurrent.ConcurrentHashMap
/**
 * @author IWEERARATHNA
 */
@CompileStatic
class Caching implements Closeable {

    private static final Logger LOGGER = LoggerFactory.getLogger(Caching)

    private final Map cache = new ConcurrentHashMap<>()

    private CompilerConfiguration compilerConfigurations
    private final NyGroovyClassLoader gcl
    private final Configurations configurations
    private final QScriptMapper mapper
    private final Object clzLoaderLock = new Object()

    Caching(Configurations theConfigs, QScriptMapper scriptMapper) {
        configurations = theConfigs
        mapper = scriptMapper

        gcl = new NyGroovyClassLoader(Thread.currentThread().contextClassLoader, makeCompilerConfigs())
    }

    void compileAllScripts(Collection sources) throws NyException {
        if (configurations.cacheRawScripts()) {
            synchronized (clzLoaderLock) {
                int n = sources.size()
                int len = String.valueOf(n).length()
                int curr = 1

                LOGGER.info("Compiling all ${n} dsl script(s)...")
                for (QSource qSource : sources) {
                    String id = qSource.id
                    try {
                        LOGGER.debug('  Compiling [' + padLeft(len, curr++) + '/' + n + ']: ' + id)
                        gcl.parseClass(qSource.codeSource, true)
                    } catch (CompilationFailedException ex) {
                        LOGGER.error("Compilation error in script '$id'", ex)
                        throw new NyException("Compilation error in script '$id'!", ex)
                    }
                }
                LOGGER.info('Compilation successful!')
                LOGGER.info('-'*80)
            }
        }
    }

    private static String padLeft(int len, int number) {
        ' '*(len - String.valueOf(number).length()) + number
    }

    void reloadScript(String scriptId) throws NyException {
        cache.remove(scriptId)
        def reloaded = mapper.reload(scriptId)
        synchronized (clzLoaderLock) {
            try {
                LOGGER.debug('-'*80)
                LOGGER.debug(' Recompiling script: ' + scriptId + '...')
                gcl.parseClass(reloaded.codeSource, true, true)
                LOGGER.debug(' Successfully recompiled the script ' + scriptId)
            } catch (CompilationFailedException ex) {
                LOGGER.error("Compilation error in script '$scriptId'", ex)
                throw new NyException("Compilation error in script '$scriptId'!", ex)
            }
        }
    }

    boolean hasGeneratedQuery(String scriptId) {
        cache.containsKey(scriptId)
    }

    QScript getGeneratedQuery(String scriptId, QSession session) {
        QScript qScript = cache.get(scriptId)
        if (qScript != null) {
            return new QScript(id: qScript.id, proxy: qScript.proxy, qSession: session)
        }
        qScript
    }

    /**
     * Spawn a new script instance from already cached instance of script.
     *
     * @param src source script to make a clone.
     * @return new instance of script.
     */
    private static QScript spawnScriptFrom(QScript src) {
        src.spawn()
    }

    /**
     * Add a generated query to the cache.
     *
     * @param scriptId script id.
     * @param script generated query instance with result.
     * @return the added script instance.
     */
    QScript addGeneratedQuery(String scriptId, QScript script) {
        cache.put(scriptId, spawnScriptFrom(script))
        script
    }

    /**
     * Returns a new instance of compiled script from the cache.
     *
     * @param sourceScript corresponding source of the script.
     * @param session session instance.
     * @return newly created script instance.
     */
    Script getCompiledScript(QSource sourceScript, QSession session) {
        Binding binding = new Binding(session?.sessionVariables ?: [:])
        if (configurations.cacheRawScripts()) {
            if (compilerConfigurations.recompileGroovySource) {
                synchronized (clzLoaderLock) {
                    parseAndGet(sourceScript, session, binding)
                }
            } else {
                parseAndGet(sourceScript, session, binding)
            }
        } else {
            GroovyShell shell = new GroovyShell(Thread.currentThread().contextClassLoader, binding, makeCompilerConfigs())
            NyBaseScript parsedScript = sourceScript.parseIn(shell)
            parsedScript.setSession(session)
            parsedScript
        }
    }

    /**
     * Parse the groovy source from class loader and apply the session and bindings.
     *
     * @param sourceScript source script.
     * @param session session instance.
     * @param binding binding instance.
     * @return loaded compiled script.
     */
    private Script parseAndGet(QSource sourceScript, QSession session, Binding binding) {
        Class clazz = gcl.parseClass(sourceScript.codeSource, true)
        NyBaseScript scr = clazz.newInstance() as NyBaseScript
        scr.setBinding(binding)
        scr.setSession(session)
        scr
    }

    /**
     * CLear the generated query cache or class loader cache.
     *
     * @param level level of cache to clean.
     */
    void clearGeneratedCache(int level) {
        if (level >= 0) {
            cache.clear()
        }
        if (level > 1) {
            gcl.clearCache()
        }
    }

    /**
     * Create a set of configurations requires for script initial compilation.
     *
     * @return compiler configuration instance newly created or already created.
     */
    CompilerConfiguration makeCompilerConfigs() {
        if (compilerConfigurations != null) {
            return compilerConfigurations
        }

        CompilerConfiguration compilerConfigurations = new CompilerConfiguration()
        compilerConfigurations.scriptBaseClass = NyBaseScript.name

        String[] defImports = configurations.defaultImports()
        if (defImports != null) {
            ImportCustomizer importCustomizer = new ImportCustomizer()
            importCustomizer.addImports(defImports)
            compilerConfigurations.addCompilationCustomizers(importCustomizer)
        }

        // setup recompilation, if specified
        boolean doRecompile = configurations.isAllowRecompilation()
        if (doRecompile) {
            LOGGER.warn('-'*100)
            LOGGER.warn('*** NyQL has enabled to recompile scripts at runtime!')
            LOGGER.warn("*** If this is NOT intentional, then set 'allowRecompilation' flag under 'caching' section to false")
            LOGGER.warn('-'*100)
        }
        compilerConfigurations.setRecompileGroovySource(doRecompile)

        this.compilerConfigurations = compilerConfigurations
        compilerConfigurations
    }

    @Override
    void close() throws IOException {
        if (gcl != null) {
            gcl.close()
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy