001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.transport.file;
020
021import javax.inject.Named;
022
023import java.io.IOException;
024import java.io.UncheckedIOException;
025import java.net.URI;
026import java.nio.file.FileSystem;
027import java.nio.file.FileSystemNotFoundException;
028import java.nio.file.FileSystems;
029import java.nio.file.Path;
030import java.nio.file.Paths;
031import java.util.HashMap;
032import java.util.Map;
033
034import org.eclipse.aether.RepositorySystemSession;
035import org.eclipse.aether.repository.RemoteRepository;
036import org.eclipse.aether.repository.RepositoryUriUtils;
037import org.eclipse.aether.spi.connector.transport.Transporter;
038import org.eclipse.aether.spi.connector.transport.TransporterFactory;
039import org.eclipse.aether.transfer.NoTransporterException;
040
041import static java.util.Objects.requireNonNull;
042
043/**
044 * A transporter factory for repositories using the {@code file:} or {@code bundle:} protocol.
045 */
046@Named(FileTransporterFactory.NAME)
047public final class FileTransporterFactory implements TransporterFactory {
048    public static final String NAME = "file";
049
050    private float priority;
051
052    @Override
053    public float getPriority() {
054        return priority;
055    }
056
057    /**
058     * Sets the priority of this component.
059     *
060     * @param priority The priority.
061     * @return This component for chaining, never {@code null}.
062     */
063    public FileTransporterFactory setPriority(float priority) {
064        this.priority = priority;
065        return this;
066    }
067
068    /**
069     * Creates new instance of {@link FileTransporter}.
070     *
071     * @param session The session.
072     * @param repository The remote repository.
073     */
074    @Override
075    public Transporter newInstance(RepositorySystemSession session, RemoteRepository repository)
076            throws NoTransporterException {
077        requireNonNull(session, "session cannot be null");
078        requireNonNull(repository, "repository cannot be null");
079
080        String repositoryUrl = repository.getUrl();
081        if (repositoryUrl.startsWith("bundle:")) {
082            try {
083                repositoryUrl = repositoryUrl.substring("bundle:".length());
084                URI bundlePath = URI.create("jar:"
085                        + Paths.get(RepositoryUriUtils.toUri(repositoryUrl))
086                                .toAbsolutePath()
087                                .toUri()
088                                .toASCIIString());
089                Map<String, String> env = new HashMap<>();
090                FileSystem fileSystem = FileSystems.newFileSystem(bundlePath, env);
091                return new FileTransporter(
092                        fileSystem,
093                        true,
094                        false,
095                        fileSystem.getPath(fileSystem.getSeparator()),
096                        FileTransporter.WriteOp.COPY);
097            } catch (IOException e) {
098                throw new UncheckedIOException(e); // hard failure; most probably user error (ie wrong path or perm)
099            }
100        } else {
101            // special case in file: transport: to support custom FS providers (like JIMFS), we cannot
102            // cover all possible protocols (to throw NoTransporterEx), hence we rely on FS rejecting the URI
103            FileTransporter.WriteOp writeOp = FileTransporter.WriteOp.COPY;
104            if (repositoryUrl.startsWith("symlink+")) {
105                writeOp = FileTransporter.WriteOp.SYMLINK;
106                repositoryUrl = repositoryUrl.substring("symlink+".length());
107            } else if (repositoryUrl.startsWith("hardlink+")) {
108                writeOp = FileTransporter.WriteOp.HARDLINK;
109                repositoryUrl = repositoryUrl.substring("hardlink+".length());
110            }
111            try {
112                Path basePath =
113                        Paths.get(RepositoryUriUtils.toUri(repositoryUrl)).toAbsolutePath();
114                return new FileTransporter(
115                        basePath.getFileSystem(),
116                        false,
117                        !basePath.getFileSystem().isReadOnly(),
118                        basePath,
119                        writeOp);
120            } catch (FileSystemNotFoundException | IllegalArgumentException e) {
121                throw new NoTransporterException(repository, e);
122            }
123        }
124    }
125}