/******************************************************************************* * Copyright (c) 2013, 2017 Spring IDE Developers * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Spring IDE Developers - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.beans.core.internal.model; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.apache.commons.logging.LogFactory; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.core.IClassFile; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.core.PackageFragment; import org.eclipse.jdt.internal.core.util.Util; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.parsing.ComponentDefinition; import org.springframework.beans.factory.parsing.CompositeComponentDefinition; import org.springframework.beans.factory.parsing.EmptyReaderEventListener; import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.ProblemReporter; import org.springframework.beans.factory.parsing.ReaderEventListener; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationBeanNameGenerator; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.annotation.ConfigurationClassPostProcessor; import org.springframework.context.annotation.ScannedGenericBeanDefinition; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.ide.eclipse.beans.core.BeansCorePlugin; import org.springframework.ide.eclipse.beans.core.internal.model.BeansConfig.InternalScannedGenericBeanDefinition; import org.springframework.ide.eclipse.beans.core.internal.model.process.BeansConfigPostProcessorFactory; import org.springframework.ide.eclipse.beans.core.model.IBean; import org.springframework.ide.eclipse.beans.core.model.IBeansComponent; import org.springframework.ide.eclipse.beans.core.model.IBeansConfig; import org.springframework.ide.eclipse.beans.core.model.IBeansConfigEventListener; import org.springframework.ide.eclipse.beans.core.model.IBeansImport; import org.springframework.ide.eclipse.beans.core.model.IBeansProject; import org.springframework.ide.eclipse.beans.core.model.IReloadableBeansConfig; import org.springframework.ide.eclipse.beans.core.model.process.IBeansConfigPostProcessor; import org.springframework.ide.eclipse.beans.core.namespaces.IModelElementProvider; import org.springframework.ide.eclipse.beans.core.namespaces.NamespaceUtils; import org.springframework.ide.eclipse.core.SpringCore; import org.springframework.ide.eclipse.core.io.ExternalFile; import org.springframework.ide.eclipse.core.java.JdtUtils; import org.springframework.ide.eclipse.core.java.classreading.CachingJdtMetadataReaderFactory; import org.springframework.ide.eclipse.core.model.ILazyInitializedModelElement; import org.springframework.ide.eclipse.core.model.IModelElement; import org.springframework.ide.eclipse.core.model.ISourceModelElement; import org.springframework.ide.eclipse.core.model.java.JavaModelSourceLocation; import org.springframework.ide.eclipse.core.model.validation.ValidationProblem; /** * This class defines a Spring beans configuration based on a Spring JavaConfig class. * * @author Martin Lippert * @since 3.3.0 */ @SuppressWarnings("restriction") public class BeansJavaConfig extends AbstractBeansConfig implements IBeansConfig, ILazyInitializedModelElement, IReloadableBeansConfig { private IType configClass; private String configClassName; private BeansConfigProblemReporter problemReporter; private UniqueBeanNameGenerator beanNameGenerator; private ScannedGenericBeanDefinitionSuppressingBeanDefinitionRegistry registry; /** Internal cache for all children */ private transient IModelElement[] children; public BeansJavaConfig(IBeansProject project, IType configClass, String configClassName, Type type) { super(project, BeansConfigFactory.JAVA_CONFIG_TYPE + configClassName, type); this.configClass = configClass; this.configClassName = configClassName; modificationTimestamp = IResource.NULL_STAMP; if (this.configClass != null) { IResource resource = this.configClass.getResource(); if (resource != null && resource instanceof IFile) { file = (IFile) resource; } else { IClassFile classFile = configClass.getClassFile(); PackageFragment pkg = (PackageFragment) configClass.getPackageFragment(); IPackageFragmentRoot root = (IPackageFragmentRoot) pkg.getParent(); if (root.isArchive()) { IPath zipPath = root.getPath(); String classFileName = classFile.getElementName(); String path = Util.concatWith(pkg.names, classFileName, '/'); file = new ExternalFile(zipPath.toFile(), path, project.getProject()); } } } if (file == null || !file.exists()) { modificationTimestamp = IResource.NULL_STAMP; String msg = "Beans Java config class '" + configClassName + "' not accessible"; problems = new CopyOnWriteArraySet<ValidationProblem>(); problems.add(new ValidationProblem(IMarker.SEVERITY_ERROR, msg, file, -1)); } else { modificationTimestamp = file.getModificationStamp(); try { file.setSessionProperty(IBeansConfig.CONFIG_FILE_TAG, IBeansConfig.CONFIG_FILE_TAG_VALUE); } catch (CoreException e) { BeansCorePlugin.log(new Status(IStatus.WARNING, BeansCorePlugin.PLUGIN_ID, String.format( "Error occured while tagging config file '%s'", file.getFullPath()), e)); } } } public IType getConfigClass() { return this.configClass; } public String getConfigClassName() { return this.configClassName; } public boolean isInitialized() { return isModelPopulated; } @Override public IModelElement[] getElementChildren() { // Lazily initialization of this config readConfig(); try { r.lock(); return children; } finally { r.unlock(); } } @Override public Set<IBeansImport> getImports() { return Collections.emptySet(); } @Override public int getElementStartLine() { return JdtUtils.getLineNumber(configClass); } @Override protected void readConfig() { if (!isModelPopulated) { w.lock(); if (this.isModelPopulated) { w.unlock(); return; } try { if (this.configClass == null) { return; } IBeansProject beansProject = BeansModelUtils.getParentOfClass(this, IBeansProject.class); if (beansProject == null) { return; } final ClassLoader cl = JdtUtils.getClassLoader(beansProject.getProject(), ApplicationContext.class.getClassLoader()); if (cl.getResource(this.configClass.getFullyQualifiedName().replace('.', '/') + ".class") == null) { return; } Callable<Integer> loadBeanDefinitionOperation = new Callable<Integer>() { public Integer call() throws Exception { // Obtain thread context classloader and override with the project classloader ClassLoader threadClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(cl); // Create special ReaderEventListener that essentially just passes through component definitions ReaderEventListener eventListener = new BeansConfigPostProcessorReaderEventListener(); problemReporter = new BeansConfigProblemReporter(); beanNameGenerator = new UniqueBeanNameGenerator(BeansJavaConfig.this); registry = new ScannedGenericBeanDefinitionSuppressingBeanDefinitionRegistry(); try { registerAnnotationProcessors(eventListener); registerBean(eventListener, cl); IBeansConfigPostProcessor[] postProcessors = BeansConfigPostProcessorFactory.createPostProcessor(ConfigurationClassPostProcessor.class.getName()); for (IBeansConfigPostProcessor postProcessor : postProcessors) { executePostProcessor(postProcessor, eventListener); } } finally { // Reset the context classloader Thread.currentThread().setContextClassLoader(threadClassLoader); LogFactory.release(cl); //Otherwise permgen leak? } return 0; } }; FutureTask<Integer> task = new FutureTask<Integer>(loadBeanDefinitionOperation); BeansCorePlugin.getExecutorService().submit(task); task.get(BeansCorePlugin.getDefault().getPreferenceStore().getInt(BeansCorePlugin.TIMEOUT_CONFIG_LOADING_PREFERENCE_ID), TimeUnit.SECONDS); } catch (TimeoutException e) { problems.add(new ValidationProblem(IMarker.SEVERITY_ERROR, "Loading of configuration '" + this.configClass.getFullyQualifiedName() + "' took more than " + BeansCorePlugin.getDefault().getPreferenceStore() .getInt(BeansCorePlugin.TIMEOUT_CONFIG_LOADING_PREFERENCE_ID) + "sec", file, 1)); } catch (Exception e) { problems.add(new ValidationProblem(IMarker.SEVERITY_ERROR, String.format( "Error occured processing Java config '%s'. See Error Log for more details", e.getCause().getMessage()), getElementResource())); BeansCorePlugin.log(new Status(IStatus.INFO, BeansCorePlugin.PLUGIN_ID, String.format( "Error occured processing '%s'", this.configClass.getFullyQualifiedName()), e.getCause())); } finally { // Prepare the internal cache of all children for faster access List<ISourceModelElement> allChildren = new ArrayList<ISourceModelElement>(imports); allChildren.addAll(aliases.values()); allChildren.addAll(components); allChildren.addAll(beans.values()); Collections.sort(allChildren, new Comparator<ISourceModelElement>() { public int compare(ISourceModelElement element1, ISourceModelElement element2) { return element1.getElementStartLine() - element2.getElementStartLine(); } }); this.children = allChildren.toArray(new IModelElement[allChildren.size()]); this.isModelPopulated = true; w.unlock(); } } } /** * Sets internal list of {@link IBean}s to <code>null</code>. Any further access to the data of this instance of * {@link IBeansConfig} leads to reloading of the corresponding beans config file. */ public void reload() { if (configClass != null) { try { w.lock(); // System.out.println(String.format("++- resetting config '%s'", file.getFullPath().toString())); isModelPopulated = false; modificationTimestamp = IResource.NULL_STAMP; defaults = null; imports.clear(); aliases.clear(); beans.clear(); components.clear(); isBeanClassesMapPopulated = false; beanClassesMap.clear(); problems.clear(); children = null; // componentDefinitions.clear(); } finally { w.unlock(); } // Reset all config sets which contain this config for (IBeansConfigEventListener eventListener : eventListeners) { eventListener.onReset(this); } } } /** * Safely execute the given {@link IBeansConfigPostProcessor}. */ private void executePostProcessor(final IBeansConfigPostProcessor postProcessor, final ReaderEventListener eventListener) { SafeRunner.run(new ISafeRunnable() { @Override public void handleException(Throwable exception) { BeansCorePlugin.log(new Status(IStatus.WARNING, BeansCorePlugin.PLUGIN_ID, "Error occured while running bean post processors", exception)); } @Override public void run() throws Exception { try { postProcessor.postProcess(BeansConfigPostProcessorFactory.createPostProcessingContext(BeansJavaConfig.this, beans.values(), eventListener, problemReporter, beanNameGenerator, registry, problems)); } catch (Exception e) { handleException(e); } } }); } /** * Registers the given component definition with this {@link BeansConfig}'s beans and component storage. */ private void registerComponentDefinition(ComponentDefinition componentDefinition, Map<String, IModelElementProvider> elementProviders) { String uri = NamespaceUtils.getNameSpaceURI(componentDefinition); IModelElementProvider provider = elementProviders.get(uri); if (provider == null) { provider = BeansConfig.DEFAULT_ELEMENT_PROVIDER; } ISourceModelElement element = provider.getElement(BeansJavaConfig.this, componentDefinition); if (element instanceof IBean) { beans.put(element.getElementName(), (IBean) element); } else if (element instanceof IBeansComponent) { components.add((IBeansComponent) element); } } public void registerBean(ReaderEventListener eventListener, ClassLoader classloader) throws IOException { IJavaProject project = this.configClass.getJavaProject(); if (project == null) { return; } CachingJdtMetadataReaderFactory metadataReaderFactory = new CachingJdtMetadataReaderFactory(project, classloader); MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(this.configClass.getFullyQualifiedName()); AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(metadataReader.getAnnotationMetadata()); // AnnotationMetadata metadata = abd.getMetadata(); // if (metadata.isAnnotated(Profile.class.getName())) { // AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class); // if (!this.environment.acceptsProfiles(profile.getStringArray("value"))) { // return; // } // } // ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd); // abd.setScope(scopeMetadata.getScopeName()); abd.setScope(BeanDefinition.SCOPE_SINGLETON); AnnotationBeanNameGenerator nameGenerator = new AnnotationBeanNameGenerator(); String beanName = nameGenerator.generateBeanName(abd, this.registry); // AnnotationConfigUtils.processCommonDefinitionAnnotations(abd); // if (qualifiers != null) { // for (Class<? extends Annotation> qualifier : qualifiers) { // if (Primary.class.equals(qualifier)) { // abd.setPrimary(true); // } // else if (Lazy.class.equals(qualifier)) { // abd.setLazyInit(true); // } // else { // abd.addQualifier(new AutowireCandidateQualifier(qualifier)); // } // } // } BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName); // definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry); eventListener.componentRegistered(new BeanComponentDefinition(abd,beanName)); } /** * register the default annotation processors */ protected void registerAnnotationProcessors(ReaderEventListener eventListener) { Set<BeanDefinitionHolder> processorDefinitions = AnnotationConfigUtils.registerAnnotationConfigProcessors(registry, null); // Nest the concrete beans in the surrounding component. for (BeanDefinitionHolder processorDefinition : processorDefinitions) { eventListener.componentRegistered(new BeanComponentDefinition(processorDefinition)); } } @Override public boolean doesAnnotationScanning() { return true; } public BeanDefinitionRegistry getRawBeanDefinitions(CompositeComponentDefinition context) { return null; } class BeansConfigPostProcessorReaderEventListener extends EmptyReaderEventListener { // Keep the contributed model element providers final Map<String, IModelElementProvider> elementProviders = NamespaceUtils.getElementProviders(); @Override public void componentRegistered(ComponentDefinition componentDefinition) { // make sure that all components that come through are safe for the model if (componentDefinition.getSource() == null) { if (componentDefinition instanceof BeanComponentDefinition) { try { AbstractBeanDefinition abstractBeanDefinition = (AbstractBeanDefinition) ((BeanComponentDefinition) componentDefinition).getBeanDefinition(); abstractBeanDefinition.setSource(new JavaModelSourceLocation(configClass)); abstractBeanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); } catch (JavaModelException e) { SpringCore.log(e); } } } registerComponentDefinition(componentDefinition, elementProviders); } } class BeansConfigProblemReporter implements ProblemReporter { public void error(Problem problem) { } public void fatal(Problem problem) { } public void warning(Problem problem) { } } class ScannedGenericBeanDefinitionSuppressingBeanDefinitionRegistry extends SimpleBeanDefinitionRegistry { @Override public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { if (beanDefinition instanceof ScannedGenericBeanDefinition) { super.registerBeanDefinition(beanName, new InternalScannedGenericBeanDefinition( (ScannedGenericBeanDefinition) beanDefinition)); } else { super.registerBeanDefinition(beanName, beanDefinition); } } } }