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.spi.connector.transport;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.nio.ByteBuffer;
25  import java.util.concurrent.atomic.AtomicBoolean;
26  
27  import org.eclipse.aether.transfer.TransferCancelledException;
28  
29  import static java.util.Objects.requireNonNull;
30  
31  /**
32   * A skeleton implementation for custom transporters.
33   */
34  public abstract class AbstractTransporter implements Transporter {
35  
36      private final AtomicBoolean closed;
37  
38      /**
39       * Enables subclassing.
40       */
41      protected AbstractTransporter() {
42          closed = new AtomicBoolean();
43      }
44  
45      public void peek(PeekTask task) throws Exception {
46          requireNonNull(task, "task cannot be null");
47  
48          failIfClosed(task);
49          implPeek(task);
50      }
51  
52      /**
53       * Implements {@link #peek(PeekTask)}, gets only called if the transporter has not been closed.
54       *
55       * @param task The existence check to perform, must not be {@code null}.
56       * @throws Exception If the existence of the specified resource could not be confirmed.
57       */
58      protected abstract void implPeek(PeekTask task) throws Exception;
59  
60      public void get(GetTask task) throws Exception {
61          requireNonNull(task, "task cannot be null");
62  
63          failIfClosed(task);
64          implGet(task);
65      }
66  
67      /**
68       * Implements {@link #get(GetTask)}, gets only called if the transporter has not been closed.
69       *
70       * @param task The download to perform, must not be {@code null}.
71       * @throws Exception If the transfer failed.
72       */
73      protected abstract void implGet(GetTask task) throws Exception;
74  
75      /**
76       * Performs stream-based I/O for the specified download task and notifies the configured transport listener.
77       * Subclasses might want to invoke this utility method from within their {@link #implGet(GetTask)} to avoid
78       * boilerplate I/O code.
79       *
80       * @param task The download to perform, must not be {@code null}.
81       * @param is The input stream to download the data from, must not be {@code null}.
82       * @param close {@code true} if the supplied input stream should be automatically closed, {@code false} to leave the
83       *            stream open.
84       * @param length The size in bytes of the downloaded resource or {@code -1} if unknown, not to be confused with the
85       *            length of the supplied input stream which might be smaller if the download is resumed.
86       * @param resume {@code true} if the download resumes from {@link GetTask#getResumeOffset()}, {@code false} if the
87       *            download starts at the first byte of the resource.
88       * @throws IOException If the transfer encountered an I/O error.
89       * @throws TransferCancelledException If the transfer was cancelled.
90       */
91      protected void utilGet(GetTask task, InputStream is, boolean close, long length, boolean resume)
92              throws IOException, TransferCancelledException {
93          if (close) {
94              try (InputStream input = is;
95                      OutputStream os = task.newOutputStream(resume)) {
96                  task.getListener().transportStarted(resume ? task.getResumeOffset() : 0L, length);
97                  copy(os, input, task.getListener());
98              }
99          } else {
100             try (OutputStream os = task.newOutputStream(resume)) {
101                 task.getListener().transportStarted(resume ? task.getResumeOffset() : 0L, length);
102                 copy(os, is, task.getListener());
103             }
104         }
105     }
106 
107     public void put(PutTask task) throws Exception {
108         requireNonNull(task, "task cannot be null");
109 
110         failIfClosed(task);
111         implPut(task);
112     }
113 
114     /**
115      * Implements {@link #put(PutTask)}, gets only called if the transporter has not been closed.
116      *
117      * @param task The upload to perform, must not be {@code null}.
118      * @throws Exception If the transfer failed.
119      */
120     protected abstract void implPut(PutTask task) throws Exception;
121 
122     /**
123      * Performs stream-based I/O for the specified upload task and notifies the configured transport listener.
124      * Subclasses might want to invoke this utility method from within their {@link #implPut(PutTask)} to avoid
125      * boilerplate I/O code.
126      *
127      * @param task The upload to perform, must not be {@code null}.
128      * @param os The output stream to upload the data to, must not be {@code null}.
129      * @param close {@code true} if the supplied output stream should be automatically closed, {@code false} to leave
130      *            the stream open.
131      * @throws IOException If the transfer encountered an I/O error.
132      * @throws TransferCancelledException If the transfer was cancelled.
133      */
134     protected void utilPut(PutTask task, OutputStream os, boolean close)
135             throws IOException, TransferCancelledException {
136         if (close) {
137             try (OutputStream output = os;
138                     InputStream is = task.newInputStream()) {
139                 task.getListener().transportStarted(0, task.getDataLength());
140                 copy(output, is, task.getListener());
141             }
142         } else {
143             try (InputStream is = task.newInputStream()) {
144                 task.getListener().transportStarted(0, task.getDataLength());
145                 copy(os, is, task.getListener());
146             } finally {
147                 os.flush();
148             }
149         }
150     }
151 
152     public void close() {
153         if (closed.compareAndSet(false, true)) {
154             implClose();
155         }
156     }
157 
158     /**
159      * Implements {@link #close()}, gets only called if the transporter has not already been closed.
160      */
161     protected abstract void implClose();
162 
163     private void failIfClosed(TransportTask task) {
164         if (closed.get()) {
165             throw new IllegalStateException("transporter closed, cannot execute task " + task);
166         }
167     }
168 
169     private static void copy(OutputStream os, InputStream is, TransportListener listener)
170             throws IOException, TransferCancelledException {
171         byte[] buffer = new byte[1024 * 32];
172         for (int read = is.read(buffer); read >= 0; read = is.read(buffer)) {
173             os.write(buffer, 0, read);
174             listener.transportProgressed(ByteBuffer.wrap(buffer, 0, read));
175         }
176     }
177 }