PL-7643 Hot deployment of Groovy beans does not work

This commit is contained in:
Andrey Subbotin 2017-01-13 15:15:13 +04:00
parent da2988e03a
commit c2f7c71a91
10 changed files with 178 additions and 118 deletions

View File

@ -21,6 +21,7 @@ import com.haulmont.cuba.core.global.Configuration;
import com.haulmont.cuba.core.global.GlobalConfig;
import com.haulmont.cuba.core.global.Scripting;
import com.haulmont.cuba.core.sys.AbstractScripting;
import com.haulmont.cuba.core.sys.SpringBeanLoader;
import com.haulmont.cuba.core.sys.javacl.JavaClassLoader;
import org.springframework.stereotype.Component;
@ -32,8 +33,8 @@ public class ScriptingClientImpl extends AbstractScripting {
private String[] scriptEngineRoots;
@Inject
public ScriptingClientImpl(JavaClassLoader javaClassLoader, Configuration configuration) {
super(javaClassLoader, configuration);
public ScriptingClientImpl(JavaClassLoader javaClassLoader, Configuration configuration, SpringBeanLoader springBeanLoader) {
super(javaClassLoader, configuration, springBeanLoader);
scriptEngineRoots = new String[] {
configuration.getConfig(GlobalConfig.class).getConfDir()
};

View File

@ -32,8 +32,8 @@ public class ScriptingImpl extends AbstractScripting {
private String[] scriptEngineRoots;
@Inject
public ScriptingImpl(JavaClassLoader javaClassLoader, Configuration configuration) {
super(javaClassLoader, configuration);
public ScriptingImpl(JavaClassLoader javaClassLoader, Configuration configuration, SpringBeanLoader springBeanLoader) {
super(javaClassLoader, configuration, springBeanLoader);
scriptEngineRoots = new String[] {
configuration.getConfig(GlobalConfig.class).getConfDir(),
configuration.getConfig(ServerConfig.class).getDbDir()

View File

@ -22,7 +22,7 @@ import com.haulmont.cuba.core.global.Configuration;
import com.haulmont.cuba.core.global.GlobalConfig;
import com.haulmont.cuba.core.sys.AppContext;
import com.haulmont.cuba.core.sys.CubaDefaultXmlWebApplicationContext;
import com.haulmont.cuba.core.sys.javacl.RemotingContextHolder;
import com.haulmont.cuba.core.sys.RemotingContextHolder;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.text.StrTokenizer;
import org.slf4j.Logger;

View File

@ -111,8 +111,15 @@ public interface Scripting {
*/
Class<?> loadClassNN(String name);
/**
* Remove compiled class from cache
* @return true if class removed from cache
*/
boolean removeClass(String name);
/**
* Clears compiled classes cache
*/
void clearCache();
}

View File

@ -30,8 +30,6 @@ import groovy.util.ResourceConnector;
import groovy.util.ResourceException;
import groovy.util.ScriptException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.pool2.BaseKeyedPooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
@ -39,6 +37,8 @@ import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
@ -46,6 +46,7 @@ import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@ -58,23 +59,21 @@ public abstract class AbstractScripting implements Scripting {
private static final Pattern IMPORT_PATTERN = Pattern.compile("\\bimport\\b\\s+");
private static final Pattern PACKAGE_PATTERN = Pattern.compile("\\bpackage\\b\\s+.+");
private JavaClassLoader javaClassLoader;
protected JavaClassLoader javaClassLoader;
protected SpringBeanLoader springBeanLoader;
protected String groovyClassPath;
protected Set<String> imports = new HashSet<>();
protected volatile GroovyScriptEngine gse;
protected volatile GroovyClassLoader gcl;
protected volatile CubaGroovyClassLoader gcl;
protected GenericKeyedObjectPool<String, Script> pool;
protected GlobalConfig globalConfig;
public AbstractScripting(JavaClassLoader javaClassLoader, Configuration configuration) {
public AbstractScripting(JavaClassLoader javaClassLoader, Configuration configuration, SpringBeanLoader springBeanLoader) {
this.javaClassLoader = javaClassLoader;
this.springBeanLoader = springBeanLoader;
globalConfig = configuration.getConfig(GlobalConfig.class);
groovyClassPath = globalConfig.getConfDir() + File.pathSeparator;
@ -109,7 +108,7 @@ public abstract class AbstractScripting implements Scripting {
return gse;
}
protected GroovyClassLoader getGroovyClassLoader() {
protected CubaGroovyClassLoader getGroovyClassLoader() {
if (gcl == null) {
synchronized (this) {
if (gcl == null) {
@ -147,10 +146,9 @@ public abstract class AbstractScripting implements Scripting {
Matcher packageMatcher = PACKAGE_PATTERN.matcher(key);
if (packageMatcher.find()) {
StringBuffer s = new StringBuffer();
packageMatcher.appendReplacement(s, "$0\n"+sb);
packageMatcher.appendReplacement(s, "$0\n" + sb);
result = packageMatcher.appendTail(s).toString();
}
else {
} else {
result = sb.append(key).toString();
}
}
@ -279,6 +277,11 @@ public abstract class AbstractScripting implements Scripting {
}
}
@Override
public boolean removeClass(String name) {
return getGroovyClassLoader().removeClass(name) || javaClassLoader.removeClass(name);
}
@Override
public void clearCache() {
getGroovyClassLoader().clearCache();
@ -313,7 +316,7 @@ public abstract class AbstractScripting implements Scripting {
// First workaround for invocation from GroovyScriptEngine.isSourceNewer()
for (String path : rootPath) {
String substrResourceName = resourceName.substring(1);
path = path.replace('\\','/');
path = path.replace('\\', '/');
if (substrResourceName.startsWith(path))
resourceName = substrResourceName;
if (resourceName.startsWith(path)) {
@ -406,6 +409,12 @@ public abstract class AbstractScripting implements Scripting {
super(AbstractScripting.this.javaClassLoader, cc);
}
public boolean removeClass(String className) {
Class clazz = getClassCacheEntry(className);
removeClassCacheEntry(className);
return clazz != null;
}
// This overridden method is almost identical to super, but prefers Groovy source over parent classloader class
@Override
public Class loadClass(String name, boolean lookupScriptFiles, boolean preferClassOverScript, boolean resolve) throws ClassNotFoundException, CompilationFailedException {
@ -440,6 +449,7 @@ public abstract class AbstractScripting implements Scripting {
removeClassCacheEntry(name);
} else {
setClassCacheEntry(cls);
springBeanLoader.updateContext(Collections.singletonList(cls));
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2016 Haulmont.
* Copyright (c) 2008-2017 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -12,11 +12,12 @@
* 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.haulmont.cuba.core.sys.javacl;
package com.haulmont.cuba.core.sys;
import com.haulmont.cuba.core.global.Scripting;
import com.haulmont.cuba.core.sys.javacl.JavaClassLoader;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.springframework.stereotype.Component;
@ -26,13 +27,15 @@ import static java.lang.String.format;
@Component("cuba_ClassLoaderManager")
public class ClassLoaderManager implements ClassLoaderManagerMBean {
@Inject
protected Scripting scripting;
@Inject
protected JavaClassLoader javaClassLoader;
@Override
public String loadClass(String className) {
try {
Class<?> aClass = javaClassLoader.loadClass(className);
Class<?> aClass = scripting.loadClassNN(className);
return format("Loaded %s", aClass.toString());
} catch (Exception e) {
return ExceptionUtils.getStackTrace(e);
@ -42,13 +45,8 @@ public class ClassLoaderManager implements ClassLoaderManagerMBean {
@Override
public String removeClass(String className) {
try {
TimestampClass removed = javaClassLoader.compiled.remove(className);
if (removed != null) {
for (String dependent : removed.dependent) {
removeClass(dependent);
}
}
return removed != null ? format("Removed %s", removed.clazz.toString()) : "No such class in cache";
boolean removed = scripting.removeClass(className);
return removed ? format("Removed %s", className) : "No such class in cache";
} catch (Exception e) {
return ExceptionUtils.getStackTrace(e);
}
@ -57,7 +55,7 @@ public class ClassLoaderManager implements ClassLoaderManagerMBean {
@Override
public String reloadClass(String className) {
try {
removeClass(className);
javaClassLoader.removeClass(className);
return loadClass(className);
} catch (Exception e) {
return ExceptionUtils.getStackTrace(e);
@ -67,11 +65,9 @@ public class ClassLoaderManager implements ClassLoaderManagerMBean {
@Override
public String getClassDependencies(String className) {
try {
TimestampClass timestampClass = javaClassLoader.compiled.get(className);
if (timestampClass != null) {
return format("Dependencies \n%s\nDependent \n%s", timestampClass.dependencies, timestampClass.dependent);
if (javaClassLoader.isLoadedClass(className)) {
return format("Dependencies \n%s\nDependent \n%s", javaClassLoader.getClassDependencies(className), javaClassLoader.getClassDependent(className));
}
return "No such class in cache";
} catch (Exception e) {
return ExceptionUtils.getStackTrace(e);
@ -81,7 +77,7 @@ public class ClassLoaderManager implements ClassLoaderManagerMBean {
@Override
public String clearCache() {
try {
javaClassLoader.clearCache();
scripting.clearCache();
return "Done";
} catch (Exception e) {
return ExceptionUtils.getStackTrace(e);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2016 Haulmont.
* Copyright (c) 2008-2017 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -12,10 +12,9 @@
* 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.haulmont.cuba.core.sys.javacl;
package com.haulmont.cuba.core.sys;
public interface ClassLoaderManagerMBean {
String reloadClass(String className) throws ClassNotFoundException;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2016 Haulmont.
* Copyright (c) 2008-2017 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -12,10 +12,9 @@
* 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.haulmont.cuba.core.sys.javacl;
package com.haulmont.cuba.core.sys;
import org.springframework.context.ApplicationContext;

View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2008-2017 Haulmont.
*
* Licensed 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.haulmont.cuba.core.sys;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import javax.annotation.ManagedBean;
import java.util.Collection;
@Component("cuba_SpringBeanLoader")
public class SpringBeanLoader implements BeanFactoryAware {
protected DefaultListableBeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof DefaultListableBeanFactory) {
this.beanFactory = (DefaultListableBeanFactory) beanFactory;
}
}
public void updateContext(Collection<Class> classes) {
if (beanFactory != null) {
boolean needToRefreshRemotingContext = false;
for (Class clazz : classes) {
Service serviceAnnotation = (Service) clazz.getAnnotation(Service.class);
ManagedBean managedBeanAnnotation = (ManagedBean) clazz.getAnnotation(ManagedBean.class);
Component componentAnnotation = (Component) clazz.getAnnotation(Component.class);
Controller controllerAnnotation = (Controller) clazz.getAnnotation(Controller.class);
String beanName = null;
if (serviceAnnotation != null) {
beanName = serviceAnnotation.value();
} else if (managedBeanAnnotation != null) {
beanName = managedBeanAnnotation.value();
} else if (componentAnnotation != null) {
beanName = componentAnnotation.value();
} else if (controllerAnnotation != null) {
beanName = controllerAnnotation.value();
}
if (StringUtils.isNotBlank(beanName)) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(clazz);
Scope scope = (Scope) clazz.getAnnotation(Scope.class);
if (scope != null) {
beanDefinition.setScope(scope.value());
}
beanFactory.registerBeanDefinition(beanName, beanDefinition);
}
if (StringUtils.isNotBlank(beanName)) {
needToRefreshRemotingContext = true;
}
}
if (needToRefreshRemotingContext) {
ApplicationContext remotingContext = RemotingContextHolder.getRemotingApplicationContext();
if (remotingContext != null && remotingContext instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) remotingContext).refresh();
}
}
}
}
}

View File

@ -21,26 +21,14 @@ import com.google.common.collect.Multimap;
import com.haulmont.cuba.core.global.Configuration;
import com.haulmont.cuba.core.global.GlobalConfig;
import com.haulmont.cuba.core.global.TimeSource;
import com.haulmont.cuba.core.sys.CubaClassPathXmlApplicationContext;
import com.haulmont.cuba.core.sys.SpringBeanLoader;
import com.haulmont.cuba.core.sys.javacl.compiler.CharSequenceCompiler;
import org.apache.commons.lang.StringUtils;
import org.perf4j.log4j.Log4JStopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import javax.annotation.ManagedBean;
import javax.inject.Inject;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaFileObject;
@ -55,7 +43,7 @@ import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Component("cuba_JavaClassLoader")
public class JavaClassLoader extends URLClassLoader implements BeanFactoryAware, ApplicationContextAware {
public class JavaClassLoader extends URLClassLoader {
private static final String JAVA_CLASSPATH = System.getProperty("java.class.path");
private static final String PATH_SEPARATOR = System.getProperty("path.separator");
private static final String JAR_EXT = ".jar";
@ -73,11 +61,10 @@ public class JavaClassLoader extends URLClassLoader implements BeanFactoryAware,
protected final ProxyClassLoader proxyClassLoader;
protected final SourceProvider sourceProvider;
protected CubaClassPathXmlApplicationContext applicationContext;
protected DefaultListableBeanFactory beanFactory;
@Inject
private TimeSource timeSource;
protected TimeSource timeSource;
@Inject
protected SpringBeanLoader springBeanLoader;
@Inject
public JavaClassLoader(Configuration configuration) {
@ -105,21 +92,6 @@ public class JavaClassLoader extends URLClassLoader implements BeanFactoryAware,
this.sourceProvider = new SourceProvider(rootDir);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof DefaultListableBeanFactory) {
this.beanFactory = (DefaultListableBeanFactory) beanFactory;
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (applicationContext instanceof CubaClassPathXmlApplicationContext) {
// ((DefaultResourceLoader) applicationContext).setClassLoader(this);
this.applicationContext = (CubaClassPathXmlApplicationContext) applicationContext;
}
}
public void clearCache() {
compiled.clear();
}
@ -141,7 +113,7 @@ public class JavaClassLoader extends URLClassLoader implements BeanFactoryAware,
CompilationScope compilationScope = new CompilationScope(this, containerClassName);
if (!compilationScope.compilationNeeded()) {
TimestampClass timestampClass = getTimestampClass(fullClassName);
if (timestampClass==null) {
if (timestampClass == null) {
throw new ClassNotFoundException(fullClassName);
}
return timestampClass.clazz;
@ -172,7 +144,7 @@ public class JavaClassLoader extends URLClassLoader implements BeanFactoryAware,
clazz = compiledClasses.get(fullClassName);
updateSpringContext(compiledClasses.values());
springBeanLoader.updateContext(compiledClasses.values());
return clazz;
} catch (Exception e) {
@ -187,49 +159,34 @@ public class JavaClassLoader extends URLClassLoader implements BeanFactoryAware,
}
}
private void updateSpringContext(Collection<Class> classes) {
if (beanFactory != null) {
boolean needToRefreshRemotingContext = false;
for (Class clazz : classes) {
Service serviceAnnotation = (Service) clazz.getAnnotation(Service.class);
ManagedBean managedBeanAnnotation = (ManagedBean) clazz.getAnnotation(ManagedBean.class);
Component componentAnnotation = (Component) clazz.getAnnotation(Component.class);
Controller controllerAnnotation = (Controller) clazz.getAnnotation(Controller.class);
String beanName = null;
if (serviceAnnotation != null) {
beanName = serviceAnnotation.value();
} else if (managedBeanAnnotation != null) {
beanName = managedBeanAnnotation.value();
} else if (componentAnnotation != null) {
beanName = componentAnnotation.value();
} else if (controllerAnnotation != null) {
beanName = controllerAnnotation.value();
}
if (StringUtils.isNotBlank(beanName)) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(clazz);
Scope scope = (Scope) clazz.getAnnotation(Scope.class);
if (scope != null) {
beanDefinition.setScope(scope.value());
}
beanFactory.registerBeanDefinition(beanName, beanDefinition);
}
if (StringUtils.isNotBlank(beanName)) {
needToRefreshRemotingContext = true;
}
}
if (needToRefreshRemotingContext) {
ApplicationContext remotingContext = RemotingContextHolder.getRemotingApplicationContext();
if (remotingContext != null && remotingContext instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) remotingContext).refresh();
}
public boolean removeClass(String className) {
TimestampClass removed = compiled.remove(className);
if (removed != null) {
for (String dependent : removed.dependent) {
removeClass(dependent);
}
}
return removed != null;
}
public boolean isLoadedClass(String className) {
return compiled.containsKey(className);
}
public Collection<String> getClassDependencies(String className) {
TimestampClass timestampClass = compiled.get(className);
if (timestampClass != null) {
return timestampClass.dependencies;
}
return Collections.emptyList();
}
public Collection<String> getClassDependent(String className) {
TimestampClass timestampClass = compiled.get(className);
if (timestampClass != null) {
return timestampClass.dependent;
}
return Collections.emptyList();
}
@Override