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

com.greenlaw110.rythm.internal.compiler.TemplateClassLoader Maven / Gradle / Ivy

Go to download

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

The newest version!
/* 
 * Copyright (C) 2013 The Rythm Engine project
 * Gelin Luo 
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
*/
package com.greenlaw110.rythm.internal.compiler;

import com.greenlaw110.rythm.Rythm;
import com.greenlaw110.rythm.RythmEngine;
import com.greenlaw110.rythm.conf.RythmConfiguration;
import com.greenlaw110.rythm.conf.RythmConfigurationKey;
import com.greenlaw110.rythm.extension.IByteCodeHelper;
import com.greenlaw110.rythm.logger.ILogger;
import com.greenlaw110.rythm.logger.Logger;
import com.greenlaw110.rythm.sandbox.RythmSecurityManager;
import com.greenlaw110.rythm.template.ITemplate;
import com.greenlaw110.rythm.utils.IO;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.ClassDefinition;
import java.security.ProtectionDomain;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by IntelliJ IDEA.
 * User: luog
 * Date: 18/01/12
 * Time: 9:01 PM
 * To change this template use File | Settings | File Templates.
 */
public class TemplateClassLoader extends ClassLoader {

    private static final ILogger logger = Logger.get(TemplateClass.class);

    public static class ClassStateHashCreator {

        private final Pattern classDefFinderPattern = Pattern.compile("\\s+class\\s([a-zA-Z0-9_]+)\\s+");

        private static class FileWithClassDefs {
            private final File file;
            private final long size;
            private final long lastModified;
            private final String classDefs;

            private FileWithClassDefs(File file, String classDefs) {
                this.file = file;
                this.classDefs = classDefs;

                // store size and time for this file..
                size = file.length();
                lastModified = file.lastModified();
            }

            /**
             * @return true if file has changed on disk
             */
            public boolean fileNotChanges() {
                return size == file.length() && lastModified == file.lastModified();
            }

            public String getClassDefs() {
                return classDefs;
            }
        }

        private final Map classDefsInFileCache = new HashMap();

        public synchronized int computePathHash(File... paths) {
            StringBuffer buf = new StringBuffer();
            for (File file : paths) {
                scan(buf, file);
            }
            // TODO: should use better hashing-algorithm.. MD5? SHA1?
            // I think hashCode() has too many collisions..
            return buf.toString().hashCode();
        }

        private void scan(StringBuffer buf, File current) {
            if (!current.isDirectory()) {
                if (current.getName().endsWith(".java")) {
                    buf.append(getClassDefsForFile(current));
                }

            } else if (!current.getName().startsWith(".")) {
                // TODO: we could later optimizie it further if we check if the entire folder is unchanged

                File[] fa = current.listFiles();
                if (null != fa) {
                    for (File file : current.listFiles()) {
                        scan(buf, file);
                    }
                }
            }
        }

        private String getClassDefsForFile(File file) {

            FileWithClassDefs fileWithClassDefs = classDefsInFileCache.get(file);
            if (fileWithClassDefs != null && fileWithClassDefs.fileNotChanges()) {
                // found the file in cache and it has not changed on disk
                return fileWithClassDefs.getClassDefs();
            }

            // didn't find it or it has changed on disk
            // we must re-parse it

            StringBuilder buf = new StringBuilder();
            Matcher matcher = classDefFinderPattern.matcher(IO.readContentAsString(file));
            buf.append(file.getName());
            buf.append("(");
            while (matcher.find()) {
                buf.append(matcher.group(1));
                buf.append(",");
            }
            buf.append(")");
            String classDefs = buf.toString();

            // store it in cache
            classDefsInFileCache.put(file, new FileWithClassDefs(file, classDefs));
            return classDefs;
        }
    }

    private final ClassStateHashCreator classStateHashCreator = new ClassStateHashCreator();

    /**
     * Each unique instance of this class represent a State of the TemplateClassLoader.
     * When some classCache is reloaded, them the TemplateClassLoader get a new state.
     * 

* This makes it easy for other parts of Play to cache stuff based on the * the current State of the TemplateClassLoader.. *

* They can store the reference to the current state, then later, before reading from cache, * they could check if the state of the TemplateClassLoader has changed.. */ public static class TemplateClassloaderState { private static AtomicLong nextStateValue = new AtomicLong(); private final long currentStateValue = nextStateValue.getAndIncrement(); @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TemplateClassloaderState that = (TemplateClassloaderState) o; return (currentStateValue == that.currentStateValue); } @Override public int hashCode() { return (int) (currentStateValue ^ (currentStateValue >>> 32)); } } /** * A representation of the current state of the TemplateClassLoader. * It gets a new value each time the state of the classloader changes. */ public TemplateClassloaderState currentState = new TemplateClassloaderState(); /** * This protection domain applies to all loaded classCache. */ public ProtectionDomain protectionDomain; public RythmEngine engine; private RythmConfiguration conf; private static ClassLoader getDefParent(RythmEngine engine) { return engine.conf().get(RythmConfigurationKey.ENGINE_CLASS_LOADER_PARENT_IMPL); } public TemplateClassLoader(RythmEngine engine) { this(getDefParent(engine), engine); } public TemplateClassLoader(ClassLoader parent, RythmEngine engine) { super(parent); this.engine = engine; this.conf = engine.conf(); for (TemplateClass tc : engine.classes().all()) { tc.uncompile(); } pathHash = computePathHash(); // try { // CodeSource codeSource = new CodeSource(new URL("file:" + Play.applicationPath.getAbsolutePath()), (Certificate[]) null); // Permissions permissions = new Permissions(); // permissions.add(new AllPermission()); // protectionDomain = new ProtectionDomain(codeSource, permissions); // } catch (MalformedURLException e) { // throw new UnexpectedException(e); // } } private static final ThreadLocal sandboxPassword = new ThreadLocal(); public static void setSandboxPassword(String password) { sandboxPassword.set(password); } @Override protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { // init secure context for sandbox SecurityManager sm; RythmSecurityManager rsm = null; String pass = null; if (Rythm.insideSandbox()) { if (conf.restrictedClasses().contains(name)) { throw new ClassNotFoundException("Access to class " + name + " is restricted in sandbox mode"); } sm = System.getSecurityManager(); if (null != sm && sm instanceof RythmSecurityManager) { rsm = (RythmSecurityManager) sm; pass = sandboxPassword.get(); } } TemplateClass tc = engine.classes().clsNameIdx.get(name); if (null == tc) { // it's not a template class, let's try to find already loaded one Class c = findLoadedClass(name); if (c != null) { return c; } } // First check if it's an application Class Class TemplateClass = loadTemplateClass(name); if (TemplateClass != null) { if (resolve) { resolveClass(TemplateClass); } return TemplateClass; } // Delegate to the classic classloader boolean unlockSM = /*engine.isDevMode() && */null != rsm; try { // release sandbox password if running inside sandbox in order to load // application classes when running is dev mode if (unlockSM) { rsm.unlock(pass); } return super.loadClass(name, resolve); } finally { if (unlockSM) { rsm.lock(pass); } } } @SuppressWarnings("unchecked") private Class loadTemplateClass(String name) { Class maybeAlreadyLoaded = findLoadedClass(name); if (maybeAlreadyLoaded != null) { return maybeAlreadyLoaded; } long start = System.currentTimeMillis(); TemplateClass templateClass = engine.classes().getByClassName(name); if (templateClass != null) { if (templateClass.isDefinable()) { return templateClass.javaClass; } byte[] bc = templateClass.enhancedByteCode;//bCache.getBytecode(name, templateClass.javaSource); if (!templateClass.isClass()) { definePackage(templateClass.getPackage(), null, null, null, null, null, null, null); } else { loadPackage(name); } if (bc != null) { //templateClass.enhancedByteCode = bc; templateClass.javaClass = (Class) defineClass(templateClass.name(), templateClass.enhancedByteCode, 0, templateClass.enhancedByteCode.length, protectionDomain); resolveClass(templateClass.javaClass); if (!templateClass.isClass()) { templateClass.javaPackage = templateClass.javaClass.getPackage(); } if (logger.isTraceEnabled()) { logger.trace("%sms to load class %s from clsNameIdx", System.currentTimeMillis() - start, name); } return templateClass.javaClass; } if (templateClass.javaByteCode != null || templateClass.compile() != null) { templateClass.enhance(); templateClass.javaClass = (Class) defineClass(templateClass.name(), templateClass.enhancedByteCode, 0, templateClass.enhancedByteCode.length, protectionDomain); resolveClass(templateClass.javaClass); if (!templateClass.isClass()) { templateClass.javaPackage = templateClass.javaClass.getPackage(); } if (logger.isTraceEnabled()) { logger.trace("%sms to load class %s", System.currentTimeMillis() - start, name); } return templateClass.javaClass; } engine.classes().remove(name); } else if (name.lastIndexOf(TemplateClass.CN_SUFFIX) == -1) { return null; } else { int pos = name.indexOf("$"); if (-1 != pos) { // should be an inner class, let's try to create it load it from cache // 1. find the root class String parentCN = name.substring(0, pos); TemplateClass parent = engine.classes().getByClassName(parentCN); if (null == parent) { throw new RuntimeException("Cannot find inner class def: " + name); } TemplateClass tc = TemplateClass.createInnerClass(name, null, parent); engine.classCache().loadTemplateClass(tc); byte[] bc = tc.enhancedByteCode; if (null == bc) { // inner class byte code cache missed some how, let's try to recover it while ((null != parent) && parent.isInner()) { parent = parent.root(); } if (null == parent) { throw new RuntimeException("Unexpected: cannot find the root class of inner class: " + name); } parent.reset(); parent.refresh(true); parent.compile(); // now try again and see if we can find the class definition tc = engine.classes().getByClassName(name); Class c = tc.javaClass; if (null != c) return c; bc = tc.enhancedByteCode; if (null == bc) { throw new RuntimeException("Cannot find bytecode cache for inner class: " + name); } } tc.javaClass = (Class) defineClass(tc.name(), bc, 0, bc.length, protectionDomain); return tc.javaClass; } } return null; } private String getPackageName(String name) { int dot = name.lastIndexOf('.'); return dot > -1 ? name.substring(0, dot) : ""; } private void loadPackage(String className) { // find the package class name int symbol = className.indexOf("$"); if (symbol > -1) { className = className.substring(0, symbol); } symbol = className.lastIndexOf("."); if (symbol > -1) { className = className.substring(0, symbol) + ".package-info"; } else { className = "package-info"; } if (findLoadedClass(className) == null) { loadTemplateClass(className); } } private Set notFoundTypes = null; private boolean typeNotFound(String name) { if (null == notFoundTypes) { notFoundTypes = engine.classes().compiler.notFoundTypes; } return notFoundTypes.contains(name); } private void setTypeNotFound(String name) { if (null == notFoundTypes) { notFoundTypes = engine.classes().compiler.notFoundTypes; } if (engine.isProdMode()) { notFoundTypes.add(name); } else if (name.matches("^(java\\.|play\\.|com\\.greenlaw110\\.).*")) { notFoundTypes.add(name); } } /** * Search for the byte code of the given class. */ protected byte[] getClassDefinition(final String name0) { if (typeNotFound(name0)) return null; byte[] ba = null; String name = name0.replace(".", "/") + ".class"; InputStream is = getResourceAsStream(name); if (null != is) { try { ByteArrayOutputStream os = new ByteArrayOutputStream(); byte[] buffer = new byte[8192]; int count; while ((count = is.read(buffer, 0, buffer.length)) > 0) { os.write(buffer, 0, count); } ba = os.toByteArray(); } catch (Exception e) { throw new RuntimeException(e); } finally { try { is.close(); } catch (IOException e) { throw new RuntimeException(e); } } } if (null == ba) { IByteCodeHelper helper = engine.conf().byteCodeHelper(); if (null != helper) { ba = helper.findByteCode(name0); } } if (null == ba) { setTypeNotFound(name0); } return ba; } public void detectChange(TemplateClass tc) { if (engine.isProdMode() && null != tc.name()) return; if (!tc.refresh()) return; if (tc.compile() == null) { engine.classes().remove(tc); currentState = new TemplateClassloaderState(); } else { throw new ClassReloadException("Need reload"); } } /** * Detect Template changes */ public void detectChanges() { if (engine.isProdMode()) return; // Now check for file modification List modifieds = new ArrayList(); for (TemplateClass tc : engine.classes().all()) { if (tc.refresh()) modifieds.add(tc); } Set modifiedWithDependencies = new HashSet(); modifiedWithDependencies.addAll(modifieds); List newDefinitions = new ArrayList(); boolean dirtySig = false; for (TemplateClass tc : modifiedWithDependencies) { if (tc.compile() == null) { engine.classes().remove(tc); currentState = new TemplateClassloaderState();//show others that we have changed.. } else { int sigChecksum = tc.sigChecksum; tc.enhance(); if (sigChecksum != tc.sigChecksum) { dirtySig = true; } newDefinitions.add(new ClassDefinition(tc.javaClass, tc.enhancedByteCode)); currentState = new TemplateClassloaderState();//show others that we have changed.. } } if (newDefinitions.size() > 0) { throw new ClassReloadException("Need Reload"); } // Check signature (variable name & annotations aware !) if (dirtySig) { throw new ClassReloadException("Signature change !"); } // Now check if there is new classCache or removed classCache int hash = computePathHash(); if (hash != this.pathHash) { // Remove class for deleted files !! for (TemplateClass tc : engine.classes().all()) { if (!tc.templateResource.isValid()) { engine.classes().remove(tc); currentState = new TemplateClassloaderState();//show others that we have changed.. } } throw new ClassReloadException("Path has changed"); } } /** * Used to track change of the application sources path */ int pathHash = 0; int computePathHash() { return engine.isProdMode() ? 0 : classStateHashCreator.computePathHash(engine.conf().tmpDir()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy