001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.rmi; 029 030import java.io.FileInputStream; 031import java.io.IOException; 032import java.io.InputStream; 033import java.io.InputStreamReader; 034import java.io.LineNumberReader; 035import java.io.PrintStream; 036import java.io.StreamTokenizer; 037import java.io.StringReader; 038import java.rmi.RemoteException; 039import java.rmi.registry.LocateRegistry; 040import java.rmi.registry.Registry; 041import java.util.ArrayList; 042import java.util.Arrays; 043import java.util.HashMap; 044import java.util.HashSet; 045import java.util.List; 046import java.util.Map; 047import java.util.Set; 048 049/** 050 * Client application used to connect locally to the CmsShell server.<p> 051 */ 052public class CmsRemoteShellClient { 053 054 /** Command parameter for passing an additional shell commands class name. */ 055 public static final String PARAM_ADDITIONAL = "additional"; 056 057 /** Command parameter for controlling the port to use for the initial RMI lookup. */ 058 public static final String PARAM_REGISTRY_PORT = "registryPort"; 059 060 /** Command parameter for controlling the host to use for the initial RMI lookup. */ 061 public static final String PARAM_REGISTRY_HOST = "registryHost"; 062 063 /** Command parameter for passing a shell script file name. */ 064 public static final String PARAM_SCRIPT = "script"; 065 066 /** The name of the additional commands classes. */ 067 private final String m_additionalCommands; 068 069 /** True if echo mode is turned on. */ 070 private boolean m_echo; 071 072 /** The error code which should be returned in case of errors. */ 073 private int m_errorCode; 074 075 /** True if exit was called. */ 076 private boolean m_exitCalled; 077 078 /** True if an error occurred. */ 079 private boolean m_hasError; 080 081 /** The input stream to read the commands from. */ 082 private InputStream m_input; 083 084 /** Controls whether shell is interactive. */ 085 private boolean m_interactive; 086 087 /** The output stream. */ 088 private PrintStream m_out; 089 090 /** The prompt. */ 091 private String m_prompt; 092 093 /** The port used for the RMI registry. */ 094 private int m_registryPort; 095 096 /** The host name used for the RMI registry. */ 097 private String m_registryHost; 098 099 /** The RMI referencce to the shell server. */ 100 private I_CmsRemoteShell m_remoteShell; 101 102 /** 103 * Creates a new instance.<p> 104 * 105 * @param args the parameters 106 * @throws IOException if something goes wrong 107 */ 108 public CmsRemoteShellClient(String[] args) 109 throws IOException { 110 111 Map<String, String> params = parseArgs(args); 112 String script = params.get(PARAM_SCRIPT); 113 if (script == null) { 114 m_interactive = true; 115 m_input = System.in; 116 } else { 117 m_input = new FileInputStream(script); 118 } 119 m_additionalCommands = params.get(PARAM_ADDITIONAL); 120 String port = params.get(PARAM_REGISTRY_PORT); 121 m_registryPort = CmsRemoteShellConstants.DEFAULT_PORT; 122 if (port != null) { 123 try { 124 m_registryPort = Integer.parseInt(port); 125 if (m_registryPort < 0) { 126 System.out.println("Invalid port: " + port); 127 System.exit(1); 128 } 129 } catch (NumberFormatException e) { 130 System.out.println("Invalid port: " + port); 131 System.exit(1); 132 } 133 } 134 m_registryHost = params.get(PARAM_REGISTRY_HOST); 135 } 136 137 /** 138 * Main method, which starts the shell client.<p> 139 * 140 * @param args the command line arguments 141 * @throws Exception if something goes wrong 142 */ 143 public static void main(String[] args) throws Exception { 144 145 CmsRemoteShellClient client = new CmsRemoteShellClient(args); 146 client.run(); 147 } 148 149 /** 150 * Validates, parses and returns the command line arguments.<p> 151 * 152 * @param args the command line arguments 153 * @return the map of parsed arguments 154 */ 155 public Map<String, String> parseArgs(String[] args) { 156 157 Map<String, String> result = new HashMap<String, String>(); 158 Set<String> allowedKeys = new HashSet<String>( 159 Arrays.asList(PARAM_ADDITIONAL, PARAM_SCRIPT, PARAM_REGISTRY_PORT, PARAM_REGISTRY_HOST)); 160 for (String arg : args) { 161 if (arg.startsWith("-")) { 162 int eqPos = arg.indexOf("="); 163 if (eqPos >= 0) { 164 String key = arg.substring(1, eqPos); 165 if (!allowedKeys.contains(key)) { 166 wrongUsage(); 167 } 168 String val = arg.substring(eqPos + 1); 169 result.put(key, val); 170 } else { 171 wrongUsage(); 172 } 173 } else { 174 wrongUsage(); 175 } 176 } 177 return result; 178 } 179 180 /** 181 * Main loop of the shell server client.<p> 182 * 183 * Reads commands from either stdin or a file, executes them remotely and displays the results. 184 * 185 * @throws Exception if something goes wrong 186 */ 187 public void run() throws Exception { 188 189 Registry registry = LocateRegistry.getRegistry(m_registryHost, m_registryPort); 190 I_CmsRemoteShellProvider provider = (I_CmsRemoteShellProvider)(registry.lookup( 191 CmsRemoteShellConstants.PROVIDER)); 192 m_remoteShell = provider.createShell(m_additionalCommands); 193 m_prompt = m_remoteShell.getPrompt(); 194 m_out = new PrintStream(System.out); 195 try { 196 LineNumberReader lnr = new LineNumberReader(new InputStreamReader(m_input, "UTF-8")); 197 while (!exitCalled()) { 198 if (m_interactive || isEcho()) { 199 // print the prompt in front of the commands to process only when 'interactive' 200 printPrompt(); 201 } 202 String line = lnr.readLine(); 203 if (line == null) { 204 break; 205 } 206 if (line.trim().startsWith("#")) { 207 m_out.println(line); 208 continue; 209 } 210 StringReader lineReader = new StringReader(line); 211 StreamTokenizer st = new StreamTokenizer(lineReader); 212 st.eolIsSignificant(true); 213 st.wordChars('*', '*'); 214 // put all tokens into a List 215 List<String> parameters = new ArrayList<String>(); 216 while (st.nextToken() != StreamTokenizer.TT_EOF) { 217 if (st.ttype == StreamTokenizer.TT_NUMBER) { 218 parameters.add(Integer.toString(Double.valueOf(st.nval).intValue())); 219 } else { 220 parameters.add(st.sval); 221 } 222 } 223 lineReader.close(); 224 225 if (parameters.size() == 0) { 226 // empty line, just need to check if echo is on 227 if (isEcho()) { 228 m_out.println(); 229 } 230 continue; 231 } 232 233 // extract command and arguments 234 String command = parameters.get(0); 235 List<String> arguments = new ArrayList<String>(parameters.subList(1, parameters.size())); 236 237 // execute the command with the given arguments 238 executeCommand(command, arguments); 239 240 } 241 exit(0); 242 } catch (Throwable t) { 243 t.printStackTrace(); 244 if (m_errorCode != -1) { 245 exit(m_errorCode); 246 } 247 } 248 } 249 250 /** 251 * Executes a command remotely, displays the command output and updates the internal state.<p> 252 * 253 * @param command the command 254 * @param arguments the arguments 255 */ 256 private void executeCommand(String command, List<String> arguments) { 257 258 try { 259 CmsShellCommandResult result = m_remoteShell.executeCommand(command, arguments); 260 m_out.print(result.getOutput()); 261 updateState(result); 262 if (m_exitCalled) { 263 exit(0); 264 } else if (m_hasError && (m_errorCode != -1)) { 265 exit(m_errorCode); 266 } 267 } catch (RemoteException r) { 268 r.printStackTrace(System.err); 269 exit(1); 270 } 271 } 272 273 /** 274 * Exits the shell with an error code, and if possible, notifies the remote shell that it is exiting.<p> 275 * 276 * @param errorCode the error code 277 */ 278 private void exit(int errorCode) { 279 280 try { 281 m_remoteShell.end(); 282 } catch (Exception e) { 283 e.printStackTrace(); 284 } 285 System.exit(errorCode); 286 } 287 288 /** 289 * Returns true if the exit command has been called.<p> 290 * 291 * @return true if the exit command has been called 292 */ 293 private boolean exitCalled() { 294 295 return m_exitCalled; 296 } 297 298 /** 299 * Returns true if echo mode is enabled.<p> 300 * 301 * @return true if echo mode is enabled 302 */ 303 private boolean isEcho() { 304 305 return m_echo; 306 } 307 308 /** 309 * Prints the prompt.<p> 310 */ 311 private void printPrompt() { 312 313 System.out.print(m_prompt); 314 } 315 316 /** 317 * Updates the internal client state based on the state received from the server.<p> 318 * 319 * @param result the result of the last shell command execution 320 */ 321 private void updateState(CmsShellCommandResult result) { 322 323 m_errorCode = result.getErrorCode(); 324 m_prompt = result.getPrompt(); 325 m_exitCalled = result.isExitCalled(); 326 m_hasError = result.hasError(); 327 m_echo = result.hasEcho(); 328 } 329 330 /** 331 * Displays text which shows the valid command line parameters, and then exits. 332 */ 333 private void wrongUsage() { 334 335 String usage = "Usage: java -cp $PATH_TO_OPENCMS_JAR org.opencms.rmi.CmsRemoteShellClient\n" 336 + " -script=[path to script] (optional) \n" 337 + " -registryPort=[port of RMI registry] (optional, default is " 338 + CmsRemoteShellConstants.DEFAULT_PORT 339 + ")\n" 340 + " -registryHost=[host of RMI registry] (optional, defaults to java.net.InetAddress.getLocalHost().getHostAddress())\n" 341 + " -additional=[additional commands class name] (optional)"; 342 System.out.println(usage); 343 System.exit(1); 344 } 345 346}