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 passing a shell script file name. */ 061 public static final String PARAM_SCRIPT = "script"; 062 063 /** The name of the additional commands class. */ 064 private String m_additionalCommands; 065 066 /** True if echo mode is turned on. */ 067 private boolean m_echo; 068 069 /** The error code which should be returned in case of errors. */ 070 private int m_errorCode; 071 072 /** True if exit was called. */ 073 private boolean m_exitCalled; 074 075 /** True if an error occurred. */ 076 private boolean m_hasError; 077 078 /** The input stream to read the commands from. */ 079 private InputStream m_input; 080 081 /** Controls whether shell is interactive. */ 082 private boolean m_interactive; 083 084 /** The output stream. */ 085 private PrintStream m_out; 086 087 /** The prompt. */ 088 private String m_prompt; 089 090 /** The port used for the RMI registry. */ 091 private int m_registryPort; 092 093 /** The RMI referencce to the shell server. */ 094 private I_CmsRemoteShell m_remoteShell; 095 096 /** 097 * Creates a new instance.<p> 098 * 099 * @param args the parameters 100 * @throws IOException if something goes wrong 101 */ 102 public CmsRemoteShellClient(String[] args) 103 throws IOException { 104 Map<String, String> params = parseArgs(args); 105 String script = params.get(PARAM_SCRIPT); 106 if (script == null) { 107 m_interactive = true; 108 m_input = System.in; 109 } else { 110 m_input = new FileInputStream(script); 111 } 112 m_additionalCommands = params.get(PARAM_ADDITIONAL); 113 String port = params.get(PARAM_REGISTRY_PORT); 114 m_registryPort = CmsRemoteShellConstants.DEFAULT_PORT; 115 if (port != null) { 116 try { 117 m_registryPort = Integer.parseInt(port); 118 if (m_registryPort < 0) { 119 System.out.println("Invalid port: " + port); 120 System.exit(1); 121 } 122 } catch (NumberFormatException e) { 123 System.out.println("Invalid port: " + port); 124 System.exit(1); 125 } 126 } 127 } 128 129 /** 130 * Main method, which starts the shell client.<p> 131 * 132 * @param args the command line arguments 133 * @throws Exception if something goes wrong 134 */ 135 public static void main(String[] args) throws Exception { 136 137 CmsRemoteShellClient client = new CmsRemoteShellClient(args); 138 client.run(); 139 } 140 141 /** 142 * Validates, parses and returns the command line arguments.<p> 143 * 144 * @param args the command line arguments 145 * @return the map of parsed arguments 146 */ 147 public Map<String, String> parseArgs(String[] args) { 148 149 Map<String, String> result = new HashMap<String, String>(); 150 Set<String> allowedKeys = new HashSet<String>( 151 Arrays.asList(PARAM_ADDITIONAL, PARAM_SCRIPT, PARAM_REGISTRY_PORT)); 152 for (String arg : args) { 153 if (arg.startsWith("-")) { 154 int eqPos = arg.indexOf("="); 155 if (eqPos >= 0) { 156 String key = arg.substring(1, eqPos); 157 if (!allowedKeys.contains(key)) { 158 wrongUsage(); 159 } 160 String val = arg.substring(eqPos + 1); 161 result.put(key, val); 162 } else { 163 wrongUsage(); 164 } 165 } else { 166 wrongUsage(); 167 } 168 } 169 return result; 170 } 171 172 /** 173 * Main loop of the shell server client.<p> 174 * 175 * Reads commands from either stdin or a file, executes them remotely and displays the results. 176 * 177 * @throws Exception if something goes wrong 178 */ 179 public void run() throws Exception { 180 181 Registry registry = LocateRegistry.getRegistry(m_registryPort); 182 I_CmsRemoteShellProvider provider = (I_CmsRemoteShellProvider)(registry.lookup( 183 CmsRemoteShellConstants.PROVIDER)); 184 m_remoteShell = provider.createShell(m_additionalCommands); 185 m_prompt = m_remoteShell.getPrompt(); 186 m_out = new PrintStream(System.out); 187 try { 188 LineNumberReader lnr = new LineNumberReader(new InputStreamReader(m_input, "UTF-8")); 189 while (!exitCalled()) { 190 if (m_interactive || isEcho()) { 191 // print the prompt in front of the commands to process only when 'interactive' 192 printPrompt(); 193 } 194 String line = lnr.readLine(); 195 if (line == null) { 196 break; 197 } 198 if (line.trim().startsWith("#")) { 199 m_out.println(line); 200 continue; 201 } 202 StringReader lineReader = new StringReader(line); 203 StreamTokenizer st = new StreamTokenizer(lineReader); 204 st.eolIsSignificant(true); 205 st.wordChars('*', '*'); 206 // put all tokens into a List 207 List<String> parameters = new ArrayList<String>(); 208 while (st.nextToken() != StreamTokenizer.TT_EOF) { 209 if (st.ttype == StreamTokenizer.TT_NUMBER) { 210 parameters.add(Integer.toString(new Double(st.nval).intValue())); 211 } else { 212 parameters.add(st.sval); 213 } 214 } 215 lineReader.close(); 216 217 if (parameters.size() == 0) { 218 // empty line, just need to check if echo is on 219 if (isEcho()) { 220 m_out.println(); 221 } 222 continue; 223 } 224 225 // extract command and arguments 226 String command = parameters.get(0); 227 List<String> arguments = new ArrayList<String>(parameters.subList(1, parameters.size())); 228 229 // execute the command with the given arguments 230 executeCommand(command, arguments); 231 232 } 233 exit(0); 234 } catch (Throwable t) { 235 t.printStackTrace(); 236 if (m_errorCode != -1) { 237 exit(m_errorCode); 238 } 239 } 240 } 241 242 /** 243 * Executes a command remotely, displays the command output and updates the internal state.<p> 244 * 245 * @param command the command 246 * @param arguments the arguments 247 */ 248 private void executeCommand(String command, List<String> arguments) { 249 250 try { 251 CmsShellCommandResult result = m_remoteShell.executeCommand(command, arguments); 252 m_out.print(result.getOutput()); 253 updateState(result); 254 if (m_exitCalled) { 255 exit(0); 256 } else if (m_hasError && (m_errorCode != -1)) { 257 exit(m_errorCode); 258 } 259 } catch (RemoteException r) { 260 r.printStackTrace(System.err); 261 exit(1); 262 } 263 } 264 265 /** 266 * Exits the shell with an error code, and if possible, notifies the remote shell that it is exiting.<p> 267 * 268 * @param errorCode the error code 269 */ 270 private void exit(int errorCode) { 271 272 try { 273 m_remoteShell.end(); 274 } catch (Exception e) { 275 e.printStackTrace(); 276 } 277 System.exit(errorCode); 278 } 279 280 /** 281 * Returns true if the exit command has been called.<p> 282 * 283 * @return true if the exit command has been called 284 */ 285 private boolean exitCalled() { 286 287 return m_exitCalled; 288 } 289 290 /** 291 * Returns true if echo mode is enabled.<p> 292 * 293 * @return true if echo mode is enabled 294 */ 295 private boolean isEcho() { 296 297 return m_echo; 298 } 299 300 /** 301 * Prints the prompt.<p> 302 */ 303 private void printPrompt() { 304 305 System.out.print(m_prompt); 306 } 307 308 /** 309 * Updates the internal client state based on the state received from the server.<p> 310 * 311 * @param result the result of the last shell command execution 312 */ 313 private void updateState(CmsShellCommandResult result) { 314 315 m_errorCode = result.getErrorCode(); 316 m_prompt = result.getPrompt(); 317 m_exitCalled = result.isExitCalled(); 318 m_hasError = result.hasError(); 319 m_echo = result.hasEcho(); 320 } 321 322 /** 323 * Displays text which shows the valid command line parameters, and then exits. 324 */ 325 private void wrongUsage() { 326 327 String usage = "Usage: java -cp $PATH_TO_OPENCMS_JAR org.opencms.rmi.CmsRemoteShellClient\n" 328 + " -script=[path to script] (optional) \n" 329 + " -registryPort=[port of RMI registry] (optional, default is " 330 + CmsRemoteShellConstants.DEFAULT_PORT 331 + ")\n" 332 + " -additional=[additional commands class name] (optional)"; 333 System.out.println(usage); 334 System.exit(1); 335 } 336 337}