View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.transport.file;
20  
21  import java.nio.ByteBuffer;
22  import java.nio.channels.FileChannel;
23  import java.nio.file.Files;
24  import java.nio.file.Path;
25  
26  import org.eclipse.aether.spi.connector.transport.AbstractTransporter;
27  import org.eclipse.aether.spi.connector.transport.GetTask;
28  import org.eclipse.aether.spi.connector.transport.PeekTask;
29  import org.eclipse.aether.spi.connector.transport.PutTask;
30  import org.eclipse.aether.spi.connector.transport.TransportTask;
31  import org.eclipse.aether.transfer.NoTransporterException;
32  
33  /**
34   * A transporter using {@link java.io.File}.
35   */
36  final class FileTransporter extends AbstractTransporter {
37      /**
38       * The file op transport can use.
39       *
40       * @since 2.0.2
41       */
42      enum FileOp {
43          COPY,
44          SYMLINK,
45          HARDLINK;
46      }
47  
48      private final Path basePath;
49      private final FileOp fileOp;
50  
51      FileTransporter(Path basePath, FileOp fileOp) throws NoTransporterException {
52          this.basePath = basePath;
53          this.fileOp = fileOp;
54      }
55  
56      Path getBasePath() {
57          return basePath;
58      }
59  
60      @Override
61      public int classify(Throwable error) {
62          if (error instanceof ResourceNotFoundException) {
63              return ERROR_NOT_FOUND;
64          }
65          return ERROR_OTHER;
66      }
67  
68      private FileOp effectiveFileOp(FileOp wanted, GetTask task) {
69          if (task.getDataPath() != null) {
70              return wanted;
71          }
72          // task carries no path, caller wants in-memory read, so COPY must be used
73          return FileOp.COPY;
74      }
75  
76      @Override
77      protected void implPeek(PeekTask task) throws Exception {
78          getPath(task, true);
79      }
80  
81      @Override
82      protected void implGet(GetTask task) throws Exception {
83          Path path = getPath(task, true);
84          long size = Files.size(path);
85          FileOp effective = effectiveFileOp(fileOp, task);
86          switch (effective) {
87              case COPY:
88                  utilGet(task, Files.newInputStream(path), true, size, false);
89                  break;
90              case SYMLINK:
91              case HARDLINK:
92                  Files.deleteIfExists(task.getDataPath());
93                  task.getListener().transportStarted(0L, size);
94                  if (effective == FileOp.HARDLINK) {
95                      Files.createLink(task.getDataPath(), path);
96                  } else {
97                      Files.createSymbolicLink(task.getDataPath(), path);
98                  }
99                  if (size > 0) {
100                     try (FileChannel fc = FileChannel.open(path)) {
101                         try {
102                             task.getListener().transportProgressed(fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()));
103                         } catch (UnsupportedOperationException e) {
104                             // not all FS support mmap: fallback to "plain read loop"
105                             ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 32);
106                             while (fc.read(byteBuffer) != -1) {
107                                 byteBuffer.flip();
108                                 task.getListener().transportProgressed(byteBuffer);
109                                 byteBuffer.clear();
110                             }
111                         }
112                     }
113                 }
114                 break;
115             default:
116                 throw new IllegalStateException("Unknown fileOp" + fileOp);
117         }
118     }
119 
120     @Override
121     protected void implPut(PutTask task) throws Exception {
122         Path path = getPath(task, false);
123         Files.createDirectories(path.getParent());
124         try {
125             utilPut(task, Files.newOutputStream(path), true);
126         } catch (Exception e) {
127             Files.deleteIfExists(path);
128             throw e;
129         }
130     }
131 
132     private Path getPath(TransportTask task, boolean required) throws Exception {
133         String path = task.getLocation().getPath();
134         if (path.contains("../")) {
135             throw new IllegalArgumentException("illegal resource path: " + path);
136         }
137         Path file = basePath.resolve(path);
138         if (required && !Files.isRegularFile(file)) {
139             throw new ResourceNotFoundException("Could not locate " + file);
140         }
141         return file;
142     }
143 
144     @Override
145     protected void implClose() {}
146 }