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]"
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
;
52 import java
.util
.HashSet
;
53 import java
.util
.Iterator
;
54 import java
.util
.logging
.Level
;
56 import java
.net
.HttpURLConnection
;
57 import java
.net
.URISyntaxException
;
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
;
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
{
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"),
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
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());
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
) {
150 public final void removeChangeListener(ChangeListener l
) {
151 synchronized (listeners
) {
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() {
182 public final String
getErrorMessage() {
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
;
197 private void setValid(boolean valid
, String errorMessage
) {
198 if ((errorMessage
!= null) && (errorMessage
.length() == 0)) {
201 boolean fire
= this.valid
!= valid
;
202 fire
|= errorMessage
!= null && (errorMessage
.equals(this.errorMessage
) == false);
204 this.errorMessage
= errorMessage
;
210 protected void validateBeforeNext() throws WizardValidationException
{
214 url
= repository
.getUrl();
215 } catch (MalformedURLException mfe
) {
216 throw new WizardValidationException((JComponent
) component
,
218 mfe
.getLocalizedMessage());
219 } catch (URISyntaxException use
) {
220 throw new WizardValidationException((JComponent
) component
,
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"));
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());
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
,
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
261 public void readSettings(Object settings
) {}
262 public void storeSettings(Object settings
) {
263 if (settings
instanceof WizardDescriptor
) {
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
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
290 public void prepareValidation() {
293 repository
.setEditable(false);
296 private void storeHistory() {
297 Transport rc
= getRepositoryConnection();
298 // TODO: implement history of URLs
300 // GitModuleConfig.getDefault().insertRecentUrl(rc);
304 private Transport
getRepositoryConnection() {
306 return repository
.getRepositoryConnection();
307 } catch (Exception ex
) {
308 displayErrorMessage(ex
.getLocalizedMessage());
314 if(support
!= null) {
319 private class RepositoryStepProgressSupport
extends WizardStepProgressSupport
{
321 public RepositoryStepProgressSupport() {
325 public void perform() {
326 final Transport rc
= getRepositoryConnection();
330 String invalidMsg
= null;
331 HttpURLConnection con
= null;
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
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
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
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
369 } catch (IOException ex
) {
370 Git
.LOG
.log(Level
.INFO
, ex
.getMessage(), ex
);
371 invalidMsg
= getMessage("MSG_Progress_Clone_CannotAccess_Err"); //NOI18N
373 } catch (RuntimeException re
) {
374 Throwable t
= re
.getCause();
376 invalidMsg
= t
.getLocalizedMessage();
378 invalidMsg
= re
.getLocalizedMessage();
380 Git
.LOG
.log(Level
.INFO
, invalidMsg
, re
);
387 displayErrorMessage(getMessage("CTL_Repository_Canceled")); //NOI18N
388 } else if(invalidMsg
== null) {
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
{
405 public void checkServerTrusted(X509Certificate
[] chain
, String authType
) throws CertificateException
{
408 public X509Certificate
[] getAcceptedIssuers() {
409 return new X509Certificate
[0];
412 HostnameVerifier hnv
= new HostnameVerifier() {
413 public boolean verify(String hostname
, SSLSession session
) {
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
);