/******************************************************************************* * Copyright (c) 2004, 2016 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.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.MultiRule; import org.eclipse.jdt.core.IType; import org.osgi.framework.Bundle; import org.springframework.ide.eclipse.beans.core.BeansCorePlugin; import org.springframework.ide.eclipse.beans.core.BeansCoreUtils; import org.springframework.ide.eclipse.beans.core.internal.project.BeansProjectDescriptionReader; import org.springframework.ide.eclipse.beans.core.internal.project.BeansProjectDescriptionWriter; import org.springframework.ide.eclipse.beans.core.model.IBean; import org.springframework.ide.eclipse.beans.core.model.IBeansConfig; import org.springframework.ide.eclipse.beans.core.model.IBeansConfig.Type; import org.springframework.ide.eclipse.beans.core.model.IBeansConfigEventListener; import org.springframework.ide.eclipse.beans.core.model.IBeansConfigSet; import org.springframework.ide.eclipse.beans.core.model.IBeansImport; import org.springframework.ide.eclipse.beans.core.model.IBeansModel; import org.springframework.ide.eclipse.beans.core.model.IBeansModelElementTypes; import org.springframework.ide.eclipse.beans.core.model.IBeansProject; import org.springframework.ide.eclipse.beans.core.model.locate.BeansConfigLocatorDefinition; import org.springframework.ide.eclipse.beans.core.model.locate.BeansConfigLocatorFactory; import org.springframework.ide.eclipse.beans.core.model.locate.IBeansConfigLocator; import org.springframework.ide.eclipse.beans.core.model.locate.IJavaConfigLocator; import org.springframework.ide.eclipse.beans.core.model.process.IBeansConfigPostProcessor; import org.springframework.ide.eclipse.core.MarkerUtils; import org.springframework.ide.eclipse.core.SpringCore; import org.springframework.ide.eclipse.core.model.AbstractModel; import org.springframework.ide.eclipse.core.model.AbstractResourceModelElement; import org.springframework.ide.eclipse.core.model.ILazyInitializedModelElement; import org.springframework.ide.eclipse.core.model.IModelElement; import org.springframework.ide.eclipse.core.model.IModelElementVisitor; import org.springframework.ide.eclipse.core.model.ISpringProject; import org.springframework.ide.eclipse.core.model.ModelChangeEvent; import org.springframework.util.ObjectUtils; /** * This class holds information for a Spring Beans project. The information is * lazily read from the corresponding project description XML file defined in * {@link IBeansProject#DESCRIPTION_FILE}. * <p> * The information can be persisted by calling the method * {@link #saveDescription()}. * * @author Torsten Juergeleit * @author Dave Watkins * @author Christian Dupuis * @author Martin Lippert * @author Leo Dos Santos */ public class BeansProject extends AbstractResourceModelElement implements IBeansProject, ILazyInitializedModelElement { private static final int AUTO_CONFIG_RESCHEDULE_SLEEP_TIME_MILLIS = 3000; private static final int AUTO_CONFIG_RESCHEDULE_MAX_COUNT = 10; private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock r = rwl.readLock(); private final Lock w = rwl.writeLock(); protected volatile boolean modelPopulated = false; private final IProject project; protected volatile Set<String> configSuffixes = new LinkedHashSet<String>(); /** the internal flag to specify if import processing is enabled */ protected volatile boolean isImportsEnabled = DEFAULT_IMPORTS_ENABLED; /** Internal version number; intentionally set to lower value */ protected volatile String version = "2.0.0"; protected volatile Map<String, IBeansConfig> configs = new LinkedHashMap<String, IBeansConfig>(); protected volatile Map<String, IBeansConfig> autoDetectedConfigs = new LinkedHashMap<String, IBeansConfig>(); protected volatile Set<IBeansConfig> allConfigs = Collections.unmodifiableSet(new CopyOnWriteArraySet<IBeansConfig>()); protected volatile Map<String, Set<String>> autoDetectedConfigsByLocator = new LinkedHashMap<String, Set<String>>(); protected volatile Map<String, String> locatorByAutoDetectedConfig = new LinkedHashMap<String, String>(); protected volatile Map<String, IBeansConfigSet> configSets = new LinkedHashMap<String, IBeansConfigSet>(); protected volatile Map<String, IBeansConfigSet> autoDetectedConfigSets = new LinkedHashMap<String, IBeansConfigSet>(); protected volatile Map<String, String> autoDetectedConfigSetsByLocator = new LinkedHashMap<String, String>(); protected volatile IBeansConfigEventListener eventListener; private boolean isAutoConfigStatePersisted = false; public BeansProject(IBeansModel model, IProject project) { super(model, project.getName()); this.project = project; } /** * {@inheritDoc} */ @Override public int getElementType() { return IBeansModelElementTypes.PROJECT_TYPE; } /** * {@inheritDoc} */ @Override public IModelElement[] getElementChildren() { Set<IModelElement> children = new LinkedHashSet<IModelElement>(getConfigs()); children.addAll(getConfigSets()); return children.toArray(new IModelElement[children.size()]); } /** * {@inheritDoc} */ @Override public IResource getElementResource() { return project; } /** * {@inheritDoc} */ @Override public boolean isElementArchived() { return false; } /** * {@inheritDoc} */ @Override public void accept(IModelElementVisitor visitor, IProgressMonitor monitor) { // First visit this project if (!monitor.isCanceled() && visitor.visit(this, monitor)) { // Now ask this project's configs for (IBeansConfig config : getConfigs()) { config.accept(visitor, monitor); if (monitor.isCanceled()) { return; } } // Finally ask this project's config sets for (IBeansConfigSet configSet : getConfigSets()) { configSet.accept(visitor, monitor); if (monitor.isCanceled()) { return; } } } } /** * {@inheritDoc} */ @Override public IProject getProject() { return project; } /** * Updates the list of config suffixes belonging to this project. * <p> * The modified project description has to be saved to disk by calling * {@link #saveDescription()}. * * @param suffixes * list of config suffixes */ public void setConfigSuffixes(Set<String> suffixes) { if (!this.modelPopulated) { populateModel(); } try { w.lock(); configSuffixes.clear(); configSuffixes.addAll(suffixes); } finally { w.unlock(); } } public boolean addConfigSuffix(String suffix) { if (suffix != null && suffix.length() > 0) { if (!this.modelPopulated) { populateModel(); } try { w.lock(); if (!configSuffixes.contains(suffix)) { configSuffixes.add(suffix); return true; } } finally { w.unlock(); } } return false; } @Override public Set<String> getConfigSuffixes() { if (!this.modelPopulated) { populateModel(); } try { r.lock(); return Collections.unmodifiableSet(configSuffixes); } finally { r.unlock(); } } /** * @deprecated use {@link #getConfigSuffixes()} instead. */ @Override @Deprecated public Set<String> getConfigExtensions() { return getConfigSuffixes(); } @Override public boolean hasConfigSuffix(String suffix) { try { r.lock(); return getConfigSuffixes().contains(suffix); } finally { r.unlock(); } } /** * @deprecated use {@link #hasConfigSuffix(String)} instead. */ @Override @Deprecated public boolean hasConfigExtension(String extension) { return hasConfigSuffix(extension); } /** * Updates the list of configs (by name) belonging to this project. From all * removed configs the Spring IDE problem markers are deleted. * <p> * The modified project description has to be saved to disk by calling * {@link #saveDescription()}. * * @param configNames * list of config names */ public void setConfigs(Set<String> configNames) { if (!this.modelPopulated) { populateModel(); } List<IResource> deleteMarkersFrom = new ArrayList<IResource>(); try { w.lock(); // Look for removed configs and // 1. delete all problem markers from them // 2. remove config from any config set for (IBeansConfig config : configs.values()) { String configName = config.getElementName(); if (!configNames.contains(configName)) { removeConfig(configName); // Defer deletion of problem markers until write lock is // released deleteMarkersFrom.add(config.getElementResource()); } } // Create new list of configs configs.clear(); for (String configName : configNames) { configs.put(configName, BeansConfigFactory.create(this, configName, Type.MANUAL)); } } finally { updateAllConfigsCache(); w.unlock(); } // Delete the problem markers after the write lock is released - // otherwise this may be interfering with a ResourceChangeListener // referring to this beans project for (IResource configResource : deleteMarkersFrom) { MarkerUtils.deleteMarkers(configResource, SpringCore.MARKER_ID); } } /** * Adds the given beans config file's name to the list of configs. * <p> * The modified project description has to be saved to disk by calling * {@link #saveDescription()}. * * @param file * the config file to add * @return <code>true</code> if config file was added to this project */ public boolean addConfig(IFile file, IBeansConfig.Type type) { return addConfig(this.getConfigName(file), type); } /** * Adds the given beans config to the list of configs. * <p> * The modified project description has to be saved to disk by calling * {@link #saveDescription()}. * * @param configName * the config name to add * @return <code>true</code> if config was added to this project */ public boolean addConfig(String configName, IBeansConfig.Type type) { if (!this.modelPopulated) { populateModel(); } try { w.lock(); if (configName.length() > 0 && !configs.containsKey(configName)) { if (type == IBeansConfig.Type.MANUAL) { IBeansConfig config = BeansConfigFactory.create(this, configName, type); addConfig(config); return true; } else if (type == IBeansConfig.Type.AUTO_DETECTED && !autoDetectedConfigs.containsKey(configName)) { populateAutoDetectedConfigsAndConfigSets(null); return true; } } } finally { updateAllConfigsCache(); w.unlock(); } return false; } /** * Adds the given beans config to the list of configs. * <p> * The modified project description has to be saved to disk by calling * {@link #saveDescription()}. * * @param config * the config to add * @return <code>true</code> if config file was added to this project */ private boolean addConfig(IBeansConfig config) { String configName = config.getElementName(); if (configs.containsKey(configName)) { return false; } configs.put(configName, config); config.registerEventListener(eventListener); if (autoDetectedConfigs.containsKey(configName)) { autoDetectedConfigs.remove(configName); String locatorId = locatorByAutoDetectedConfig.remove(configName); if (locatorId != null && autoDetectedConfigsByLocator.containsKey(locatorId)) { autoDetectedConfigsByLocator.get(locatorId).remove(configName); } } return true; } /** * I * Removes the given beans config from the list of configs and from all * config sets. * <p> * The modified project description has to be saved to disk by calling * {@link #saveDescription()}. * * @param file * the config file to remove * @return <code>true</code> if config was removed to this project */ public boolean removeConfig(IFile file) { if (file.getProject().equals(project)) { return removeConfig(getConfigName(file)); } // External configs only remove from all config sets return removeConfigFromConfigSets(getConfigName(file)); } /** * Removes the given beans config from the list of configs and from all * config sets. * <p> * The modified project description has to be saved to disk by calling * {@link #saveDescription()}. * * @param configName * the config name to remove * @return <code>true</code> if config was removed to this project */ public boolean removeConfig(String configName) { if (hasConfig(configName)) { try { w.lock(); IBeansConfig config = configs.remove(configName); IBeansConfig autoDetectedConfig = autoDetectedConfigs.remove(configName); if (config != null) { config.unregisterEventListener(eventListener); } if (autoDetectedConfig != null) { autoDetectedConfig.unregisterEventListener(eventListener); } String locatorId = locatorByAutoDetectedConfig.remove(configName); if (locatorId != null && autoDetectedConfigsByLocator.containsKey(locatorId)) { autoDetectedConfigsByLocator.get(locatorId).remove(configName); } } finally { updateAllConfigsCache(); w.unlock(); } removeConfigFromConfigSets(configName); return true; } return false; } @Override public boolean hasConfig(IFile file) { return hasConfig(getConfigName(file)); } @Override public boolean hasConfig(String configName) { if (!this.modelPopulated) { populateModel(); } try { r.lock(); return (configs.containsKey(configName) || autoDetectedConfigs.containsKey(configName)); } finally { r.unlock(); } } @Override public boolean hasConfig(IFile configFile, String configName, boolean includeImported) { if (hasConfig(configName)) { return true; } for (IBeansConfig config : getConfigs()) { if (config.getElementResource() != null && config.getElementResource().equals(configFile)) { return true; } } if (isImportsEnabled() && includeImported) { try { r.lock(); for (IBeansConfig bc : getConfigs()) { if (hasImportedBeansConfig(configFile, bc)) { return true; } } } finally { r.unlock(); } } return false; } @Override public IBeansConfig getConfig(IFile configFile, boolean includeImported) { Set<IBeansConfig> beansConfigs = getConfigs(configFile, includeImported); Iterator<IBeansConfig> iterator = beansConfigs.iterator(); if (iterator.hasNext()) { return iterator.next(); } return null; } @Override public Set<IBeansConfig> getConfigs(IFile file, boolean includeImported) { Set<IBeansConfig> beansConfigs = new LinkedHashSet<IBeansConfig>(); if (file.getProject() != null && !this.project.equals(file.getProject())) { IBeansProject otherBeansProject = BeansCorePlugin.getModel().getProject(file.getProject()); if (otherBeansProject != null) { Set<IBeansConfig> otherProjectConfigs = otherBeansProject.getConfigs(file, false); for (IBeansConfig otherProjectConfig : otherProjectConfigs) { beansConfigs.add(otherProjectConfig); } } } Set<IBeansConfig> ownConfigs = getConfigs(); if (ownConfigs != null) { for (IBeansConfig config : ownConfigs) { if (config.getElementResource() != null && config.getElementResource().equals(file)) { beansConfigs.add(config); } } } // make sure that we run into the next block only if <import> support is // enabled // not executing the block will safe lots of execution time as // configuration files don't // need to get loaded. if ((isImportsEnabled() && includeImported)) { try { r.lock(); if (ownConfigs != null) { for (IBeansConfig bc : ownConfigs) { checkForImportedBeansConfig(file, bc, beansConfigs); } } } finally { r.unlock(); } } return beansConfigs; } private void checkForImportedBeansConfig(IFile file, IBeansConfig bc, Set<IBeansConfig> beansConfigs) { if (bc.getElementResource() != null && bc.getElementResource().equals(file)) { beansConfigs.add(bc); } for (IBeansImport bi : bc.getImports()) { for (IBeansConfig importedBc : bi.getImportedBeansConfigs()) { checkForImportedBeansConfig(file, importedBc, beansConfigs); } } } private boolean hasImportedBeansConfig(IFile file, IBeansConfig bc) { if (bc.getElementResource() != null && bc.getElementResource().equals(file)) { return true; } for (IBeansImport bi : bc.getImports()) { for (IBeansConfig importedBc : bi.getImportedBeansConfigs()) { if (hasImportedBeansConfig(file, importedBc)) { return true; } } } return false; } /** * {@inheritDoc} */ @Override public IBeansConfig getConfig(IFile file) { IBeansConfig config = getConfig(getConfigName(file)); if (config == null) { if (!this.modelPopulated) { populateModel(); } try { r.lock(); for (IBeansConfig beansConfig : configs.values()) { if (beansConfig.getElementResource() != null && beansConfig.getElementResource().equals(file)) { return beansConfig; } } } finally { r.unlock(); } } return config; } /** * {@inheritDoc} */ @Override public IBeansConfig getConfig(String configName) { if (configName != null && configName.length() > 0 && configName.charAt(0) == '/') { return BeansCorePlugin.getModel().getConfig(configName); } if (!this.modelPopulated) { populateModel(); } try { r.lock(); if (configs.containsKey(configName)) { return configs.get(configName); } else if (autoDetectedConfigs.containsKey(configName)) { return autoDetectedConfigs.get(configName); } return null; } finally { r.unlock(); } } public Set<String> getConfigNames() { if (!this.modelPopulated) { populateModel(); } try { r.lock(); Set<String> configNames = new LinkedHashSet<String>(configs.keySet()); configNames.addAll(autoDetectedConfigs.keySet()); return configNames; } finally { r.unlock(); } } public Set<String> getManualConfigNames() { if (!this.modelPopulated) { populateModel(); } try { r.lock(); return new LinkedHashSet<String>(configs.keySet()); } finally { r.unlock(); } } public Set<String> getAutoConfigNames() { if (!this.modelPopulated) { populateModel(); } try { r.lock(); return new LinkedHashSet<String>(autoDetectedConfigs.keySet()); } finally { r.unlock(); } } public Set<String> getManualConfigSetNames() { if (!this.modelPopulated) { populateModel(); } try { r.lock(); return new LinkedHashSet<String>(configSets.keySet()); } finally { r.unlock(); } } public Set<String> getAutoConfigSetNames() { if (!this.modelPopulated) { populateModel(); } try { r.lock(); return new LinkedHashSet<String>(autoDetectedConfigSets.keySet()); } finally { r.unlock(); } } /** * {@inheritDoc} */ @Override public Set<IBeansConfig> getConfigs() { if (!this.modelPopulated) { populateModel(); } try { r.lock(); return allConfigs; // Set<IBeansConfig> beansConfigs = new // LinkedHashSet<IBeansConfig>(configs.values()); // beansConfigs.addAll(autoDetectedConfigs.values()); // return beansConfigs; } finally { r.unlock(); } } /** * Updates the {@link BeansConfigSet}s defined within this project. * <p> * The modified project description has to be saved to disk by calling * {@link #saveDescription()}. * * @param configSets * list of {@link BeansConfigSet} instances */ public void setConfigSets(Set<IBeansConfigSet> configSets) { if (!this.modelPopulated) { populateModel(); } try { w.lock(); this.configSets.clear(); for (IBeansConfigSet configSet : configSets) { this.configSets.put(configSet.getElementName(), configSet); } } finally { w.unlock(); } } public boolean addConfigSet(IBeansConfigSet configSet) { if (!this.modelPopulated) { populateModel(); } try { r.lock(); if (!configSets.values().contains(configSet)) { configSets.put(configSet.getElementName(), configSet); if (autoDetectedConfigSets.containsKey(configSet.getElementName())) { autoDetectedConfigSets.remove(configSet.getElementName()); autoDetectedConfigSetsByLocator.remove(configSet.getElementName()); } return true; } } finally { r.unlock(); } return false; } public void removeConfigSet(String configSetName) { try { w.lock(); configSets.remove(configSetName); } finally { w.unlock(); } } /** * {@inheritDoc} */ @Override public boolean hasConfigSet(String configSetName) { if (!this.modelPopulated) { populateModel(); } try { r.lock(); return configSets.containsKey(configSetName); } finally { r.unlock(); } } /** * {@inheritDoc} */ @Override public IBeansConfigSet getConfigSet(String configSetName) { if (!this.modelPopulated) { populateModel(); } try { r.lock(); IBeansConfigSet set = configSets.get(configSetName); if (set != null) { return set; } return autoDetectedConfigSets.get(configSetName); } finally { r.unlock(); } } /** * {@inheritDoc} */ @Override public Set<IBeansConfigSet> getConfigSets() { if (!this.modelPopulated) { populateModel(); } try { r.lock(); Set<IBeansConfigSet> configSets = new LinkedHashSet<IBeansConfigSet>(this.configSets.values()); configSets.addAll(autoDetectedConfigSets.values()); return configSets; } finally { r.unlock(); } } /** * {@inheritDoc} */ @Override public boolean isBeanClass(String className) { for (IBeansConfig config : getConfigs()) { if (config.isBeanClass(className)) { return true; } } return false; } /** * {@inheritDoc} */ @Override public Set<String> getBeanClasses() { Set<String> beanClasses = new LinkedHashSet<String>(); for (IBeansConfig config : getConfigs()) { beanClasses.addAll(config.getBeanClasses()); } return beanClasses; } /** * {@inheritDoc} */ @Override public Set<IBean> getBeans(String className) { Set<IBean> beans = new LinkedHashSet<IBean>(); for (IBeansConfig config : getConfigs()) { if (config.isBeanClass(className)) { beans.addAll(config.getBeans(className)); } } return beans; } /** * Writes the current project description to the corresponding XML file * defined in {@link IBeansProject#DESCRIPTION_FILE}. */ public void saveDescription() { // We can't acquire the write lock here - otherwise this may be // interfering with a ResourceChangeListener referring to this beans // project BeansProjectDescriptionWriter.write(this); } /** * Resets the internal data. Any further access to the data of this instance * of {@link BeansProject} leads to reloading of this beans project's config * description file. */ public void reset() { try { w.lock(); this.modelPopulated = false; configSuffixes.clear(); configs.clear(); configSets.clear(); autoDetectedConfigs.clear(); autoDetectedConfigsByLocator.clear(); locatorByAutoDetectedConfig.clear(); autoDetectedConfigSets.clear(); autoDetectedConfigSetsByLocator.clear(); } finally { updateAllConfigsCache(); w.unlock(); } } /** * {@inheritDoc} */ @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof BeansProject)) { return false; } BeansProject that = (BeansProject) other; if (!ObjectUtils.nullSafeEquals(this.project, that.project)) return false; return super.equals(other); } /** * {@inheritDoc} */ @Override public int hashCode() { int hashCode = ObjectUtils.nullSafeHashCode(project); return getElementType() * hashCode + super.hashCode(); } /** * {@inheritDoc} */ @Override public String toString() { try { r.lock(); return "Project=" + getElementName() + ", ConfigExtensions=" + configSuffixes + ", Configs=" + configs.values() + ", ConfigsSets=" + configSets; } finally { r.unlock(); } } /** * {@inheritDoc} */ @Override public boolean isImportsEnabled() { return isImportsEnabled; } public void setImportsEnabled(boolean importEnabled) { this.isImportsEnabled = importEnabled; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } /** * {@inheritDoc} */ @Override public boolean isUpdatable() { IFile file = project.getProject().getFile(new Path(IBeansProject.DESCRIPTION_FILE)); return !file.isReadOnly(); } public void removeAutoDetectedConfigs(String locatorId) { try { w.lock(); Set<String> configs = autoDetectedConfigsByLocator.get(locatorId); if (configs != null) { autoDetectedConfigsByLocator.remove(locatorId); } if (configs != null) { for (String configName : configs) { // Before actually removing make sure to delete ALL markers MarkerUtils.deleteAllMarkers(getConfig(configName).getElementResource(), SpringCore.MARKER_ID); // Remove the config from the internal list autoDetectedConfigs.remove(configName); locatorByAutoDetectedConfig.remove(configName); } } String configSet = autoDetectedConfigSetsByLocator.get(locatorId); if (configSets != null) { autoDetectedConfigSets.remove(configSet); autoDetectedConfigSetsByLocator.remove(configSet); } } finally { updateAllConfigsCache(); w.unlock(); } } /** * {@inheritDoc} */ @Override public boolean isExternal() { return false; } private boolean removeConfigFromConfigSets(String configName) { if (!this.modelPopulated) { populateModel(); } boolean hasRemoved = false; try { r.lock(); for (IBeansConfigSet configSet : configSets.values()) { if (configSet.hasConfig(configName)) { ((BeansConfigSet) configSet).removeConfig(configName); hasRemoved = true; } } for (IBeansConfigSet configSet : autoDetectedConfigSets.values()) { if (configSet.hasConfig(configName)) { ((BeansConfigSet) configSet).removeConfig(configName); hasRemoved = true; } } } finally { r.unlock(); } return hasRemoved; } /** * Returns the config name from given file. If the file belongs to this * project then the config name is the project-relative path of the given * file otherwise it's the workspace-relative path with a leading '/'. */ private String getConfigName(IFile file) { return BeansConfigFactory.getConfigName(file, this.project); } /** * Populate the project's model with the information read from project * description (an XML file defined in * {@link ISpringProject.DESCRIPTION_FILE}). */ private void populateModel() { try { w.lock(); if (this.modelPopulated) { return; } this.eventListener = new DefaultBeansConfigEventListener(); this.modelPopulated = true; BeansProjectDescriptionReader.read(this); // Remove all invalid configs from this project Set<IBeansConfig> configuredConfigs = new LinkedHashSet<IBeansConfig>(configs.values()); for (IBeansConfig config : configuredConfigs) { if (config.getElementResource() == null || !config.getElementResource().exists()) { removeConfig(config.getElementName()); } } // Remove all invalid config names from this project's config sets Map<IBeansConfigSet, Set<String>> removedConfigsFromSets = new HashMap<IBeansConfigSet, Set<String>>(); IBeansModel model = BeansCorePlugin.getModel(); for (IBeansConfigSet configSet : configSets.values()) { for (String configName : configSet.getConfigNames()) { if (!hasConfig(configName) && model.getConfig(configName) == null) { ((BeansConfigSet) configSet).removeConfig(configName); Set<String> removedConfigs = removedConfigsFromSets.get(configSet); if (removedConfigs == null) { removedConfigs = new HashSet<String>(); removedConfigsFromSets.put(configSet, removedConfigs); } removedConfigs.add(configName); } } } // Add auto detected configs and config sets populateAutoDetectedConfigsAndConfigSets(removedConfigsFromSets); for (IBeansConfig config : configs.values()) { config.registerEventListener(eventListener); } } finally { updateAllConfigsCache(); w.unlock(); } } /** * Runs the registered detectors and registers {@link IBeansConfig} and * {@link IBeansConfigSet} with this project. * <p> * This method should only be called with having a write lock. * * @param removedConfigsFromSets */ protected void populateAutoDetectedConfigsAndConfigSets(final Map<IBeansConfigSet, Set<String>> removedConfigsFromSets) { if (BeansCorePlugin.getDefault().isAutoDetectionEnabled()) { Job job = new Job("populate auto detected configs") { private int rescheduleCount = 0; @Override protected IStatus run(IProgressMonitor monitor) { try { boolean reschedule = populateAutoDetectedConfigsAndConfigSetsInternally(); if (reschedule) { rescheduleCount++; if (rescheduleCount < AUTO_CONFIG_RESCHEDULE_MAX_COUNT) { schedule(AUTO_CONFIG_RESCHEDULE_SLEEP_TIME_MILLIS); } } w.lock(); restoreConfigSetState(removedConfigsFromSets); } finally { updateAllConfigsCache(); w.unlock(); } ((AbstractModel) (BeansCorePlugin.getModel())).notifyListeners(BeansProject.this, ModelChangeEvent.Type.CHANGED); return Status.OK_STATUS; } @Override public boolean belongsTo(Object family) { return family.equals("populateAutoConfigsJobFamily"); } }; job.setPriority(Job.BUILD); job.setRule(MultiRule.combine(ResourcesPlugin.getWorkspace().getRoot(), BeansCoreUtils.BEANS_MODEL_INIT_RULE)); job.schedule(); } } protected boolean populateAutoDetectedConfigsAndConfigSetsInternally() { final Boolean[] reschedule = new Boolean[1]; reschedule[0] = Boolean.FALSE; final Map<BeansConfigLocatorDefinition, Map<String, IBeansConfig>> newAutoConfigs = new HashMap<BeansConfigLocatorDefinition, Map<String, IBeansConfig>>(); final Map<BeansConfigLocatorDefinition, String> newConfigSetNames = new HashMap<BeansConfigLocatorDefinition, String>(); // Find auto detected beans configs for (final BeansConfigLocatorDefinition locator : BeansConfigLocatorFactory.getBeansConfigLocatorDefinitions()) { if (locator.isEnabled(getProject()) && locator.getBeansConfigLocator().supports(getProject())) { final Map<String, IBeansConfig> detectedConfigs = new HashMap<String, IBeansConfig>(); newAutoConfigs.put(locator, detectedConfigs); // Prevent extension contribution from crashing the model // creation SafeRunner.run(new ISafeRunnable() { @Override public void handleException(Throwable exception) { } @Override public void run() throws Exception { IBeansConfigLocator configLocator = locator.getBeansConfigLocator(); Set<IFile> files = configLocator.locateBeansConfigs(getProject(), null); for (IFile file : files) { BeansConfig config = new BeansConfig(BeansProject.this, file.getProjectRelativePath().toString(), Type.AUTO_DETECTED); String configName = getConfigName(file); if (!hasConfig(configName)) { detectedConfigs.put(configName, config); } } if (files.size() > 1) { String configSet = locator.getBeansConfigLocator().getBeansConfigSetName(files); if (configSet.length() > 0) { newConfigSetNames.put(locator, configSet); } } if (configLocator instanceof IJavaConfigLocator) { if (workaroundM2EActivationTimeout(getProject())) { reschedule[0] = Boolean.TRUE; } else { Set<IType> types = ((IJavaConfigLocator) configLocator).locateJavaConfigs(getProject(), null); for (IType type : types) { IBeansConfig config = new BeansJavaConfig(BeansProject.this, type, type.getFullyQualifiedName(), Type.AUTO_DETECTED); String configName = BeansConfigFactory.JAVA_CONFIG_TYPE + type.getFullyQualifiedName(); if (!hasConfig(configName)) { detectedConfigs.put(configName, config); } } } } } }); } } setAutoDetectedConfigs(newAutoConfigs, newConfigSetNames); return reschedule[0].booleanValue(); } protected boolean workaroundM2EActivationTimeout(IProject project2) { try { if (project.hasNature("org.eclipse.m2e.core.maven2Nature")) { Bundle bundle = Platform.getBundle("org.eclipse.m2e.jdt"); if (bundle != null) { if (bundle.getState() != Bundle.ACTIVE) { return true; } } } } catch (CoreException e) { } return false; } protected void setAutoDetectedConfigs(Map<BeansConfigLocatorDefinition, Map<String, IBeansConfig>> newAutoConfigs, Map<BeansConfigLocatorDefinition, String> newConfigSetNames) { try { w.lock(); for (IBeansConfig config : autoDetectedConfigs.values()) { config.unregisterEventListener(eventListener); } autoDetectedConfigs.clear(); autoDetectedConfigsByLocator.clear(); locatorByAutoDetectedConfig.clear(); autoDetectedConfigSets.clear(); autoDetectedConfigSetsByLocator.clear(); Iterator<BeansConfigLocatorDefinition> locators = newAutoConfigs.keySet().iterator(); while (locators.hasNext()) { BeansConfigLocatorDefinition locator = locators.next(); Map<String, IBeansConfig> detectedConfigs = newAutoConfigs.get(locator); String configSetName = newConfigSetNames.get(locator); if (detectedConfigs.size() > 0) { Set<String> configNamesByLocator = new LinkedHashSet<String>(); for (Map.Entry<String, IBeansConfig> detectedConfig : detectedConfigs.entrySet()) { String configName = detectedConfig.getKey(); autoDetectedConfigs.put(configName, detectedConfig.getValue()); detectedConfig.getValue().registerEventListener(eventListener); configNamesByLocator.add(configName); locatorByAutoDetectedConfig.put(configName, locator.getNamespaceUri() + "." + locator.getId()); } autoDetectedConfigsByLocator.put(locator.getNamespaceUri() + "." + locator.getId(), configNamesByLocator); // Create a config set for auto detected configs if desired // by the extension if (configSetName != null && configSetName.length() > 0) { IBeansConfigSet configSet = new BeansConfigSet(BeansProject.this, configSetName, configNamesByLocator, IBeansConfigSet.Type.AUTO_DETECTED); // configure the created IBeansConfig locator.getBeansConfigLocator().configureBeansConfigSet(configSet); autoDetectedConfigSets.put(configSetName, configSet); autoDetectedConfigSetsByLocator.put(locator.getNamespaceUri() + "." + locator.getId(), configSetName); } } } } finally { w.unlock(); //The commented line below was a fix for 'STS-3541: added additional project build after asynchronous beans config scanning' // It was removed because it appears to cause 'endless builds' by spawning many small 'single project builds in large workspace. // SpringCoreUtils.buildFullProject(project); } } protected void restoreConfigSetState(Map<IBeansConfigSet, Set<String>> removedConfigsFromSets) { if (removedConfigsFromSets != null) { IBeansModel model = BeansCorePlugin.getModel(); for (IBeansConfigSet configSet : removedConfigsFromSets.keySet()) { Set<String> removedConfigs = removedConfigsFromSets.get(configSet); for (String removedConfig : removedConfigs) { if (hasConfig(removedConfig) || model.getConfig(removedConfig) != null) { ((BeansConfigSet) configSet).addConfig(removedConfig); } } } } } /** * Update the internal cache for all configs in case something changed to * this.configs or this.autoDetectedConfigs. This has to be called in a * write-guarded block. */ protected void updateAllConfigsCache() { CopyOnWriteArraySet<IBeansConfig> newAllConfigs = new CopyOnWriteArraySet<IBeansConfig>(configs.values()); newAllConfigs.addAll(autoDetectedConfigs.values()); this.allConfigs = Collections.unmodifiableSet(newAllConfigs); } /** * Default implementation of {@link IBeansConfigEventListener} that handles * events and propagates those to {@link IBeansConfigSet}s and other * {@link IBeansConfig}. * * @author Christian Dupuis * @since 2.2.5 */ class DefaultBeansConfigEventListener implements IBeansConfigEventListener { /** * {@inheritDoc} */ @Override public void onPostProcessorDetected(IBeansConfig config, IBeansConfigPostProcessor postProcessor) { for (IBeansProject project : BeansCorePlugin.getModel().getProjects()) { for (IBeansConfigSet configSet : project.getConfigSets()) { if (configSet.hasConfig((IFile) config.getElementResource())) { for (IBeansConfig configSetConfig : configSet.getConfigs()) { if (!configSetConfig.equals(config) && configSetConfig instanceof BeansConfig) { ((BeansConfig) configSetConfig).addExternalPostProcessor(postProcessor, config); } } } } } } /** * {@inheritDoc} */ @Override public void onPostProcessorRemoved(IBeansConfig config, IBeansConfigPostProcessor postProcessor) { for (IBeansProject project : BeansCorePlugin.getModel().getProjects()) { for (IBeansConfigSet configSet : project.getConfigSets()) { if (configSet.hasConfig((IFile) config.getElementResource())) { for (IBeansConfig configSetConfig : configSet.getConfigs()) { if (!configSetConfig.equals(config) && configSetConfig instanceof BeansConfig) { ((BeansConfig) configSetConfig).removeExternalPostProcessor(postProcessor, config); } } } } } } /** * {@inheritDoc} */ @Override public void onReadEnd(IBeansConfig config) { } /** * {@inheritDoc} */ @Override public void onReadStart(IBeansConfig config) { } /** * {@inheritDoc} */ @Override public void onReset(IBeansConfig config) { for (IBeansProject project : BeansCorePlugin.getModel().getProjects()) { for (IBeansConfigSet configSet : project.getConfigSets()) { if (configSet.hasConfig((IFile) config.getElementResource())) { if (configSet instanceof BeansConfigSet) { ((BeansConfigSet) configSet).reset(); } } } } } } @Override public boolean isInitialized() { if (!this.modelPopulated) { return false; } try { r.lock(); for (IBeansConfig config : configs.values()) { if (!((ILazyInitializedModelElement) config).isInitialized()) { return false; } } for (IBeansConfig config : autoDetectedConfigs.values()) { if (!((ILazyInitializedModelElement) config).isInitialized()) { return false; } } return true; } finally { r.unlock(); } } @Override public boolean isAutoConfigStatePersisted() { return this.isAutoConfigStatePersisted; } public void setAutoConfigStatePersisted(boolean autoConfigPersisted) { this.isAutoConfigStatePersisted = autoConfigPersisted; } }