Package org.sonatype.tycho.m2e.felix.internal

Source Code of org.sonatype.tycho.m2e.felix.internal.MavenBundlePluginConfigurator

/*******************************************************************************
* Copyright (c) 2008 Sonatype, Inc.
* 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
*******************************************************************************/
package org.sonatype.tycho.m2e.felix.internal;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.m2e.core.MavenPlugin;
import org.eclipse.m2e.core.embedder.IMaven;
import org.eclipse.m2e.core.lifecyclemapping.model.IPluginExecutionMetadata;
import org.eclipse.m2e.core.project.IMavenProjectFacade;
import org.eclipse.m2e.core.project.MavenProjectChangedEvent;
import org.eclipse.m2e.core.project.configurator.AbstractBuildParticipant;
import org.eclipse.m2e.core.project.configurator.AbstractProjectConfigurator;
import org.eclipse.m2e.core.project.configurator.ILifecycleMappingConfiguration;
import org.eclipse.m2e.core.project.configurator.MojoExecutionKey;
import org.eclipse.m2e.core.project.configurator.ProjectConfigurationRequest;
import org.eclipse.osgi.util.ManifestElement;
import org.osgi.framework.BundleException;
import org.sonatype.plexus.build.incremental.BuildContext;

/**
* Straightforward maven-bundle-plugin configurator.
* <p>
* For projects that use {@code manifest} goal, executes the goal and refreshes generated/regenerated resources in
* workspace.
* <p>
* For projects that use {@code bundle} goal, executes {@code manifest} goal instead. The idea is to only generate
* bundle manifest inside Eclipse workspace, never generate packaged bundle jar.
*/
public class MavenBundlePluginConfigurator
    extends AbstractProjectConfigurator
{
    private static final IMaven maven = MavenPlugin.getMaven();

    private static final QualifiedName PROP_FORCE_GENERATE =
        new QualifiedName( MavenBundlePluginConfigurator.class.getName(), "forceGenerate" );

    private static final ArtifactVersion VERSION_2_3_6 = new DefaultArtifactVersion( "2.3.6" );

    public static final String PARAM_MANIFESTLOCATION = "manifestLocation";

    @Override
    public void configure( ProjectConfigurationRequest request, IProgressMonitor monitor )
        throws CoreException
    {
    }

    @Override
    public AbstractBuildParticipant getBuildParticipant( IMavenProjectFacade projectFacade,
                                                         final MojoExecution execution,
                                                         IPluginExecutionMetadata executionMetadata )
    {
        return new AbstractBuildParticipant()
        {
            @Override
            public Set<IProject> build( int kind, IProgressMonitor monitor )
                throws Exception
            {
                BuildContext buildContext = getBuildContext();
                IMavenProjectFacade facade = getMavenProjectFacade();
                IProject project = facade.getProject();
                MavenProject mavenProject = facade.getMavenProject( monitor );

                @SuppressWarnings( "unchecked" )
                Map<String, String> instructions =
                    maven.getMojoParameterValue( mavenProject, execution, "instructions", Map.class, monitor );

                MojoExecution _execution = amendMojoExecution( mavenProject, execution, instructions );

                IFile manifest = getManifestFile( facade, _execution, monitor );

                // regenerate bundle manifest if any of the following is true
                // - full workspace build
                // - PROP_FORCE_GENERATE project session property is set (see the comment below)
                // - any of included bnd files changed

                boolean generate = IncrementalProjectBuilder.FULL_BUILD == kind;

                // the property is set by OsgiBundleProjectConfigurator.mavenProjectChanged is a workaround for
                // m2e design limitation, which does not allow project configurators trigger resource deltas
                // visible to build participants. See comment in OsgiBundleProjectConfigurator.mavenProjectChanged
                generate =
                    generate || Boolean.parseBoolean( (String) project.getSessionProperty( PROP_FORCE_GENERATE ) );
                // reset FORCE flag so we don't regenerate forever
                project.setSessionProperty( PROP_FORCE_GENERATE, null );

                generate = generate || isIncludeBndFileChange( buildContext, instructions );

                if ( !generate )
                {
                    return null;
                }

                maven.execute( mavenProject, _execution, monitor );

                manifest.refreshLocal( IResource.DEPTH_INFINITE, monitor ); // refresh parent?

                if ( isDeclerativeServices( mavenProject.getBasedir(), instructions ) )
                {
                    IFolder outputFolder = getOutputFolder( monitor, facade, _execution );
                    outputFolder.getFolder( "OSGI-OPT" ).refreshLocal( IResource.DEPTH_INFINITE, monitor );
                    outputFolder.getFolder( "OSGI-INF" ).refreshLocal( IResource.DEPTH_INFINITE, monitor );
                }

                return null;
            }

            protected IFolder getOutputFolder( IProgressMonitor monitor, IMavenProjectFacade facade,
                                               MojoExecution _execution )
                throws CoreException
            {
                File outputDirectory =
                    getParameterValue( facade.getMavenProject(), "outputDirectory", File.class, _execution, monitor );
                IPath outputPath = facade.getProjectRelativePath( outputDirectory.getAbsolutePath() );
                IFolder outputFolder = facade.getProject().getFolder( outputPath );
                return outputFolder;
            }

            private boolean isIncludeBndFileChange( BuildContext buildContext, Map<String, String> instructions )
                throws CoreException
            {
                for ( String path : getIncludeBndFilePaths( instructions ) )
                {
                    // this does not detect changes in outside ${project.basedir}

                    if ( buildContext.hasDelta( path ) )
                    {
                        return true;
                    }
                }

                return false;
            }

            @Override
            public void clean( IProgressMonitor monitor )
                throws CoreException
            {
                IMavenProjectFacade facade = getMavenProjectFacade();
                MavenProject mavenProject = facade.getMavenProject( monitor );

                @SuppressWarnings( "unchecked" )
                Map<String, String> instructions =
                    maven.getMojoParameterValue( mavenProject, execution, "instructions", Map.class, monitor );

                if ( isDeclerativeServices( mavenProject.getBasedir(), instructions ) )
                {
                    IFolder outputFolder = getOutputFolder( monitor, facade, execution );
                    outputFolder.getFolder( "OSGI-OPT" ).delete( true, monitor );
                    outputFolder.getFolder( "OSGI-INF" ).delete( true, monitor );
                }
            }
        };
    }

    protected static MojoExecution amendMojoExecution( MavenProject mavenProject, MojoExecution execution,
                                                       Map<String, String> instructions )
    {
        if ( "bundle".equals( execution.getGoal() ) )
        {
            // do not generate complete bundle. this is both slow and can produce unexpected workspace changes
            // that will trigger unexpected/endless workspace build.
            // we rely on the fact that ManifestPlugin mojo extends BundlePlugin and does not introduce any
            // additional required parameters, so can run manifest goal in place of bundle goal.
            MojoDescriptor descriptor = execution.getMojoDescriptor().clone();
            descriptor.setGoal( "manifest" );
            descriptor.setImplementation( "org.apache.felix.bundleplugin.ManifestPlugin" );
            MojoExecution _execution =
                new MojoExecution( execution.getPlugin(), "manifest", "m2e-tycho:" + execution.getExecutionId()
                    + ":manifest" );
            _execution.setConfiguration( execution.getConfiguration() );
            _execution.setMojoDescriptor( descriptor );
            _execution.setLifecyclePhase( execution.getLifecyclePhase() );
            execution = _execution;
        }

        Xpp3Dom configuration = new Xpp3Dom( execution.getConfiguration() );
        if ( VERSION_2_3_6.compareTo( new DefaultArtifactVersion( execution.getVersion() ) ) <= 0 )
        {
            setBoolean( configuration, "rebuildBundle", true );
        }

        if ( isDeclerativeServices( mavenProject.getBasedir(), instructions ) )
        {
            setBoolean( configuration, "unpackBundle", true );
        }

        execution.setConfiguration( configuration );

        return execution;
    }

    protected static boolean isDeclerativeServices( Map<String, String> instructions )
    {
        return instructions.containsKey( "Service-Component" ) || instructions.containsKey( "_dsannotations" );
    }

    protected static boolean isDeclerativeServices( File basedir, Map<String, String> instructions )
    {
        if ( isDeclerativeServices( instructions ) )
        {
            return true;
        }

        // Properties class can be used to read bnd files http://www.aqute.biz/Bnd/Format
        for ( String path : getIncludeBndFilePaths( instructions ) )
        {
            if ( isDeclerativeServices( loadBndFile( new File( basedir, path ) ) ) )
            {
                return true;
            }
        }

        return false;
    }

    private static Map<String, String> loadBndFile( File file )
    {
        Properties properties = new Properties();
        try (InputStream is = new BufferedInputStream( new FileInputStream( file ) ))
        {
            properties.load( is );
        }
        catch ( IOException e )
        {
            // TODO create error marker
            return Collections.emptyMap();
        }
        Map<String, String> map = new LinkedHashMap<>();
        for ( String key : properties.stringPropertyNames() )
        {
            map.put( key, properties.getProperty( key ) );
        }
        return map;
    }

    static List<String> getIncludeBndFilePaths( Map<String, String> instructions )
    {
        if ( instructions == null )
        {
            return Collections.emptyList();
        }

        String include = instructions.get( "_include" );
        if ( include == null )
        {
            return Collections.emptyList();
        }

        ManifestElement[] elements;
        try
        {
            elements = ManifestElement.parseHeader( "_include", include );
        }
        catch ( BundleException e )
        {
            // assume no included bnd files
            return Collections.emptyList();
        }

        List<String> includes = new ArrayList<String>();
        for ( ManifestElement element : elements )
        {
            String path = element.getValueComponents()[0];
            if ( path.startsWith( "-" ) || path.startsWith( "~" ) )
            {
                path = path.substring( 1 );
            }

            includes.add( path );
        }

        return includes;
    }

    private static void setBoolean( Xpp3Dom configuration, String name, boolean value )
    {
        Xpp3Dom parameter = configuration.getChild( name );
        if ( parameter == null )
        {
            parameter = new Xpp3Dom( name );
            configuration.addChild( parameter );
        }
        parameter.setValue( Boolean.toString( value ) );
    }

    @Override
    public void mavenProjectChanged( MavenProjectChangedEvent event, IProgressMonitor monitor )
        throws CoreException
    {
        if ( MavenProjectChangedEvent.KIND_CHANGED == event.getKind()
            && MavenProjectChangedEvent.FLAG_DEPENDENCIES == event.getFlags() )
        {
            forceManifestRegeneration( event.getMavenProject().getProject(), monitor );
        }
    }

    protected IFile getManifestFile( IMavenProjectFacade facade, MojoExecution execution, IProgressMonitor monitor )
        throws CoreException
    {
        File manifestFile =
            getParameterValue( facade.getMavenProject(), PARAM_MANIFESTLOCATION, File.class, execution, monitor );
        IPath projectPath = facade.getProjectRelativePath( manifestFile.getAbsolutePath() ).append( "MANIFEST.MF" );
        return facade.getProject().getFile( projectPath );
    }

    @Override
    public boolean hasConfigurationChanged( IMavenProjectFacade newFacade,
                                            ILifecycleMappingConfiguration oldProjectConfiguration,
                                            MojoExecutionKey key, IProgressMonitor monitor )
    {
        if ( super.hasConfigurationChanged( newFacade, oldProjectConfiguration, key, monitor ) )
        {
            try
            {
                if ( !equalsManifestLocation( newFacade.getMojoExecution( key, monitor ),
                                              oldProjectConfiguration.getMojoExecutionConfiguration( key ) ) )
                {
                    return true;
                }

                forceManifestRegeneration( newFacade.getProject(), monitor );
            }
            catch ( CoreException e )
            {
                return true;
            }
        }

        return false;
    }

    private boolean equalsManifestLocation( MojoExecution mojoExecution, Xpp3Dom oldConfiguration )
    {
        // for now, just compare xml configuration, but ideally, getMetainfPath should be used to determine new manifest
        // location and the result should be compared to the currently configured PDE manifest location.

        if ( mojoExecution == null )
        {
            return true;
        }

        Xpp3Dom configuration = mojoExecution.getConfiguration();

        Xpp3Dom metainf = getManifestLocation( configuration );
        Xpp3Dom oldMetainf = getManifestLocation( oldConfiguration );

        return metainf != null ? metainf.equals( oldMetainf ) : oldMetainf == null;
    }

    protected Xpp3Dom getManifestLocation( Xpp3Dom configuration )
    {
        return configuration != null ? configuration.getChild( PARAM_MANIFESTLOCATION ) : null;
    }

    protected void forceManifestRegeneration( IProject project, IProgressMonitor monitor )
        throws CoreException
    {
        // this is a less pretty way to force bundle manifest regeneration.
        // the property is checked and reset by the build participant
        project.setSessionProperty( PROP_FORCE_GENERATE, "true" );
    }

}
TOP

Related Classes of org.sonatype.tycho.m2e.felix.internal.MavenBundlePluginConfigurator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.