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 }