001    /**
002     *
003     *  Licensed to the Apache Software Foundation (ASF) under one or more
004     *  contributor license agreements.  See the NOTICE file distributed with
005     *  this work for additional information regarding copyright ownership.
006     *  The ASF licenses this file to You under the Apache License, Version 2.0
007     *  (the "License"); you may not use this file except in compliance with
008     *  the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     *  Unless required by applicable law or agreed to in writing, software
013     *  distributed under the License is distributed on an "AS IS" BASIS,
014     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     *  See the License for the specific language governing permissions and
016     *  limitations under the License.
017     */
018    
019    package org.apache.geronimo.deployment.cli;
020    
021    import java.io.BufferedInputStream;
022    import java.io.File;
023    import java.io.FileInputStream;
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.io.PrintWriter;
027    import java.io.Serializable;
028    import java.io.FileNotFoundException;
029    import java.util.LinkedHashMap;
030    import java.util.List;
031    import java.util.Map;
032    import java.util.Properties;
033    import java.util.jar.JarFile;
034    
035    import javax.enterprise.deploy.shared.factories.DeploymentFactoryManager;
036    import javax.enterprise.deploy.spi.DeploymentManager;
037    import javax.enterprise.deploy.spi.exceptions.DeploymentManagerCreationException;
038    import javax.enterprise.deploy.spi.factories.DeploymentFactory;
039    
040    import org.apache.geronimo.common.DeploymentException;
041    import org.apache.geronimo.deployment.plugin.factories.AuthenticationFailedException;
042    import org.apache.geronimo.deployment.plugin.factories.DeploymentFactoryImpl;
043    import org.apache.geronimo.deployment.plugin.jmx.JMXDeploymentManager;
044    import org.apache.geronimo.deployment.plugin.jmx.LocalDeploymentManager;
045    import org.apache.geronimo.util.SimpleEncryption;
046    import org.apache.geronimo.kernel.Kernel;
047    import org.apache.geronimo.kernel.config.ConfigurationManager;
048    import org.apache.geronimo.kernel.config.ConfigurationUtil;
049    import org.apache.geronimo.system.main.LocalServer;
050    
051    /**
052     * Supports online connections to the server, via JSR-88, valid only
053     * when the server is online.
054     *
055     * @version $Rev: 470597 $ $Date: 2006-11-02 15:30:55 -0800 (Thu, 02 Nov 2006) $
056     */
057    public class ServerConnection {
058        private final static Map OPTION_HELP = new LinkedHashMap(9);
059    
060        static {
061            OPTION_HELP.put("--uri", "A URI to contact the server.  If not specified, the deployer defaults to " +
062                    "operating on a Geronimo server running on the standard port on localhost.\n" +
063                    "A URI to connect to Geronimo (including optional host and port parameters) has the form: " +
064                    "deployer:geronimo:jmx[://host[:port]] (though you could also just use --host and --port instead).");
065            OPTION_HELP.put("--host", "The host name of a Geronimo server to deploy to.  This option is " +
066                    "not compatible with --uri, but is often used with --port.");
067            OPTION_HELP.put("--port", "The RMI listen port of a Geronimo server to deploy to.  This option is " +
068                    "not compatible with --uri, but is often used with --host.  The default port is 1099.");
069            OPTION_HELP.put("--driver", "If you want to use this tool with a server other than Geronimo, " +
070                    "then you must provide the path to its driver JAR.  Currently, manifest " +
071                    "Class-Path entries in that JAR are ignored.");
072            OPTION_HELP.put("--user", "If the deployment operation requires authentication, then you can " +
073                    "specify the username to use to connect.  If no password is specified, the " +
074                    "deployer will attempt to connect to the server with no password, and if " +
075                    "that fails, will prompt you for a password.");
076            OPTION_HELP.put("--password", "Specifies a password to use to authenticate to the server.");
077            OPTION_HELP.put("--syserr", "Enables error logging to syserr.  Disabled by default.");
078            OPTION_HELP.put("--verbose", "Enables verbose execution mode.  Disabled by default.");
079            OPTION_HELP.put("--offline", "Deploy offline to a local server, using whatever deployers are available in the local server");
080        }
081    
082        public static Map getOptionHelp() {
083            return OPTION_HELP;
084        }
085    
086        /**
087         * Checks whether the stated command-line argument is a general argument (which
088         * may be the general argument itself, or a required parameter after the general
089         * argument).  For example, if the arguments were "--user bob foo" then
090         * this should return true for "--user" and "bob" and false for "foo".
091         *
092         * @param args   The previous arguments on the command line
093         * @param option The argument we're checking at the moment
094         * @return True if the argument we're checking is part of a general argument
095         */
096        public static boolean isGeneralOption(List args, String option) {
097            if (OPTION_HELP.containsKey(option) || option.equals("--url")) {
098                return true;
099            }
100            if (args.size() == 0) {
101                return false;
102            }
103            String last = (String) args.get(args.size() - 1);
104            return last.equals("--uri") || last.equals("--url") || last.equals("--driver") || last.equals("--user") ||
105                    last.equals("--password") || last.equals("--host") || last.equals("--port");
106        }
107    
108        private final static String DEFAULT_URI = "deployer:geronimo:jmx";
109    
110        private DeploymentManager manager;
111        private PrintWriter out;
112        private InputStream in;
113        private SavedAuthentication auth;
114        private boolean logToSysErr;
115        private boolean verboseMessages;
116    
117        public ServerConnection(String[] args, PrintWriter out, InputStream in) throws DeploymentException {
118            String uri = null, driver = null, user = null, password = null, host = null;
119            Integer port = null;
120            this.out = out;
121            this.in = in;
122            boolean offline = false;
123            for (int i = 0; i < args.length; i++) {
124                String arg = args[i];
125                if (arg.equals("--uri") || arg.equals("--url")) {
126                    if (uri != null) {
127                        throw new DeploymentSyntaxException("Cannot specify more than one URI");
128                    } else if (i >= args.length - 1) {
129                        throw new DeploymentSyntaxException("Must specify a URI (e.g. --uri deployer:...)");
130                    }
131                    if (host != null || port != null) {
132                        throw new DeploymentSyntaxException("Cannot specify a URI as well as a host/port");
133                    }
134                    uri = args[++i];
135                } else if (arg.equals("--host")) {
136                    if (host != null) {
137                        throw new DeploymentSyntaxException("Cannot specify more than one host");
138                    } else if (i >= args.length - 1) {
139                        throw new DeploymentSyntaxException("Must specify a hostname (e.g. --host localhost)");
140                    }
141                    if (uri != null) {
142                        throw new DeploymentSyntaxException("Cannot specify a URI as well as a host/port");
143                    }
144                    host = args[++i];
145                } else if (arg.equals("--port")) {
146                    if (port != null) {
147                        throw new DeploymentSyntaxException("Cannot specify more than one port");
148                    } else if (i >= args.length - 1) {
149                        throw new DeploymentSyntaxException("Must specify a port (e.g. --port 1099)");
150                    }
151                    if (uri != null) {
152                        throw new DeploymentSyntaxException("Cannot specify a URI as well as a host/port");
153                    }
154                    try {
155                        port = new Integer(args[++i]);
156                    } catch (NumberFormatException e) {
157                        throw new DeploymentSyntaxException("Port must be a number (" + e.getMessage() + ")");
158                    }
159                } else if (arg.equals("--driver")) {
160                    if (driver != null) {
161                        throw new DeploymentSyntaxException("Cannot specify more than one driver");
162                    } else if (i >= args.length - 1) {
163                        throw new DeploymentSyntaxException("Must specify a driver JAR (--driver jarfile)");
164                    }
165                    driver = args[++i];
166                } else if (arg.equals("--offline")) {
167                    //throw new DeploymentSyntaxException("This tool no longer handles offline deployment");
168                    offline = true;
169                } else if (arg.equals("--user")) {
170                    if (user != null) {
171                        throw new DeploymentSyntaxException("Cannot specify more than one user name");
172                    } else if (i >= args.length - 1) {
173                        throw new DeploymentSyntaxException("Must specify a username (--user username)");
174                    }
175                    user = args[++i];
176                } else if (arg.equals("--password")) {
177                    if (password != null) {
178                        throw new DeploymentSyntaxException("Cannot specify more than one password");
179                    } else if (i >= args.length - 1) {
180                        throw new DeploymentSyntaxException("Must specify a password (--password password)");
181                    }
182                    password = args[++i];
183                } else if (arg.equals("--verbose")) {
184                    verboseMessages = true;
185                } else if (arg.equals("--syserr")) {
186                    logToSysErr = true;
187                } else {
188                    throw new DeploymentException("Invalid option " + arg);
189                }
190            }
191            if ((driver != null) && uri == null) {
192                throw new DeploymentSyntaxException("A custom driver requires a custom URI");
193            }
194            if (host != null || port != null) {
195                uri = DEFAULT_URI + "://" + (host == null ? "" : host) + (port == null ? "" : ":" + port);
196            }
197            if (offline) {
198                LocalServer localServer;
199                try {
200                    localServer = new LocalServer("org.apache.geronimo.configs/j2ee-system//car", "var/config/offline-deployer-list");
201                } catch (Exception e) {
202                    throw new DeploymentException("Could not start local server", e);
203                }
204                Kernel kernel = localServer.getKernel();
205                ConfigurationManager configurationManager = ConfigurationUtil.getConfigurationManager(kernel);
206                configurationManager.setOnline(false);
207    
208                manager = new LocalDeploymentManager(localServer.getKernel());
209            } else {
210                tryToConnect(uri, driver, user, password, true);
211            }
212            if (manager == null) {
213                throw new DeploymentException("Unexpected error; connection failed.");
214            }
215        }
216    
217        public void close() throws DeploymentException {
218            if (manager != null) {
219                manager.release();
220            }
221        }
222    
223        Serializable getAuthentication() {
224            return auth;
225        }
226    
227        String getServerURI() {
228            return auth.uri;
229        }
230    
231        private void tryToConnect(String argURI, String driver, String user, String password, boolean authPrompt) throws DeploymentException {
232            DeploymentFactoryManager mgr = DeploymentFactoryManager.getInstance();
233            if (driver != null) {
234                loadDriver(driver, mgr);
235            } else {
236                mgr.registerDeploymentFactory(new DeploymentFactoryImpl());
237            }
238            String useURI = argURI == null ? DEFAULT_URI : argURI;
239    
240            if (authPrompt && user == null && password == null) {
241                InputStream in;
242                // First check for .geronimo-deployer on class path (e.g. packaged in deployer.jar)
243                in = ServerConnection.class.getResourceAsStream("/.geronimo-deployer");
244                // If not there, check in home directory
245                if (in == null) {
246                    File authFile = new File(System.getProperty("user.home"), ".geronimo-deployer");
247                    if (authFile.exists() && authFile.canRead()) {
248                        try {
249                            in = new BufferedInputStream(new FileInputStream(authFile));
250                        } catch (FileNotFoundException e) {
251                            // ignore
252                        }
253                    }
254                }
255                if (in != null) {
256                    try {
257                        Properties props = new Properties();
258                        props.load(in);
259                        String encryped = props.getProperty("login." + useURI);
260                        if (encryped != null) {
261                            if (encryped.startsWith("{Standard}")) {
262                                SavedAuthentication auth = (SavedAuthentication) SimpleEncryption.decrypt(encryped.substring(10));
263                                if (auth.uri.equals(useURI)) {
264                                    user = auth.user;
265                                    password = new String(auth.password);
266                                }
267                            } else if (encryped.startsWith("{Plain}")) {
268                                int pos = encryped.indexOf("/");
269                                user = encryped.substring(7, pos);
270                                password = encryped.substring(pos + 1);
271                            } else {
272                                System.out.print(DeployUtils.reformat("Unknown encryption used in saved login file", 4, 72));
273                            }
274                        }
275                    } catch (IOException e) {
276                        System.out.print(DeployUtils.reformat("Unable to read authentication from saved login file: " + e.getMessage(), 4, 72));
277                    } finally {
278                        try {
279                            in.close();
280                        } catch (IOException e) {
281                            // ingore
282                        }
283                    }
284                }
285            }
286    
287            if (authPrompt && !useURI.equals(DEFAULT_URI) && user == null && password == null) {
288                // Non-standard URI, but no authentication information
289                doAuthPromptAndRetry(useURI, user, password);
290                return;
291            } else { // Standard URI with no auth, Non-standard URI with auth, or else this is the 2nd try already
292                try {
293                    manager = mgr.getDeploymentManager(useURI, user, password);
294                    auth = new SavedAuthentication(useURI, user, password == null ? null : password.toCharArray());
295                } catch (AuthenticationFailedException e) { // server's there, you just can't talk to it
296                    if (authPrompt) {
297                        doAuthPromptAndRetry(useURI, user, password);
298                        return;
299                    } else {
300                        throw new DeploymentException("Login Failed");
301                    }
302                } catch (DeploymentManagerCreationException e) {
303                    throw new DeploymentException("Unable to connect to server at " + useURI + " -- " + e.getMessage());
304                }
305            }
306    
307            if (manager instanceof JMXDeploymentManager) {
308                JMXDeploymentManager deploymentManager = (JMXDeploymentManager) manager;
309                deploymentManager.setLogConfiguration(logToSysErr, verboseMessages);
310            }
311        }
312    
313        private void loadDriver(String driver, DeploymentFactoryManager mgr) throws DeploymentException {
314            File file = new File(driver);
315            if (!file.exists() || !file.canRead() || !DeployUtils.isJarFile(file)) {
316                throw new DeploymentSyntaxException("Driver '" + file.getAbsolutePath() + "' is not a readable JAR file");
317            }
318            String className = null;
319            try {
320                JarFile jar = new JarFile(file);
321                className = jar.getManifest().getMainAttributes().getValue("J2EE-DeploymentFactory-Implementation-Class");
322                if (className == null) {
323                    throw new DeploymentException("The driver JAR " + file.getAbsolutePath() + " does not specify a J2EE-DeploymentFactory-Implementation-Class; cannot load driver.");
324                }
325                jar.close();
326                DeploymentFactory factory = (DeploymentFactory) Class.forName(className).newInstance();
327                mgr.registerDeploymentFactory(factory);
328            } catch (DeploymentException e) {
329                throw e;
330            } catch (Exception e) {
331                throw new DeploymentSyntaxException("Unable to load driver class " + className + " from JAR " + file.getAbsolutePath(), e);
332            }
333        }
334    
335        private void doAuthPromptAndRetry(String uri, String user, String password) throws DeploymentException {
336            try {
337                InputPrompt prompt = new InputPrompt(in, out);
338                if (user == null) {
339                    user = prompt.getInput("Username: ");
340                }
341                if (password == null) {
342                    password = prompt.getPassword("Password: ");
343                }
344            } catch (IOException e) {
345                throw new DeploymentException("Unable to prompt for login", e);
346            }
347            tryToConnect(uri, null, user, password, false);
348        }
349    
350        public DeploymentManager getDeploymentManager() {
351            return manager;
352        }
353    
354        public boolean isGeronimo() {
355            return manager.getClass().getName().startsWith("org.apache.geronimo.");
356        }
357    
358        private final static class SavedAuthentication implements Serializable {
359            private String uri;
360            private String user;
361            private char[] password;
362    
363            public SavedAuthentication(String uri, String user, char[] password) {
364                this.uri = uri;
365                this.user = user;
366                this.password = password;
367            }
368        }
369    }