001/*
002 * IzPack - Copyright 2001-2005 Julien Ponge, All Rights Reserved.
003 * 
004 * http://www.izforge.com/izpack/
005 * http://developer.berlios.de/projects/izpack/
006 * 
007 * Copyright 2002 Johannes Lehtinen
008 * 
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *     http://www.apache.org/licenses/LICENSE-2.0
014 *     
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 */
021
022package com.izforge.izpack.installer;
023
024import java.awt.BorderLayout;
025import java.awt.Component;
026import java.awt.Cursor;
027import java.awt.GridLayout;
028import java.awt.Toolkit;
029import java.io.InputStream;
030import java.net.Authenticator;
031import java.net.ConnectException;
032import java.net.InetAddress;
033import java.net.PasswordAuthentication;
034import java.net.URL;
035import java.net.URLConnection;
036import java.util.Locale;
037
038import javax.swing.JDialog;
039import javax.swing.JLabel;
040import javax.swing.JOptionPane;
041import javax.swing.JPanel;
042import javax.swing.JPasswordField;
043import javax.swing.JTextField;
044import javax.swing.UIManager;
045
046/**
047 * Dialogs for password authentication and firewall specification, when needed, during web
048 * installation.
049 * 
050 * @author Chadwick McHenry
051 * @version 1.0
052 */
053public class WebAccessor
054{
055
056    private Thread openerThread = null;
057
058    private InputStream iStream = null;
059
060    private Exception exception = null;
061
062    private Object soloCancelOption = null;
063
064    private Component parent = null;
065
066    private JDialog dialog = null;
067
068    private boolean tryProxy = false;
069
070    private JPanel passwordPanel = null;
071
072    private JLabel promptLabel;
073
074    private JTextField nameField;
075
076    private JPasswordField passField;
077
078    private JPanel proxyPanel = null;
079
080    private JLabel errorLabel;
081
082    private JTextField hostField;
083
084    private JTextField portField;
085
086    /**
087     * Not yet Implemented: placeholder for headless installs.
088     * 
089     * @throws UnsupportedOperationException
090     */
091    public WebAccessor()
092    {
093        // the class should probably be rearranged to do this.
094        throw new UnsupportedOperationException();
095    }
096
097    /**
098     * Create a WebAccessor that prompts for proxies and passwords using a JDialog.
099     * 
100     * @param parent determines the frame in which the dialog is displayed; if the parentComponent
101     * has no Frame, a default Frame is used
102     */
103    public WebAccessor(Component parent)
104    {
105        this.parent = parent;
106        Locale l = null;
107        if (parent != null) parent.getLocale();
108        soloCancelOption = UIManager.get("OptionPane.cancelButtonText", l);// TODO:
109        // i18n?
110        Authenticator.setDefault(new MyDialogAuthenticator());
111    }
112
113    /**
114     * Opens a URL connection and returns it's InputStream for the specified URL.
115     * 
116     * @param url the url to open the stream to.
117     * @return an input stream ready to read, or null on failure
118     */
119    public InputStream openInputStream(URL url)
120    {
121        // TODO: i18n everything
122        Object[] options = { soloCancelOption};
123        JOptionPane pane = new JOptionPane("Connecting to the Internet",
124                JOptionPane.INFORMATION_MESSAGE, JOptionPane.DEFAULT_OPTION, null, options,
125                options[0]);
126        dialog = pane.createDialog(parent, "Accessing Install Files");
127        pane.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
128
129        Object value = null;
130        OPEN_URL: while (true)
131        {
132            startOpening(url); // this starts a thread that may dismiss the
133            // dialog before user
134            dialog.setVisible(true);
135            value = pane.getValue();
136
137            // dialog closed or canceled (by widget)
138            if (value == null || value == soloCancelOption)
139            {
140                try
141                {
142                    openerThread.interrupt();// stop the connection
143                }
144                catch (Exception e)
145                {}
146                iStream = null; // even if connection was made just after cancel
147                break;
148            }
149
150            // dialog closed by thread so either a connection error or success!
151            else if (value == JOptionPane.UNINITIALIZED_VALUE)
152            {
153                // success!
154                if (iStream != null) break;
155
156                // System.err.println(exception);
157
158                // an exception we don't expect setting a proxy to fix
159                if (!tryProxy) break;
160
161                // else (exception != null)
162                // show proxy dialog until valid values or cancel
163                JPanel panel = getProxyPanel();
164                errorLabel.setText("Unable to connect: " + exception.getMessage());
165                while (true)
166                {
167                    int result = JOptionPane.showConfirmDialog(parent, panel,
168                            "Proxy Configuration", JOptionPane.OK_CANCEL_OPTION,
169                            JOptionPane.QUESTION_MESSAGE);
170                    if (result != JOptionPane.OK_OPTION) // canceled
171                        break OPEN_URL;
172
173                    String host = null;
174                    String port = null;
175
176                    try
177                    {
178                        InetAddress addr = InetAddress.getByName(hostField.getText());
179                        host = addr.getHostName();
180                    }
181                    catch (Exception x)
182                    {
183                        errorLabel.setText("Unable to resolve Host");
184                        Toolkit.getDefaultToolkit().beep();
185                    }
186
187                    try
188                    {
189                        if (host != null) port = Integer.valueOf(portField.getText()).toString();
190                    }
191                    catch (NumberFormatException x)
192                    {
193                        errorLabel.setText("Invalid Port");
194                        Toolkit.getDefaultToolkit().beep();
195                    }
196
197                    if (host != null && port != null)
198                    {
199                        // System.err.println ("Setting http proxy: "+ host
200                        // +":"+ port);
201                        System.getProperties().put("proxySet", "true");
202                        System.getProperties().put("proxyHost", host);
203                        System.getProperties().put("proxyPort", port);
204                        break;
205                    }
206                }
207            }
208        }
209        return iStream;
210    }
211
212    private void startOpening(final URL url)
213    {
214        openerThread = new Thread() {
215
216            public void run()
217            {
218                iStream = null;
219                try
220                {
221                    tryProxy = false;
222                    URLConnection connection = url.openConnection();
223                    iStream = connection.getInputStream(); // just to make
224                    // connection
225
226                }
227                catch (ConnectException x)
228                { // could be an incorrect proxy
229                    tryProxy = true;
230                    exception = x;
231
232                }
233                catch (Exception x)
234                {
235                    // Exceptions that get here are considered cancels or
236                    // missing
237                    // pages, eg 401 if user finally cancels auth
238                    exception = x;
239
240                }
241                finally
242                {
243                    // if dialog is in use, allow it to become visible /before/
244                    // closing
245                    // it, else on /fast/ connectinos, it may open later and
246                    // hang!
247                    if (dialog != null)
248                    {
249                        Thread.yield();
250                        dialog.setVisible(false);
251                    }
252                }
253            }
254        };
255        openerThread.start();
256    }
257
258    /**
259     * Only to be called after an initial error has indicated a connection problem
260     */
261    private JPanel getProxyPanel()
262    {
263        if (proxyPanel == null)
264        {
265            proxyPanel = new JPanel(new BorderLayout(5, 5));
266
267            errorLabel = new JLabel();
268
269            JPanel fields = new JPanel(new GridLayout(2, 2));
270            String h = (String) System.getProperties().get("proxyHost");
271            String p = (String) System.getProperties().get("proxyPort");
272            hostField = new JTextField(h != null ? h : "");
273            portField = new JTextField(p != null ? p : "");
274            JLabel host = new JLabel("Host: "); // TODO: i18n
275            JLabel port = new JLabel("Port: "); // TODO: i18n
276            fields.add(host);
277            fields.add(hostField);
278            fields.add(port);
279            fields.add(portField);
280
281            JLabel exampleLabel = new JLabel("e.g. host=\"gatekeeper.example.com\" port=\"80\"");
282
283            proxyPanel.add(errorLabel, BorderLayout.NORTH);
284            proxyPanel.add(fields, BorderLayout.CENTER);
285            proxyPanel.add(exampleLabel, BorderLayout.SOUTH);
286        }
287        proxyPanel.validate();
288
289        return proxyPanel;
290    }
291
292    private JPanel getPasswordPanel()
293    {
294        if (passwordPanel == null)
295        {
296            passwordPanel = new JPanel(new BorderLayout(5, 5));
297
298            promptLabel = new JLabel();
299
300            JPanel fields = new JPanel(new GridLayout(2, 2));
301            nameField = new JTextField();
302            passField = new JPasswordField();
303            JLabel name = new JLabel("Name: "); // TODO: i18n
304            JLabel pass = new JLabel("Password: "); // TODO: i18n
305            fields.add(name);
306            fields.add(nameField);
307            fields.add(pass);
308            fields.add(passField);
309
310            passwordPanel.add(promptLabel, BorderLayout.NORTH);
311            passwordPanel.add(fields, BorderLayout.CENTER);
312        }
313        passField.setText("");
314
315        return passwordPanel;
316    }
317
318    /**
319     * Authenticates via dialog when needed.
320     */
321    private class MyDialogAuthenticator extends Authenticator
322    {
323
324        public PasswordAuthentication getPasswordAuthentication()
325        {
326            // TODO: i18n
327            JPanel p = getPasswordPanel();
328            String prompt = getRequestingPrompt();
329            InetAddress addr = getRequestingSite();
330            if (addr != null) prompt += " (" + addr.getHostName() + ")";
331            promptLabel.setText(prompt);
332            int result = JOptionPane.showConfirmDialog(parent, p, "Enter Password",
333                    JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
334            if (result != JOptionPane.OK_OPTION) return null;
335
336            return new PasswordAuthentication(nameField.getText(), passField.getPassword());
337        }
338    };
339}