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    }