All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.maven.doxia.tools.DefaultSiteTool Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
package org.apache.maven.doxia.tools;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;

import org.apache.commons.io.FilenameUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.doxia.site.decoration.Banner;
import org.apache.maven.doxia.site.decoration.DecorationModel;
import org.apache.maven.doxia.site.decoration.Menu;
import org.apache.maven.doxia.site.decoration.MenuItem;
import org.apache.maven.doxia.site.decoration.Skin;
import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler;
import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader;
import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer;
import org.apache.maven.model.DistributionManagement;
import org.apache.maven.model.Site;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.reporting.MavenReport;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.i18n.I18N;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.interpolation.EnvarBasedValueSource;
import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.interpolation.MapBasedValueSource;
import org.codehaus.plexus.interpolation.ObjectBasedValueSource;
import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource;
import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

/**
 * Default implementation of the site tool.
 *
 * @author Vincent Siveton
 * @version $Id: DefaultSiteTool.java 1763022 2016-10-01 16:31:29Z hboutemy $
 */
@Component( role = SiteTool.class )
public class DefaultSiteTool
    extends AbstractLogEnabled
    implements SiteTool
{
    // ----------------------------------------------------------------------
    // Components
    // ----------------------------------------------------------------------

    /**
     * The component that is used to resolve additional artifacts required.
     */
    @Requirement
    private ArtifactResolver artifactResolver;

    /**
     * The component used for creating artifact instances.
     */
    @Requirement
    private ArtifactFactory artifactFactory;

    /**
     * Internationalization.
     */
    @Requirement
    protected I18N i18n;

    /**
     * The component for assembling inheritance.
     */
    @Requirement
    protected DecorationModelInheritanceAssembler assembler;

    /**
     * Project builder (deprecated in Maven 3: should use ProjectBuilder, which will avoid
     * issues like DOXIASITETOOLS-166)
     */
    @Requirement
    protected MavenProjectBuilder mavenProjectBuilder;

    // ----------------------------------------------------------------------
    // Public methods
    // ----------------------------------------------------------------------

    /** {@inheritDoc} */
    public Artifact getSkinArtifactFromRepository( ArtifactRepository localRepository,
                                                   List remoteArtifactRepositories,
                                                   DecorationModel decoration )
        throws SiteToolException
    {
        checkNotNull( "localRepository", localRepository );
        checkNotNull( "remoteArtifactRepositories", remoteArtifactRepositories );
        checkNotNull( "decoration", decoration );

        Skin skin = decoration.getSkin();

        if ( skin == null )
        {
            skin = Skin.getDefaultSkin();
        }

        String version = skin.getVersion();
        Artifact artifact;
        try
        {
            if ( version == null )
            {
                version = Artifact.RELEASE_VERSION;
            }
            VersionRange versionSpec = VersionRange.createFromVersionSpec( version );
            artifact = artifactFactory.createDependencyArtifact( skin.getGroupId(), skin.getArtifactId(), versionSpec,
                                                                 "jar", null, null );

            artifactResolver.resolve( artifact, remoteArtifactRepositories, localRepository );
        }
        catch ( InvalidVersionSpecificationException e )
        {
            throw new SiteToolException( "InvalidVersionSpecificationException: The skin version '" + version
                + "' is not valid: " + e.getMessage(), e );
        }
        catch ( ArtifactResolutionException e )
        {
            throw new SiteToolException( "ArtifactResolutionException: Unable to find skin", e );
        }
        catch ( ArtifactNotFoundException e )
        {
            throw new SiteToolException( "ArtifactNotFoundException: The skin does not exist: " + e.getMessage(), e );
        }

        return artifact;
    }

    /** {@inheritDoc} */
    public Artifact getDefaultSkinArtifact( ArtifactRepository localRepository,
                                            List remoteArtifactRepositories )
        throws SiteToolException
    {
        return getSkinArtifactFromRepository( localRepository, remoteArtifactRepositories, new DecorationModel() );
    }

    /** {@inheritDoc} */
    public String getRelativePath( String to, String from )
    {
        checkNotNull( "to", to );
        checkNotNull( "from", from );

        URL toUrl = null;
        URL fromUrl = null;

        String toPath = to;
        String fromPath = from;

        try
        {
            toUrl = new URL( to );
        }
        catch ( MalformedURLException e )
        {
            try
            {
                toUrl = new File( getNormalizedPath( to ) ).toURI().toURL();
            }
            catch ( MalformedURLException e1 )
            {
                getLogger().warn( "Unable to load a URL for '" + to + "': " + e.getMessage() );
            }
        }

        try
        {
            fromUrl = new URL( from );
        }
        catch ( MalformedURLException e )
        {
            try
            {
                fromUrl = new File( getNormalizedPath( from ) ).toURI().toURL();
            }
            catch ( MalformedURLException e1 )
            {
                getLogger().warn( "Unable to load a URL for '" + from + "': " + e.getMessage() );
            }
        }

        if ( toUrl != null && fromUrl != null )
        {
            // URLs, determine if they share protocol and domain info

            if ( ( toUrl.getProtocol().equalsIgnoreCase( fromUrl.getProtocol() ) )
                && ( toUrl.getHost().equalsIgnoreCase( fromUrl.getHost() ) )
                && ( toUrl.getPort() == fromUrl.getPort() ) )
            {
                // shared URL domain details, use URI to determine relative path

                toPath = toUrl.getFile();
                fromPath = fromUrl.getFile();
            }
            else
            {
                // don't share basic URL information, no relative available

                return to;
            }
        }
        else if ( ( toUrl != null && fromUrl == null ) || ( toUrl == null && fromUrl != null ) )
        {
            // one is a URL and the other isn't, no relative available.

            return to;
        }

        // either the two locations are not URLs or if they are they
        // share the common protocol and domain info and we are left
        // with their URI information

        String relativePath = getRelativeFilePath( fromPath, toPath );

        if ( relativePath == null )
        {
            relativePath = to;
        }

        if ( getLogger().isDebugEnabled() && !relativePath.toString().equals( to ) )
        {
            getLogger().debug( "Mapped url: " + to + " to relative path: " + relativePath );
        }

        return relativePath;
    }

    private static String getRelativeFilePath( final String oldPath, final String newPath )
    {
        // normalize the path delimiters

        String fromPath = new File( oldPath ).getPath();
        String toPath = new File( newPath ).getPath();

        // strip any leading slashes if its a windows path
        if ( toPath.matches( "^\\[a-zA-Z]:" ) )
        {
            toPath = toPath.substring( 1 );
        }
        if ( fromPath.matches( "^\\[a-zA-Z]:" ) )
        {
            fromPath = fromPath.substring( 1 );
        }

        // lowercase windows drive letters.
        if ( fromPath.startsWith( ":", 1 ) )
        {
            fromPath = Character.toLowerCase( fromPath.charAt( 0 ) ) + fromPath.substring( 1 );
        }
        if ( toPath.startsWith( ":", 1 ) )
        {
            toPath = Character.toLowerCase( toPath.charAt( 0 ) ) + toPath.substring( 1 );
        }

        // check for the presence of windows drives. No relative way of
        // traversing from one to the other.

        if ( ( toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) )
            && ( !toPath.substring( 0, 1 ).equals( fromPath.substring( 0, 1 ) ) ) )
        {
            // they both have drive path element but they don't match, no
            // relative path

            return null;
        }

        if ( ( toPath.startsWith( ":", 1 ) && !fromPath.startsWith( ":", 1 ) )
            || ( !toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) )
        {

            // one has a drive path element and the other doesn't, no relative
            // path.

            return null;

        }

        final String relativePath = buildRelativePath( toPath, fromPath, File.separatorChar );

        return relativePath.toString();
    }

    /** {@inheritDoc} */
    public File getSiteDescriptor( File siteDirectory, Locale locale )
    {
        checkNotNull( "siteDirectory", siteDirectory );
        final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale;

        File siteDescriptor = new File( siteDirectory, "site_" + llocale.getLanguage() + ".xml" );

        if ( !siteDescriptor.isFile() )
        {
            siteDescriptor = new File( siteDirectory, "site.xml" );
        }
        return siteDescriptor;
    }

    /**
     * Get a site descriptor from one of the repositories.
     *
     * @param project the Maven project, not null.
     * @param localRepository the Maven local repository, not null.
     * @param repositories the Maven remote repositories, not null.
     * @param locale the locale wanted for the site descriptor. If not null, searching for
     * site_localeLanguage.xml, otherwise searching for site.xml.
     * @return the site descriptor into the local repository after download of it from repositories or null if not
     * found in repositories.
     * @throws SiteToolException if any
     */
    File getSiteDescriptorFromRepository( MavenProject project, ArtifactRepository localRepository,
                                                 List repositories, Locale locale )
        throws SiteToolException
    {
        checkNotNull( "project", project );
        checkNotNull( "localRepository", localRepository );
        checkNotNull( "repositories", repositories );

        final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale;

        try
        {
            return resolveSiteDescriptor( project, localRepository, repositories, llocale );
        }
        catch ( ArtifactNotFoundException e )
        {
            getLogger().debug( "ArtifactNotFoundException: Unable to locate site descriptor: " + e );
            return null;
        }
        catch ( ArtifactResolutionException e )
        {
            throw new SiteToolException( "ArtifactResolutionException: Unable to locate site descriptor: "
                + e.getMessage(), e );
        }
        catch ( IOException e )
        {
            throw new SiteToolException( "IOException: Unable to locate site descriptor: " + e.getMessage(), e );
        }
    }

    /**
     * Read site descriptor content from Reader, adding support for deprecated ${reports},
     * ${parentProject} and ${modules} tags.
     *
     * @param reader
     * @return the input content interpolated with deprecated tags 
     * @throws IOException
     */
    private String readSiteDescriptor( Reader reader, String projectId )
        throws IOException
    {
        String siteDescriptorContent = IOUtil.toString( reader );

        // This is to support the deprecated ${reports}, ${parentProject} and ${modules} tags.
        Properties props = new Properties();
        props.put( "reports", "" );
        props.put( "modules", "" );
        props.put( "parentProject", "" );

        // warn if interpolation required
        for ( Object prop : props.keySet() )
        {
            if ( siteDescriptorContent.contains( "$" + prop ) )
            {
                getLogger().warn( "Site descriptor for " + projectId + " contains $" + prop
                    + ": should be replaced with " + props.getProperty( (String) prop ) );
            }
            if ( siteDescriptorContent.contains( "${" + prop + "}" ) )
            {
                getLogger().warn( "Site descriptor for " + projectId + " contains ${" + prop
                    + "}: should be replaced with " + props.getProperty( (String) prop ) );
            }
        }

        return StringUtils.interpolate( siteDescriptorContent, props );
    }
    
    /** {@inheritDoc} */
    public DecorationModel getDecorationModel( File siteDirectory, Locale locale, MavenProject project,
                                               List reactorProjects, ArtifactRepository localRepository,
                                               List repositories )
        throws SiteToolException
    {
        checkNotNull( "project", project );
        checkNotNull( "reactorProjects", reactorProjects );
        checkNotNull( "localRepository", localRepository );
        checkNotNull( "repositories", repositories );

        final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale;

        getLogger().debug( "Computing decoration model of " + project.getId() + " for locale " + llocale );

        Map.Entry result =
            getDecorationModel( 0, siteDirectory, llocale, project, reactorProjects, localRepository, repositories );
        DecorationModel decorationModel = result.getKey();
        MavenProject parentProject = result.getValue();

        if ( decorationModel == null )
        {
            getLogger().debug( "Using default site descriptor" );

            String siteDescriptorContent;

            Reader reader = null;
            try
            {
                // Note the default is not a super class - it is used when nothing else is found
                reader = ReaderFactory.newXmlReader( getClass().getResourceAsStream( "/default-site.xml" ) );
                siteDescriptorContent = readSiteDescriptor( reader, "default-site.xml" );
            }
            catch ( IOException e )
            {
                throw new SiteToolException( "Error reading default site descriptor: " + e.getMessage(), e );
            }
            finally
            {
                IOUtil.close( reader );
            }

            decorationModel = readDecorationModel( siteDescriptorContent );
        }

        // DecorationModel back to String to interpolate, then go back to DecorationModel
        String siteDescriptorContent = decorationModelToString( decorationModel );

        // "classical" late interpolation, after full inheritance
        siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, false );

        decorationModel = readDecorationModel( siteDescriptorContent );

        if ( parentProject != null )
        {
            populateParentMenu( decorationModel, llocale, project, parentProject, true );
        }

        try
        {
            populateModulesMenu( decorationModel, llocale, project, reactorProjects, localRepository, true );
        }
        catch ( IOException e )
        {
            throw new SiteToolException( "Error while populating modules menu: " + e.getMessage(), e );
        }

        if ( decorationModel.getBannerLeft() == null )
        {
            // extra default to set
            Banner banner = new Banner();
            banner.setName( project.getName() );
            decorationModel.setBannerLeft( banner );
        }

        return decorationModel;
    }

    /** {@inheritDoc} */
    public String getInterpolatedSiteDescriptorContent( Map props, MavenProject aProject,
                                                        String siteDescriptorContent )
        throws SiteToolException
    {
        checkNotNull( "props", props );

        // "classical" late interpolation
        return getInterpolatedSiteDescriptorContent( aProject, siteDescriptorContent, false );
    }

    private String getInterpolatedSiteDescriptorContent( MavenProject aProject,
                                                        String siteDescriptorContent, boolean isEarly )
        throws SiteToolException
    {
        checkNotNull( "aProject", aProject );
        checkNotNull( "siteDescriptorContent", siteDescriptorContent );

        RegexBasedInterpolator interpolator = new RegexBasedInterpolator();

        if ( isEarly )
        {
            interpolator.addValueSource( new PrefixedObjectValueSource( "this.", aProject ) );
            interpolator.addValueSource( new PrefixedPropertiesValueSource( "this.", aProject.getProperties() ) );
        }
        else
        {
            interpolator.addValueSource( new ObjectBasedValueSource( aProject ) );
            interpolator.addValueSource( new MapBasedValueSource( aProject.getProperties() ) );

            try
            {
                interpolator.addValueSource( new EnvarBasedValueSource() );
            }
            catch ( IOException e )
            {
                // Prefer logging?
                throw new SiteToolException( "IOException: cannot interpolate environment properties: "
                    + e.getMessage(), e );
            }
        }

        try
        {
            // FIXME: this does not escape xml entities, see MSITE-226, PLXCOMP-118
            return interpolator.interpolate( siteDescriptorContent, isEarly ? null : "project" );
        }
        catch ( InterpolationException e )
        {
            throw new SiteToolException( "Cannot interpolate site descriptor: " + e.getMessage(), e );
        }
    }

    /** {@inheritDoc} */
    public MavenProject getParentProject( MavenProject aProject, List reactorProjects,
                                          ArtifactRepository localRepository )
    {
        checkNotNull( "aProject", aProject );
        checkNotNull( "reactorProjects", reactorProjects );
        checkNotNull( "localRepository", localRepository );

        if ( isMaven3OrMore() )
        {
            // no need to make voodoo with Maven 3: job already done
            return aProject.getParent();
        }

        MavenProject parentProject = null;

        MavenProject origParent = aProject.getParent();
        if ( origParent != null )
        {
            for ( MavenProject reactorProject : reactorProjects )
            {
                if ( reactorProject.getGroupId().equals( origParent.getGroupId() )
                    && reactorProject.getArtifactId().equals( origParent.getArtifactId() )
                    && reactorProject.getVersion().equals( origParent.getVersion() ) )
                {
                    parentProject = reactorProject;

                    getLogger().debug( "Parent project " + origParent.getId() + " picked from reactor" );
                    break;
                }
            }

            if ( parentProject == null && aProject.getBasedir() != null
                && StringUtils.isNotEmpty( aProject.getModel().getParent().getRelativePath() ) )
            {
                try
                {
                    String relativePath = aProject.getModel().getParent().getRelativePath();

                    File pomFile = new File( aProject.getBasedir(), relativePath );

                    if ( pomFile.isDirectory() )
                    {
                        pomFile = new File( pomFile, "pom.xml" );
                    }
                    pomFile = new File( getNormalizedPath( pomFile.getPath() ) );

                    if ( pomFile.isFile() )
                    {
                        MavenProject mavenProject = mavenProjectBuilder.build( pomFile, localRepository, null );

                        if ( mavenProject.getGroupId().equals( origParent.getGroupId() )
                            && mavenProject.getArtifactId().equals( origParent.getArtifactId() )
                            && mavenProject.getVersion().equals( origParent.getVersion() ) )
                        {
                            parentProject = mavenProject;

                            getLogger().debug( "Parent project " + origParent.getId() + " loaded from a relative path: "
                                + relativePath );
                        }
                    }
                }
                catch ( ProjectBuildingException e )
                {
                    getLogger().info( "Unable to load parent project " + origParent.getId() + " from a relative path: "
                        + e.getMessage() );
                }
            }

            if ( parentProject == null )
            {
                try
                {
                    parentProject = mavenProjectBuilder.buildFromRepository( aProject.getParentArtifact(), aProject
                        .getRemoteArtifactRepositories(), localRepository );

                    getLogger().debug( "Parent project " + origParent.getId() + " loaded from repository" );
                }
                catch ( ProjectBuildingException e )
                {
                    getLogger().warn( "Unable to load parent project " + origParent.getId() + " from repository: "
                        + e.getMessage() );
                }
            }

            if ( parentProject == null )
            {
                // fallback to original parent, which may contain uninterpolated value (still need a unit test)

                parentProject = origParent;

                getLogger().debug( "Parent project " + origParent.getId() + " picked from original value" );
            }
        }
        return parentProject;
    }

    /**
     * Populate the pre-defined parent menu of the decoration model,
     * if used through <menu ref="parent"/>.
     *
     * @param decorationModel the Doxia Sitetools DecorationModel, not null.
     * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm.
     * @param project a Maven project, not null.
     * @param parentProject a Maven parent project, not null.
     * @param keepInheritedRefs used for inherited references.
     */
    private void populateParentMenu( DecorationModel decorationModel, Locale locale, MavenProject project,
                                    MavenProject parentProject, boolean keepInheritedRefs )
    {
        checkNotNull( "decorationModel", decorationModel );
        checkNotNull( "project", project );
        checkNotNull( "parentProject", parentProject );

        Menu menu = decorationModel.getMenuRef( "parent" );

        if ( menu == null )
        {
            return;
        }

        if ( keepInheritedRefs && menu.isInheritAsRef() )
        {
            return;
        }

        final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale;

        String parentUrl = getDistMgmntSiteUrl( parentProject );

        if ( parentUrl != null )
        {
            if ( parentUrl.endsWith( "/" ) )
            {
                parentUrl += "index.html";
            }
            else
            {
                parentUrl += "/index.html";
            }

            parentUrl = getRelativePath( parentUrl, getDistMgmntSiteUrl( project ) );
        }
        else
        {
            // parent has no url, assume relative path is given by site structure
            File parentBasedir = parentProject.getBasedir();
            // First make sure that the parent is available on the file system
            if ( parentBasedir != null )
            {
                // Try to find the relative path to the parent via the file system
                String parentPath = parentBasedir.getAbsolutePath();
                String projectPath = project.getBasedir().getAbsolutePath();
                parentUrl = getRelativePath( parentPath, projectPath ) + "/index.html";
            }
        }

        // Only add the parent menu if we were able to find a URL for it
        if ( parentUrl == null )
        {
            getLogger().warn( "Unable to find a URL to the parent project. The parent menu will NOT be added." );
        }
        else
        {
            if ( menu.getName() == null )
            {
                menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.parentproject" ) );
            }

            MenuItem item = new MenuItem();
            item.setName( parentProject.getName() );
            item.setHref( parentUrl );
            menu.addItem( item );
        }
    }

    /**
     * Populate the pre-defined modules menu of the decoration model,
     * if used through <menu ref="modules"/>.
     *
     * @param decorationModel the Doxia Sitetools DecorationModel, not null.
     * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm.
     * @param project a Maven project, not null.
     * @param reactorProjects the Maven reactor projects, not null.
     * @param localRepository the Maven local repository, not null.
     * @param keepInheritedRefs used for inherited references.
     * @throws SiteToolException if any
     * @throws IOException 
     */
    private void populateModulesMenu( DecorationModel decorationModel, Locale locale, MavenProject project,
                                     List reactorProjects, ArtifactRepository localRepository,
                                     boolean keepInheritedRefs )
        throws SiteToolException, IOException
    {
        checkNotNull( "project", project );
        checkNotNull( "reactorProjects", reactorProjects );
        checkNotNull( "localRepository", localRepository );
        checkNotNull( "decorationModel", decorationModel );

        Menu menu = decorationModel.getMenuRef( "modules" );

        if ( menu == null )
        {
            return;
        }

        if ( keepInheritedRefs && menu.isInheritAsRef() )
        {
            return;
        }

        final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale ;

        // we require child modules and reactors to process module menu
        if ( project.getModules().size() > 0 )
        {
            if ( menu.getName() == null )
            {
                menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectmodules" ) );
            }

            for ( String module : (List) project.getModules() )
            {
                MavenProject moduleProject = getModuleFromReactor( project, reactorProjects, module );

                if ( moduleProject == null )
                {
                    getLogger().warn( "Module " + module
                        + " not found in reactor: loading locally" );

                    File f = new File( project.getBasedir(), module + "/pom.xml" );
                    if ( f.exists() )
                    {
                        try
                        {
                            moduleProject = mavenProjectBuilder.build( f, localRepository, null );
                        }
                        catch ( ProjectBuildingException e )
                        {
                            throw new SiteToolException( "Unable to read local module-POM", e );
                        }
                    }
                    else
                    {
                        getLogger().warn( "No filesystem module-POM available" );
    
                        moduleProject = new MavenProject();
                        moduleProject.setName( module );
                        moduleProject.setDistributionManagement( new DistributionManagement() );
                        moduleProject.getDistributionManagement().setSite( new Site() );
                        moduleProject.getDistributionManagement().getSite().setUrl( module );
                    }
                }

                String siteUrl = getDistMgmntSiteUrl( moduleProject );
                String itemName =
                    ( moduleProject.getName() == null ) ? moduleProject.getArtifactId() : moduleProject.getName();

                appendMenuItem( project, menu, itemName, siteUrl, moduleProject.getArtifactId() );
            }
        }
        else if ( decorationModel.getMenuRef( "modules" ).getInherit() == null )
        {
            // only remove if project has no modules AND menu is not inherited, see MSHARED-174
            decorationModel.removeMenuRef( "modules" );
        }
    }

    private static MavenProject getModuleFromReactor( MavenProject project, List reactorProjects,
                                                      String module )
        throws IOException
    {
        File moduleBasedir = new File( project.getBasedir(), module ).getCanonicalFile();

        for ( MavenProject reactorProject : reactorProjects )
        {
            if ( moduleBasedir.equals( reactorProject.getBasedir() ) )
            {
                return reactorProject;
            }
        }

        // module not found in reactor
        return null;
    }

    /** {@inheritDoc} */
    public void populateReportsMenu( DecorationModel decorationModel, Locale locale,
                                     Map> categories )
    {
        checkNotNull( "decorationModel", decorationModel );
        checkNotNull( "categories", categories );

        Menu menu = decorationModel.getMenuRef( "reports" );

        if ( menu == null )
        {
            return;
        }

        final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale;

        if ( menu.getName() == null )
        {
            menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectdocumentation" ) );
        }

        boolean found = false;
        if ( menu.getItems().isEmpty() )
        {
            List categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION );
            if ( !isEmptyList( categoryReports ) )
            {
                MenuItem item = createCategoryMenu(
                                                    i18n.getString( "site-tool", llocale,
                                                                    "decorationModel.menu.projectinformation" ),
                                                    "/project-info.html", categoryReports, llocale );
                menu.getItems().add( item );
                found = true;
            }

            categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS );
            if ( !isEmptyList( categoryReports ) )
            {
                MenuItem item =
                    createCategoryMenu( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectreports" ),
                                        "/project-reports.html", categoryReports, llocale );
                menu.getItems().add( item );
                found = true;
            }
        }
        if ( !found )
        {
            decorationModel.removeMenuRef( "reports" );
        }
    }

    /** {@inheritDoc} */
    public List getSiteLocales( String locales )
    {
        if ( locales == null )
        {
            return Collections.singletonList( DEFAULT_LOCALE );
        }

        String[] localesArray = StringUtils.split( locales, "," );
        List localesList = new ArrayList( localesArray.length );

        for ( String localeString : localesArray )
        {
            Locale locale = codeToLocale( localeString );

            if ( locale == null )
            {
                continue;
            }

            if ( !Arrays.asList( Locale.getAvailableLocales() ).contains( locale ) )
            {
                if ( getLogger().isWarnEnabled() )
                {
                    getLogger().warn( "The locale defined by '" + locale
                        + "' is not available in this Java Virtual Machine ("
                        + System.getProperty( "java.version" )
                        + " from " + System.getProperty( "java.vendor" ) + ") - IGNORING" );
                }
                continue;
            }

            // Default bundles are in English
            if ( ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) )
                && ( !i18n.getBundle( "site-tool", locale ).getLocale().getLanguage()
                    .equals( locale.getLanguage() ) ) )
            {
                if ( getLogger().isWarnEnabled() )
                {
                    getLogger().warn( "The locale '" + locale + "' (" + locale.getDisplayName( Locale.ENGLISH )
                        + ") is not currently supported by Maven Site - IGNORING."
                        + "\nContributions are welcome and greatly appreciated!"
                        + "\nIf you want to contribute a new translation, please visit "
                        + "http://maven.apache.org/plugins/localization.html for detailed instructions." );
                }

                continue;
            }

            localesList.add( locale );
        }

        if ( localesList.isEmpty() )
        {
            localesList = Collections.singletonList( DEFAULT_LOCALE );
        }

        return localesList;
    }

    /**
     * Converts a locale code like "en", "en_US" or "en_US_win" to a java.util.Locale
     * object.
     * 

If localeCode = default, return the current value of the default locale for this instance * of the Java Virtual Machine.

* * @param localeCode the locale code string. * @return a java.util.Locale object instanced or null if errors occurred * @see java.util.Locale#getDefault() */ private Locale codeToLocale( String localeCode ) { if ( localeCode == null ) { return null; } if ( "default".equalsIgnoreCase( localeCode ) ) { return Locale.getDefault(); } String language = ""; String country = ""; String variant = ""; StringTokenizer tokenizer = new StringTokenizer( localeCode, "_" ); final int maxTokens = 3; if ( tokenizer.countTokens() > maxTokens ) { if ( getLogger().isWarnEnabled() ) { getLogger().warn( "Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING" ); } return null; } if ( tokenizer.hasMoreTokens() ) { language = tokenizer.nextToken(); if ( tokenizer.hasMoreTokens() ) { country = tokenizer.nextToken(); if ( tokenizer.hasMoreTokens() ) { variant = tokenizer.nextToken(); } } } return new Locale( language, country, variant ); } // ---------------------------------------------------------------------- // Protected methods // ---------------------------------------------------------------------- /** * @param path could be null. * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path. * @see FilenameUtils#normalize(String) */ protected static String getNormalizedPath( String path ) { String normalized = FilenameUtils.normalize( path ); if ( normalized == null ) { normalized = path; } return ( normalized == null ) ? null : normalized.replace( '\\', '/' ); } // ---------------------------------------------------------------------- // Private methods // ---------------------------------------------------------------------- /** * @param project not null * @param localRepository not null * @param repositories not null * @param locale not null * @return the resolved site descriptor * @throws IOException if any * @throws ArtifactResolutionException if any * @throws ArtifactNotFoundException if any */ private File resolveSiteDescriptor( MavenProject project, ArtifactRepository localRepository, List repositories, Locale locale ) throws IOException, ArtifactResolutionException, ArtifactNotFoundException { File result; // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1? Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(), project.getVersion(), "xml", "site_" + locale.getLanguage() ); boolean found = false; try { artifactResolver.resolve( artifact, repositories, localRepository ); result = artifact.getFile(); // we use zero length files to avoid re-resolution (see below) if ( result.length() > 0 ) { found = true; } else { getLogger().debug( "No site descriptor found for " + project.getId() + " for locale " + locale.getLanguage() + ", trying without locale..." ); } } catch ( ArtifactNotFoundException e ) { getLogger().debug( "Unable to locate site descriptor for locale " + locale.getLanguage() + ": " + e ); // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote // repository, because the parent was already released (and snapshots are updated automatically if changed) result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); result.getParentFile().mkdirs(); result.createNewFile(); } if ( !found ) { artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(), project.getVersion(), "xml", "site" ); try { artifactResolver.resolve( artifact, repositories, localRepository ); } catch ( ArtifactNotFoundException e ) { // see above regarding this zero length file result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) ); result.getParentFile().mkdirs(); result.createNewFile(); throw e; } result = artifact.getFile(); // we use zero length files to avoid re-resolution (see below) if ( result.length() == 0 ) { getLogger().debug( "No site descriptor found for " + project.getId() + " without locale." ); result = null; } } return result; } /** * @param depth depth of project * @param siteDirectory, can be null if project.basedir is null, ie POM from repository * @param locale not null * @param project not null * @param reactorProjects not null * @param localRepository not null * @param repositories not null * @param origProps not null * @return the decoration model depending the locale and the parent project * @throws SiteToolException if any */ private Map.Entry getDecorationModel( int depth, File siteDirectory, Locale locale, MavenProject project, List reactorProjects, ArtifactRepository localRepository, List repositories ) throws SiteToolException { // 1. get site descriptor File File siteDescriptor; if ( project.getBasedir() == null ) { // POM is in the repository: look into the repository for site descriptor try { siteDescriptor = getSiteDescriptorFromRepository( project, localRepository, repositories, locale ); } catch ( SiteToolException e ) { throw new SiteToolException( "The site descriptor cannot be resolved from the repository: " + e.getMessage(), e ); } } else { // POM is in build directory: look for site descriptor as local file siteDescriptor = getSiteDescriptor( siteDirectory, locale ); } // 2. read DecorationModel from site descriptor File and do early interpolation (${this.*}) DecorationModel decoration = null; Reader siteDescriptorReader = null; try { if ( siteDescriptor != null && siteDescriptor.exists() ) { getLogger().debug( "Reading" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + " site descriptor from " + siteDescriptor ); siteDescriptorReader = ReaderFactory.newXmlReader( siteDescriptor ); String siteDescriptorContent = readSiteDescriptor( siteDescriptorReader, project.getId() ); // interpolate ${this.*} = early interpolation siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, true ); decoration = readDecorationModel( siteDescriptorContent ); decoration.setLastModified( siteDescriptor.lastModified() ); } else { getLogger().debug( "No" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + " site descriptor." ); } } catch ( IOException e ) { throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from " + siteDescriptor, e ); } finally { IOUtil.close( siteDescriptorReader ); } // 3. look for parent project MavenProject parentProject = getParentProject( project, reactorProjects, localRepository ); // 4. merge with parent project DecorationModel if ( parentProject != null && ( decoration == null || decoration.isMergeParent() ) ) { depth++; getLogger().debug( "Looking for site descriptor of level " + depth + " parent project: " + parentProject.getId() ); File parentSiteDirectory = null; if ( parentProject.getBasedir() != null ) { // extrapolate parent project site directory String siteRelativePath = getRelativeFilePath( project.getBasedir().getAbsolutePath(), siteDescriptor.getParentFile().getAbsolutePath() ); parentSiteDirectory = new File( parentProject.getBasedir(), siteRelativePath ); // notice: using same siteRelativePath for parent as current project; may be wrong if site plugin // has different configuration. But this is a rare case (this only has impact if parent is from reactor) } DecorationModel parentDecoration = getDecorationModel( depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository, repositories ).getKey(); // MSHARED-116 requires an empty decoration model (instead of a null one) // MSHARED-145 requires us to do this only if there is a parent to merge it with if ( decoration == null && parentDecoration != null ) { // we have no site descriptor: merge the parent into an empty one decoration = new DecorationModel(); } String name = project.getName(); if ( decoration != null && StringUtils.isNotEmpty( decoration.getName() ) ) { name = decoration.getName(); } // Merge the parent and child DecorationModels String projectDistMgmnt = getDistMgmntSiteUrl( project ); String parentDistMgmnt = getDistMgmntSiteUrl( parentProject ); if ( getLogger().isDebugEnabled() ) { getLogger().debug( "Site decoration model inheritance: assembling child with level " + depth + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = " + parentDistMgmnt ); } assembler.assembleModelInheritance( name, decoration, parentDecoration, projectDistMgmnt, parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt ); } return new AbstractMap.SimpleEntry( decoration, parentProject ); } /** * @param siteDescriptorContent not null * @return the decoration model object * @throws SiteToolException if any */ private DecorationModel readDecorationModel( String siteDescriptorContent ) throws SiteToolException { try { return new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) ); } catch ( XmlPullParserException e ) { throw new SiteToolException( "Error parsing site descriptor", e ); } catch ( IOException e ) { throw new SiteToolException( "Error reading site descriptor", e ); } } private String decorationModelToString( DecorationModel decoration ) throws SiteToolException { StringWriter writer = new StringWriter(); try { new DecorationXpp3Writer().write( writer, decoration ); return writer.toString(); } catch ( IOException e ) { throw new SiteToolException( "Error reading site descriptor", e ); } finally { IOUtil.close( writer ); } } private static String buildRelativePath( final String toPath, final String fromPath, final char separatorChar ) { // use tokenizer to traverse paths and for lazy checking StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); int count = 0; // walk along the to path looking for divergence from the from path while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() ) { if ( separatorChar == '\\' ) { if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) ) { break; } } else { if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) ) { break; } } count++; } // reinitialize the tokenizers to count positions to retrieve the // gobbled token toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) ); fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) ); while ( count-- > 0 ) { fromTokeniser.nextToken(); toTokeniser.nextToken(); } StringBuilder relativePath = new StringBuilder(); // add back refs for the rest of from location. while ( fromTokeniser.hasMoreTokens() ) { fromTokeniser.nextToken(); relativePath.append( ".." ); if ( fromTokeniser.hasMoreTokens() ) { relativePath.append( separatorChar ); } } if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() ) { relativePath.append( separatorChar ); } // add fwd fills for whatever's left of to. while ( toTokeniser.hasMoreTokens() ) { relativePath.append( toTokeniser.nextToken() ); if ( toTokeniser.hasMoreTokens() ) { relativePath.append( separatorChar ); } } return relativePath.toString(); } /** * @param project not null * @param menu not null * @param name not null * @param href could be null * @param defaultHref not null */ private void appendMenuItem( MavenProject project, Menu menu, String name, String href, String defaultHref ) { String selectedHref = href; if ( selectedHref == null ) { selectedHref = defaultHref; } MenuItem item = new MenuItem(); item.setName( name ); String baseUrl = getDistMgmntSiteUrl( project ); if ( baseUrl != null ) { selectedHref = getRelativePath( selectedHref, baseUrl ); } if ( selectedHref.endsWith( "/" ) ) { item.setHref( selectedHref + "index.html" ); } else { item.setHref( selectedHref + "/index.html" ); } menu.addItem( item ); } /** * @param name not null * @param href not null * @param categoryReports not null * @param locale not null * @return the menu item object */ private MenuItem createCategoryMenu( String name, String href, List categoryReports, Locale locale ) { MenuItem item = new MenuItem(); item.setName( name ); item.setCollapse( true ); item.setHref( href ); // MSHARED-172, allow reports to define their order in some other way? //Collections.sort( categoryReports, new ReportComparator( locale ) ); for ( MavenReport report : categoryReports ) { MenuItem subitem = new MenuItem(); subitem.setName( report.getName( locale ) ); subitem.setHref( report.getOutputName() + ".html" ); item.getItems().add( subitem ); } return item; } // ---------------------------------------------------------------------- // static methods // ---------------------------------------------------------------------- /** * Convenience method. * * @param list could be null * @return true if the list is null or empty */ private static boolean isEmptyList( List list ) { return list == null || list.isEmpty(); } /** * Return distributionManagement.site.url if defined, null otherwise. * * @param project not null * @return could be null */ private static String getDistMgmntSiteUrl( MavenProject project ) { return getDistMgmntSiteUrl( project.getDistributionManagement() ); } private static String getDistMgmntSiteUrl( DistributionManagement distMgmnt ) { if ( distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null ) { return urlEncode( distMgmnt.getSite().getUrl() ); } return null; } private static String urlEncode( final String url ) { if ( url == null ) { return null; } try { return new File( url ).toURI().toURL().toExternalForm(); } catch ( MalformedURLException ex ) { return url; // this will then throw somewhere else } } private void checkNotNull( String name, Object value ) { if ( value == null ) { throw new IllegalArgumentException( "The parameter '" + name + "' cannot be null." ); } } /** * Check the current Maven version to see if it's Maven 3.0 or newer. */ private static boolean isMaven3OrMore() { return new DefaultArtifactVersion( getMavenVersion() ).getMajorVersion() >= 3; } private static String getMavenVersion() { // This relies on the fact that MavenProject is the in core classloader // and that the core classloader is for the maven-core artifact // and that should have a pom.properties file // if this ever changes, we will have to revisit this code. final Properties properties = new Properties(); final String corePomProperties = "META-INF/maven/org.apache.maven/maven-core/pom.properties"; final InputStream in = MavenProject.class.getClassLoader().getResourceAsStream( corePomProperties ); try { properties.load( in ); } catch ( IOException ioe ) { return ""; } finally { IOUtil.close( in ); } return properties.getProperty( "version" ).trim(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy