Scan screen classes using ASM without loading classes eagerly

This commit is contained in:
Yuriy Artamonov 2018-08-21 16:49:01 +04:00
parent 8e49e663af
commit bebd89b070
7 changed files with 212 additions and 112 deletions

View File

@ -30,8 +30,8 @@ import com.haulmont.cuba.gui.screen.Screen;
import com.haulmont.cuba.gui.screen.UiController;
import com.haulmont.cuba.gui.screen.UiDescriptor;
import com.haulmont.cuba.gui.sys.ScreenDescriptorUtils;
import com.haulmont.cuba.gui.sys.ScreensConfiguration;
import com.haulmont.cuba.gui.sys.ScreensConfiguration.UiControllerDefinition;
import com.haulmont.cuba.gui.sys.UiControllerDefinition;
import com.haulmont.cuba.gui.sys.UiControllersConfiguration;
import com.haulmont.cuba.gui.xml.layout.ScreenXmlLoader;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
@ -78,7 +78,7 @@ public class WindowConfig {
@Inject
protected Metadata metadata;
@Inject
protected List<ScreensConfiguration> screensConfigurations;
protected List<UiControllersConfiguration> configurations;
@Inject
protected ScreenXmlLoader screenXmlLoader;
@ -221,7 +221,7 @@ public class WindowConfig {
}
protected void loadScreenConfigurations() {
for (ScreensConfiguration provider : screensConfigurations) {
for (UiControllersConfiguration provider : configurations) {
List<UiControllerDefinition> uiControllers = provider.getUIControllers();
for (UiControllerDefinition definition : uiControllers) {

View File

@ -12,10 +12,13 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface UiController {
@AliasFor("id")
String ID_ATTRIBUTE = "id";
String VALUE_ATTRIBUTE = "value";
@AliasFor(ID_ATTRIBUTE)
String value() default "";
@AliasFor("value")
@AliasFor(VALUE_ATTRIBUTE)
String id() default "";
// todo move to separate annotation

View File

@ -3,9 +3,9 @@ package com.haulmont.cuba.gui.sys;
import com.google.common.base.Strings;
import com.haulmont.cuba.core.global.DevelopmentException;
import com.haulmont.cuba.gui.screen.Screen;
import com.haulmont.cuba.gui.screen.UiDescriptor;
import com.haulmont.cuba.gui.screen.Subscribe;
import com.haulmont.cuba.gui.screen.UiController;
import com.haulmont.cuba.gui.screen.UiDescriptor;
import static com.haulmont.bali.util.Preconditions.checkNotNullArgument;
@ -16,13 +16,23 @@ public final class ScreenDescriptorUtils {
public static String getInferredScreenId(UiController uiController, Class<? extends Screen> annotatedScreenClass) {
checkNotNullArgument(uiController);
checkNotNullArgument(annotatedScreenClass);
String id = uiController.value();
return getInferredScreenId(uiController.id(), uiController.value(), annotatedScreenClass.getName());
}
public static String getInferredScreenId(String idAttribute, String valueAttribute, String className) {
String id = valueAttribute;
if (Strings.isNullOrEmpty(id)) {
id = uiController.id();
id = idAttribute;
if (Strings.isNullOrEmpty(id)) {
throw new DevelopmentException("Screen class annotated with @UiController without id " + annotatedScreenClass);
int indexOfDot = className.lastIndexOf('.');
if (indexOfDot < 0) {
id = className;
} else {
id = className.substring(indexOfDot + 1);
}
}
}

View File

@ -1,100 +0,0 @@
package com.haulmont.cuba.gui.sys;
import com.haulmont.cuba.core.global.Scripting;
import com.haulmont.cuba.gui.screen.Screen;
import com.haulmont.cuba.gui.screen.UiController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import javax.inject.Inject;
import java.util.List;
import java.util.stream.Collectors;
/**
* Configuration that performs ClassPath scanning of {@link UiController}s and provides {@link UiControllerDefinition}.
*/
public class ScreensConfiguration {
private static final Logger log = LoggerFactory.getLogger(ScreensConfiguration.class);
@Inject
protected Scripting scripting;
protected List<String> packages;
protected ApplicationContext applicationContext;
// todo add explicit exports
public ScreensConfiguration() {
}
public List<String> getPackages() {
return packages;
}
public void setPackages(List<String> packages) {
this.packages = packages;
}
public List<UiControllerDefinition> getUIControllers() {
ClassPathScanningCandidateComponentProvider provider = createComponentScanner();
log.trace("Scanning packages {}", packages);
return packages.stream()
.flatMap(scanPackage -> provider.findCandidateComponents(scanPackage).stream())
.map(BeanDefinition::getBeanClassName)
.map(className -> {
log.trace("Found screen controller {}", className);
@SuppressWarnings("unchecked")
Class<? extends Screen> screenClass = (Class<? extends Screen>) scripting.loadClassNN(className);
UiController uiController = screenClass.getAnnotation(UiController.class);
if (uiController == null) {
throw new RuntimeException("Screen class does not have @UiController annotation : " + screenClass);
}
String id = ScreenDescriptorUtils.getInferredScreenId(uiController, screenClass);
return new UiControllerDefinition(id, className);
})
.collect(Collectors.toList());
}
protected ClassPathScanningCandidateComponentProvider createComponentScanner() {
// Don't pull default filters (@Component, etc.):
ClassPathScanningCandidateComponentProvider provider
= new ClassPathScanningCandidateComponentProvider(false);
provider.setResourceLoader(getResourceLoader());
provider.addIncludeFilter(new AnnotationTypeFilter(UiController.class));
return provider;
}
protected ResourceLoader getResourceLoader() {
return applicationContext;
}
public final static class UiControllerDefinition {
private final String id;
private final String controllerClass;
public UiControllerDefinition(String id, String controllerClass) {
this.id = id;
this.controllerClass = controllerClass;
}
public String getId() {
return id;
}
public String getControllerClass() {
return controllerClass;
}
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2008-2018 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.gui.sys;
public final class UiControllerDefinition {
private final String id;
private final String controllerClass;
public UiControllerDefinition(String id, String controllerClass) {
this.id = id;
this.controllerClass = controllerClass;
}
public String getId() {
return id;
}
public String getControllerClass() {
return controllerClass;
}
}

View File

@ -0,0 +1,153 @@
package com.haulmont.cuba.gui.sys;
import com.haulmont.bali.util.Preconditions;
import com.haulmont.cuba.gui.screen.UiController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import javax.inject.Inject;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Configuration that performs ClassPath scanning of {@link UiController}s and provides {@link UiControllerDefinition}.
*/
public class UiControllersConfiguration {
public static final String DEFAULT_CLASS_RESOURCE_PATTERN = "**/*.class";
private static final Logger log = LoggerFactory.getLogger(UiControllersConfiguration.class);
protected ApplicationContext applicationContext;
protected MetadataReaderFactory metadataReaderFactory;
protected List<String> packages = Collections.emptyList();
protected List<String> classNames = Collections.emptyList();
public UiControllersConfiguration() {
}
@Inject
protected void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
this.metadataReaderFactory = new CachingMetadataReaderFactory(applicationContext);
}
public List<String> getPackages() {
return packages;
}
public void setPackages(List<String> packages) {
Preconditions.checkNotNullArgument(packages);
this.packages = packages;
}
public List<String> getClassNames() {
return classNames;
}
public void setClassNames(List<String> classNames) {
this.classNames = classNames;
}
public List<UiControllerDefinition> getUIControllers() {
log.trace("Scanning packages {}", packages);
Stream<UiControllerDefinition> scannedControllersStream = packages.stream()
.flatMap(this::scanPackage)
.filter(this::isCandidateUiController)
.map(this::extractControllerDefinition);
Stream<UiControllerDefinition> explicitControllersStream = classNames.stream()
.map(this::loadClassMetadata)
.map(this::extractControllerDefinition);
return Stream.concat(scannedControllersStream, explicitControllersStream)
.collect(Collectors.toList());
}
protected MetadataReader loadClassMetadata(String className) {
Resource resource = getResourceLoader().getResource(className);
if (!resource.isReadable()) {
throw new RuntimeException(String.format("Resource %s is not readable for class %s", resource, className));
}
try {
return getMetadataReaderFactory().getMetadataReader(resource);
} catch (IOException e) {
throw new RuntimeException("Unable to read resource " + resource, e);
}
}
protected UiControllerDefinition extractControllerDefinition(MetadataReader metadataReader) {
Map<String, Object> uiControllerAnn =
metadataReader.getAnnotationMetadata().getAnnotationAttributes(UiController.class.getName());
String idAttr = null;
String valueAttr = null;
if (uiControllerAnn != null) {
idAttr = (String) uiControllerAnn.get(UiController.ID_ATTRIBUTE);
valueAttr = (String) uiControllerAnn.get(UiController.VALUE_ATTRIBUTE);
}
String className = metadataReader.getClassMetadata().getClassName();
String controllerId = ScreenDescriptorUtils.getInferredScreenId(idAttr, valueAttr, className);
return new UiControllerDefinition(controllerId, className);
}
protected Stream<MetadataReader> scanPackage(String packageName) {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(packageName) + '/' + DEFAULT_CLASS_RESOURCE_PATTERN;
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource[] resources;
try {
resources = resourcePatternResolver.getResources(packageSearchPath);
} catch (IOException e) {
throw new RuntimeException("Unable to scan package " + packageName, e);
}
return Arrays.stream(resources)
.peek(resource -> log.trace("Scanning {}", resource))
.filter(Resource::isReadable)
.map(resource -> {
try {
return getMetadataReaderFactory().getMetadataReader(resource);
} catch (IOException e) {
throw new RuntimeException("Unable to read resource " + resource, e);
}
});
}
protected MetadataReaderFactory getMetadataReaderFactory() {
return metadataReaderFactory;
}
protected String resolveBasePackage(String basePackage) {
Environment environment = applicationContext.getEnvironment();
return ClassUtils.convertClassNameToResourcePath(environment.resolveRequiredPlaceholders(basePackage));
}
protected boolean isCandidateUiController(MetadataReader metadataReader) {
return metadataReader.getClassMetadata().isConcrete()
&& metadataReader.getAnnotationMetadata().hasAnnotation(UiController.class.getName());
}
protected ResourceLoader getResourceLoader() {
return applicationContext;
}
}

View File

@ -63,8 +63,7 @@
<!-- todo custom XSD for screen configuration: scanning/defining screens -->
<bean id="cuba_web_ScreensConfiguration"
class="com.haulmont.cuba.gui.sys.ScreensConfiguration">
<bean id="cuba_web_UiControllersConfiguration" class="com.haulmont.cuba.gui.sys.UiControllersConfiguration">
<property name="packages">
<list>
<value>com.haulmont.cuba.gui.app</value>