/******************************************************************************* * Copyright (c) 2010 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.metadata.internal.model; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtension; import org.eclipse.core.runtime.IExtensionPoint; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.IStatus; 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.ui.progress.IProgressConstants; import org.springframework.ide.eclipse.beans.core.BeansCoreImages; import org.springframework.ide.eclipse.beans.core.BeansCorePlugin; import org.springframework.ide.eclipse.beans.core.internal.model.BeansModel; import org.springframework.ide.eclipse.beans.core.internal.model.BeansModelUtils; import org.springframework.ide.eclipse.beans.core.metadata.BeansMetadataPlugin; import org.springframework.ide.eclipse.beans.core.metadata.model.IBeanMetadata; import org.springframework.ide.eclipse.beans.core.metadata.model.IBeanMetadataProvider; import org.springframework.ide.eclipse.beans.core.metadata.model.IMethodMetadata; import org.springframework.ide.eclipse.beans.core.model.IBean; import org.springframework.ide.eclipse.beans.core.model.IBeanProperty; import org.springframework.ide.eclipse.beans.core.model.IBeansConfig; import org.springframework.ide.eclipse.beans.core.model.IBeansProject; import org.springframework.ide.eclipse.core.model.ModelChangeEvent.Type; /** * {@link Job} implementation that handles loading and attaching {@link IBeanMetadata} for {@link IBeansConfig}. * @author Christian Dupuis * @since 2.0.5 */ public class BeanMetadataBuilderJob extends Job { /** The metadataProvider element in the extension point contribution */ private static final String METADATA_PROVIDER_ELEMENT = "metadataProvider"; /** The class attribute in the extension point contribution */ private static final String CLASS_ATTRIBUTE = "class"; /** The id of the metadata providers extension point */ public static final String META_DATA_PROVIDERS_EXTENSION_POINT = BeansMetadataPlugin.PLUGIN_ID + ".metadataproviders"; /** Object identifying the job family */ private static final Object CONTENT_FAMILY = new Object(); /** Internal cache of the affected {@link IBean}s keyed by the containing {@link IBeansConfig} */ private Map<IBeansConfig, Set<IBean>> affectedBeans; /** * Constructor * @param affectedBeans the list of affected {@link IBean} keyed by a corresponding {@link IBeansConfig}. */ public BeanMetadataBuilderJob(Map<IBeansConfig, Set<IBean>> affectedBeans) { super("Resolving Spring Meta Data"); this.affectedBeans = affectedBeans; setPriority(Job.BUILD); setSystem(true); setProperty(IProgressConstants.ICON_PROPERTY, BeansCoreImages.DESC_OBJS_ANNOTATATION); } /** * {@inheritDoc} */ @Override public boolean belongsTo(Object family) { return CONTENT_FAMILY == family; } public boolean isCoveredBy(BeanMetadataBuilderJob other) { if (other.affectedBeans != null && this.affectedBeans != null) { Set<IBean> allBeans = new HashSet<IBean>(); for (Set<IBean> beans : other.affectedBeans.values()) { allBeans.addAll(beans); } Set<IBean> allMyBeans = new HashSet<IBean>(); for (Set<IBean> beans : this.affectedBeans.values()) { allMyBeans.addAll(beans); } return allBeans.containsAll(allMyBeans); } return false; } /** * {@inheritDoc} */ @Override public IStatus run(IProgressMonitor monitor) { try { // Remove similar jobs from the chain synchronized (getClass()) { if (monitor.isCanceled()) { return Status.CANCEL_STATUS; } Job[] buildJobs = Job.getJobManager().find(CONTENT_FAMILY); for (int i = 0; i < buildJobs.length; i++) { Job curr = buildJobs[i]; if (curr != this && curr instanceof BeanMetadataBuilderJob) { BeanMetadataBuilderJob job = (BeanMetadataBuilderJob) curr; if (job.isCoveredBy(this)) { curr.cancel(); } } } } monitor.beginTask("Attaching Spring bean meta data", affectedBeans.size()); // Reading contributed IBeanMetadataProviders from the extension point IBeanMetadataProvider[] providers = getMetadataProviders(); Set<IBeansProject> projects = new LinkedHashSet<IBeansProject>(); for (Map.Entry<IBeansConfig, Set<IBean>> entry : affectedBeans.entrySet()) { // Do some profiling long start = System.currentTimeMillis(); IResource resource = entry.getKey().getElementResource(); projects.add(BeansModelUtils.getParentOfClass(entry.getKey(), IBeansProject.class)); monitor.subTask("Attaching Spring bean meta data to file [" + resource.getFullPath().toString() + "]"); attachMetadata(entry.getKey(), entry.getValue(), monitor, providers); monitor.worked(1); if (BeanMetadataModel.DEBUG) { System.out.println("Attaching meta data [" + resource.getFullPath().toString() + "] took " + (System.currentTimeMillis() - start) + "ms"); } } // Notify that the model has changed. for (IBeansProject project : projects) { ((BeansModel) BeansCorePlugin.getModel()).notifyListeners(project, Type.CHANGED); } } finally { affectedBeans = null; } return Status.OK_STATUS; } /** * Iterates over the provided list of {@link IBeanMetadataProvider}s and attaches {@link IBeanMetadata} and * {@link IBeanProperty}s to the given {@link IBean} instance. */ protected void attachMetadata(IBeansConfig beansConfig, Set<IBean> beans, IProgressMonitor progressMonitor, IBeanMetadataProvider[] providers) { for (IBean bean : beans) { attachMetadataToBean(beansConfig, progressMonitor, providers, bean); } } /** * Attaches {@link IBeanMetadata} and {@link IBeanProperty} to a single {@link IBean}. */ private void attachMetadataToBean(final IBeansConfig beansConfig, final IProgressMonitor progressMonitor, IBeanMetadataProvider[] providers, final IBean bean) { // Reset meta data attachment before adding BeansMetadataPlugin.getMetadataModel().clearBeanMetadata(bean); BeansMetadataPlugin.getMetadataModel().clearBeanProperties(bean); Set<IBeanMetadata> beanMetaData = new LinkedHashSet<IBeanMetadata>(); Set<IMethodMetadata> methodMetaData = new LinkedHashSet<IMethodMetadata>(); final Set<IBeanMetadata> beanMetaDataSet = new LinkedHashSet<IBeanMetadata>(); final Set<IBeanProperty> beanProperties = new LinkedHashSet<IBeanProperty>(); for (final IBeanMetadataProvider provider : providers) { // make sure third-party extensions don't crash the build SafeRunner.run(new ISafeRunnable() { public void handleException(Throwable exception) { // nothing to do here } public void run() throws Exception { beanMetaDataSet.addAll(provider.provideBeanMetadata(bean, beansConfig, progressMonitor)); beanProperties.addAll(provider.provideBeanProperties(bean, beansConfig, progressMonitor)); } }); for (IBeanMetadata metaData : beanMetaDataSet) { if (metaData instanceof IMethodMetadata) { methodMetaData.add((IMethodMetadata) metaData); } else { beanMetaData.add(metaData); } } } if (beanMetaData.size() > 0 || methodMetaData.size() > 0) { BeansMetadataPlugin.getMetadataModel().setBeanMetadata(bean, beanMetaData, methodMetaData); } if (beanProperties.size() > 0) { BeansMetadataPlugin.getMetadataModel().setBeanProperties(bean, beanProperties); } } /** * Returns the {@link IBeanMetadataProvider}s contributed to the Eclipse extension point registry. */ protected IBeanMetadataProvider[] getMetadataProviders() { List<IBeanMetadataProvider> providers = new ArrayList<IBeanMetadataProvider>(); IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(META_DATA_PROVIDERS_EXTENSION_POINT); if (point != null) { for (IExtension extension : point.getExtensions()) { for (IConfigurationElement config : extension.getConfigurationElements()) { if (METADATA_PROVIDER_ELEMENT.equals(config.getName()) && config.getAttribute(CLASS_ATTRIBUTE) != null) { try { Object handler = config.createExecutableExtension(CLASS_ATTRIBUTE); if (handler instanceof IBeanMetadataProvider) { IBeanMetadataProvider entityResolver = (IBeanMetadataProvider) handler; providers.add(entityResolver); } } catch (CoreException e) { BeansMetadataPlugin.getDefault().getLog().log( new Status(IStatus.ERROR, BeansMetadataPlugin.PLUGIN_ID, 1, "Error loading metadata provider", e)); } } } } } return providers.toArray(new IBeanMetadataProvider[providers.size()]); } }