/* * Copyright 2012 Red Hat, Inc. and/or its affiliates. * * 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 org.guvnor.common.services.builder; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.event.Event; import javax.enterprise.event.Observes; import javax.inject.Inject; import org.guvnor.common.services.project.builder.model.BuildResults; import org.guvnor.common.services.project.builder.model.IncrementalBuildResults; import org.guvnor.common.services.project.builder.service.BuildService; import org.guvnor.common.services.project.model.Package; import org.guvnor.common.services.project.model.Project; import org.guvnor.common.services.project.service.ProjectService; import org.guvnor.common.services.shared.config.AppConfigService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.uberfire.backend.vfs.Path; import org.uberfire.commons.services.cdi.ApplicationStarted; import org.uberfire.workbench.events.ResourceChange; /** * Listener for changes to project resources to handle incremental builds */ @ApplicationScoped public class ResourceChangeIncrementalBuilder { private static final String INCREMENTAL_BUILD_PROPERTY_NAME = "build.enable-incremental"; protected static final Logger logger = LoggerFactory.getLogger( ResourceChangeIncrementalBuilder.class ); @Inject protected ProjectService<? extends Project> projectService; @Inject private AppConfigService appConfigService; @Inject private IncrementalBuilderExecutorManagerFactory executorManagerProducer; private IncrementalBuilderExecutorManager executorManager = null; protected boolean isIncrementalEnabled = false; @PostConstruct private void setup() { isIncrementalEnabled = isIncrementalBuildEnabled(); } public void configureOnEvent( @Observes ApplicationStarted applicationStartedEvent ) { isIncrementalEnabled = isIncrementalBuildEnabled(); getExecutor(); } private boolean isIncrementalBuildEnabled() { final String value = appConfigService.loadPreferences().get( INCREMENTAL_BUILD_PROPERTY_NAME ); return Boolean.parseBoolean( value ); } @PreDestroy private void destroyExecutorService() { if ( executorManager != null && !isEjb( executorManager, IncrementalBuilderExecutorManager.class ) ) { executorManager.shutdown(); } } private boolean isEjb( Object o, Class<?> expected ) { if ( o.getClass() != expected ) { return true; } return false; } private synchronized IncrementalBuilderExecutorManager getExecutor() { if ( executorManager == null ) { executorManager = executorManagerProducer.getExecutorManager(); } return executorManager; } public void addResource( final Path resource ) { //Do nothing if incremental builds are disabled if ( !isIncrementalEnabled ) { return; } logger.info( "Incremental build request received for: " + resource.toURI() + " (added)." ); //If resource is not within a Package it cannot be used for an incremental build final Package pkg = projectService.resolvePackage( resource ); if ( pkg == null ) { return; } //Schedule an incremental build getExecutor().execute( new AsyncIncrementalBuilder() { @Override public void execute( final ProjectService projectService, final BuildService buildService, final Event<IncrementalBuildResults> incrementalBuildResultsEvent, final Event<BuildResults> buildResultsEvent ) { try { logger.info( "Incremental build request being processed: " + resource.toURI() + " (added)." ); final Project project = projectService.resolveProject( resource ); //Fall back to a Full Build in lieu of an Incremental Build if the Project has not been previously built if ( buildService.isBuilt( project ) ) { final IncrementalBuildResults results = buildService.addPackageResource( resource ); incrementalBuildResultsEvent.fire( results ); } else { final BuildResults results = buildService.build( project ); buildResultsEvent.fire( results ); } } catch ( Exception e ) { logger.error( e.getMessage(), e ); } } @Override public String getDescription() { return "Incremental Build [" + resource.toURI() + " (added)]"; } } ); } public void deleteResource( final Path resource ) { //Do nothing if incremental builds are disabled if ( !isIncrementalEnabled ) { return; } logger.info( "Incremental build request received for: " + resource.toURI() + " (deleted)." ); //If resource is not within a Package it cannot be used for an incremental build final Package pkg = projectService.resolvePackage( resource ); if ( pkg == null ) { return; } //Schedule an incremental build getExecutor().execute( new AsyncIncrementalBuilder() { @Override public void execute( final ProjectService projectService, final BuildService buildService, final Event<IncrementalBuildResults> incrementalBuildResultsEvent, final Event<BuildResults> buildResultsEvent ) { try { logger.info( "Incremental build request being processed: " + resource.toURI() + " (deleted)." ); final Project project = projectService.resolveProject( resource ); //Fall back to a Full Build in lieu of an Incremental Build if the Project has not been previously built if ( buildService.isBuilt( project ) ) { final IncrementalBuildResults results = buildService.deletePackageResource( resource ); incrementalBuildResultsEvent.fire( results ); } else { final BuildResults results = buildService.build( project ); buildResultsEvent.fire( results ); } } catch ( Exception e ) { logger.error( e.getMessage(), e ); } } @Override public String getDescription() { return "Incremental Build [" + resource.toURI() + " (deleted)]"; } } ); } public void updateResource( final Path resource ) { //Do nothing if incremental builds are disabled if ( !isIncrementalEnabled ) { return; } logger.info( "Incremental build request received for: " + resource.toURI() + " (updated)." ); //The pom.xml cannot be processed incrementally if ( isProjectResourceUpdateNeeded( resource ) ) { scheduleProjectResourceUpdate( resource ); } else { //If resource is not within a Package it cannot be used for an incremental build final Package pkg = projectService.resolvePackage( resource ); if ( pkg == null ) { return; } schedulePackageResourceUpdate( resource ); } } protected boolean isProjectResourceUpdateNeeded( Path resource ) { return projectService.isPom( resource ); } //Schedule a re-build of a Project (changes to pom.xml or kmodule.xml require a full build) protected void scheduleProjectResourceUpdate( final Path resource ) { final Project project = projectService.resolveProject( resource ); getExecutor().execute( new AsyncIncrementalBuilder() { @Override public void execute( final ProjectService projectService, final BuildService buildService, final Event<IncrementalBuildResults> incrementalBuildResultsEvent, final Event<BuildResults> buildResultsEvent ) { try { logger.info( "Incremental build request being processed: " + project.getRootPath() + " (updated)." ); final BuildResults results = buildService.build( project ); buildResultsEvent.fire( results ); } catch ( Exception e ) { logger.error( e.getMessage(), e ); } } @Override public String getDescription() { return "Incremental Build [" + resource.toURI() + " (updated)]"; } } ); } //Schedule an incremental build for a package resource protected void schedulePackageResourceUpdate( final Path resource ) { getExecutor().execute( new AsyncIncrementalBuilder() { @Override public void execute( final ProjectService projectService, final BuildService buildService, final Event<IncrementalBuildResults> incrementalBuildResultsEvent, final Event<BuildResults> buildResultsEvent ) { try { logger.info( "Incremental build request being processed: " + resource.toURI() + " (updated)." ); final Project project = projectService.resolveProject( resource ); //Fall back to a Full Build in lieu of an Incremental Build if the Project has not been previously built if ( buildService.isBuilt( project ) ) { final IncrementalBuildResults results = buildService.updatePackageResource( resource ); incrementalBuildResultsEvent.fire( results ); } else { final BuildResults results = buildService.build( project ); buildResultsEvent.fire( results ); } } catch ( Exception e ) { logger.error( e.getMessage(), e ); } } @Override public String getDescription() { return "Incremental Build [" + resource.toURI() + " (updated)]"; } } ); } public void batchResourceChanges( final Map<Path, Collection<ResourceChange>> batch ) { //Do nothing if incremental builds are disabled if ( !isIncrementalEnabled ) { return; } logger.info( "Batch incremental build request received." ); //Block changes together with their respective project as Builder operates at the Project level final Map<Project, Map<Path, Collection<ResourceChange>>> projectBatchChanges = new HashMap<Project, Map<Path, Collection<ResourceChange>>>(); for ( Map.Entry<Path, Collection<ResourceChange>> pathCollectionEntry : batch.entrySet() ) { for ( final ResourceChange change : pathCollectionEntry.getValue() ) { final Path resource = pathCollectionEntry.getKey(); //If resource is not within a Package it cannot be used for an incremental build final Project project = projectService.resolveProject( resource ); final Package pkg = projectService.resolvePackage( resource ); if ( project != null && pkg != null ) { if ( !projectBatchChanges.containsKey( project ) ) { projectBatchChanges.put( project, new HashMap<Path, Collection<ResourceChange>>() ); } final Map<Path, Collection<ResourceChange>> projectChanges = projectBatchChanges.get( project ); if ( !projectChanges.containsKey( pathCollectionEntry.getKey() ) ) { projectChanges.put( pathCollectionEntry.getKey(), new ArrayList<ResourceChange>() ); } projectChanges.get( pathCollectionEntry.getKey() ).add( change ); logger.info( "- Batch content: " + pathCollectionEntry.getKey().toURI() + " (" + change.getType().toString() + ")." ); } } } //Schedule an incremental build for each Project for ( final Map.Entry<Project, Map<Path, Collection<ResourceChange>>> e : projectBatchChanges.entrySet() ) { getExecutor().execute( new AsyncIncrementalBuilder() { @Override public void execute( final ProjectService projectService, final BuildService buildService, final Event<IncrementalBuildResults> incrementalBuildResultsEvent, final Event<BuildResults> buildResultsEvent ) { try { logger.info( "Batch incremental build request being processed." ); final Project project = e.getKey(); final Map<Path, Collection<ResourceChange>> changes = e.getValue(); //Fall back to a Full Build in lieu of an Incremental Build if the Project has not been previously built if ( buildService.isBuilt( project ) ) { final IncrementalBuildResults results = buildService.applyBatchResourceChanges( project, changes ); incrementalBuildResultsEvent.fire( results ); } else { final BuildResults results = buildService.build( project ); buildResultsEvent.fire( results ); } } catch ( Exception e ) { logger.error( e.getMessage(), e ); } } @Override public String getDescription() { return "Batch incremental build [" + e.getKey().getProjectName() + "]"; } } ); } } }