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