/*
 * The MIT License
 *
 * Copyright (c) 2016, CloudBees, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package org.jenkinsci.plugins.pipeline.maven.util;

import hudson.FilePath;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.pipeline.maven.MavenArtifact;
import org.jenkinsci.plugins.pipeline.maven.MavenDependency;
import org.jenkinsci.plugins.pipeline.maven.MavenSpyLogProcessor;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

/**
 * @author <a href="mailto:[email protected]">Cyrille Le Clerc</a>
 */
public class XmlUtils {
    private static final Logger LOGGER = Logger.getLogger(XmlUtils.class.getName());

    public static MavenArtifact newMavenArtifact(Element artifactElt) {
        MavenArtifact mavenArtifact = new MavenArtifact();
        loadMavenArtifact(artifactElt, mavenArtifact);

        return mavenArtifact;
    }

    public static MavenDependency newMavenDependency(Element dependencyElt) {
        MavenDependency dependency = new MavenDependency();
        loadMavenArtifact(dependencyElt, dependency);
        dependency.setScope(dependencyElt.getAttribute("scope"));
        dependency.optional = Boolean.valueOf(dependencyElt.getAttribute("optional"));

        return dependency;
    }

    private static void loadMavenArtifact(Element artifactElt, MavenArtifact mavenArtifact) {
        mavenArtifact.setGroupId(artifactElt.getAttribute("groupId"));
        mavenArtifact.setArtifactId(artifactElt.getAttribute("artifactId"));
        mavenArtifact.setVersion(artifactElt.getAttribute("version"));
        mavenArtifact.setBaseVersion(artifactElt.getAttribute("baseVersion"));
        if (mavenArtifact.getBaseVersion() == null || mavenArtifact.getBaseVersion().isEmpty()) {
            mavenArtifact.setBaseVersion(mavenArtifact.getVersion());
        }
        mavenArtifact.setSnapshot(Boolean.valueOf(artifactElt.getAttribute("snapshot")));
        mavenArtifact.setType(artifactElt.getAttribute("type"));
        if (mavenArtifact.getType() == null || mavenArtifact.getType().isEmpty()) {
            // workaround: sometimes we use "XmlUtils.newMavenArtifact()" on "project" elements, in this case, "packaging" is defined but "type" is not defined
            // we should  probably not use "MavenArtifact"
            mavenArtifact.setType(artifactElt.getAttribute("packaging"));
        }
        mavenArtifact.setClassifier(artifactElt.hasAttribute("classifier") ? artifactElt.getAttribute("classifier") : null);
        mavenArtifact.setExtension(artifactElt.getAttribute("extension"));
    }


    /*
  <plugin executionId="default-test" goal="test" groupId="org.apache.maven.plugins" artifactId="maven-surefire-plugin" version="2.19.1">
 */
    public static MavenSpyLogProcessor.PluginInvocation newPluginInvocation(Element pluginInvocationElt) {
        MavenSpyLogProcessor.PluginInvocation pluginInvocation = new MavenSpyLogProcessor.PluginInvocation();
        pluginInvocation.groupId = pluginInvocationElt.getAttribute("groupId");
        pluginInvocation.artifactId = pluginInvocationElt.getAttribute("artifactId");
        pluginInvocation.version = pluginInvocationElt.getAttribute("version");
        pluginInvocation.goal = pluginInvocationElt.getAttribute("goal");
        pluginInvocation.executionId = pluginInvocationElt.getAttribute("executionId");
        return pluginInvocation;
    }

    @Nonnull
    public static Element getUniqueChildElement(@Nonnull Element element, @Nonnull String childElementName) {
        Element child = getUniqueChildElementOrNull(element, childElementName);
        if (child == null) {
            throw new IllegalStateException("No <" + childElementName + "> element found");
        }
        return child;
    }

    @Nullable
    public static Element getUniqueChildElementOrNull(@Nonnull Element element, String... childElementName) {
        Element result = element;
        for (String childEltName : childElementName) {
            List<Element> childElts = getChildrenElements(result, childEltName);
            if (childElts.size() == 0) {
                return null;
            } else if (childElts.size() > 1) {
                throw new IllegalStateException("More than 1 (" + childElts.size() + ") elements <" + childEltName + "> found in " + toString(element));
            }

            result = childElts.get(0);
        }
        return result;
    }

    @Nonnull
    public static List<Element> getChildrenElements(@Nonnull Element element, @Nonnull String childElementName) {
        NodeList childElts = element.getChildNodes();
        List<Element> result = new ArrayList<>();

        for (int i = 0; i < childElts.getLength(); i++) {
            Node node = childElts.item(i);
            if (node instanceof Element && node.getNodeName().equals(childElementName)) {
                result.add((Element) node);
            }
        }

        return result;
    }

    @Nonnull
    public static String toString(@Nullable Node node) {
        try {
            StringWriter out = new StringWriter();
            Transformer identityTransformer = TransformerFactory.newInstance().newTransformer();
            identityTransformer.transform(new DOMSource(node), new StreamResult(out));
            return out.toString();
        } catch (TransformerException e) {
            LOGGER.log(Level.WARNING, "Exception dumping node " + node, e);
            return e.toString();
        }
    }

    @Nonnull
    public static List<Element> getExecutionEvents(@Nonnull Element mavenSpyLogs, String... expectedType) {

        Set<String> expectedTypes = new HashSet<>(Arrays.asList(expectedType));
        List<Element> result = new ArrayList<>();
        for (Element element : getChildrenElements(mavenSpyLogs, "ExecutionEvent")) {
            if (expectedTypes.contains(element.getAttribute("type"))) {
                result.add(element);
            }
        }
        return result;
    }

    /*
    <RepositoryEvent type="ARTIFACT_DEPLOYED" class="org.eclipse.aether.RepositoryEvent" _time="2018-02-11 16:18:26.505">
        <artifact extension="jar" file="/path/to/my-project-workspace/target/my-jar-0.5-SNAPSHOT.jar" baseVersion="0.5-SNAPSHOT" groupId="com.example" classifier="" artifactId="my-jar" id="com.example:my-jar:jar:0.5-20180211.151825-18" version="0.5-20180211.151825-18" snapshot="true"/>
        <repository layout="default" id="nexus.beescloud.com" url="https://nexus.beescloud.com/content/repositories/snapshots/"/>
    </RepositoryEvent>
    <ExecutionEvent type="ProjectSucceeded" class="org.apache.maven.lifecycle.internal.DefaultExecutionEvent" _time="2018-02-11 16:18:30.971">
        <project baseDir="/path/to/my-project-workspace" file="/path/to/my-project-workspace/pom.xml" groupId="com.example" name="my-jar" artifactId="my-jar" version="0.5-SNAPSHOT">
          <build sourceDirectory="/path/to/my-project-workspace/src/main/java" directory="/path/to/my-project-workspace/target"/>
        </project>
        <no-execution-found/>
        <artifact extension="jar" baseVersion="0.5-SNAPSHOT" groupId="com.example" artifactId="my-jar" id="com.example:my-jar:jar:0.5-SNAPSHOT" type="jar" version="0.5-20180211.151825-18" snapshot="true">
          <file>/path/to/my-project-workspace/target/my-jar-0.5-SNAPSHOT.jar</file>
        </artifact>
        <attachedArtifacts/>
    </ExecutionEvent>
     */
    @Nonnull
    public static List<Element> getArtifactDeployedEvents(@Nonnull Element mavenSpyLogs) {
        List<Element> elements = new ArrayList<>();

        NodeList nodes = mavenSpyLogs.getChildNodes();

        for (int i = 0; i < nodes.getLength(); i++) {
            Node node = nodes.item(i);
            if (node instanceof Element) {
                Element element = (Element) node;
                if (StringUtils.equals(element.getNodeName(), "RepositoryEvent")) {
                    Attr type = element.getAttributeNode("type");
                    if (null != type && StringUtils.equals(type.getValue(), "ARTIFACT_DEPLOYED")) {
                        elements.add(element);
                    }
                }
            }
        }
        return elements;
    }

    /*
    <RepositoryEvent type="ARTIFACT_DEPLOYED" class="org.eclipse.aether.RepositoryEvent" _time="2018-02-11 16:18:26.505">
        <artifact extension="jar" file="/path/to/my-project-workspace/target/my-jar-0.5-SNAPSHOT.jar" baseVersion="0.5-SNAPSHOT" groupId="com.example" classifier="" artifactId="my-jar" id="com.example:my-jar:jar:0.5-20180211.151825-18" version="0.5-20180211.151825-18" snapshot="true"/>
        <repository layout="default" id="nexus.beescloud.com" url="https://nexus.beescloud.com/content/repositories/snapshots/"/>
    </RepositoryEvent>
    <ExecutionEvent type="ProjectSucceeded" class="org.apache.maven.lifecycle.internal.DefaultExecutionEvent" _time="2018-02-11 16:18:30.971">
        <project baseDir="/path/to/my-project-workspace" file="/path/to/my-project-workspace/pom.xml" groupId="com.example" name="my-jar" artifactId="my-jar" version="0.5-SNAPSHOT">
          <build sourceDirectory="/path/to/my-project-workspace/src/main/java" directory="/path/to/my-project-workspace/target"/>
        </project>
        <no-execution-found/>
        <artifact extension="jar" baseVersion="0.5-SNAPSHOT" groupId="com.example" artifactId="my-jar" id="com.example:my-jar:jar:0.5-SNAPSHOT" type="jar" version="0.5-20180211.151825-18" snapshot="true">
          <file>/path/to/my-project-workspace/target/my-jar-0.5-SNAPSHOT.jar</file>
        </artifact>
        <attachedArtifacts/>
    </ExecutionEvent>
     */

    /**
     *
     * @param artifactDeployedEvents list of "RepositoryEvent" of type "ARTIFACT_DEPLOYED"
     * @param filePath file path of the artifact we search for
     * @return The "RepositoryEvent" of type "ARTIFACT_DEPLOYED" or {@code null} if non found
     */
    @Nullable
    public static Element getArtifactDeployedEvent(@Nonnull List<Element> artifactDeployedEvents, @Nonnull String filePath) {
        for (Element artifactDeployedEvent: artifactDeployedEvents) {
            if (!"RepositoryEvent".equals(artifactDeployedEvent.getNodeName()) || !"ARTIFACT_DEPLOYED".equals(artifactDeployedEvent.getAttribute("type"))) {
                // skip unexpected element
                continue;
            }
            String deployedArtifactFilePath = getUniqueChildElement(artifactDeployedEvent, "artifact").getAttribute("file");
            if (Objects.equals(filePath, deployedArtifactFilePath)) {
                return artifactDeployedEvent;
            }
        }
        return null;
    }

    /*
   <ExecutionEvent type="MojoSucceeded" class="org.apache.maven.lifecycle.internal.DefaultExecutionEvent" _time="2017-02-02 23:03:17.06">
      <project artifactIdId="supplychain-portal" groupId="com.acmewidgets.supplychain" name="supplychain-portal" version="0.0.7" />
      <plugin executionId="default-test" goal="test" groupId="org.apache.maven.plugins" artifactId="maven-surefire-plugin" version="2.18.1">
         <reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory>
      </plugin>
   </ExecutionEvent>
     */
    @Nonnull
    public static List<Element> getExecutionEventsByPlugin(@Nonnull Element mavenSpyLogs, String pluginGroupId, String pluginArtifactId, String pluginGoal, String... eventType) {
        Set<String> eventTypes = new HashSet<>(Arrays.asList(eventType));

        List<Element> result = new ArrayList<>();
        for (Element executionEventElt : getChildrenElements(mavenSpyLogs, "ExecutionEvent")) {

            if (eventTypes.contains(executionEventElt.getAttribute("type"))) {
                Element pluginElt = XmlUtils.getUniqueChildElementOrNull(executionEventElt, "plugin");
                if (pluginElt == null) {

                } else {
                    if (pluginElt.getAttribute("groupId").equals(pluginGroupId) &&
                            pluginElt.getAttribute("artifactId").equals(pluginArtifactId) &&
                            pluginElt.getAttribute("goal").equals(pluginGoal)) {
                        result.add(executionEventElt);
                    } else {

                    }
                }
            }

        }
        return result;
    }

    /*
    <ExecutionEvent type="MojoSucceeded" class="org.apache.maven.lifecycle.internal.DefaultExecutionEvent" _time="2017-09-26 23:55:44.188">
    <project baseDir="/path/to/my-project-workspace" file="/path/to/my-project-workspace/pom.xml" groupId="com.example" name="my-jar" artifactId="my-jar" version="0.3-SNAPSHOT">
      <build sourceDirectory="/path/to/my-project-workspace/src/main/java" directory="/path/to/my-project-workspace/target"/>
    </project>
    <plugin executionId="default-jar" goal="jar" lifecyclePhase="package" groupId="org.apache.maven.plugins" artifactId="maven-jar-plugin" version="2.4">
      <finalName>${jar.finalName}</finalName>
      <outputDirectory>${project.build.directory}</outputDirectory>
    </plugin>
  </ExecutionEvent>
     */
    @Nonnull
    public static List<String> getExecutedLifecyclePhases(@Nonnull Element mavenSpyLogs) {
        List<String> lifecyclePhases = new ArrayList<>();
        for (Element mojoSucceededEvent :getExecutionEvents(mavenSpyLogs, "MojoSucceeded")) {
            Element pluginElement = getUniqueChildElement(mojoSucceededEvent, "plugin");
            String lifecyclePhase = pluginElement.getAttribute("lifecyclePhase");
            if (!lifecyclePhases.contains(lifecyclePhase)) {
                lifecyclePhases.add(lifecyclePhase);
            }
        }

        return lifecyclePhases;
    }

    /**
     * Relativize path
     * <p>
     * TODO replace all the workarounds (JENKINS-44088, JENKINS-46084, mac special folders...) by a unique call to
     * {@link File#getCanonicalPath()} on the workspace for the whole "MavenSpyLogProcessor#processMavenSpyLogs" code block.
     * We donb't want to pay an RPC call to {@link File#getCanonicalPath()} each time.
     *
     * @return relativized path
     * @throws IllegalArgumentException if {@code other} is not a {@code Path} that can be relativized
     *                                  against this path
     * @see java.nio.file.Path#relativize(Path)
     */
    @Nonnull
    public static String getPathInWorkspace(@Nonnull final String absoluteFilePath, @Nonnull FilePath workspace) {
        boolean windows = FileUtils.isWindows(workspace);

        final String workspaceRemote = workspace.getRemote();

        String sanitizedAbsoluteFilePath;
        String sanitizedWorkspaceRemote;
        if (windows) {
            // sanitize to workaround JENKINS-44088
            sanitizedWorkspaceRemote = workspaceRemote.replace('/', '\\');
            sanitizedAbsoluteFilePath = absoluteFilePath.replace('/', '\\');
        } else if (workspaceRemote.startsWith("/var/") && absoluteFilePath.startsWith("/private/var/")) {
            // workaround MacOSX special folders path
            // eg String workspace = "/var/folders/lq/50t8n2nx7l316pwm8gc_2rt40000gn/T/jenkinsTests.tmp/jenkins3845105900446934883test/workspace/build-on-master-with-tool-provided-maven";
            // eg String absolutePath = "/private/var/folders/lq/50t8n2nx7l316pwm8gc_2rt40000gn/T/jenkinsTests.tmp/jenkins3845105900446934883test/workspace/build-on-master-with-tool-provided-maven/pom.xml";
            sanitizedWorkspaceRemote = workspaceRemote;
            sanitizedAbsoluteFilePath = absoluteFilePath.substring("/private".length());
        } else {
            sanitizedAbsoluteFilePath = absoluteFilePath;
            sanitizedWorkspaceRemote = workspaceRemote;
        }

        if (StringUtils.startsWithIgnoreCase(sanitizedAbsoluteFilePath, sanitizedWorkspaceRemote)) {
            // OK
        } else if (sanitizedWorkspaceRemote.contains("/workspace/") && sanitizedAbsoluteFilePath.contains("/workspace/")) {
            // workaround JENKINS-46084
            // sanitizedAbsoluteFilePath = '/app/Jenkins/home/workspace/testjob/pom.xml'
            // sanitizedWorkspaceRemote = '/var/lib/jenkins/workspace/testjob'
            sanitizedAbsoluteFilePath = "/workspace/" + StringUtils.substringAfter(sanitizedAbsoluteFilePath, "/workspace/");
            sanitizedWorkspaceRemote = "/workspace/" + StringUtils.substringAfter(sanitizedWorkspaceRemote, "/workspace/");
        } else if (sanitizedWorkspaceRemote.endsWith("/workspace") && sanitizedAbsoluteFilePath.contains("/workspace/")) {
            // workspace = "/var/lib/jenkins/jobs/Test-Pipeline/workspace";
            // absolutePath = "/storage/jenkins/jobs/Test-Pipeline/workspace/pom.xml";
            sanitizedAbsoluteFilePath = "workspace/" + StringUtils.substringAfter(sanitizedAbsoluteFilePath, "/workspace/");
            sanitizedWorkspaceRemote = "workspace/";
        } else {
            throw new IllegalArgumentException("Cannot relativize '" + absoluteFilePath + "' relatively to '" + workspace.getRemote() + "'");
        }

        String relativePath = StringUtils.removeStartIgnoreCase(sanitizedAbsoluteFilePath, sanitizedWorkspaceRemote);
        String fileSeparator = windows ? "\\" : "/";

        if (relativePath.startsWith(fileSeparator)) {
            relativePath = relativePath.substring(fileSeparator.length());
        }
        LOGGER.log(Level.FINEST, "getPathInWorkspace({0}, {1}: {2}", new Object[]{absoluteFilePath, workspaceRemote, relativePath});
        return relativePath;
    }

    /**
     * @deprecated  use {@link FileUtils#isWindows(FilePath)}
     */
    @Deprecated
    public static boolean isWindows(@Nonnull FilePath path) {
        return FileUtils.isWindows(path);
    }

    /**
     * Return the File separator "/" or "\" that is effective on the remote agent.
     *
     * @param filePath
     * @return "/" or "\"
     */
    @Nonnull
    public static String getFileSeparatorOnRemote(@Nonnull FilePath filePath) {
        int indexOfSlash = filePath.getRemote().indexOf('/');
        int indexOfBackSlash = filePath.getRemote().indexOf('\\');
        if (indexOfSlash == -1) {
            return "\\";
        } else if (indexOfBackSlash == -1) {
            return "/";
        } else if (indexOfSlash < indexOfBackSlash) {
            return "/";
        } else {
            return "\\";
        }
    }

    /**
     * @param projectElt
     * @return {@code project/build/@directory"}
     */
    @Nullable
    public static String getProjectBuildDirectory(@Nonnull Element projectElt) {
        Element build = XmlUtils.getUniqueChildElementOrNull(projectElt, "build");
        if (build == null) {
            return null;
        }
        return build.getAttribute("directory");
    }

    /**
     * Concatenate the given {@code elements} using the given {@code delimiter} to concatenate.
     */
    @Nonnull
    public static String join(@Nonnull Iterable<String> elements, @Nonnull String delimiter) {
        StringBuilder result = new StringBuilder();
        Iterator<String> it = elements.iterator();
        while (it.hasNext()) {
            String element = it.next();
            result.append(element);
            if (it.hasNext()) {
                result.append(delimiter);
            }
        }
        return result.toString();
    }


    @Nonnull
    public static List<MavenArtifact> listGeneratedArtifacts(Element mavenSpyLogs, boolean includeAttachedArtifacts) {

        List<Element> artifactDeployedEvents = XmlUtils.getArtifactDeployedEvents(mavenSpyLogs);

        List<MavenArtifact> result = new ArrayList<>();

        for (Element projectSucceededElt : XmlUtils.getExecutionEvents(mavenSpyLogs, "ProjectSucceeded")) {

            Element projectElt = XmlUtils.getUniqueChildElement(projectSucceededElt, "project");
            MavenArtifact projectArtifact = XmlUtils.newMavenArtifact(projectElt);
            MavenArtifact pomArtifact = new MavenArtifact();
            pomArtifact.setGroupId(projectArtifact.getGroupId());
            pomArtifact.setArtifactId(projectArtifact.getArtifactId());
            pomArtifact.setBaseVersion(projectArtifact.getBaseVersion());
            pomArtifact.setVersion(projectArtifact.getVersion());
            pomArtifact.setSnapshot(projectArtifact.isSnapshot());
            pomArtifact.setType("pom");
            pomArtifact.setExtension("pom");
            pomArtifact.setFile(projectElt.getAttribute("file"));

            result.add(pomArtifact);

            Element artifactElt = XmlUtils.getUniqueChildElement(projectSucceededElt, "artifact");
            MavenArtifact mavenArtifact = XmlUtils.newMavenArtifact(artifactElt);
            if ("pom".equals(mavenArtifact.getType())) {
                // No file is generated in a "pom" type project, don't add the pom file itself
                // TODO: evaluate if we really want to skip this file - cyrille le clerc 2018-04-12
            } else {
                Element fileElt = XmlUtils.getUniqueChildElementOrNull(artifactElt, "file");
                if (fileElt == null || fileElt.getTextContent() == null || fileElt.getTextContent().isEmpty()) {
                    if (LOGGER.isLoggable(Level.FINER)) {
                        LOGGER.log(Level.FINE, "listGeneratedArtifacts: Project " + projectArtifact + ":  no associated file found for " +
                                mavenArtifact + " in " + XmlUtils.toString(artifactElt));
                    }
                } else {
                    mavenArtifact.setFile(StringUtils.trim(fileElt.getTextContent()));

                    Element artifactDeployedEvent = XmlUtils.getArtifactDeployedEvent(artifactDeployedEvents, mavenArtifact.getFile());
                    if(artifactDeployedEvent == null) {
                        // artifact has not been deployed ("mvn deploy")
                    } else {
                        mavenArtifact.setRepositoryUrl(XmlUtils.getUniqueChildElement(artifactDeployedEvent, "repository").getAttribute("url"));
                    }
                }
                result.add(mavenArtifact);
            }

            if (includeAttachedArtifacts) {
                Element attachedArtifactsParentElt = XmlUtils.getUniqueChildElement(projectSucceededElt, "attachedArtifacts");
                List<Element> attachedArtifactsElts = XmlUtils.getChildrenElements(attachedArtifactsParentElt, "artifact");
                for (Element attachedArtifactElt : attachedArtifactsElts) {
                    MavenArtifact attachedMavenArtifact = XmlUtils.newMavenArtifact(attachedArtifactElt);

                    Element fileElt = XmlUtils.getUniqueChildElementOrNull(attachedArtifactElt, "file");
                    if (fileElt == null || fileElt.getTextContent() == null || fileElt.getTextContent().isEmpty()) {
                        if (LOGGER.isLoggable(Level.FINER)) {
                            LOGGER.log(Level.FINER, "Project " + projectArtifact + ", no associated file found for attached artifact " +
                                    attachedMavenArtifact + " in " + XmlUtils.toString(attachedArtifactElt));
                        }
                    } else {
                        attachedMavenArtifact.setFile(StringUtils.trim(fileElt.getTextContent()));

                        Element attachedArtifactDeployedEvent = XmlUtils.getArtifactDeployedEvent(artifactDeployedEvents, attachedMavenArtifact.getFile());
                        if(attachedArtifactDeployedEvent == null) {
                            // artifact has not been deployed ("mvn deploy")
                        } else {
                            attachedMavenArtifact.setRepositoryUrl(XmlUtils.getUniqueChildElement(attachedArtifactDeployedEvent, "repository").getAttribute("url"));
                        }

                    }
                    result.add(attachedMavenArtifact);
                }
            }
        }

        return result;
    }

    /**
     * Copy {@link jenkins.util.xml.RestrictiveEntityResolver} as it is secured by {@link org.kohsuke.accmod.restrictions.NoExternalUse}.
     *
     * @see jenkins.util.xml.RestrictiveEntityResolver
     */
    public final static class RestrictiveEntityResolver implements EntityResolver {

        public final static RestrictiveEntityResolver INSTANCE = new RestrictiveEntityResolver();

        private RestrictiveEntityResolver() {
            // prevent multiple instantiation.
            super();
        }

        /**
         * Throws a SAXException if this tried to resolve any entity.
         */
        @Override
        public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
            throw new SAXException("Refusing to resolve entity with publicId(" + publicId + ") and systemId (" + systemId + ")");
        }
    }
}