001package org.eclipse.aether.spi.connector.transport; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.OutputStream; 025import java.nio.Buffer; 026import java.nio.ByteBuffer; 027import java.util.Objects; 028import java.util.concurrent.atomic.AtomicBoolean; 029 030import org.eclipse.aether.transfer.TransferCancelledException; 031 032/** 033 * A skeleton implementation for custom transporters. 034 */ 035public abstract class AbstractTransporter 036 implements Transporter 037{ 038 039 private final AtomicBoolean closed; 040 041 /** 042 * Enables subclassing. 043 */ 044 protected AbstractTransporter() 045 { 046 closed = new AtomicBoolean(); 047 } 048 049 public void peek( PeekTask task ) 050 throws Exception 051 { 052 Objects.requireNonNull( "task", "task cannot be null" ); 053 054 failIfClosed( task ); 055 implPeek( task ); 056 } 057 058 /** 059 * Implements {@link #peek(PeekTask)}, gets only called if the transporter has not been closed. 060 * 061 * @param task The existence check to perform, must not be {@code null}. 062 * @throws Exception If the existence of the specified resource could not be confirmed. 063 */ 064 protected abstract void implPeek( PeekTask task ) 065 throws Exception; 066 067 public void get( GetTask task ) 068 throws Exception 069 { 070 Objects.requireNonNull( "task", "task cannot be null" ); 071 072 failIfClosed( task ); 073 implGet( task ); 074 } 075 076 /** 077 * Implements {@link #get(GetTask)}, gets only called if the transporter has not been closed. 078 * 079 * @param task The download to perform, must not be {@code null}. 080 * @throws Exception If the transfer failed. 081 */ 082 protected abstract void implGet( GetTask task ) 083 throws Exception; 084 085 /** 086 * Performs stream-based I/O for the specified download task and notifies the configured transport listener. 087 * Subclasses might want to invoke this utility method from within their {@link #implGet(GetTask)} to avoid 088 * boilerplate I/O code. 089 * 090 * @param task The download to perform, must not be {@code null}. 091 * @param is The input stream to download the data from, must not be {@code null}. 092 * @param close {@code true} if the supplied input stream should be automatically closed, {@code false} to leave the 093 * stream open. 094 * @param length The size in bytes of the downloaded resource or {@code -1} if unknown, not to be confused with the 095 * length of the supplied input stream which might be smaller if the download is resumed. 096 * @param resume {@code true} if the download resumes from {@link GetTask#getResumeOffset()}, {@code false} if the 097 * download starts at the first byte of the resource. 098 * @throws IOException If the transfer encountered an I/O error. 099 * @throws TransferCancelledException If the transfer was cancelled. 100 */ 101 protected void utilGet( GetTask task, InputStream is, boolean close, long length, boolean resume ) 102 throws IOException, TransferCancelledException 103 { 104 OutputStream os = null; 105 try 106 { 107 os = task.newOutputStream( resume ); 108 task.getListener().transportStarted( resume ? task.getResumeOffset() : 0L, length ); 109 copy( os, is, task.getListener() ); 110 os.close(); 111 os = null; 112 113 if ( close ) 114 { 115 is.close(); 116 is = null; 117 } 118 } 119 finally 120 { 121 try 122 { 123 if ( os != null ) 124 { 125 os.close(); 126 } 127 } 128 catch ( final IOException e ) 129 { 130 // Suppressed due to an exception already thrown in the try block. 131 } 132 finally 133 { 134 try 135 { 136 if ( close && is != null ) 137 { 138 is.close(); 139 } 140 } 141 catch ( final IOException e ) 142 { 143 // Suppressed due to an exception already thrown in the try block. 144 } 145 } 146 } 147 } 148 149 public void put( PutTask task ) 150 throws Exception 151 { 152 Objects.requireNonNull( "task", "task cannot be null" ); 153 154 failIfClosed( task ); 155 implPut( task ); 156 } 157 158 /** 159 * Implements {@link #put(PutTask)}, gets only called if the transporter has not been closed. 160 * 161 * @param task The upload to perform, must not be {@code null}. 162 * @throws Exception If the transfer failed. 163 */ 164 protected abstract void implPut( PutTask task ) 165 throws Exception; 166 167 /** 168 * Performs stream-based I/O for the specified upload task and notifies the configured transport listener. 169 * Subclasses might want to invoke this utility method from within their {@link #implPut(PutTask)} to avoid 170 * boilerplate I/O code. 171 * 172 * @param task The upload to perform, must not be {@code null}. 173 * @param os The output stream to upload the data to, must not be {@code null}. 174 * @param close {@code true} if the supplied output stream should be automatically closed, {@code false} to leave 175 * the stream open. 176 * @throws IOException If the transfer encountered an I/O error. 177 * @throws TransferCancelledException If the transfer was cancelled. 178 */ 179 protected void utilPut( PutTask task, OutputStream os, boolean close ) 180 throws IOException, TransferCancelledException 181 { 182 InputStream is = null; 183 try 184 { 185 task.getListener().transportStarted( 0, task.getDataLength() ); 186 is = task.newInputStream(); 187 copy( os, is, task.getListener() ); 188 189 if ( close ) 190 { 191 os.close(); 192 } 193 else 194 { 195 os.flush(); 196 } 197 198 os = null; 199 200 is.close(); 201 is = null; 202 } 203 finally 204 { 205 try 206 { 207 if ( close && os != null ) 208 { 209 os.close(); 210 } 211 } 212 catch ( final IOException e ) 213 { 214 // Suppressed due to an exception already thrown in the try block. 215 } 216 finally 217 { 218 try 219 { 220 if ( is != null ) 221 { 222 is.close(); 223 } 224 } 225 catch ( final IOException e ) 226 { 227 // Suppressed due to an exception already thrown in the try block. 228 } 229 } 230 } 231 } 232 233 public void close() 234 { 235 if ( closed.compareAndSet( false, true ) ) 236 { 237 implClose(); 238 } 239 } 240 241 /** 242 * Implements {@link #close()}, gets only called if the transporter has not already been closed. 243 */ 244 protected abstract void implClose(); 245 246 private void failIfClosed( TransportTask task ) 247 { 248 if ( closed.get() ) 249 { 250 throw new IllegalStateException( "transporter closed, cannot execute task " + task ); 251 } 252 } 253 254 private static void copy( OutputStream os, InputStream is, TransportListener listener ) 255 throws IOException, TransferCancelledException 256 { 257 ByteBuffer buffer = ByteBuffer.allocate( 1024 * 32 ); 258 byte[] array = buffer.array(); 259 for ( int read = is.read( array ); read >= 0; read = is.read( array ) ) 260 { 261 os.write( array, 0, read ); 262 ( (Buffer) buffer ).rewind(); 263 ( (Buffer) buffer ).limit( read ); 264 listener.transportProgressed( buffer ); 265 } 266 } 267 268}