001 /** 002 * Copyright 2010-2012 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.maven.wagon; 017 018 import java.io.File; 019 import java.io.UnsupportedEncodingException; 020 import java.net.URLEncoder; 021 import java.util.ArrayList; 022 import java.util.List; 023 024 import org.apache.maven.wagon.ConnectionException; 025 import org.apache.maven.wagon.ResourceDoesNotExistException; 026 import org.apache.maven.wagon.TransferFailedException; 027 import org.apache.maven.wagon.Wagon; 028 import org.apache.maven.wagon.authentication.AuthenticationException; 029 import org.apache.maven.wagon.authentication.AuthenticationInfo; 030 import org.apache.maven.wagon.authorization.AuthorizationException; 031 import org.apache.maven.wagon.events.SessionListener; 032 import org.apache.maven.wagon.events.TransferEvent; 033 import org.apache.maven.wagon.events.TransferListener; 034 import org.apache.maven.wagon.observers.Debug; 035 import org.apache.maven.wagon.proxy.ProxyInfo; 036 import org.apache.maven.wagon.proxy.ProxyInfoProvider; 037 import org.apache.maven.wagon.repository.Repository; 038 import org.apache.maven.wagon.resource.Resource; 039 import org.slf4j.Logger; 040 import org.slf4j.LoggerFactory; 041 042 /** 043 * An abstract implementation of the Wagon interface. This implementation manages listener and other common behaviors. 044 * 045 * @author Ben Hale 046 * @author Jeff Caddel - Updates for version 2.0 of the Wagon interface 047 * @since 1.1 048 */ 049 public abstract class AbstractWagon implements Wagon { 050 private final static Logger log = LoggerFactory.getLogger(AbstractWagon.class); 051 052 private int timeout; 053 054 private boolean interactive; 055 056 private Repository repository; 057 058 private final boolean supportsDirectoryCopy; 059 060 private final SessionListenerSupport sessionListeners = new SessionListenerSupport(this); 061 062 private final TransferListenerSupport transferListeners = new TransferListenerSupport(this); 063 064 protected AbstractWagon(final boolean supportsDirectoryCopy) { 065 this.supportsDirectoryCopy = supportsDirectoryCopy; 066 } 067 068 public final void addSessionListener(final SessionListener listener) { 069 if (listener.getClass().equals(Debug.class)) { 070 // This is a junky listener that spews things to System.out in an ugly way 071 return; 072 } 073 sessionListeners.addListener(listener); 074 } 075 076 protected final SessionListenerSupport getSessionListeners() { 077 return sessionListeners; 078 } 079 080 public final boolean hasSessionListener(final SessionListener listener) { 081 return sessionListeners.hasListener(listener); 082 } 083 084 public final void removeSessionListener(final SessionListener listener) { 085 sessionListeners.removeListener(listener); 086 } 087 088 public final void addTransferListener(final TransferListener listener) { 089 transferListeners.addListener(listener); 090 } 091 092 protected final TransferListenerSupport getTransferListeners() { 093 return transferListeners; 094 } 095 096 public final boolean hasTransferListener(final TransferListener listener) { 097 return transferListeners.hasListener(listener); 098 } 099 100 public final void removeTransferListener(final TransferListener listener) { 101 transferListeners.removeListener(listener); 102 } 103 104 public final Repository getRepository() { 105 return repository; 106 } 107 108 public final boolean isInteractive() { 109 return interactive; 110 } 111 112 public final void setInteractive(final boolean interactive) { 113 this.interactive = interactive; 114 } 115 116 public final void connect(final Repository source) throws ConnectionException, AuthenticationException { 117 doConnect(source, null, null); 118 } 119 120 public final void connect(final Repository source, final ProxyInfo proxyInfo) throws ConnectionException, 121 AuthenticationException { 122 connect(source, null, proxyInfo); 123 } 124 125 public final void connect(final Repository source, final AuthenticationInfo authenticationInfo) 126 throws ConnectionException, AuthenticationException { 127 doConnect(source, authenticationInfo, null); 128 } 129 130 protected void doConnect(final Repository source, final AuthenticationInfo authenticationInfo, 131 final ProxyInfo proxyInfo) throws ConnectionException, AuthenticationException { 132 repository = source; 133 log.debug("Connecting to " + repository.getUrl()); 134 sessionListeners.fireSessionOpening(); 135 try { 136 connectToRepository(source, authenticationInfo, proxyInfo); 137 } catch (ConnectionException e) { 138 sessionListeners.fireSessionConnectionRefused(); 139 throw e; 140 } catch (AuthenticationException e) { 141 sessionListeners.fireSessionConnectionRefused(); 142 throw e; 143 } catch (Exception e) { 144 sessionListeners.fireSessionConnectionRefused(); 145 throw new ConnectionException("Could not connect to repository", e); 146 } 147 sessionListeners.fireSessionLoggedIn(); 148 sessionListeners.fireSessionOpened(); 149 } 150 151 public final void connect(final Repository source, final AuthenticationInfo authenticationInfo, 152 final ProxyInfo proxyInfo) throws ConnectionException, AuthenticationException { 153 doConnect(source, authenticationInfo, proxyInfo); 154 } 155 156 public final void disconnect() throws ConnectionException { 157 sessionListeners.fireSessionDisconnecting(); 158 try { 159 disconnectFromRepository(); 160 } catch (ConnectionException e) { 161 sessionListeners.fireSessionConnectionRefused(); 162 throw e; 163 } catch (Exception e) { 164 sessionListeners.fireSessionConnectionRefused(); 165 throw new ConnectionException("Could not disconnect from repository", e); 166 } 167 sessionListeners.fireSessionLoggedOff(); 168 sessionListeners.fireSessionDisconnected(); 169 } 170 171 public final void get(final String resourceName, final File destination) throws TransferFailedException, 172 ResourceDoesNotExistException, AuthorizationException { 173 Resource resource = new Resource(resourceName); 174 transferListeners.fireTransferInitiated(resource, TransferEvent.REQUEST_GET); 175 transferListeners.fireTransferStarted(resource, TransferEvent.REQUEST_GET); 176 177 try { 178 getResource(resourceName, destination, new TransferProgress(resource, TransferEvent.REQUEST_GET, 179 transferListeners)); 180 transferListeners.fireTransferCompleted(resource, TransferEvent.REQUEST_GET); 181 } catch (TransferFailedException e) { 182 throw e; 183 } catch (ResourceDoesNotExistException e) { 184 throw e; 185 } catch (AuthorizationException e) { 186 throw e; 187 } catch (Exception e) { 188 transferListeners.fireTransferError(resource, TransferEvent.REQUEST_GET, e); 189 throw new TransferFailedException("Transfer of resource " + destination + "failed", e); 190 } 191 } 192 193 public final List<String> getFileList(final String destinationDirectory) throws TransferFailedException, 194 ResourceDoesNotExistException, AuthorizationException { 195 try { 196 return listDirectory(destinationDirectory); 197 } catch (TransferFailedException e) { 198 throw e; 199 } catch (ResourceDoesNotExistException e) { 200 throw e; 201 } catch (AuthorizationException e) { 202 throw e; 203 } catch (Exception e) { 204 sessionListeners.fireSessionError(e); 205 throw new TransferFailedException("Listing of directory " + destinationDirectory + "failed", e); 206 } 207 } 208 209 public final boolean getIfNewer(final String resourceName, final File destination, final long timestamp) 210 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException { 211 Resource resource = new Resource(resourceName); 212 try { 213 if (isRemoteResourceNewer(resourceName, timestamp)) { 214 get(resourceName, destination); 215 return true; 216 } else { 217 return false; 218 } 219 } catch (TransferFailedException e) { 220 throw e; 221 } catch (ResourceDoesNotExistException e) { 222 throw e; 223 } catch (AuthorizationException e) { 224 throw e; 225 } catch (Exception e) { 226 transferListeners.fireTransferError(resource, TransferEvent.REQUEST_GET, e); 227 throw new TransferFailedException("Transfer of resource " + destination + "failed", e); 228 } 229 } 230 231 public final void openConnection() throws ConnectionException, AuthenticationException { 232 // Nothing to do here (never called by the wagon manager) 233 } 234 235 protected PutFileContext getPutFileContext(File source, String destination) { 236 Resource resource = new Resource(destination); 237 PutFileContext context = new PutFileContext(); 238 context.setResource(resource); 239 context.setProgress(new TransferProgress(resource, TransferEvent.REQUEST_PUT, transferListeners)); 240 context.setListeners(transferListeners); 241 context.setDestination(destination); 242 context.setSource(source); 243 return context; 244 } 245 246 public final void put(final File source, final String destination) throws TransferFailedException, 247 ResourceDoesNotExistException, AuthorizationException { 248 PutFileContext context = getPutFileContext(source, destination); 249 250 try { 251 context.fireStart(); 252 putResource(source, destination, context.getProgress()); 253 context.fireComplete(); 254 } catch (Exception e) { 255 handleException(e, context); 256 } 257 } 258 259 protected void handleException(Exception e, PutFileContext context) throws TransferFailedException, 260 ResourceDoesNotExistException, AuthorizationException { 261 if (e instanceof TransferFailedException) { 262 throw (TransferFailedException) e; 263 } 264 if (e instanceof ResourceDoesNotExistException) { 265 throw (ResourceDoesNotExistException) e; 266 } 267 if (e instanceof AuthorizationException) { 268 throw (AuthorizationException) e; 269 } 270 transferListeners.fireTransferError(context.getResource(), TransferEvent.REQUEST_PUT, e); 271 throw new TransferFailedException("Transfer of resource " + context.getDestination() + "failed", e); 272 } 273 274 protected List<PutFileContext> getPutFileContexts(File sourceDirectory, String destinationDirectory) { 275 List<PutFileContext> contexts = new ArrayList<PutFileContext>(); 276 // Cycle through all the files in this directory 277 for (File f : sourceDirectory.listFiles()) { 278 279 /** 280 * The filename is used 2 ways:<br> 281 * 282 * 1 - as a "key" into the bucket<br> 283 * 2 - In the http url itself<br> 284 * 285 * We encode it here so the key matches the url AND to guarantee that the url is valid even in cases where 286 * filenames contain characters (eg spaces) that are not allowed in urls 287 */ 288 String filename = encodeUTF8(f.getName()); 289 290 // We hit a directory 291 if (f.isDirectory()) { 292 // Recurse into the sub-directory and create put requests for any files we find 293 contexts.addAll(getPutFileContexts(f, destinationDirectory + "/" + filename)); 294 } else { 295 PutFileContext context = getPutFileContext(f, destinationDirectory + "/" + filename); 296 contexts.add(context); 297 } 298 } 299 return contexts; 300 } 301 302 protected String encodeUTF8(String s) { 303 try { 304 return URLEncoder.encode(s, "UTF-8"); 305 } catch (UnsupportedEncodingException e) { 306 throw new RuntimeException(e); 307 } 308 } 309 310 public final boolean resourceExists(final String resourceName) throws TransferFailedException, 311 AuthorizationException { 312 try { 313 return doesRemoteResourceExist(resourceName); 314 } catch (TransferFailedException e) { 315 throw e; 316 } catch (AuthorizationException e) { 317 throw e; 318 } catch (Exception e) { 319 sessionListeners.fireSessionError(e); 320 throw new TransferFailedException("Listing of resource " + resourceName + "failed", e); 321 } 322 } 323 324 public final boolean supportsDirectoryCopy() { 325 return supportsDirectoryCopy; 326 } 327 328 /** 329 * Subclass must implement with specific connection behavior 330 * 331 * @param source 332 * The repository connection information 333 * @param authenticationInfo 334 * Authentication information, if any 335 * @param proxyInfo 336 * Proxy information, if any 337 * @throws Exception 338 * Implementations can throw any exception and it will be handled by the base class 339 */ 340 protected abstract void connectToRepository(Repository source, AuthenticationInfo authenticationInfo, 341 ProxyInfo proxyInfo) throws Exception; 342 343 /** 344 * Subclass must implement with specific detection behavior 345 * 346 * @param resourceName 347 * The remote resource to detect 348 * @return true if the remote resource exists 349 * @throws Exception 350 * Implementations can throw any exception and it will be handled by the base class 351 */ 352 protected abstract boolean doesRemoteResourceExist(String resourceName) throws Exception; 353 354 /** 355 * Subclasses must implement with specific disconnection behavior 356 * 357 * @throws Exception 358 * Implementations can throw any exception and it will be handled by the base class 359 */ 360 protected abstract void disconnectFromRepository() throws Exception; 361 362 /** 363 * Subclass must implement with specific get behavior 364 * 365 * @param resourceName 366 * The name of the remote resource to read 367 * @param destination 368 * The local file to write to 369 * @param progress 370 * A progress notifier for the upload. It must be used or hashes will not be calculated correctly 371 * @throws Exception 372 * Implementations can throw any exception and it will be handled by the base class 373 */ 374 protected abstract void getResource(String resourceName, File destination, TransferProgress progress) 375 throws Exception; 376 377 /** 378 * Subclass must implement with newer detection behavior 379 * 380 * @param resourceName 381 * The name of the resource being compared 382 * @param timestamp 383 * The timestamp to compare against 384 * @return true if the current version of the resource is newer than the timestamp 385 * @throws Exception 386 * Implementations can throw any exception and it will be handled by the base class 387 */ 388 protected abstract boolean isRemoteResourceNewer(String resourceName, long timestamp) throws Exception; 389 390 /** 391 * Subclass must implement with specific directory listing behavior 392 * 393 * @param directory 394 * The directory to list files in 395 * @return A collection of file names 396 * @throws Exception 397 * Implementations can throw any exception and it will be handled by the base class 398 */ 399 protected abstract List<String> listDirectory(String directory) throws Exception; 400 401 /** 402 * Subclasses must implement with specific put behavior 403 * 404 * @param source 405 * The local source file to read from 406 * @param destination 407 * The name of the remote resource to write to 408 * @param progress 409 * A progress notifier for the upload. It must be used or hashes will not be calculated correctly 410 * @throws Exception 411 * Implementations can throw any exception and it will be handled by the base class 412 */ 413 protected abstract void putResource(File source, String destination, TransferProgress progress) throws Exception; 414 415 public void connect(final Repository source, final AuthenticationInfo authenticationInfo, 416 final ProxyInfoProvider proxyInfoProvider) throws ConnectionException, AuthenticationException { 417 doConnect(source, authenticationInfo, null); 418 } 419 420 public void connect(final Repository source, final ProxyInfoProvider proxyInfoProvider) throws ConnectionException, 421 AuthenticationException { 422 doConnect(source, null, null); 423 } 424 425 public int getTimeout() { 426 return this.timeout; 427 } 428 429 public void setTimeout(final int timeoutValue) { 430 this.timeout = timeoutValue; 431 } 432 433 }