View Javadoc
1   package net.trajano.wagon.git.internal;
2   
3   import static java.lang.String.format;
4   
5   import java.io.File;
6   import java.io.FileInputStream;
7   import java.io.FileOutputStream;
8   import java.io.IOException;
9   import java.net.URI;
10  import java.net.URISyntaxException;
11  import java.util.LinkedList;
12  import java.util.List;
13  import java.util.ResourceBundle;
14  import java.util.concurrent.ConcurrentHashMap;
15  import java.util.concurrent.ConcurrentMap;
16  import java.util.logging.Level;
17  import java.util.logging.Logger;
18  
19  import org.apache.maven.wagon.ConnectionException;
20  import org.apache.maven.wagon.InputData;
21  import org.apache.maven.wagon.OutputData;
22  import org.apache.maven.wagon.ResourceDoesNotExistException;
23  import org.apache.maven.wagon.StreamWagon;
24  import org.apache.maven.wagon.TransferFailedException;
25  import org.apache.maven.wagon.authentication.AuthenticationException;
26  import org.apache.maven.wagon.authorization.AuthorizationException;
27  import org.codehaus.plexus.util.FileUtils;
28  import org.eclipse.jgit.api.Git;
29  import org.eclipse.jgit.api.errors.GitAPIException;
30  import org.eclipse.jgit.api.errors.InvalidRemoteException;
31  import org.eclipse.jgit.errors.NoRemoteRepositoryException;
32  import org.eclipse.jgit.lib.Constants;
33  import org.eclipse.jgit.lib.RefUpdate;
34  import org.eclipse.jgit.transport.CredentialsProvider;
35  import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
36  
37  /**
38   * Common parts of handling Git based repositories.
39   */
40  public abstract class AbstractGitWagon extends StreamWagon {
41  
42      /**
43       * Logger.
44       */
45      private static final Logger LOG;
46  
47      /**
48       * Messages resource path.
49       */
50      private static final String MESSAGES = "META-INF/Messages";
51  
52      /**
53       * Resource bundle.
54       */
55      private static final ResourceBundle R;
56  
57      static {
58          LOG = Logger.getLogger("net.trajano.wagon.git", MESSAGES);
59          R = ResourceBundle.getBundle(MESSAGES);
60      }
61  
62      /**
63       * Credentials provider.
64       */
65      private CredentialsProvider credentialsProvider;
66  
67      /**
68       * Git cache.
69       */
70      private final ConcurrentMap<String, Git> gitCache = new ConcurrentHashMap<String, Git>();
71  
72      /**
73       * Git URI.
74       */
75      private GitUri gitUri;
76  
77      /**
78       * Builds the wagon specific Git URI based on the repository URL. This is
79       * made public rather than protected to allow testing of the method.
80       *
81       * @param repositoryUrl
82       *            repository URL
83       * @return Git URI
84       * @throws IOException
85       * @throws URISyntaxException
86       */
87      public abstract GitUri buildGitUri(URI repositoryUrl) throws IOException,
88      URISyntaxException;
89  
90      /**
91       * This will commit the local changes and push them to the repository. If
92       * the method is unable to push to the repository without force, it will
93       * throw an exception. {@inheritDoc}
94       */
95      @Override
96      public void closeConnection() throws ConnectionException {
97          try {
98              for (final String gitRemoteUri : gitCache.keySet()) {
99                  final Git git = gitCache.get(gitRemoteUri);
100                 git.add().addFilepattern(".").call(); //$NON-NLS-1$
101                 git.commit().setMessage(R.getString("commitmessage")).call(); //$NON-NLS-1$
102                 git.push().setRemote(gitRemoteUri)
103                         .setCredentialsProvider(credentialsProvider).call();
104                 git.close();
105                 FileUtils.deleteDirectory(git.getRepository().getDirectory());
106             }
107         } catch (final GitAPIException e) {
108             throw new ConnectionException(e.getMessage(), e);
109         } catch (final IOException e) {
110             throw new ConnectionException(e.getMessage(), e);
111         }
112     }
113 
114     /**
115      * This will read from the working copy. File modification date would not be
116      * available as it does not really have any meaningful value. {@inheritDoc}
117      *
118      * @throws ResourceDoesNotExistException
119      *             when the file does not exist
120      * @throws AuthorizationException
121      *             when the file cannot be read
122      */
123     @Override
124     public void fillInputData(final InputData inputData)
125             throws TransferFailedException, ResourceDoesNotExistException,
126             AuthorizationException {
127         try {
128             final File file = getFileForResource(inputData.getResource()
129                     .getName());
130             if (!file.exists()) {
131                 throw new ResourceDoesNotExistException(format(
132                         R.getString("filenotfound"), file)); //$NON-NLS-1$
133             }
134             if (!file.canRead()) {
135                 throw new AuthorizationException(format(
136                         R.getString("cannotreadfile"), file)); //$NON-NLS-1$
137             }
138             inputData.setInputStream(new FileInputStream(file));
139             inputData.getResource().setContentLength(file.length());
140         } catch (final IOException e) {
141             throw new TransferFailedException(e.getMessage(), e);
142         } catch (final GitAPIException e) {
143             throw new TransferFailedException(e.getMessage(), e);
144         } catch (final URISyntaxException e) {
145             throw new TransferFailedException(e.getMessage(), e);
146         }
147     }
148 
149     /**
150      * This will write to the working copy. {@inheritDoc}
151      */
152     @Override
153     public void fillOutputData(final OutputData outputData)
154             throws TransferFailedException {
155         try {
156             final File file = getFileForResource(outputData.getResource()
157                     .getName());
158             if (!file.getParentFile().mkdirs()
159                     && !file.getParentFile().exists()) {
160                 throw new TransferFailedException(format(
161                         R.getString("unabletocreatedirs"), //$NON-NLS-1$
162                         file.getParentFile()));
163             }
164             outputData.setOutputStream(new FileOutputStream(file));
165         } catch (final IOException e) {
166             throw new TransferFailedException(e.getMessage(), e);
167         } catch (final GitAPIException e) {
168             throw new TransferFailedException(e.getMessage(), e);
169         } catch (final URISyntaxException e) {
170             throw new TransferFailedException(e.getMessage(), e);
171         }
172     }
173 
174     /**
175      * This will get the file object for the given resource relative to the
176      * {@link Git} specified for the connection. It will handle resources where
177      * it jumps up past the parent folder.
178      *
179      * @param resourceName
180      *            resource name.
181      * @return file used for the resource.
182      * @throws IOException
183      * @throws GitAPIException
184      *             problem with the GIT API.
185      * @throws URISyntaxException
186      * @throws ResourceDoesNotExistException
187      */
188     protected abstract File getFileForResource(String resourceName)
189             throws GitAPIException, IOException, URISyntaxException;
190 
191     /**
192      * {@inheritDoc}
193      * <p>
194      * Warnings are suppressed for false positive with Sonar and multiple
195      * exceptions on public API. {@inheritDoc}
196      * </p>
197      */
198     @Override
199     @SuppressWarnings("all")
200     public List<String> getFileList(final String directory)
201             throws TransferFailedException, ResourceDoesNotExistException,
202             AuthorizationException {
203         final File dir;
204         try {
205             dir = getFileForResource(directory);
206         } catch (final GitAPIException e) {
207             throw new AuthorizationException(e.getMessage(), e);
208         } catch (final IOException e) {
209             throw new TransferFailedException(e.getMessage(), e);
210         } catch (final URISyntaxException e) {
211             throw new ResourceDoesNotExistException(e.getMessage(), e);
212         }
213         final File[] files = dir.listFiles();
214         if (files == null) {
215             throw new ResourceDoesNotExistException(format(
216                     R.getString("dirnotfound"), dir)); //$NON-NLS-1$
217         }
218         final List<String> list = new LinkedList<String>();
219         for (final File file : files) {
220             String name = file.getName();
221             if (file.isDirectory() && !name.endsWith("/")) { //$NON-NLS-1$
222                 name += "/"; // NOPMD this is easier to read. //$NON-NLS-1$
223             }
224             list.add(name);
225         }
226         return list;
227     }
228 
229     /**
230      * This will create or refresh the working copy. If the working copy cannot
231      * be pulled cleanly this method will fail.
232      *
233      * @param gitRepositoryUri
234      *            remote git repository URI string
235      * @return git
236      * @throws GitAPIException
237      * @throws IOException
238      * @throws URISyntaxException
239      * @thorws ResourceDoesNotExistException remote repository does not exist.
240      */
241     protected Git getGit(final String gitRepositoryUri) throws GitAPIException,
242             IOException, URISyntaxException, ResourceDoesNotExistException {
243         final Git cachedGit = gitCache.get(gitRepositoryUri);
244         LOG.fine("Loading " + gitRepositoryUri);
245         if (cachedGit != null) {
246             return cachedGit;
247         }
248         final File gitDir = File.createTempFile(
249                 gitRepositoryUri.replaceAll("[^A-Za-z]", "_"), "wagon-git"); //$NON-NLS-1$
250         gitDir.delete();
251         gitDir.mkdir();
252 
253         if (getAuthenticationInfo().getUserName() != null) {
254             credentialsProvider = new UsernamePasswordCredentialsProvider(
255                     getAuthenticationInfo().getUserName(),
256                     getAuthenticationInfo().getPassword() == null ? "" //$NON-NLS-1$
257                             : getAuthenticationInfo().getPassword());
258         } else {
259             credentialsProvider = new PassphraseCredentialsProvider(
260                     getAuthenticationInfo().getPassword());
261         }
262         try {
263             final Git git = Git.cloneRepository().setURI(gitRepositoryUri)
264                     .setCredentialsProvider(credentialsProvider)
265                     .setBranch(gitUri.getBranchName()).setDirectory(gitDir)
266                     .call();
267             if (!gitUri.getBranchName().equals(git.getRepository().getBranch())) {
268                 LOG.log(Level.INFO, "missingbranch", gitUri.getBranchName());
269                 final RefUpdate refUpdate = git.getRepository()
270                         .getRefDatabase().newUpdate(Constants.HEAD, true);
271                 refUpdate.setForceUpdate(true);
272                 refUpdate.link("refs/heads/" + gitUri.getBranchName()); //$NON-NLS-1$
273             }
274             gitCache.put(gitRepositoryUri, git);
275             return git;
276         } catch (final InvalidRemoteException e) {
277             throw new ResourceDoesNotExistException(e.getMessage(), e);
278         } catch (final NoRemoteRepositoryException e) {
279             throw new ResourceDoesNotExistException(e.getMessage(), e);
280         }
281     }
282 
283     protected GitUri getGitUri() {
284         return gitUri;
285     }
286 
287     /**
288      * Sets the initial git URI.
289      */
290     @Override
291     protected void openConnectionInternal() throws ConnectionException,
292     AuthenticationException {
293         URI uri;
294         try {
295             uri = new URI(
296                     new URI(getRepository().getUrl().replace("##", "#"))
297                     .getSchemeSpecificPart()).normalize();
298             gitUri = buildGitUri(uri);
299         } catch (final URISyntaxException e) {
300             throw new ConnectionException(e.getMessage(), e);
301         } catch (final IOException e) {
302             throw new ConnectionException(e.getMessage(), e);
303         }
304     }
305 
306     /**
307      * If the destination directory is not inside the source directory (denoted
308      * by starting with "../"), then another git repository is registered.
309      * Warnings are suppressed for false positive with Sonar and multiple
310      * exceptions on public API. {@inheritDoc}
311      */
312     @Override
313     @SuppressWarnings("all")
314     public void putDirectory(final File sourceDirectory,
315             final String destinationDirectory) throws TransferFailedException,
316             ResourceDoesNotExistException, AuthorizationException {
317         try {
318             if (!sourceDirectory.isDirectory()) {
319                 throw new ResourceDoesNotExistException(format(
320                         R.getString("dirnotfound"), sourceDirectory)); //$NON-NLS-1$
321             }
322             final File fileForResource = getFileForResource(destinationDirectory);
323             FileUtils.copyDirectoryStructure(sourceDirectory, fileForResource);
324         } catch (final IOException e) {
325             throw new TransferFailedException(e.getMessage(), e);
326         } catch (final GitAPIException e) {
327             throw new TransferFailedException(e.getMessage(), e);
328         } catch (final URISyntaxException e) {
329             throw new TransferFailedException(e.getMessage(), e);
330         }
331     }
332 
333     /**
334      * {@inheritDoc}
335      */
336     @Override
337     public boolean resourceExists(final String resourceName)
338             throws TransferFailedException {
339         final File file;
340         try {
341             file = getFileForResource(resourceName);
342         } catch (final GitAPIException e) {
343             throw new TransferFailedException(e.getMessage(), e);
344         } catch (final IOException e) {
345             throw new TransferFailedException(e.getMessage(), e);
346         } catch (final URISyntaxException e) {
347             throw new TransferFailedException(e.getMessage(), e);
348         }
349 
350         if (resourceName.endsWith("/")) { //$NON-NLS-1$
351             return file.isDirectory();
352         }
353 
354         return file.exists();
355     }
356 
357     /**
358      * Directory copy is supported.
359      *
360      * @return true
361      */
362     @Override
363     public boolean supportsDirectoryCopy() {
364         return true;
365     }
366 }