org.maven.ide.eclipse.wtp.WebProjectConfiguratorDelegate.java Source code

Java tutorial

Introduction

Here is the source code for org.maven.ide.eclipse.wtp.WebProjectConfiguratorDelegate.java

Source

/*******************************************************************************
 * 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.maven.ide.eclipse.wtp;

import static org.maven.ide.eclipse.wtp.WTPProjectsUtil.removeConflictingFacets;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.StringUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jst.j2ee.classpathdep.IClasspathDependencyConstants;
import org.eclipse.jst.j2ee.internal.project.J2EEProjectUtilities;
import org.eclipse.jst.j2ee.project.facet.IJ2EEModuleFacetInstallDataModelProperties;
import org.eclipse.jst.j2ee.web.project.facet.IWebFacetInstallDataModelProperties;
import org.eclipse.jst.j2ee.web.project.facet.WebFacetInstallDataModelProvider;
import org.eclipse.jst.j2ee.web.project.facet.WebFacetUtils;
import org.eclipse.m2e.core.MavenPlugin;
import org.eclipse.m2e.core.embedder.ArtifactKey;
import org.eclipse.m2e.core.project.IMavenProjectFacade;
import org.eclipse.m2e.jdt.IClasspathDescriptor;
import org.eclipse.m2e.jdt.IClasspathEntryDescriptor;
import org.eclipse.wst.common.componentcore.ComponentCore;
import org.eclipse.wst.common.componentcore.ModuleCoreNature;
import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
import org.eclipse.wst.common.componentcore.resources.IVirtualReference;
import org.eclipse.wst.common.componentcore.resources.IVirtualResource;
import org.eclipse.wst.common.frameworks.datamodel.DataModelFactory;
import org.eclipse.wst.common.frameworks.datamodel.IDataModel;
import org.eclipse.wst.common.project.facet.core.IFacetedProject;
import org.eclipse.wst.common.project.facet.core.IFacetedProject.Action;
import org.eclipse.wst.common.project.facet.core.IProjectFacetVersion;
import org.eclipse.wst.common.project.facet.core.ProjectFacetsManager;
import org.maven.ide.eclipse.wtp.filtering.WebResourceFilteringConfiguration;
import org.maven.ide.eclipse.wtp.internal.ExtensionReader;
import org.maven.ide.eclipse.wtp.namemapping.FileNameMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * WebProjectConfiguratorDelegate
 * 
 * @author Igor Fedorenko
 * @author Fred Bricon
 */
@SuppressWarnings("restriction")
class WebProjectConfiguratorDelegate extends AbstractProjectConfiguratorDelegate {

    private static final Logger LOG = LoggerFactory.getLogger(WebProjectConfiguratorDelegate.class);
    /**
     * See http://wiki.eclipse.org/ClasspathEntriesPublishExportSupport
     */
    static final IClasspathAttribute DEPENDENCY_ATTRIBUTE = JavaCore
            .newClasspathAttribute(IClasspathDependencyConstants.CLASSPATH_COMPONENT_DEPENDENCY, "/WEB-INF/lib");

    /**
    * Name of maven property that overrides WTP context root.
    */
    private static final String M2ECLIPSE_WTP_CONTEXT_ROOT = "m2eclipse.wtp.contextRoot";

    protected void configure(IProject project, MavenProject mavenProject, IProgressMonitor monitor)
            throws CoreException {
        IFacetedProject facetedProject = ProjectFacetsManager.create(project, true, monitor);

        // make sure to update the main deployment folder
        WarPluginConfiguration config = new WarPluginConfiguration(mavenProject, project);
        String warSourceDirectory = config.getWarSourceDirectory();
        IFile defaultWebXml = project.getFolder(warSourceDirectory).getFile("WEB-INF/web.xml");
        IFolder libDir = project.getFolder(warSourceDirectory).getFolder("WEB-INF/lib");

        IFolder firstInexistentfolder = null;
        IFolder contentFolder = project.getFolder(warSourceDirectory);
        IFile manifest = contentFolder.getFile("META-INF/MANIFEST.MF");
        boolean manifestAlreadyExists = manifest.exists();
        if (!manifestAlreadyExists) {
            firstInexistentfolder = findFirstInexistentFolder(project, contentFolder, manifest);
        }

        boolean alreadyHasWebXml = defaultWebXml.exists();
        boolean alreadyHasLibDir = libDir.exists();

        Set<Action> actions = new LinkedHashSet<Action>();

        installJavaFacet(actions, project, facetedProject);

        IVirtualComponent component = ComponentCore.createComponent(project, true);

        //MNGECLIPSE-2279 get the context root from the final name of the project, or artifactId by default.
        String contextRoot = getContextRoot(mavenProject, config.getWarName());

        IProjectFacetVersion webFv = config.getWebFacetVersion(project);
        IDataModel webModelCfg = getWebModelConfig(warSourceDirectory, contextRoot);
        if (!facetedProject.hasProjectFacet(WebFacetUtils.WEB_FACET)) {
            removeConflictingFacets(facetedProject, webFv, actions);
            actions.add(new IFacetedProject.Action(IFacetedProject.Action.Type.INSTALL, webFv, webModelCfg));
        } else {
            IProjectFacetVersion projectFacetVersion = facetedProject
                    .getProjectFacetVersion(WebFacetUtils.WEB_FACET);
            if (webFv.getVersionString() != null
                    && !webFv.getVersionString().equals(projectFacetVersion.getVersionString())) {
                actions.add(
                        new IFacetedProject.Action(IFacetedProject.Action.Type.VERSION_CHANGE, webFv, webModelCfg));
            }
        }

        if (!actions.isEmpty()) {
            facetedProject.modify(actions, monitor);
        }

        //MECLIPSEWTP-41 Fix the missing moduleCoreNature
        fixMissingModuleCoreNature(project, monitor);

        // MNGECLIPSE-632 remove test sources/resources from WEB-INF/classes
        removeTestFolderLinks(project, mavenProject, monitor, "/WEB-INF/classes");

        addContainerAttribute(project, DEPENDENCY_ATTRIBUTE, monitor);

        //MNGECLIPSE-2279 change the context root if needed
        if (!contextRoot.equals(J2EEProjectUtilities.getServerContextRoot(project))) {
            J2EEProjectUtilities.setServerContextRoot(project, contextRoot);
        }

        //MNGECLIPSE-2357 support custom location of web.xml
        String customWebXml = config.getCustomWebXml(project);
        //If we have a custom web.xml but WTP created one against our will, we delete it 
        if (customWebXml != null && !alreadyHasWebXml && defaultWebXml.exists()) {
            defaultWebXml.delete(true, monitor);
        }
        //Maven /m2eclipse doesn't need a new lib dir. 
        if (!alreadyHasLibDir && libDir.exists()) {
            libDir.delete(true, monitor);
        }

        linkFileFirst(project, customWebXml, "/WEB-INF/web.xml", monitor);

        component = ComponentCore.createComponent(project, true);
        if (component != null) {

            IPath warPath = new Path("/").append(contentFolder.getProjectRelativePath());
            List<IPath> sourcePaths = new ArrayList<IPath>();
            sourcePaths.add(warPath);
            if (!WTPProjectsUtil.hasLink(project, ROOT_PATH, warPath, monitor)) {
                component.getRootFolder().createLink(warPath, IVirtualResource.NONE, monitor);
            }
            //MECLIPSEWTP-22 support web filtered resources. Filtered resources directory must be declared BEFORE
            //the regular web source directory. First resources discovered take precedence on deployment
            IPath filteredFolder = new Path("/")
                    .append(WebResourceFilteringConfiguration.getTargetFolder(mavenProject, project));

            boolean useBuildDir = MavenWtpPlugin.getDefault().getMavenWtpPreferencesManager()
                    .getPreferences(project).isWebMavenArchiverUsesBuildDirectory();
            boolean useWebresourcefiltering = config.getWebResources() != null
                    && config.getWebResources().length > 0 || config.isFilteringDeploymentDescriptorsEnabled();

            if (useBuildDir || useWebresourcefiltering) {

                if (!useBuildDir && useWebresourcefiltering) {
                    mavenMarkerManager.addMarker(project, MavenWtpConstants.WTP_MARKER_CONFIGURATION_ERROR_ID,
                            Messages.markers_mavenarchiver_output_settings_ignored_warning, -1,
                            IMarker.SEVERITY_WARNING);
                }
                sourcePaths.add(filteredFolder);
                WTPProjectsUtil.insertLinkBefore(project, filteredFolder, warPath, new Path("/"), monitor);
            } else {
                component.getRootFolder().removeLink(filteredFolder, IVirtualResource.NONE, monitor);
            }

            WTPProjectsUtil.setDefaultDeploymentDescriptorFolder(component.getRootFolder(), warPath, monitor);

            WTPProjectsUtil.deleteLinks(project, ROOT_PATH, sourcePaths, monitor);
        }

        if (!manifestAlreadyExists && manifest.exists()) {
            manifest.delete(true, monitor);
        }
        if (firstInexistentfolder != null && firstInexistentfolder.exists()
                && firstInexistentfolder.members().length == 0) {
            firstInexistentfolder.delete(true, monitor);
        }

        WTPProjectsUtil.removeWTPClasspathContainer(project);

        //MECLIPSEWTP-214 : add (in|ex)clusion patterns as .component metadata
        addComponentExclusionPatterns(component, config);
    }

    private IDataModel getWebModelConfig(String warSourceDirectory, String contextRoot) {
        IDataModel webModelCfg = DataModelFactory.createDataModel(new WebFacetInstallDataModelProvider());
        webModelCfg.setProperty(IJ2EEModuleFacetInstallDataModelProperties.CONFIG_FOLDER, warSourceDirectory);
        webModelCfg.setProperty(IWebFacetInstallDataModelProperties.CONTEXT_ROOT, contextRoot);
        webModelCfg.setProperty(IJ2EEModuleFacetInstallDataModelProperties.GENERATE_DD, false);
        return webModelCfg;
    }

    public void setModuleDependencies(IProject project, MavenProject mavenProject, IProgressMonitor monitor)
            throws CoreException {
        IVirtualComponent component = ComponentCore.createComponent(project);
        //if the attempt to create dependencies happens before the project is actually created, abort. 
        //this will be created again when the project exists.
        if (component == null) {
            return;
        }
        //MECLIPSEWTP-41 Fix the missing moduleCoreNature
        fixMissingModuleCoreNature(project, monitor);

        DebugUtilities.debug("==============Processing " + project.getName() + " dependencies ===============");
        WarPluginConfiguration config = new WarPluginConfiguration(mavenProject, project);
        IPackagingConfiguration opts = new PackagingConfiguration(config.getPackagingIncludes(),
                config.getPackagingExcludes());
        FileNameMapping fileNameMapping = config.getFileNameMapping();

        List<AbstractDependencyConfigurator> depConfigurators = ExtensionReader
                .readDependencyConfiguratorExtensions(projectManager,
                        MavenPlugin.getDefault().getMavenRuntimeManager(), mavenMarkerManager);

        Set<IVirtualReference> references = new LinkedHashSet<IVirtualReference>();
        List<IMavenProjectFacade> exportedDependencies = getWorkspaceDependencies(project, mavenProject);
        for (IMavenProjectFacade dependency : exportedDependencies) {
            String depPackaging = dependency.getPackaging();
            if ("pom".equals(depPackaging) //MNGECLIPSE-744 pom dependencies shouldn't be deployed
                    || "war".equals(depPackaging) //Overlays are dealt with the overlay configurator
                    || "zip".equals(depPackaging)) {
                continue;
            }

            try {
                preConfigureDependencyProject(dependency, monitor);

                if (!ModuleCoreNature.isFlexibleProject(dependency.getProject())) {
                    //Projects unsupported by WTP (ex. adobe flex projects) should not be added as references
                    continue;
                }
                MavenProject depMavenProject = dependency.getMavenProject(monitor);

                IVirtualComponent depComponent = ComponentCore.createComponent(dependency.getProject());

                ArtifactKey artifactKey = ArtifactHelper.toArtifactKey(depMavenProject.getArtifact());
                //Get artifact using the proper classifier
                Artifact artifact = ArtifactHelper.getArtifact(mavenProject.getArtifacts(), artifactKey);
                if (artifact == null) {
                    //could not map key to artifact
                    artifact = depMavenProject.getArtifact();
                }
                ArtifactHelper.fixArtifactHandler(artifact.getArtifactHandler());
                String deployedName = fileNameMapping.mapFileName(artifact);

                boolean isDeployed = !artifact.isOptional() && opts.isPackaged("WEB-INF/lib/" + deployedName);

                //an artifact in mavenProject.getArtifacts() doesn't have the "optional" value as depMavenProject.getArtifact();  
                if (isDeployed) {
                    IVirtualReference reference = ComponentCore.createReference(component, depComponent);
                    IPath path = new Path("/WEB-INF/lib");
                    reference.setArchiveName(deployedName);
                    reference.setRuntimePath(path);
                    references.add(reference);
                }
            } catch (RuntimeException ex) {
                //Should probably be NPEs at this point
                String dump = DebugUtilities.dumpProjectState("An error occured while configuring a dependency of  "
                        + project.getName() + DebugUtilities.SEP, dependency.getProject());
                LOG.error(dump);
                throw ex;
            }
        }

        IVirtualReference[] oldRefs = WTPProjectsUtil.extractHardReferences(component, false);

        IVirtualReference[] newRefs = references.toArray(new IVirtualReference[references.size()]);

        if (WTPProjectsUtil.hasChanged(oldRefs, newRefs)) {
            //Only write in the .component file if necessary 
            IVirtualReference[] overlayRefs = WTPProjectsUtil.extractHardReferences(component, true);
            IVirtualReference[] allRefs = new IVirtualReference[overlayRefs.length + newRefs.length];
            System.arraycopy(newRefs, 0, allRefs, 0, newRefs.length);
            System.arraycopy(overlayRefs, 0, allRefs, newRefs.length, overlayRefs.length);
            component.setReferences(allRefs);
        }

        //TODO why a 2nd loop???
        for (IMavenProjectFacade dependency : exportedDependencies) {
            MavenProject depMavenProject = dependency.getMavenProject(monitor);
            Iterator<AbstractDependencyConfigurator> configurators = depConfigurators.iterator();
            while (configurators.hasNext()) {
                try {
                    configurators.next().configureDependency(mavenProject, project, depMavenProject,
                            dependency.getProject(), monitor);
                } catch (MarkedException ex) {
                    //XXX handle this
                }
            }
        }

    }

    /**
     * Get the context root from a maven web project
     * @param mavenProject
     * @param warName 
     * @return the final name of the project if it exists, or the project's artifactId.
     */
    protected String getContextRoot(MavenProject mavenProject, String warName) {
        String contextRoot;
        //MECLIPSEWTP-43 : Override with maven property
        String property = mavenProject.getProperties().getProperty(M2ECLIPSE_WTP_CONTEXT_ROOT);
        if (StringUtils.isBlank(property)) {
            String finalName = warName;
            if (StringUtils.isBlank(finalName)
                    || finalName.equals(mavenProject.getArtifactId() + "-" + mavenProject.getVersion())) {
                contextRoot = mavenProject.getArtifactId();
            } else {
                contextRoot = finalName;
            }
        } else {
            contextRoot = property;
        }

        return contextRoot.trim().replace(" ", "_");
    }

    public void configureClasspath(IProject project, MavenProject mavenProject, IClasspathDescriptor classpath,
            IProgressMonitor monitor) throws CoreException {

        //Improve skinny war support by generating the manifest classpath
        //similar to mvn eclipse:eclipse 
        //http://maven.apache.org/plugins/maven-war-plugin/examples/skinny-wars.html
        WarPluginConfiguration config = new WarPluginConfiguration(mavenProject, project);
        IPackagingConfiguration opts = new PackagingConfiguration(config.getPackagingIncludes(),
                config.getPackagingExcludes());

        /*
         * Need to take care of three separate cases
         * 
         * 1. remove any project dependencies (they are represented as J2EE module dependencies)
         * 2. add non-dependency attribute for entries originated by artifacts with
         *    runtime, system, test scopes or optional dependencies (not sure about the last one)
         * 3. make sure all dependency JAR files have unique file names, i.e. artifactId/version collisions
         */

        Set<String> dups = new LinkedHashSet<String>();
        Set<String> names = new HashSet<String>();
        FileNameMapping fileNameMapping = config.getFileNameMapping();
        String targetDir = mavenProject.getBuild().getDirectory();

        // first pass removes projects, adds non-dependency attribute and collects colliding filenames
        Iterator<IClasspathEntryDescriptor> iter = classpath.getEntryDescriptors().iterator();
        while (iter.hasNext()) {
            IClasspathEntryDescriptor descriptor = iter.next();
            IClasspathEntry entry = descriptor.toClasspathEntry();
            String scope = descriptor.getScope();
            Artifact artifact = ArtifactHelper.getArtifact(mavenProject.getArtifacts(),
                    descriptor.getArtifactKey());

            ArtifactHelper.fixArtifactHandler(artifact.getArtifactHandler());

            String deployedName = fileNameMapping.mapFileName(artifact);

            boolean isDeployed = (Artifact.SCOPE_COMPILE.equals(scope) || Artifact.SCOPE_RUNTIME.equals(scope))
                    && !descriptor.isOptionalDependency() && opts.isPackaged("WEB-INF/lib/" + deployedName)
                    && !isWorkspaceProject(artifact);

            // add non-dependency attribute if this classpathentry is not meant to be deployed
            // or if it's a workspace project (projects already have a reference created in configure())
            if (!isDeployed) {
                descriptor.setClasspathAttribute(NONDEPENDENCY_ATTRIBUTE.getName(),
                        NONDEPENDENCY_ATTRIBUTE.getValue());
            }

            //If custom fileName is used, then copy the artifact and rename the artifact under the build dir
            String fileName = entry.getPath().lastSegment();
            if (!deployedName.equals(fileName)) {
                IPath newPath = renameArtifact(targetDir, entry.getPath(), deployedName);
                if (newPath != null) {
                    descriptor.setPath(newPath);
                }
            }

            if (!names.add(deployedName)) {
                dups.add(deployedName);
            }
        }

        // second pass disambiguates colliding entry file names
        iter = classpath.getEntryDescriptors().iterator();
        while (iter.hasNext()) {
            IClasspathEntryDescriptor descriptor = iter.next();
            IClasspathEntry entry = descriptor.toClasspathEntry();

            if (dups.contains(entry.getPath().lastSegment())) {
                String newName = descriptor.getGroupId() + "-" + entry.getPath().lastSegment();
                IPath newPath = renameArtifact(targetDir, entry.getPath(), newName);
                if (newPath != null) {
                    descriptor.setPath(newPath);
                }
            }
        }
    }

    private IPath renameArtifact(String targetDir, IPath source, String newName) {
        File src = new File(source.toOSString());
        File dst = new File(targetDir, newName);
        try {
            if (src.isFile() && src.canRead()) {
                if (isDifferent(src, dst)) { // uses lastModified
                    FileUtils.copyFile(src, dst);
                    dst.setLastModified(src.lastModified());
                }
                return Path.fromOSString(dst.getCanonicalPath());
            }
        } catch (IOException ex) {
            LOG.error("File copy failed", ex);
        }
        return null;
    }

    private boolean isWorkspaceProject(Artifact artifact) {
        IMavenProjectFacade facade = projectManager.getMavenProject(artifact.getGroupId(), artifact.getArtifactId(),
                artifact.getVersion());

        return facade != null && facade.getFullPath(artifact.getFile()) != null;

    }

    private static boolean isDifferent(File src, File dst) {
        if (!dst.exists()) {
            return true;
        }

        return src.length() != dst.length() || src.lastModified() != dst.lastModified();
    }

}