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    }