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 try (OutputStream os = task.newOutputStream(resume)) { 94 task.getListener().transportStarted(resume ? task.getResumeOffset() : 0L, length); 95 copy(os, is, task.getListener()); 96 } finally { 97 if (close) { 98 is.close(); 99 } 100 } 101 } 102 103 public void put(PutTask task) throws Exception { 104 requireNonNull(task, "task cannot be null"); 105 106 failIfClosed(task); 107 implPut(task); 108 } 109 110 /** 111 * Implements {@link #put(PutTask)}, gets only called if the transporter has not been closed. 112 * 113 * @param task The upload to perform, must not be {@code null}. 114 * @throws Exception If the transfer failed. 115 */ 116 protected abstract void implPut(PutTask task) throws Exception; 117 118 /** 119 * Performs stream-based I/O for the specified upload task and notifies the configured transport listener. 120 * Subclasses might want to invoke this utility method from within their {@link #implPut(PutTask)} to avoid 121 * boilerplate I/O code. 122 * 123 * @param task The upload to perform, must not be {@code null}. 124 * @param os The output stream to upload the data to, must not be {@code null}. 125 * @param close {@code true} if the supplied output stream should be automatically closed, {@code false} to leave 126 * the stream open. 127 * @throws IOException If the transfer encountered an I/O error. 128 * @throws TransferCancelledException If the transfer was cancelled. 129 */ 130 protected void utilPut(PutTask task, OutputStream os, boolean close) 131 throws IOException, TransferCancelledException { 132 try (InputStream is = task.newInputStream()) { 133 task.getListener().transportStarted(0, task.getDataLength()); 134 copy(os, is, task.getListener()); 135 } finally { 136 if (close) { 137 os.close(); 138 } else { 139 os.flush(); 140 } 141 } 142 } 143 144 public void close() { 145 if (closed.compareAndSet(false, true)) { 146 implClose(); 147 } 148 } 149 150 /** 151 * Implements {@link #close()}, gets only called if the transporter has not already been closed. 152 */ 153 protected abstract void implClose(); 154 155 private void failIfClosed(TransportTask task) { 156 if (closed.get()) { 157 throw new IllegalStateException("transporter closed, cannot execute task " + task); 158 } 159 } 160 161 private static void copy(OutputStream os, InputStream is, TransportListener listener) 162 throws IOException, TransferCancelledException { 163 byte[] buffer = new byte[1024 * 32]; 164 for (int read = is.read(buffer); read >= 0; read = is.read(buffer)) { 165 os.write(buffer, 0, read); 166 listener.transportProgressed(ByteBuffer.wrap(buffer, 0, read)); 167 } 168 } 169 }