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