View Javadoc
1   package org.cyclopsgroup.gitcon.jgit;
2   
3   import java.io.File;
4   
5   import org.eclipse.jgit.api.errors.GitAPIException;
6   import org.eclipse.jgit.transport.JschConfigSessionFactory;
7   import org.eclipse.jgit.transport.OpenSshConfig;
8   import org.eclipse.jgit.transport.SshSessionFactory;
9   import org.eclipse.jgit.util.FS;
10  
11  import com.jcraft.jsch.JSch;
12  import com.jcraft.jsch.JSchException;
13  import com.jcraft.jsch.Session;
14  
15  /**
16   * Class that invokes {@link GitCall} with special logic around invocations
17   */
18  abstract class JGitCallExecutor
19  {
20      private static class Direct
21          extends JGitCallExecutor
22      {
23          @Override
24          <T> T invokeCall( JGitCall<T> call )
25              throws GitAPIException
26          {
27              return call.call();
28          }
29      }
30  
31      private static class KeySessionFactory
32          extends JschConfigSessionFactory
33      {
34          private final String privateKeyPath;
35  
36          private KeySessionFactory( String keyPath )
37          {
38              this.privateKeyPath = keyPath;
39          }
40  
41          @Override
42          protected void configure( OpenSshConfig.Host host, Session session )
43          {
44              session.setConfig( "StrictHostKeyChecking", "no" );
45          }
46  
47          @Override
48          protected JSch getJSch( final OpenSshConfig.Host hostConfig, FS fs )
49              throws JSchException
50          {
51              JSch jsch = super.getJSch( hostConfig, fs );
52              jsch.removeAllIdentity();
53              jsch.addIdentity( privateKeyPath );
54              return jsch;
55          }
56      }
57  
58      private static class SshContext
59          extends JGitCallExecutor
60      {
61          private final KeySessionFactory context;
62  
63          private SshContext( KeySessionFactory context )
64          {
65              this.context = context;
66          }
67  
68          @Override
69          <T> T invokeCall( JGitCall<T> call )
70              throws GitAPIException
71          {
72              if ( !new File( context.privateKeyPath ).canRead() )
73              {
74                  throw new IllegalArgumentException( "Private key "
75                      + context.privateKeyPath + " is not accessible" );
76              }
77              synchronized ( JGitCallExecutor.class )
78              {
79                  SshSessionFactory original = SshSessionFactory.getInstance();
80                  SshSessionFactory.setInstance( context );
81                  try
82                  {
83                      return call.call();
84                  }
85                  finally
86                  {
87                      SshSessionFactory.setInstance( original );
88                  }
89              }
90          }
91      }
92  
93      private static class Synchronized
94          extends JGitCallExecutor
95      {
96          private final JGitCallExecutor executor;
97  
98          private Synchronized( JGitCallExecutor executor )
99          {
100             this.executor = executor;
101         }
102 
103         @Override
104         <T> T invokeCall( JGitCall<T> call )
105             throws GitAPIException
106         {
107             synchronized ( JGitCallExecutor.class )
108             {
109                 return executor.invokeCall( call );
110             }
111         }
112     }
113 
114     private static final JGitCallExecutor DIRECT_INSTANCE = new Direct();
115 
116     /**
117      * @return A no-op implementation that invokes calls directly
118      */
119     static JGitCallExecutor direct()
120     {
121         return DIRECT_INSTANCE;
122     }
123 
124     /**
125      * @param executor Given executor
126      * @return A wrapper of given executor that makes sure invocation is
127      *         synchronized globally
128      */
129     static JGitCallExecutor synchronize( JGitCallExecutor executor )
130     {
131         return new Synchronized( executor );
132     }
133 
134     /**
135      * Because JGit relies on JSch to handle SSH transport, which binds SSH key
136      * via static instance, there's no elegant way to allow multiple SSH keys in
137      * the same JVM. This decorator of executor binds given SSH private key with
138      * a global lock before each invocation, and restore to previously used SSH
139      * key after invocation.
140      *
141      * @param sshPrivateKey Path to SSH private key
142      * @return Executor that knows to setup Jsch to use given SSH private key
143      *         before each call, and retore it after the invocation.
144      */
145     static JGitCallExecutor withSshPrivateKey( String sshPrivateKey )
146     {
147         return new SshContext( new KeySessionFactory( sshPrivateKey ) );
148     }
149 
150     /**
151      * Invoke given call
152      *
153      * @param call The call to invoke
154      * @return Some result
155      * @throws GitAPIException Allows JGit exceptions
156      */
157     abstract <T> T invokeCall( JGitCall<T> call )
158         throws GitAPIException;
159 }