/****************************************************************************** * Copyright (c) 2010-2013, Linagora * * 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: * Linagora - initial API and implementation *******************************************************************************/ package com.ebmwebsourcing.petals.services.sa.nature; import java.util.Arrays; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import com.ebmwebsourcing.petals.common.internal.provisional.utils.PetalsConstants; import com.ebmwebsourcing.petals.services.PetalsServicesPlugin; import com.ebmwebsourcing.petals.services.utils.ServiceProjectRelationUtils; /** * A resource listener in charge of monitoring the dependencies of SA projects. * <p> * When one of its dependencies changes, the SA project is built. * The dependencies are always SU projects. If the JBI descriptor or * the POM are modified, the JBI descriptor of the SA will be validated. * </p> * * @author Vincent Zurczak - EBM WebSourcing */ public class SaDependencyResourceListener implements IResourceChangeListener { /** * The size of the blocking queue. * <p> * 1000 was chosen arbitrarily.<br /> * In fact, 1000 SA projects in a same workspace would be huge! * </p> */ private static final int SA_PROJECTS_QUEUE_SIZE = 1000; /** * The unique instance of this class. */ private static SaDependencyResourceListener instance; /** * A blocking queue to store the SA projects whose dependencies must be watched. */ private final LinkedBlockingQueue<IProject> saProjects; /** * A basic lock to avoid an infinite loop (build => post build event => build => ...). */ private final AtomicBoolean lock = new AtomicBoolean( false ); /** * Private constructor. */ private SaDependencyResourceListener() { this.saProjects = new LinkedBlockingQueue<IProject>( SA_PROJECTS_QUEUE_SIZE ); } /** * @return the unique instance of this class */ public synchronized static SaDependencyResourceListener getInstance() { if( instance == null ) { instance = new SaDependencyResourceListener(); ResourcesPlugin.getWorkspace().addResourceChangeListener( instance, IResourceChangeEvent.POST_BUILD ); } return instance; } /** * Registers a SA project whose dependencies must be monitored. * @param saProject the SA project * */ public void registerSaProject( IProject saProject ) { this.saProjects.add( saProject ); } /* (non-Javadoc) * @see org.eclipse.core.resources.IResourceChangeListener * #resourceChanged(org.eclipse.core.resources.IResourceChangeEvent) */ @Override public void resourceChanged( IResourceChangeEvent event ) { // Do not propagate builds that come from this builder. // Otherwise, we end up with an infinite loop. if( event.getSource() instanceof IProject && ((IProject) event.getSource()).isAccessible() && ServiceProjectRelationUtils.isSaProject((IProject) event.getSource())) return; // Check every project for( IProject saProject : this.saProjects ) { // Check the existence of the SA project if( ! saProject.exists()) { this.saProjects.remove( saProject ); continue; } // Get the SA dependencies List<IProject> suProjects = null; if( saProject.isAccessible()) { try { suProjects = Arrays.asList( saProject.getReferencedProjects()); } catch( CoreException e ) { PetalsServicesPlugin.log( e, IStatus.ERROR ); } } // Determine whether a build should be done or not if( suProjects != null ) { // Check the modified resources DependencyDeltaVisitor deltaVisitor = new DependencyDeltaVisitor( suProjects ); try { event.getDelta().accept( deltaVisitor ); } catch( CoreException e ) { PetalsServicesPlugin.log( e, IStatus.ERROR ); } // Build? if( deltaVisitor.buildSaProject && ! this.lock.get()) { try { saProject.build( IncrementalProjectBuilder.FULL_BUILD, new NullProgressMonitor()); this.lock.set( true ); } catch( CoreException e ) { PetalsServicesPlugin.log( e, IStatus.ERROR ); } } } } this.lock.set( false ); } /** * The delta visitor used to determine whether a referenced SU project has changed. */ private static class DependencyDeltaVisitor implements IResourceDeltaVisitor { private final List<IProject> suProjects; private boolean buildSaProject = false; /** * @param suProjects */ public DependencyDeltaVisitor( List<IProject> suProjects ) { this.suProjects = suProjects; } /* * (non-Javadoc) * @see org.eclipse.core.resources.IResourceDeltaVisitor * #visit(org.eclipse.core.resources.IResourceDelta) */ @Override public boolean visit( IResourceDelta delta ) throws CoreException { // Get the resource. IResource resource = delta.getResource(); boolean visit = false; if( resource != null ) { if( resource instanceof IWorkspaceRoot ) { visit = true; } else if( resource instanceof IProject && this.suProjects.contains( resource )) { if( delta.getKind() == IResourceDelta.REMOVED || (delta.getFlags() & IResourceDelta.LOCAL_CHANGED) != 0 || (delta.getFlags() & IResourceDelta.OPEN) != 0) this.buildSaProject = true; else visit = true; } else if( resource instanceof IFile ) { if( "pom.xml".equals( resource.getName()) || "jbi.xml".equals( resource.getName())) this.buildSaProject = true; } else { visit = PetalsConstants.LOC_JBI_FILE.startsWith( resource.getProjectRelativePath().toString()); } } return visit; } } }