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

org.rythmengine.resource.TemplateResourceManager Maven / Gradle / Ivy

Go to download

A strong typed high performance Java Template engine with .Net Razor like syntax

There is a newer version: 1.4.2
Show newest version
/**
 * Copyright (C) 2013-2016 The Rythm Engine project
 * for LICENSE and other details see:
 * https://github.com/rythmengine/rythmengine
 */
package org.rythmengine.resource;

import org.rythmengine.RythmEngine;
import org.rythmengine.Sandbox;
import org.rythmengine.conf.RythmConfiguration;
import org.rythmengine.conf.RythmConfigurationKey;
import org.rythmengine.extension.ICodeType;
import org.rythmengine.extension.ITemplateResourceLoader;
import org.rythmengine.internal.RythmThreadFactory;
import org.rythmengine.internal.compiler.ParamTypeInferencer;
import org.rythmengine.internal.compiler.TemplateClass;
import org.rythmengine.logger.ILogger;
import org.rythmengine.logger.Logger;
import org.rythmengine.utils.S;

import java.io.File;
import java.net.URI;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;

/**
 * The template resource manager manages all template resource loaders and also cache the resource after they
 * get loaded
 */
public class TemplateResourceManager {

    @SuppressWarnings("unused")
    private static final ILogger logger = Logger.get(TemplateResourceManager.class); 
    
    /**
     * NULL TemplateResource
     */
    @SuppressWarnings("serial")
    public static final ITemplateResource NULL = new ITemplateResource() {
        // this NULL TemplateResource can actually carry an error
        // might not be necessary but is there in any case
        private Throwable error;

        @Override
        public Object getKey() {
            return null;
        }

        @Override
        public String getSuggestedClassName() {
            return null;
        }

        @Override
        public String asTemplateContent() {
            return null;
        }

        @Override
        public boolean refresh() {
            return false;
        }

        @Override
        public boolean isValid() {
            return false;
        }

        @Override
        public ICodeType codeType(RythmEngine engine) {
            return null;
        }

        @Override
        public ITemplateResourceLoader getLoader() {
            return null;
        }

        @Override
        public Throwable getError() {
          return error;
        }

        @Override
        public void setError(Throwable error) {
          this.error=error;
        }
    };

    private RythmEngine engine;

    private Map cache = new HashMap();

    private List loaders;
    
    private FileResourceLoader adhocFileLoader = null;

    // the  map allows 
    private Map whichLoader = new HashMap();
    
    private boolean typeInference;

    /**
     * Store the String that is NOT a resource
     */
    private static Set blackList = new HashSet();
    
    private static ThreadLocal>> tmpBlackList = new ThreadLocal>>() {
        @Override
        protected Stack> initialValue() {
            return new Stack>();
        }
    };
    
    public static void setUpTmpBlackList() {
        tmpBlackList.get().push(new HashSet());
    } 
    
    public static void reportNonResource(String str) {
        Stack> ss = tmpBlackList.get();
        if (ss.isEmpty()) {
            // invoked dynamically when running @invoke(...)
            tmpBlackList.remove();
            blackList.add(str);
        } else {
            ss.peek().add(str);
        }
    }
    
    public static void commitTmpBlackList() {
        Stack> sss = tmpBlackList.get();
        if (!sss.isEmpty()) {
            Set ss = sss.pop();
            blackList.addAll(ss);
        }
        if (sss.isEmpty()) {
            tmpBlackList.remove();
        }
    }
    
    public static void rollbackTmpBlackList() {
        Stack> sss = tmpBlackList.get();
        if (!sss.isEmpty()) {
            sss.pop();
        }
        if (sss.isEmpty()) {
            tmpBlackList.remove();
        }
    }

    public static void cleanUpTmplBlackList() {
//        Stack> ss = tmpBlackList.get();
//        if (null != ss) {
//            ss.clear();
//        }
        tmpBlackList.remove();
    }

    /**
     * construct the TemplateResourceManager for the give engine
     * @param engine
     */
    public TemplateResourceManager(RythmEngine engine) {
        this.engine = engine;
        RythmConfiguration conf = engine.conf();
        typeInference = conf.typeInferenceEnabled();
        loaders = new ArrayList(conf.getList(RythmConfigurationKey.RESOURCE_LOADER_IMPLS, ITemplateResourceLoader.class));
        if (!loaders.isEmpty()) {
            for (ITemplateResourceLoader loader: loaders) {
                loader.setEngine(this.engine);
            }
            Boolean defLoader = conf.get(RythmConfigurationKey.RESOURCE_DEF_LOADER_ENABLED);
            if (!defLoader) {
                return;
            }
        }
        List roots = conf.templateHome();
        for (URI root : roots) {
            if (null == root) continue;
            String scheme = root.getScheme();
            if (S.eq(scheme, "jar")) {
                String s = root.getSchemeSpecificPart();
                int pos = s.indexOf(".jar!");
                String home = s.substring(pos + 5);
                ClasspathResourceLoader crl = new ClasspathResourceLoader(engine, home);
                loaders.add(crl);
            } else if (S.eq(scheme, "file")) {
                FileResourceLoader frl = new FileResourceLoader(engine, new File(root.getPath()));
                if (null == adhocFileLoader) {
                    adhocFileLoader = frl;
                }
                loaders.add(frl);
            }
        }
    }

    public void addResourceLoader(ITemplateResourceLoader loader) {
        if (!loaders.contains(loader)) loaders.add(loader);
    }

    public void prependResourceLoader(ITemplateResourceLoader loader) {
        if (!loaders.contains(loader)) loaders.add(0, loader);
    }

    private ITemplateResource cache(ITemplateResource resource) {
        if (resource.isValid()) {
            cache.put(resource.getKey(), resource);
        }
        return resource;
    }

    public TemplateClass tryLoadTemplate(String tmplName, TemplateClass callerClass, ICodeType codeType) {
        if (blackList.contains(tmplName)) {
            //logger.info(">>> %s is in the black list", tmplName);
            return null;
        }
        TemplateClass tc = null;
        RythmEngine engine = this.engine;
        if (null != callerClass) {
            ITemplateResourceLoader loader = whichLoader(callerClass.templateResource);
            if (null != loader) {
                return loader.tryLoadTemplate(tmplName, engine, callerClass, codeType);
            }
        }
        for (ITemplateResourceLoader loader : loaders) {
            tc = loader.tryLoadTemplate(tmplName, engine, callerClass, codeType);
            if (null != tc) {
                break;
            }
        }
        return tc;
    }
    
    public ITemplateResource get(File file) {
        return cache(new FileTemplateResource(file, adhocFileLoader));
    }

    public ITemplateResource get(String str) {
        ITemplateResource resource = getResource(str);
        if (!resource.isValid()) {
            resource = new StringTemplateResource(str);
        }
        return cache(resource);
    }

    public ITemplateResourceLoader whichLoader(ITemplateResource resource) {
        return whichLoader.get(resource.getKey());
    }

    public ITemplateResource getResource(String str) {
        ITemplateResource resource = cache.get(str);
        if (null != resource) return resource;

        if (Sandbox.isRestricted()) return NULL;

        for (ITemplateResourceLoader loader : loaders) {
            resource = loader.load(str);
            if (null != resource && resource.isValid()) {
                whichLoader.put(resource.getKey(), loader);
                break;
            }
        }

        return null == resource ? NULL : cache(resource);
    }
    
    public void scan() {
        for (ITemplateResourceLoader loader : loaders) {
            loader.scan(this);
        }
    }

    public void resourceLoaded(final ITemplateResource resource) {
        resourceLoaded(resource, true);
    }
    
    public TemplateClass resourceLoaded(final ITemplateResource resource, boolean async) {
        final ITemplateResourceLoader loader = resource.getLoader();
        //if (!async) { no async load at the moment
        if (true) {
            whichLoader.put(resource.getKey(), loader);
            return _resourceLoaded(resource);
        } else {
            loadingService.submit(new Callable() {
                @Override
                public Object call() throws Exception {
                    whichLoader.put(resource.getKey(), loader);
                    _resourceLoaded(resource);
                    return null;
                }
            });
            return null;
        }
    }
    
    private TemplateClass _resourceLoaded(ITemplateResource resource) {
        if (!resource.isValid()) return null;
        String key = S.str(resource.getKey());
        if (typeInference) {
            key += ParamTypeInferencer.uuid();
        }
        RythmEngine engine = this.engine;
        TemplateClass tc = engine.classes().getByTemplate(key);
        if (null == tc) {
            tc = new TemplateClass(resource, engine);
        }
        tc.asTemplate(engine);
        return tc;
    }

    private static class ScannerThreadFactory extends RythmThreadFactory {
        private ScannerThreadFactory() {
            super("rythm-scanner");
        }
    }

    /* 
     * At the moment we don't support parsing templates in parallel, so ...
     */
    private ScheduledExecutorService loadingService = new ScheduledThreadPoolExecutor(1, new ScannerThreadFactory());
    
    public void shutdown() {
        loadingService.shutdown();
    }
}