Initial implementation of Clone
[nbgit.git] / src / org / nbgit / ui / wizards / CloneRepositoryWizardPanel.java
blob23366dcd036f6507b329d292a582e5fc61ca412b
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
6 * The contents of this file are subject to the terms of either the GNU
7 * General Public License Version 2 only ("GPL") or the Common
8 * Development and Distribution License("CDDL") (collectively, the
9 * "License"). You may not use this file except in compliance with the
10 * License. You can obtain a copy of the License at
11 * http://www.netbeans.org/cddl-gplv2.html
12 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13 * specific language governing permissions and limitations under the
14 * License. When distributing the software, include this License Header
15 * Notice in each file and include the License file at
16 * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17 * particular file as subject to the "Classpath" exception as provided
18 * by Sun in the GPL Version 2 section of the License file that
19 * accompanied this code. If applicable, add the following below the
20 * License Header, with the fields enclosed by brackets [] replaced by
21 * your own identifying information:
22 * "Portions Copyrighted [year] [name of copyright owner]"
24 * Contributor(s):
26 * The Original Software is NetBeans. The Initial Developer of the Original
27 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2009 Sun
28 * Microsystems, Inc. All Rights Reserved.
29 * Portions Copyright 2009 Alexander Coles (Ikonoklastik Productions).
31 * If you wish your version of this file to be governed by only the CDDL
32 * or only the GPL Version 2, indicate your decision by adding
33 * "[Contributor] elects to include this software in this distribution
34 * under the [CDDL or GPL Version 2] license." If you do not indicate a
35 * single choice of license, a recipient has the option to distribute
36 * your version of this file under either the CDDL, the GPL Version 2 or
37 * to extend the choice of license to its licensees as provided above.
38 * However, if you add GPL Version 2 code and therefore, elected the GPL
39 * Version 2 license, then the option applies only if the new code is
40 * made subject to such option by the copyright holder.
42 package org.nbgit.ui.wizards;
44 import java.awt.Component;
45 import java.awt.BorderLayout;
46 import java.security.KeyManagementException;
47 import java.util.logging.Logger;
48 import javax.swing.event.ChangeEvent;
49 import javax.swing.event.ChangeListener;
50 import javax.swing.JPanel;
51 import java.util.Set;
52 import java.util.HashSet;
53 import java.util.Iterator;
54 import java.util.logging.Level;
55 import java.net.URL;
56 import java.net.HttpURLConnection;
57 import java.net.URISyntaxException;
58 import java.io.File;
59 import java.io.IOException;
60 import java.net.MalformedURLException;
61 import java.security.NoSuchAlgorithmException;
62 import java.security.cert.CertificateException;
63 import java.security.cert.X509Certificate;
64 import javax.net.ssl.HostnameVerifier;
65 import javax.net.ssl.HttpsURLConnection;
66 import javax.net.ssl.SSLContext;
67 import javax.net.ssl.SSLSession;
68 import javax.net.ssl.TrustManager;
69 import javax.net.ssl.X509TrustManager;
70 import javax.swing.JComponent;
71 import org.nbgit.Git;
72 import org.nbgit.GitModuleConfig;
73 import org.nbgit.ui.repository.GitRepositoryUI;
74 import org.nbgit.ui.repository.GitURIScheme;
75 import org.openide.WizardDescriptor;
76 import org.openide.WizardValidationException;
77 import org.openide.util.HelpCtx;
78 import org.openide.util.NbBundle;
79 import org.openide.util.RequestProcessor;
80 import org.spearce.jgit.transport.Transport;
81 import org.spearce.jgit.transport.URIish;
82 import static org.nbgit.ui.repository.GitURIScheme.FILE;
83 import static org.nbgit.ui.repository.GitURIScheme.HTTP;
84 import static org.nbgit.ui.repository.GitURIScheme.HTTPS;
86 public class CloneRepositoryWizardPanel implements WizardDescriptor.AsynchronousValidatingPanel, ChangeListener {
88 /**
89 * The visual component that displays this panel. If you need to access the
90 * component from this class, just use getComponent().
92 private JComponent component;
93 private GitRepositoryUI repository;
94 private boolean valid;
95 private String errorMessage;
96 private WizardStepProgressSupport support;
98 public CloneRepositoryWizardPanel() {
99 support = new RepositoryStepProgressSupport();
102 // Get the visual component for the panel. In this template, the component
103 // is kept separate. This can be more efficient: if the wizard is created
104 // but never displayed, or not all panels are displayed, it is better to
105 // create only those which really need to be visible.
106 public Component getComponent() {
107 if (component == null) {
109 repository = new GitRepositoryUI(
110 GitRepositoryUI.FLAG_URL_ENABLED | GitRepositoryUI.FLAG_SHOW_HINTS | GitRepositoryUI.FLAG_SHOW_PROXY,
111 getMessage("CTL_Repository_Location"),
112 false);
113 repository.addChangeListener(this);
115 support = new RepositoryStepProgressSupport();
117 component = new JPanel(new BorderLayout());
118 component.add(repository.getPanel(), BorderLayout.CENTER);
119 component.add(support.getProgressComponent(), BorderLayout.SOUTH);
121 component.setName(getMessage("repositoryPanel.Name")); //NOI18N
123 valid();
125 return component;
128 public HelpCtx getHelp() {
129 return new HelpCtx(CloneRepositoryWizardPanel.class);
132 private static String getMessage(String msgKey) {
133 return NbBundle.getMessage(CloneRepositoryWizardPanel.class, msgKey);
136 public void stateChanged(ChangeEvent evt) {
137 if(repository.isValid()) {
138 valid(repository.getMessage());
139 } else {
140 invalid(repository.getMessage());
144 private final Set<ChangeListener> listeners = new HashSet<ChangeListener>(1); // or can use ChangeSupport in NB 6.0
145 public final void addChangeListener(ChangeListener l) {
146 synchronized (listeners) {
147 listeners.add(l);
150 public final void removeChangeListener(ChangeListener l) {
151 synchronized (listeners) {
152 listeners.remove(l);
155 protected final void fireChangeEvent() {
156 Iterator<ChangeListener> it;
157 synchronized (listeners) {
158 it = new HashSet<ChangeListener>(listeners).iterator();
160 ChangeEvent ev = new ChangeEvent(this);
161 while (it.hasNext()) {
162 it.next().stateChanged(ev);
166 protected final void valid() {
167 setValid(true, null);
170 protected final void valid(String extErrorMessage) {
171 setValid(true, extErrorMessage);
174 protected final void invalid(String message) {
175 setValid(false, message);
178 public final boolean isValid() {
179 return valid;
182 public final String getErrorMessage() {
183 return errorMessage;
186 private void displayErrorMessage(String errorMessage) {
187 if (errorMessage == null) {
188 throw new IllegalArgumentException("<null> message");
191 if (!errorMessage.equals(this.errorMessage)) {
192 this.errorMessage = errorMessage;
193 fireChangeEvent();
197 private void setValid(boolean valid, String errorMessage) {
198 if ((errorMessage != null) && (errorMessage.length() == 0)) {
199 errorMessage = null;
201 boolean fire = this.valid != valid;
202 fire |= errorMessage != null && (errorMessage.equals(this.errorMessage) == false);
203 this.valid = valid;
204 this.errorMessage = errorMessage;
205 if (fire) {
206 fireChangeEvent();
210 protected void validateBeforeNext() throws WizardValidationException {
211 try {
212 URIish url;
213 try {
214 url = repository.getUrl();
215 } catch (MalformedURLException mfe) {
216 throw new WizardValidationException((JComponent) component,
217 mfe.getMessage(),
218 mfe.getLocalizedMessage());
219 } catch (URISyntaxException use) {
220 throw new WizardValidationException((JComponent) component,
221 use.getMessage(),
222 use.getLocalizedMessage());
225 if (support == null) {
226 support = new RepositoryStepProgressSupport();
227 component.add(support.getProgressComponent(), BorderLayout.SOUTH);
229 support.setRepositoryRoot(url.toString());
230 RequestProcessor rp = Git.getInstance().getRequestProcessor(url.toString());
231 RequestProcessor.Task task = support.start(rp, url.toString(), NbBundle.getMessage(CloneRepositoryWizardPanel.class, "BK2012"));
232 task.waitFinished();
233 } finally {
234 if (support != null) { //see bug #167172
236 * We cannot reuse the progress component because
237 * org.netbeans.api.progress.ProgressHandle cannot be reused.
239 component.remove(support.getProgressComponent());
240 support = null;
246 // comes on next or finish
247 public final void validate() throws WizardValidationException {
248 validateBeforeNext();
249 if (isValid() == false || errorMessage != null) {
250 throw new WizardValidationException(
251 (javax.swing.JComponent) component,
252 errorMessage,
253 errorMessage);
257 // You can use a settings object to keep track of state. Normally the
258 // settings object will be the WizardDescriptor, so you can use
259 // WizardDescriptor.getProperty & putProperty to store information entered
260 // by the user.
261 public void readSettings(Object settings) {}
262 public void storeSettings(Object settings) {
263 if (settings instanceof WizardDescriptor) {
264 try {
265 ((WizardDescriptor) settings).putProperty("repository", repository.getUrl()); // NOI18N
266 } catch (MalformedURLException ex) {
268 * The panel's data may not be validated yet (bug #163078)
269 * so we cannot assume that the entered URL is valid - so
270 * we must catch the URISyntaxException.
272 Logger.getLogger(getClass().getName()).throwing(
273 getClass().getName(),
274 "storeSettings",//NOI18N
275 ex);
276 } catch (URISyntaxException ex) {
278 * The panel's data may not be validated yet (bug #163078)
279 * so we cannot assume that the entered URL is valid - so
280 * we must catch the URISyntaxException.
282 Logger.getLogger(getClass().getName()).throwing(
283 getClass().getName(),
284 "storeSettings",//NOI18N
285 ex);
290 public void prepareValidation() {
291 errorMessage = null;
293 repository.setEditable(false);
296 private void storeHistory() {
297 Transport rc = getRepositoryConnection();
298 // TODO: implement history of URLs
299 //if(rc != null) {
300 // GitModuleConfig.getDefault().insertRecentUrl(rc);
304 private Transport getRepositoryConnection() {
305 try {
306 return repository.getRepositoryConnection();
307 } catch (Exception ex) {
308 displayErrorMessage(ex.getLocalizedMessage());
309 return null;
313 public void stop() {
314 if(support != null) {
315 support.cancel();
319 private class RepositoryStepProgressSupport extends WizardStepProgressSupport {
321 public RepositoryStepProgressSupport() {
322 super();
325 public void perform() {
326 final Transport rc = getRepositoryConnection();
327 if (rc == null) {
328 return;
330 String invalidMsg = null;
331 HttpURLConnection con = null;
332 try {
333 URIish gitUrl = getRepositoryRoot();
335 GitURIScheme uriSch = GitURIScheme.valueOf(gitUrl.getScheme());
337 if (uriSch == FILE) {
338 File f = new File(gitUrl.getPath());
339 if(!f.exists() || !f.canRead()){
340 invalidMsg = getMessage("MSG_Progress_Clone_CannotAccess_Err"); //NOI18N
341 return;
343 } else if ((uriSch == HTTP) || (uriSch == HTTPS)) {
344 URL url = new java.net.URL(gitUrl.toPrivateString());
345 con = (HttpURLConnection) url.openConnection();
346 // Note: valid repository returns con.getContentLength() = -1
347 // so no way to reliably test if this url exists, without using git
348 if (con != null) {
349 String userInfo = url.getUserInfo();
350 boolean bNoUserAndOrPasswordInURL = userInfo == null;
351 // If username or username:password is in the URL the con.getResponseCode() returns -1 and this check would fail
352 if (uriSch == HTTPS) {
353 setupHttpsConnection(con);
355 if (bNoUserAndOrPasswordInURL && con.getResponseCode() != HttpURLConnection.HTTP_OK){
356 invalidMsg = getMessage("MSG_Progress_Clone_CannotAccess_Err"); //NOI18N
357 con.disconnect();
358 return;
359 }else if (userInfo != null){
360 Git.LOG.log(Level.FINE,
361 "RepositoryStepProgressSupport.perform(): UserInfo - {0}", new Object[]{userInfo}); // NOI18N
365 } catch (java.lang.IllegalArgumentException ex) {
366 Git.LOG.log(Level.INFO, ex.getMessage(), ex);
367 invalidMsg = getMessage("MSG_Progress_Clone_InvalidURL_Err"); //NOI18N
368 return;
369 } catch (IOException ex) {
370 Git.LOG.log(Level.INFO, ex.getMessage(), ex);
371 invalidMsg = getMessage("MSG_Progress_Clone_CannotAccess_Err"); //NOI18N
372 return;
373 } catch (RuntimeException re) {
374 Throwable t = re.getCause();
375 if(t != null) {
376 invalidMsg = t.getLocalizedMessage();
377 } else {
378 invalidMsg = re.getLocalizedMessage();
380 Git.LOG.log(Level.INFO, invalidMsg, re);
381 return;
382 } finally {
383 if(con != null) {
384 con.disconnect();
386 if(isCanceled()) {
387 displayErrorMessage(getMessage("CTL_Repository_Canceled")); //NOI18N
388 } else if(invalidMsg == null) {
389 storeHistory();
390 } else {
391 displayErrorMessage(invalidMsg);
396 public void setEditable(boolean editable) {
397 repository.setEditable(editable);
400 private void setupHttpsConnection(HttpURLConnection con) {
401 X509TrustManager tm = new X509TrustManager() {
402 public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
403 // do nothing
405 public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
406 // do nothing
408 public X509Certificate[] getAcceptedIssuers() {
409 return new X509Certificate[0];
412 HostnameVerifier hnv = new HostnameVerifier() {
413 public boolean verify(String hostname, SSLSession session) {
414 return true;
417 try {
418 SSLContext context = SSLContext.getInstance("SSLv3");
419 TrustManager[] trustManagerArray = { tm };
420 context.init(null, trustManagerArray, null);
421 HttpsURLConnection c = (HttpsURLConnection) con;
422 c.setSSLSocketFactory(context.getSocketFactory());
423 c.setHostnameVerifier(hnv);
424 } catch (KeyManagementException ex) {
425 Git.LOG.log(Level.INFO, ex.getMessage(), ex);
426 } catch (NoSuchAlgorithmException ex) {
427 Git.LOG.log(Level.INFO, ex.getMessage(), ex);