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 }