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