001 /**
002 * Copyright 2010-2013 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, AuthenticationException {
121 connect(source, null, proxyInfo);
122 }
123
124 public final void connect(final Repository source, final AuthenticationInfo authenticationInfo) throws ConnectionException, AuthenticationException {
125 doConnect(source, authenticationInfo, null);
126 }
127
128 protected void doConnect(final Repository source, final AuthenticationInfo authenticationInfo, final ProxyInfo proxyInfo) throws ConnectionException, AuthenticationException {
129 repository = source;
130 log.debug("Connecting to " + repository.getUrl());
131 sessionListeners.fireSessionOpening();
132 try {
133 connectToRepository(source, authenticationInfo, proxyInfo);
134 } catch (ConnectionException e) {
135 sessionListeners.fireSessionConnectionRefused();
136 throw e;
137 } catch (AuthenticationException e) {
138 sessionListeners.fireSessionConnectionRefused();
139 throw e;
140 } catch (Exception e) {
141 sessionListeners.fireSessionConnectionRefused();
142 throw new ConnectionException("Could not connect to repository", e);
143 }
144 sessionListeners.fireSessionLoggedIn();
145 sessionListeners.fireSessionOpened();
146 }
147
148 public final void connect(final Repository source, final AuthenticationInfo authenticationInfo, final ProxyInfo proxyInfo) throws ConnectionException, AuthenticationException {
149 doConnect(source, authenticationInfo, proxyInfo);
150 }
151
152 public final void disconnect() throws ConnectionException {
153 sessionListeners.fireSessionDisconnecting();
154 try {
155 disconnectFromRepository();
156 } catch (ConnectionException e) {
157 sessionListeners.fireSessionConnectionRefused();
158 throw e;
159 } catch (Exception e) {
160 sessionListeners.fireSessionConnectionRefused();
161 throw new ConnectionException("Could not disconnect from repository", e);
162 }
163 sessionListeners.fireSessionLoggedOff();
164 sessionListeners.fireSessionDisconnected();
165 }
166
167 public final void get(final String resourceName, final File destination) throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
168 Resource resource = new Resource(resourceName);
169 transferListeners.fireTransferInitiated(resource, TransferEvent.REQUEST_GET);
170 transferListeners.fireTransferStarted(resource, TransferEvent.REQUEST_GET);
171
172 try {
173 getResource(resourceName, destination, new TransferProgress(resource, TransferEvent.REQUEST_GET, transferListeners));
174 transferListeners.fireTransferCompleted(resource, TransferEvent.REQUEST_GET);
175 } catch (TransferFailedException e) {
176 throw e;
177 } catch (ResourceDoesNotExistException e) {
178 throw e;
179 } catch (AuthorizationException e) {
180 throw e;
181 } catch (Exception e) {
182 transferListeners.fireTransferError(resource, TransferEvent.REQUEST_GET, e);
183 throw new TransferFailedException("Transfer of resource " + destination + "failed", e);
184 }
185 }
186
187 public final List<String> getFileList(final String destinationDirectory) throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
188 try {
189 return listDirectory(destinationDirectory);
190 } catch (TransferFailedException e) {
191 throw e;
192 } catch (ResourceDoesNotExistException e) {
193 throw e;
194 } catch (AuthorizationException e) {
195 throw e;
196 } catch (Exception e) {
197 sessionListeners.fireSessionError(e);
198 throw new TransferFailedException("Listing of directory " + destinationDirectory + "failed", e);
199 }
200 }
201
202 public final boolean getIfNewer(final String resourceName, final File destination, final long timestamp) throws TransferFailedException, ResourceDoesNotExistException,
203 AuthorizationException {
204 Resource resource = new Resource(resourceName);
205 try {
206 if (isRemoteResourceNewer(resourceName, timestamp)) {
207 get(resourceName, destination);
208 return true;
209 } else {
210 return false;
211 }
212 } catch (TransferFailedException e) {
213 throw e;
214 } catch (ResourceDoesNotExistException e) {
215 throw e;
216 } catch (AuthorizationException e) {
217 throw e;
218 } catch (Exception e) {
219 transferListeners.fireTransferError(resource, TransferEvent.REQUEST_GET, e);
220 throw new TransferFailedException("Transfer of resource " + destination + "failed", e);
221 }
222 }
223
224 public final void openConnection() throws ConnectionException, AuthenticationException {
225 // Nothing to do here (never called by the wagon manager)
226 }
227
228 protected PutFileContext getPutFileContext(File source, String destination) {
229 Resource resource = new Resource(destination);
230 PutFileContext context = new PutFileContext();
231 context.setResource(resource);
232 context.setProgress(new TransferProgress(resource, TransferEvent.REQUEST_PUT, transferListeners));
233 context.setListeners(transferListeners);
234 context.setDestination(destination);
235 context.setSource(source);
236 return context;
237 }
238
239 public final void put(final File source, final String destination) throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
240 PutFileContext context = getPutFileContext(source, destination);
241
242 try {
243 context.fireStart();
244 putResource(source, destination, context.getProgress());
245 context.fireComplete();
246 } catch (Exception e) {
247 handleException(e, context);
248 }
249 }
250
251 protected void handleException(Exception e, PutFileContext context) throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
252 if (e instanceof TransferFailedException) {
253 throw (TransferFailedException) e;
254 }
255 if (e instanceof ResourceDoesNotExistException) {
256 throw (ResourceDoesNotExistException) e;
257 }
258 if (e instanceof AuthorizationException) {
259 throw (AuthorizationException) e;
260 }
261 transferListeners.fireTransferError(context.getResource(), TransferEvent.REQUEST_PUT, e);
262 throw new TransferFailedException("Transfer of resource " + context.getDestination() + "failed", e);
263 }
264
265 protected List<PutFileContext> getPutFileContexts(File sourceDirectory, String destinationDirectory) {
266 List<PutFileContext> contexts = new ArrayList<PutFileContext>();
267 // Cycle through all the files in this directory
268 for (File f : sourceDirectory.listFiles()) {
269
270 /**
271 * The filename is used 2 ways:<br>
272 *
273 * 1 - as a "key" into the bucket<br>
274 * 2 - In the http url itself<br>
275 *
276 * We encode it here so the key matches the url AND to guarantee that the url is valid even in cases where filenames contain
277 * characters (eg spaces) that are not allowed in urls
278 */
279 String filename = encodeUTF8(f.getName());
280
281 // We hit a directory
282 if (f.isDirectory()) {
283 // Recurse into the sub-directory and create put requests for any files we find
284 contexts.addAll(getPutFileContexts(f, destinationDirectory + "/" + filename));
285 } else {
286 PutFileContext context = getPutFileContext(f, destinationDirectory + "/" + filename);
287 contexts.add(context);
288 }
289 }
290 return contexts;
291 }
292
293 protected String encodeUTF8(String s) {
294 try {
295 return URLEncoder.encode(s, "UTF-8");
296 } catch (UnsupportedEncodingException e) {
297 throw new RuntimeException(e);
298 }
299 }
300
301 public final boolean resourceExists(final String resourceName) throws TransferFailedException, AuthorizationException {
302 try {
303 return doesRemoteResourceExist(resourceName);
304 } catch (TransferFailedException e) {
305 throw e;
306 } catch (AuthorizationException e) {
307 throw e;
308 } catch (Exception e) {
309 sessionListeners.fireSessionError(e);
310 throw new TransferFailedException("Listing of resource " + resourceName + "failed", e);
311 }
312 }
313
314 public final boolean supportsDirectoryCopy() {
315 return supportsDirectoryCopy;
316 }
317
318 /**
319 * Subclass must implement with specific connection behavior
320 *
321 * @param source
322 * The repository connection information
323 * @param authenticationInfo
324 * Authentication information, if any
325 * @param proxyInfo
326 * Proxy information, if any
327 * @throws Exception
328 * Implementations can throw any exception and it will be handled by the base class
329 */
330 protected abstract void connectToRepository(Repository source, AuthenticationInfo authenticationInfo, ProxyInfo proxyInfo) throws Exception;
331
332 /**
333 * Subclass must implement with specific detection behavior
334 *
335 * @param resourceName
336 * The remote resource to detect
337 * @return true if the remote resource exists
338 * @throws Exception
339 * Implementations can throw any exception and it will be handled by the base class
340 */
341 protected abstract boolean doesRemoteResourceExist(String resourceName) throws Exception;
342
343 /**
344 * Subclasses must implement with specific disconnection behavior
345 *
346 * @throws Exception
347 * Implementations can throw any exception and it will be handled by the base class
348 */
349 protected abstract void disconnectFromRepository() throws Exception;
350
351 /**
352 * Subclass must implement with specific get behavior
353 *
354 * @param resourceName
355 * The name of the remote resource to read
356 * @param destination
357 * The local file to write to
358 * @param progress
359 * A progress notifier for the upload. It must be used or hashes will not be calculated correctly
360 * @throws Exception
361 * Implementations can throw any exception and it will be handled by the base class
362 */
363 protected abstract void getResource(String resourceName, File destination, TransferProgress progress) throws Exception;
364
365 /**
366 * Subclass must implement with newer detection behavior
367 *
368 * @param resourceName
369 * The name of the resource being compared
370 * @param timestamp
371 * The timestamp to compare against
372 * @return true if the current version of the resource is newer than the timestamp
373 * @throws Exception
374 * Implementations can throw any exception and it will be handled by the base class
375 */
376 protected abstract boolean isRemoteResourceNewer(String resourceName, long timestamp) throws Exception;
377
378 /**
379 * Subclass must implement with specific directory listing behavior
380 *
381 * @param directory
382 * The directory to list files in
383 * @return A collection of file names
384 * @throws Exception
385 * Implementations can throw any exception and it will be handled by the base class
386 */
387 protected abstract List<String> listDirectory(String directory) throws Exception;
388
389 /**
390 * Subclasses must implement with specific put behavior
391 *
392 * @param source
393 * The local source file to read from
394 * @param destination
395 * The name of the remote resource to write to
396 * @param progress
397 * A progress notifier for the upload. It must be used or hashes will not be calculated correctly
398 * @throws Exception
399 * Implementations can throw any exception and it will be handled by the base class
400 */
401 protected abstract void putResource(File source, String destination, TransferProgress progress) throws Exception;
402
403 public void connect(final Repository source, final AuthenticationInfo authenticationInfo, final ProxyInfoProvider proxyInfoProvider) throws ConnectionException,
404 AuthenticationException {
405 doConnect(source, authenticationInfo, null);
406 }
407
408 public void connect(final Repository source, final ProxyInfoProvider proxyInfoProvider) throws ConnectionException, AuthenticationException {
409 doConnect(source, null, null);
410 }
411
412 public int getTimeout() {
413 return this.timeout;
414 }
415
416 public void setTimeout(final int timeoutValue) {
417 this.timeout = timeoutValue;
418 }
419
420 }