/*
 *  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
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package org.apache.ivy.core.retrieve;

import org.apache.ivy.Ivy;
import org.apache.ivy.TestHelper;
import org.apache.ivy.core.IvyPatternHelper;
import org.apache.ivy.core.cache.ResolutionCacheManager;
import org.apache.ivy.core.event.IvyEvent;
import org.apache.ivy.core.event.IvyListener;
import org.apache.ivy.core.event.retrieve.EndRetrieveArtifactEvent;
import org.apache.ivy.core.event.retrieve.EndRetrieveEvent;
import org.apache.ivy.core.event.retrieve.StartRetrieveArtifactEvent;
import org.apache.ivy.core.event.retrieve.StartRetrieveEvent;
import org.apache.ivy.core.module.descriptor.Configuration;
import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.core.module.id.ModuleId;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.report.ArtifactDownloadReport;
import org.apache.ivy.core.report.ResolveReport;
import org.apache.ivy.core.resolve.ResolveOptions;
import org.apache.ivy.util.DefaultMessageLogger;
import org.apache.ivy.util.Message;
import org.apache.ivy.util.MockMessageLogger;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.taskdefs.Delete;
import org.apache.tools.ant.taskdefs.condition.JavaVersion;
import org.apache.tools.ant.types.FilterChain;
import org.apache.tools.ant.filters.TokenFilter.ReplaceString;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

public class RetrieveTest {

    private Ivy ivy;

    private static boolean systemHasSymlinkAbility;

    @BeforeClass
    public static void beforeClass() {
        final List<File> tmpFilesCreated = new ArrayList<>();
        // create a dummy symlink and see if it works fine
        try {
            final File tmpDir = Files.createTempDirectory(null).toFile();
            tmpFilesCreated.add(tmpDir);

            final Path tmpFile = Files.createTempFile(tmpDir.toPath(), null, null);
            tmpFilesCreated.add(tmpFile.toFile());

            final File symlinkedFile = new File(tmpDir, "symlinked-test-file");
            tmpFilesCreated.add(symlinkedFile);

            // attempt to create the symlink
            Files.createSymbolicLink(symlinkedFile.toPath(), tmpFile);
            systemHasSymlinkAbility = true;
        } catch (IOException ioe) {
            Message.info("Current system is considered as not having symlink ability due to failure to create a test symlink", ioe);
            systemHasSymlinkAbility = false;
        }
        // delete on exit, the tmp files we created
        for (final File file : tmpFilesCreated) {
            file.deleteOnExit();
        }
    }

    @Before
    public void setUp() throws Exception {
        ivy = Ivy.newInstance();
        ivy.configure(new File("test/repositories/ivysettings.xml"));
        TestHelper.createCache();
        Message.setDefaultLogger(new DefaultMessageLogger(Message.MSG_INFO));
    }

    @After
    public void tearDown() {
        TestHelper.cleanCache();
        Delete del = new Delete();
        del.setProject(new Project());
        del.setDir(new File("build/test/retrieve"));
        del.execute();
    }

    @Test
    public void testRetrieveSimple() throws Exception {
        // mod1.1 depends on mod1.2
        ResolveReport report = ivy.resolve(new File(
                "test/repositories/1/org1/mod1.1/ivys/ivy-1.0.xml").toURI().toURL(),
            getResolveOptions(new String[] {"*"}));
        assertNotNull(report);
        ModuleDescriptor md = report.getModuleDescriptor();
        assertNotNull(md);

        String pattern = "build/test/retrieve/[module]/[conf]/[artifact]-[revision].[ext]";
        ivy.retrieve(md.getModuleRevisionId(),
            getRetrieveOptions().setDestArtifactPattern(pattern));
        assertTrue(new File(IvyPatternHelper.substitute(pattern, "org1", "mod1.2", "2.0", "mod1.2",
            "jar", "jar", "default")).exists());

        pattern = "build/test/retrieve/[module]/[conf]/[type]s/[artifact]-[revision].[ext]";
        ivy.retrieve(md.getModuleRevisionId(),
            getRetrieveOptions().setDestArtifactPattern(pattern));
        assertTrue(new File(IvyPatternHelper.substitute(pattern, "org1", "mod1.2", "2.0", "mod1.2",
            "jar", "jar", "default")).exists());
    }

    @Test
    public void wontRetrieveOutsideOfDestRoot() throws Exception {
        ResolveReport report = ivy.resolve(new File(
                "test/repositories/1/org1/mod1.1/ivys/ivy-1.0.xml").toURI().toURL(),
            getResolveOptions(new String[] {"*"}));
        assertNotNull(report);
        ModuleDescriptor md = report.getModuleDescriptor();
        assertNotNull(md);

        Ivy testIvy = Ivy.newInstance();
        testIvy.configure(new File("test/repositories/ivysettings.xml"));
        testIvy.getSettings()
            .setResolutionCacheManager(new FindAllResolutionCacheManager(ivy.getResolutionCacheManager(), md));

        Copy copy = new Copy();
        copy.setProject(new Project());
        copy.setFile(new File("build/cache/org1-mod1.1-default.xml"));
        copy.setTofile(new File("build/cache/fake-default.xml"));
        FilterChain fc = copy.createFilterChain();
        ReplaceString rsOrg = new ReplaceString();
        rsOrg.setFrom("organisation=\"org1\"");
        rsOrg.setTo("organisation=\"fake\"");
        fc.addReplaceString(rsOrg);
        copy.setOverwrite(true);
        copy.execute();

        MockMessageLogger mockLogger = new MockMessageLogger();
        Message.setDefaultLogger(mockLogger);

        ModuleRevisionId id = ModuleRevisionId.newInstance("fake", "a", "1.1");
        ModuleDescriptor fake = DefaultModuleDescriptor.newDefaultInstance(id);
        String pattern = "build/[organisation]/../../../[artifact]-[revision].[ext]";
        testIvy.retrieve(fake.getModuleRevisionId(),
            getRetrieveOptions().setDestArtifactPattern(pattern));
        mockLogger.assertLogContains("not retrieving artifact");
    }

    @Test
    public void testRetrieveSameFileConflict() throws Exception {
        // mod1.1 depends on mod1.2
        ResolveReport report = ivy.resolve(new File(
                "test/repositories/1/org1/mod1.4/ivys/ivy-1.0.1.xml").toURI().toURL(),
            getResolveOptions(new String[] {"*"}));
        assertNotNull(report);
        ModuleDescriptor md = report.getModuleDescriptor();
        assertNotNull(md);

        String pattern = "build/test/retrieve/[module]/[artifact]-[revision].[ext]";
        MockMessageLogger mockLogger = new MockMessageLogger();
        Message.setDefaultLogger(mockLogger);
        ivy.retrieve(md.getModuleRevisionId(),
            getRetrieveOptions().setDestArtifactPattern(pattern));
        assertTrue(new File(IvyPatternHelper.substitute(pattern, "org1", "mod1.2", "2.2", "mod1.2",
            "jar", "jar", "default")).exists());
        mockLogger.assertLogDoesntContain("conflict on");
    }

    @Test(expected = RuntimeException.class)
    public void testRetrieveDifferentArtifactsOfSameModuleToSameFile() throws Exception {
        ResolveReport report = ivy.resolve(new File(
                "test/repositories/1/org2/mod2.2/ivys/ivy-0.5.xml").toURI().toURL(),
            getResolveOptions(new String[] {"*"}));
        assertNotNull(report);
        ModuleDescriptor md = report.getModuleDescriptor();
        assertNotNull(md);

        String pattern = "build/test/retrieve/[module]/[module].[ext]";
        MockMessageLogger mockLogger = new MockMessageLogger();
        Message.setDefaultLogger(mockLogger);
        try {
            ivy.retrieve(md.getModuleRevisionId(),
                getRetrieveOptions().setDestArtifactPattern(pattern));
        } finally {
            mockLogger.assertLogDoesntContain("multiple artifacts");
        }
    }

    @Test
    public void testEvent() throws Exception {
        ResolveReport report = ivy.resolve(new File(
                "test/repositories/1/org1/mod1.1/ivys/ivy-1.0.xml").toURI().toURL(),
            getResolveOptions(new String[] {"*"}));

        final List<IvyEvent> events = new ArrayList<>();
        ivy.getEventManager().addIvyListener(new IvyListener() {
            public void progress(IvyEvent event) {
                events.add(event);
            }
        });
        ModuleDescriptor md = report.getModuleDescriptor();
        String pattern = "build/test/retrieve/[module]/[conf]/[artifact]-[revision].[ext]";
        ivy.retrieve(md.getModuleRevisionId(),
            getRetrieveOptions().setDestArtifactPattern(pattern));
        assertEquals(4, events.size());
        assertTrue(events.get(0) instanceof StartRetrieveEvent);
        assertTrue(events.get(1) instanceof StartRetrieveArtifactEvent);
        assertTrue(events.get(2) instanceof EndRetrieveArtifactEvent);
        assertTrue(events.get(3) instanceof EndRetrieveEvent);
        EndRetrieveEvent ev = (EndRetrieveEvent) events.get(3);
        assertEquals(1, ev.getNbCopied());
        assertEquals(0, ev.getNbUpToDate());

        events.clear();
        ivy.retrieve(md.getModuleRevisionId(),
            getRetrieveOptions().setDestArtifactPattern(pattern));
        assertEquals(2, events.size());
        assertTrue(events.get(0) instanceof StartRetrieveEvent);
        assertTrue(events.get(1) instanceof EndRetrieveEvent);
        ev = (EndRetrieveEvent) events.get(1);
        assertEquals(0, ev.getNbCopied());
        assertEquals(1, ev.getNbUpToDate());
    }

    @Test
    public void testRetrieveOverwrite() throws Exception {
        // mod1.1 depends on mod1.2
        ResolveReport report = ivy.resolve(new File(
                "test/repositories/1/org1/mod1.1/ivys/ivy-1.0.xml").toURI().toURL(),
            getResolveOptions(new String[] {"*"}));
        assertNotNull(report);
        ModuleDescriptor md = report.getModuleDescriptor();
        assertNotNull(md);

        String pattern = "build/test/retrieve/[module]/[conf]/[artifact]-[revision].[ext]";

        // we create a fake old file to see if it is overwritten
        File file = new File(IvyPatternHelper.substitute(pattern, "org1", "mod1.2", "2.0",
            "mod1.2", "jar", "jar", "default")).getCanonicalFile();
        file.getParentFile().mkdirs();
        file.createNewFile();
        ivy.retrieve(md.getModuleRevisionId(),
            getRetrieveOptions().setOverwriteMode("always").setDestArtifactPattern(pattern));
        assertEquals(new File("test/repositories/1/org1/mod1.2/jars/mod1.2-2.0.jar").lastModified(),
            file.lastModified());
    }

    @Test
    public void testRetrieveWithSymlinks() throws Exception {
        // mod1.1 depends on mod1.2
        ResolveReport report = ivy.resolve(new File(
                "test/repositories/1/org1/mod1.1/ivys/ivy-1.0.xml").toURI().toURL(),
            getResolveOptions(new String[] {"*"}));
        assertNotNull(report);
        ModuleDescriptor md = report.getModuleDescriptor();
        assertNotNull(md);

        String pattern = "build/test/retrieve/[module]/[conf]/[artifact]-[revision].[ext]";
        ivy.retrieve(md.getModuleRevisionId(),
            getRetrieveOptions().setMakeSymlinks(true).setDestArtifactPattern(pattern));
        assertLinkOrExists(IvyPatternHelper.substitute(pattern, "org1", "mod1.2", "2.0", "mod1.2", "jar",
            "jar", "default"));

        pattern = "build/test/retrieve/[module]/[conf]/[type]s/[artifact]-[revision].[ext]";
        ivy.retrieve(md.getModuleRevisionId(),
            getRetrieveOptions().setMakeSymlinks(true).setDestArtifactPattern(pattern));
        assertLinkOrExists(IvyPatternHelper.substitute(pattern, "org1", "mod1.2", "2.0", "mod1.2", "jar",
            "jar", "default"));
    }

    /**
     * Tests that retrieve, when invoked with "symlink" enabled, creates the necessary symlink
     * when the artifact being retrieved is a directory instead of a regular file
     *
     * @throws Exception
     * @see <a href="https://issues.apache.org/jira/browse/IVY-1594">IVY-1594</a>
     */
    @Test
    public void testRetrieveZipArtifactWithSymlinks() throws Exception {
        // resolve (inline) with org1:mod1.1:3.0 as a dependency
        final ResolveReport report = ivy.resolve(new ModuleRevisionId(new ModuleId("org1", "mod1.7"), "3.0"),
                getResolveOptions(new String[]{"*"}), false);
        assertNotNull("Resolution report is null", report);
        final ModuleDescriptor md = report.getModuleDescriptor();
        assertNotNull("Module descriptor is null", md);

        final String retrievePattern = "build/test/retrieve/[module]/[conf]/[artifact]-[revision]";
        ivy.retrieve(md.getModuleRevisionId(),
                getRetrieveOptions().setMakeSymlinks(true).setDestArtifactPattern(retrievePattern));

        final String expectedRetrieveLocation = IvyPatternHelper.substitute(retrievePattern, "org1", "mod1.7",
                "3.0", "zipped-artifact", null, null, "default");
        // make sure it's retrieved as a symlink (on systems that support symlink)
        assertLinkOrExists(expectedRetrieveLocation);
    }

    /**
     * This test is here to just test the deprecated {@code symlinkmass} option for retrieve task.
     * A version or two down the line, after 2.5 release, we can remove this test and the option
     * altogether.
     *
     * @throws Exception if something goes wrong
     */
    @SuppressWarnings("deprecation")
    @Test
    public void testRetrieveWithSymlinksMass() throws Exception {
        // mod1.1 depends on mod1.2
        ResolveReport report = ivy.resolve(new File(
                "test/repositories/1/org1/mod1.1/ivys/ivy-1.0.xml").toURI().toURL(),
            getResolveOptions(new String[] {"*"}));
        assertNotNull(report);
        ModuleDescriptor md = report.getModuleDescriptor();
        assertNotNull(md);

        String pattern = "build/test/retrieve/[module]/[conf]/[artifact]-[revision].[ext]";
        ivy.retrieve(md.getModuleRevisionId(),
            getRetrieveOptions().setMakeSymlinksInMass(true).setDestArtifactPattern(pattern));
        assertLinkOrExists(IvyPatternHelper.substitute(pattern, "org1", "mod1.2", "2.0", "mod1.2", "jar",
            "jar", "default"));

        pattern = "build/test/retrieve/[module]/[conf]/[type]s/[artifact]-[revision].[ext]";
        ivy.retrieve(md.getModuleRevisionId(),
            getRetrieveOptions().setMakeSymlinksInMass(true).setDestArtifactPattern(pattern));
        assertLinkOrExists(IvyPatternHelper.substitute(pattern, "org1", "mod1.2", "2.0", "mod1.2", "jar",
            "jar", "default"));
    }

    /**
     * If the system {@link #systemHasSymlinkAbility has symlink ability} then asserts that the
     * passed {@code filePath} is a {@link Files#isSymbolicLink(Path) symbolic link}. Else asserts
     * that the {@code filePath} {@link Files#exists(Path, LinkOption...) exists}.
     *
     * @param filePath String
     */
    private void assertLinkOrExists(final String filePath) {
        if (systemHasSymlinkAbility) {
            assertTrue(filePath + " was expected to be a symlink", Files.isSymbolicLink(Paths.get(filePath)));
            return;
        }
        Message.info("System doesn't have symlink ability so checking if path " + filePath + " exists instead of checking for it to be a symlink");
        assertTrue("Missing " + filePath, Files.exists(Paths.get(filePath)));
    }

    @Test
    public void testRetrieveWithVariable() throws Exception {
        // mod1.1 depends on mod1.2
        ivy.setVariable("retrieve.dir", "retrieve");
        ResolveReport report = ivy.resolve(new File(
                "test/repositories/1/org1/mod1.1/ivys/ivy-1.0.xml").toURI().toURL(),
            getResolveOptions(new String[] {"*"}));
        assertNotNull(report);
        ModuleDescriptor md = report.getModuleDescriptor();
        assertNotNull(md);

        String pattern = "build/test/${retrieve.dir}/[module]/[conf]/[artifact]-[revision].[ext]";
        ivy.retrieve(md.getModuleRevisionId(),
            getRetrieveOptions().setDestArtifactPattern(pattern));
        pattern = IvyPatternHelper.substituteVariable(pattern, "retrieve.dir", "retrieve");
        assertTrue(new File(IvyPatternHelper.substitute(pattern, "org1", "mod1.2", "2.0", "mod1.2",
            "jar", "jar", "default")).exists());

        pattern = "build/test/${retrieve.dir}/[module]/[conf]/[type]s/[artifact]-[revision].[ext]";
        ivy.retrieve(md.getModuleRevisionId(),
            getRetrieveOptions().setDestArtifactPattern(pattern));
        pattern = IvyPatternHelper.substituteVariable(pattern, "retrieve.dir", "retrieve");
        assertTrue(new File(IvyPatternHelper.substitute(pattern, "org1", "mod1.2", "2.0", "mod1.2",
            "jar", "jar", "default")).exists());
    }

    @Test
    public void testRetrieveReport() throws Exception {
        // mod1.1 depends on mod1.2
        ResolveReport report = ivy.resolve(new File(
                "test/repositories/1/org20/mod20.1/ivys/ivy-1.2.xml").toURI().toURL(),
            getResolveOptions(new String[] {"*"}));
        assertNotNull(report);
        ModuleDescriptor md = report.getModuleDescriptor();
        assertNotNull(md);

        ModuleRevisionId mrid = md.getModuleRevisionId();
        RetrieveOptions options = getRetrieveOptions();
        options.setConfs(new String[] {"A"});
        Map<ArtifactDownloadReport, Set<String>> artifactsToCopy = ivy.getRetrieveEngine()
                .determineArtifactsToCopy(mrid,
                    "build/test/retrieve/[module]/[conf]/[artifact]-[revision].[ext]", options);
        assertEquals(2, artifactsToCopy.size());

        options.setConfs(new String[] {"B"});
        artifactsToCopy = ivy.getRetrieveEngine().determineArtifactsToCopy(mrid,
            "build/test/retrieve/[module]/[conf]/[artifact]-[revision].[ext]", options);
        assertEquals(2, artifactsToCopy.size());

        options.setConfs(new String[] {"A", "B"});
        artifactsToCopy = ivy.getRetrieveEngine().determineArtifactsToCopy(mrid,
            "build/test/retrieve/[module]/[conf]/[artifact]-[revision].[ext]", options);
        assertEquals(3, artifactsToCopy.size());
    }

    @Test
    public void testUnpack() throws Exception {
        ResolveOptions roptions = getResolveOptions(new String[] {"*"});

        URL url = new File("test/repositories/1/packaging/module1/ivys/ivy-1.0.xml").toURI()
                .toURL();

        // normal resolve, the file goes in the cache
        ResolveReport report = ivy.resolve(url, roptions);
        assertFalse(report.hasError());
        ModuleDescriptor md = report.getModuleDescriptor();
        assertNotNull(md);

        String pattern = "build/test/retrieve/[organization]/[module]/[conf]/[type]s/[artifact]-[revision](.[ext])";

        ivy.retrieve(md.getModuleRevisionId(),
            getRetrieveOptions().setDestArtifactPattern(pattern));

        File dest = new File("build/test/retrieve/packaging/module2/default/jars/module2-1.0");
        assertTrue(dest.exists());
        assertTrue(dest.isDirectory());
        File[] jarContents = dest.listFiles();
        Arrays.sort(jarContents);
        assertEquals(new File(dest, "META-INF"), jarContents[0]);
        assertEquals(new File(dest, "test.txt"), jarContents[1]);
        assertEquals(new File(dest, "META-INF/MANIFEST.MF"), jarContents[0].listFiles()[0]);
    }

    @Test
    public void testUnpackSync() throws Exception {
        ResolveOptions roptions = getResolveOptions(new String[] {"*"});

        URL url = new File("test/repositories/1/packaging/module1/ivys/ivy-1.0.xml").toURI()
                .toURL();

        // normal resolve, the file goes in the cache
        ResolveReport report = ivy.resolve(url, roptions);
        assertFalse(report.hasError());
        ModuleDescriptor md = report.getModuleDescriptor();
        assertNotNull(md);

        String pattern = "build/test/retrieve/[organization]/[module]/[conf]/[type]s/[artifact]-[revision](.[ext])";

        ivy.retrieve(md.getModuleRevisionId(),
            getRetrieveOptions().setSync(true).setDestArtifactPattern(pattern));

        File dest = new File("build/test/retrieve/packaging/module2/default/jars/module2-1.0");
        assertTrue(dest.exists());
        assertTrue(dest.isDirectory());
        File[] jarContents = dest.listFiles();
        Arrays.sort(jarContents);
        assertEquals(new File(dest, "META-INF"), jarContents[0]);
        assertEquals(new File(dest, "test.txt"), jarContents[1]);
        assertEquals(new File(dest, "META-INF/MANIFEST.MF"), jarContents[0].listFiles()[0]);
    }

    /**
     * Test case for IVY-1478.
     * {@link RetrieveEngine} must retrieve artifacts with the correct extension if the artifact
     * is unpacked.
     *
     * @throws Exception if something goes wrong
     * @see <a href="https://issues.apache.org/jira/browse/IVY-1478">IVY-1478</a>
     */
    @Test
    public void testUnpackExt() throws Exception {
        final JavaVersion java14OrHigher = new JavaVersion();
        java14OrHigher.setAtLeast("14");
        Assume.assumeFalse("Pack200 tools and API have been removed since JDK 14", java14OrHigher.eval());

        final ResolveOptions roptions = getResolveOptions(new String[] {"*"});

        final URL url = new File("test/repositories/1/packaging/module10/ivys/ivy-1.0.xml").toURI()
                .toURL();

        // normal resolve, the file goes in the cache
        final ResolveReport report = ivy.resolve(url, roptions);
        assertFalse("Resolution report has errors", report.hasError());
        final ModuleDescriptor md = report.getModuleDescriptor();
        assertNotNull("Module descriptor from report was null", md);

        final String pattern = "build/test/retrieve/[organization]/[module]/[conf]/[type]s/[artifact]-[revision](.[ext])";

        ivy.retrieve(md.getModuleRevisionId(),
            getRetrieveOptions().setDestArtifactPattern(pattern));

        final File dest = new File("build/test/retrieve/packaging/module9/default/jars/module9-1.0.jar");
        assertTrue("Retrieved artifact is missing at " + dest.getAbsolutePath(), dest.exists());
        assertTrue("Retrieved artifact at " + dest.getAbsolutePath() + " is not a file", dest.isFile());
    }

    /**
     * Test case for IVY-1498.
     * More than one retrieve, one having symlink enabled and the other not,
     * must not lead to repository cache corruption.
     * <p>
     * The test does the following:
     * </p>
     * <ul>
     * <li>
     * Resolves a module, let's call it "irrelevant-A" which has a dependency on
     * "org:foo-bar:1.2.3"
     * </li>
     * <li>
     * Invokes a retrieve RT1, with {@link RetrieveOptions#setMakeSymlinks(boolean) symlinks true}
     * on that resolved module with a pattern that translates to a path "a/b/c/foo-bar.jar". When
     * the RT1 retrieve is done, the path "a/b/c/foo-bar.jar" will be a symlink to a path
     * "repo/cache/org/foo-bar/foo-bar-1.2.3.jar" in the Ivy cache.
     * All fine so far.
     * </li>
     * <li>
     * We then resolve another module, let's call it "irrelevant-B" which has a dependency on
     * "org:foo-bar:2.3.4"
     * </li>
     * <li>
     * Next, do a new retrieve RT2, on this newly resolved module with
     * {@link RetrieveOptions#setMakeSymlinks(boolean) symlinks false} and
     * {@link RetrieveOptions#getOverwriteMode() overwrite semantics enabled}
     * and with the same pattern as before), that translates to a path "a/b/c/foo-bar.jar".
     * </li>
     * </ul>
     * <p>
     * When RT2 retrieve is done, we expect the path "a/b/c/foo-bar.jar" will *not* be a symlink
     * and instead be an actual file that represents the org:foo-bar:2.3.4 artifact jar.
     * </p>
     * <p>
     * We also expect that this RT2 retrieve will not update/overwrite the file at path
     * "repo/cache/org/foo-bar/foo-bar-1.2.3.jar" in the Ivy cache - the file that was the end
     * target of the symlink generated by the previous RT1.
     * </p>
     *
     * @throws Exception if something goes wrong
     * @see <a href="https://issues.apache.org/jira/browse/IVY-1498">IVY-1498</a>
     */
    @Test
    public void testRetrieveWithSymlinkToggling() throws Exception {
        // org:mod1:2.0 depends on org:foo-bar:1.2.3
        final ResolveReport resolve1Report = ivy.resolve(new File("test/repositories/1/org/mod1/ivys/ivy-2.0.xml").toURI().toURL(),
                getResolveOptions(new String[]{"*"}));
        assertNotNull("Resolve report isn't available", resolve1Report);
        assertNotNull("Module descriptor missing in resolve report", resolve1Report.getModuleDescriptor());

        // verify the downloaded dependency artifact in cache
        final ModuleRevisionId fooBar123Mrid = ModuleRevisionId.newInstance("org", "foo-bar", "1.2.3");
        final ArtifactDownloadReport[] downloadReports = resolve1Report.getArtifactsReports(fooBar123Mrid);
        assertNotNull("No artifact download report found for the " + fooBar123Mrid + " dependency", downloadReports);
        assertEquals("Unexpected number for artifact download reports for the " + fooBar123Mrid + " dependency", 1, downloadReports.length);
        final File fooBar123ArtifactInCache = downloadReports[0].getLocalFile();
        assertNotNull("Local file for " + fooBar123Mrid + " missing in download report", fooBar123ArtifactInCache);
        assertTrue("Artifact file for " + fooBar123Mrid + " isn't a file, in cache at " + fooBar123ArtifactInCache,
                fooBar123ArtifactInCache.isFile());
        final byte[] fooBar123ArtifactContentsInCache = Files.readAllBytes(fooBar123ArtifactInCache.toPath());
        assertTrue("Artifact content was empty at " + new String(fooBar123ArtifactContentsInCache), fooBar123ArtifactContentsInCache.length > 0);


        // now do a retrieve of the resolved module
        final String retrievePattern = "build/test/retrieve/symlink-test/[module]/[artifact].[ext]";
        ivy.retrieve(resolve1Report.getModuleDescriptor().getModuleRevisionId(),
                getRetrieveOptions().setMakeSymlinks(true).setOverwriteMode(RetrieveOptions.OVERWRITEMODE_ALWAYS)
                        .setDestArtifactPattern(retrievePattern));
        // we expect org:foo-bar:1.2.3 to have been retrieved
        final String retrievedArtifactSymlinkPath = IvyPatternHelper.substitute(retrievePattern, "org", "foo-bar",
                "1.2.3", "foo-bar", "jar", "jar", "default");
        assertLinkOrExists(retrievedArtifactSymlinkPath);

        // get hold of the contents of the retrieved artifact
        final byte[] retrievedArtifactContent = Files.readAllBytes(Paths.get(retrievedArtifactSymlinkPath));
        // compare it to the contents of org:foo-bar:1.2.3 artifact in repo cache. Should be the same
        assertTrue("Unexpected content in the retrieved artifact at " + retrievedArtifactSymlinkPath,
                Arrays.equals(fooBar123ArtifactContentsInCache, retrievedArtifactContent));

        // let this retrieved artifact file stay and let's now try and resolve a module and then
        // retrieve a different version of the same artifact, *without* symlinking enabled, which
        // will end up at this exact location org:mod1:2.0 depends on org:foo-bar:1.2.3
        final ResolveReport resolve2Report = ivy.resolve(new File("test/repositories/1/org/mod1/ivys/ivy-5.0.xml").toURI().toURL(),
                getResolveOptions(new String[]{"*"}));
        assertNotNull("Resolve report isn't available", resolve2Report);
        assertNotNull("Module descriptor missing in resolve report", resolve2Report.getModuleDescriptor());

        // verify the downloaded dependency artifact in cache
        final ModuleRevisionId fooBar234Mrid = ModuleRevisionId.newInstance("org", "foo-bar", "2.3.4");
        final ArtifactDownloadReport[] foobar234report = resolve2Report.getArtifactsReports(fooBar234Mrid);
        assertNotNull("No artifact download report found for the " + fooBar234Mrid + " dependency", foobar234report);
        assertEquals("Unexpected number for artifact download reports for the " + fooBar234Mrid + " dependency", 1, foobar234report.length);
        final File foobar234InCache = foobar234report[0].getLocalFile();
        assertNotNull("Local file for " + fooBar234Mrid + " missing in download report", foobar234InCache);
        assertTrue("Artifact file for " + fooBar234Mrid + " isn't a file, in cache at " + foobar234InCache,
                foobar234InCache.isFile());
        final byte[] foobar234CacheFileContents = Files.readAllBytes(foobar234InCache.toPath());
        assertTrue("Artifact content was empty at " + new String(foobar234CacheFileContents), foobar234CacheFileContents.length > 0);

        // do the retrieve with symlinks disabled
        ivy.retrieve(resolve2Report.getModuleDescriptor().getModuleRevisionId(),
                getRetrieveOptions().setMakeSymlinks(false).setDestArtifactPattern(retrievePattern)
                        .setOverwriteMode(RetrieveOptions.OVERWRITEMODE_ALWAYS));
        // we expect org:foo-bar:2.3.4 to have been retrieved
        final Path secondRetrieveArtifactPath = new File(IvyPatternHelper.substitute(retrievePattern, "org", "foo-bar",
                "2.3.4", "foo-bar", "jar", "jar", "default")).toPath();
        assertTrue("Artifact wasn't retrieved to " + secondRetrieveArtifactPath, Files.exists(secondRetrieveArtifactPath));
        // expected to be a regular file and not a symlink
        assertFalse("Artifact retrieved at " + secondRetrieveArtifactPath + " wasn't expected to be a "
                + "symlink", Files.isSymbolicLink(secondRetrieveArtifactPath));

        // get hold of the contents of the retrieved artifact
        final byte[] secondRetrievedArtifactContents = Files.readAllBytes(secondRetrieveArtifactPath);
        // compare it to the contents of org:foo-bar:2.3.4 artifact in repo cache. Should be the same
        assertTrue("Unexpected content in the retrieved artifact at " + secondRetrieveArtifactPath,
                Arrays.equals(foobar234CacheFileContents, secondRetrievedArtifactContents));

        // also make sure that this retrieved content hasn't messed up the other (unrelated) file,
        // in the cache, that was previously symlinked to this retrieved path
        assertTrue("A second retrieve of an artifact has corrupted an unrelated (previously symlinked) artifact in cache",
                Arrays.equals(fooBar123ArtifactContentsInCache, Files.readAllBytes(Paths.get(fooBar123ArtifactInCache.getPath()))));


    }

    private RetrieveOptions getRetrieveOptions() {
        return new RetrieveOptions();
    }

    private ResolveOptions getResolveOptions(final String[] confs) {
        return new ResolveOptions().setConfs(confs);
    }


    private static class FindAllResolutionCacheManager implements ResolutionCacheManager {

        private final ResolutionCacheManager real;
        private final ModuleRevisionId staticMrid;
        private final ModuleDescriptor staticModuleDescriptor;
        private static final String RESOLVE_ID = "org1-mod1.1";

        private FindAllResolutionCacheManager(ResolutionCacheManager real, ModuleDescriptor md) {
            this.real = real;
            staticModuleDescriptor = md;
            staticMrid = md.getModuleRevisionId();
        }

        public File getResolutionCacheRoot() {
            return real.getResolutionCacheRoot();
        }

        public File getResolvedIvyFileInCache(ModuleRevisionId mrid) {
            return real.getResolvedIvyFileInCache(staticMrid);
        }

        public File getResolvedIvyPropertiesInCache(ModuleRevisionId mrid) {
            return real.getResolvedIvyPropertiesInCache(staticMrid);
        }

        public File getConfigurationResolveReportInCache(String resolveId, String conf) {
            return new File("build/cache/fake-default.xml");
        }

        public File[] getConfigurationResolveReportsInCache(final String resolveId) {
            return real.getConfigurationResolveReportsInCache(RESOLVE_ID);
        }

        public ModuleDescriptor getResolvedModuleDescriptor(ModuleRevisionId mrid)
            throws ParseException, IOException {
            if (mrid.getOrganisation().equals("fake")) {
                DefaultModuleDescriptor md = new DefaultModuleDescriptor(staticModuleDescriptor.getParser(), staticModuleDescriptor.getResource());
                md.setModuleRevisionId(mrid);
                md.setPublicationDate(staticModuleDescriptor.getPublicationDate());
                for (Configuration conf : staticModuleDescriptor.getConfigurations()) {
                    md.addConfiguration(conf);
                }
                return md;
            }
            return real.getResolvedModuleDescriptor(mrid);
        }

        public void saveResolvedModuleDescriptor(ModuleDescriptor md) {
        }

        public void clean() {
        }
    }
}
