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