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.main; 029 030import org.opencms.configuration.CmsParameterConfiguration; 031import org.opencms.db.CmsUserSettings; 032import org.opencms.file.CmsObject; 033import org.opencms.file.CmsUser; 034import org.opencms.i18n.CmsLocaleManager; 035import org.opencms.i18n.CmsMessages; 036import org.opencms.security.CmsRole; 037import org.opencms.util.CmsDataTypeUtil; 038import org.opencms.util.CmsFileUtil; 039import org.opencms.util.CmsStringUtil; 040 041import java.awt.event.KeyEvent; 042import java.io.FileDescriptor; 043import java.io.FileInputStream; 044import java.io.IOException; 045import java.io.InputStream; 046import java.io.InputStreamReader; 047import java.io.LineNumberReader; 048import java.io.PrintStream; 049import java.io.Reader; 050import java.io.StreamTokenizer; 051import java.io.StringReader; 052import java.lang.reflect.InvocationTargetException; 053import java.lang.reflect.Method; 054import java.lang.reflect.Modifier; 055import java.util.ArrayList; 056import java.util.Collection; 057import java.util.Iterator; 058import java.util.List; 059import java.util.Locale; 060import java.util.Map; 061import java.util.TreeMap; 062 063/** 064 * A command line interface to access OpenCms functions which 065 * is used for the initial setup and also can be used for scripting access to the OpenCms 066 * repository without the Workplace.<p> 067 * 068 * The CmsShell has direct access to all methods in the "command objects". 069 * Currently the following classes are used as command objects: 070 * <code>{@link org.opencms.main.CmsShellCommands}</code>, 071 * <code>{@link org.opencms.file.CmsRequestContext}</code> and 072 * <code>{@link org.opencms.file.CmsObject}</code>.<p> 073 * 074 * It is also possible to add a custom command object when calling the script API, 075 * like in {@link CmsShell#CmsShell(String, String, String, String, I_CmsShellCommands, PrintStream, PrintStream, boolean)}.<p> 076 * 077 * Only public methods in the command objects that use supported data types 078 * as parameters can be called from the shell. Supported data types are: 079 * <code>String, {@link org.opencms.util.CmsUUID}, boolean, int, long, double, float</code>.<p> 080 * 081 * If a method name is ambiguous, i.e. the method name with the same number of parameter exist 082 * in more then one of the command objects, the method is only executed on the first matching method object.<p> 083 * 084 * @since 6.0.0 085 * 086 * @see org.opencms.main.CmsShellCommands 087 * @see org.opencms.file.CmsRequestContext 088 * @see org.opencms.file.CmsObject 089 */ 090public class CmsShell { 091 092 /** 093 * Command object class.<p> 094 */ 095 private class CmsCommandObject { 096 097 /** The list of methods. */ 098 private Map<String, List<Method>> m_methods; 099 100 /** The object to execute the methods on. */ 101 private Object m_object; 102 103 /** 104 * Creates a new command object.<p> 105 * 106 * @param object the object to execute the methods on 107 */ 108 protected CmsCommandObject(Object object) { 109 110 m_object = object; 111 initShellMethods(); 112 } 113 114 /** 115 * Tries to execute a method for the provided parameters on this command object.<p> 116 * 117 * If methods with the same name and number of parameters exist in this command object, 118 * the given parameters are tried to be converted from String to matching types.<p> 119 * 120 * @param command the command entered by the user in the shell 121 * @param parameters the parameters entered by the user in the shell 122 * @return true if a method was executed, false otherwise 123 */ 124 protected boolean executeMethod(String command, List<String> parameters) { 125 126 // build the method lookup 127 String lookup = buildMethodLookup(command, parameters.size()); 128 129 // try to look up the methods of this command object 130 List<Method> possibleMethods = m_methods.get(lookup); 131 if (possibleMethods == null) { 132 return false; 133 } 134 135 // a match for the method name was found, now try to figure out if the parameters are ok 136 Method onlyStringMethod = null; 137 Method foundMethod = null; 138 Object[] params = null; 139 Iterator<Method> i; 140 141 // first check if there is one method with only has String parameters, make this the fall back 142 i = possibleMethods.iterator(); 143 while (i.hasNext()) { 144 Method method = i.next(); 145 Class<?>[] clazz = method.getParameterTypes(); 146 boolean onlyString = true; 147 for (int j = 0; j < clazz.length; j++) { 148 if (!(clazz[j].equals(String.class))) { 149 onlyString = false; 150 break; 151 } 152 } 153 if (onlyString) { 154 onlyStringMethod = method; 155 break; 156 } 157 } 158 159 // now check a method matches the provided parameters 160 // if so, use this method, else continue searching 161 i = possibleMethods.iterator(); 162 while (i.hasNext()) { 163 Method method = i.next(); 164 if (method == onlyStringMethod) { 165 // skip the String only signature because this would always match 166 continue; 167 } 168 // now try to convert the parameters to the required types 169 Class<?>[] clazz = method.getParameterTypes(); 170 Object[] converted = new Object[clazz.length]; 171 boolean match = true; 172 for (int j = 0; j < clazz.length; j++) { 173 String value = parameters.get(j); 174 try { 175 converted[j] = CmsDataTypeUtil.parse(value, clazz[j]); 176 } catch (Throwable t) { 177 match = false; 178 break; 179 } 180 } 181 if (match) { 182 // we found a matching method signature 183 params = converted; 184 foundMethod = method; 185 break; 186 } 187 188 } 189 190 if ((foundMethod == null) && (onlyStringMethod != null)) { 191 // no match found but String only signature available, use this 192 params = parameters.toArray(); 193 foundMethod = onlyStringMethod; 194 } 195 196 if ((params == null) || (foundMethod == null)) { 197 // no match found at all 198 return false; 199 } 200 201 // now try to invoke the method 202 try { 203 Object result = foundMethod.invoke(m_object, params); 204 if (result != null) { 205 if (result instanceof Collection<?>) { 206 Collection<?> c = (Collection<?>)result; 207 m_out.println(c.getClass().getName() + " (size: " + c.size() + ")"); 208 int count = 0; 209 if (result instanceof Map<?, ?>) { 210 Map<?, ?> m = (Map<?, ?>)result; 211 Iterator<?> j = m.entrySet().iterator(); 212 while (j.hasNext()) { 213 Map.Entry<?, ?> entry = (Map.Entry<?, ?>)j.next(); 214 m_out.println(count++ + ": " + entry.getKey() + "= " + entry.getValue()); 215 } 216 } else { 217 Iterator<?> j = c.iterator(); 218 while (j.hasNext()) { 219 m_out.println(count++ + ": " + j.next()); 220 } 221 } 222 } else { 223 m_out.println(result.toString()); 224 } 225 } 226 } catch (InvocationTargetException ite) { 227 m_out.println( 228 Messages.get().getBundle(getLocale()).key( 229 Messages.GUI_SHELL_EXEC_METHOD_1, 230 new Object[] {foundMethod.getName()})); 231 ite.getTargetException().printStackTrace(m_out); 232 } catch (Throwable t) { 233 m_out.println( 234 Messages.get().getBundle(getLocale()).key( 235 Messages.GUI_SHELL_EXEC_METHOD_1, 236 new Object[] {foundMethod.getName()})); 237 t.printStackTrace(m_out); 238 } 239 240 return true; 241 } 242 243 /** 244 * Returns a signature overview of all methods containing the given search String.<p> 245 * 246 * If no method name matches the given search String, the empty String is returned.<p> 247 * 248 * @param searchString the String to search for, if null all methods are shown 249 * 250 * @return a signature overview of all methods containing the given search String 251 */ 252 protected String getMethodHelp(String searchString) { 253 254 StringBuffer buf = new StringBuffer(512); 255 Iterator<String> i = m_methods.keySet().iterator(); 256 while (i.hasNext()) { 257 List<Method> l = m_methods.get(i.next()); 258 Iterator<Method> j = l.iterator(); 259 while (j.hasNext()) { 260 Method method = j.next(); 261 if ((searchString == null) 262 || (method.getName().toLowerCase().indexOf(searchString.toLowerCase()) > -1)) { 263 buf.append("* "); 264 buf.append(method.getName()); 265 buf.append("("); 266 Class<?>[] params = method.getParameterTypes(); 267 for (int k = 0; k < params.length; k++) { 268 String par = params[k].getName(); 269 par = par.substring(par.lastIndexOf('.') + 1); 270 if (k != 0) { 271 buf.append(", "); 272 } 273 buf.append(par); 274 } 275 buf.append(")\n"); 276 } 277 } 278 } 279 return buf.toString(); 280 } 281 282 /** 283 * Returns the object to execute the methods on.<p> 284 * 285 * @return the object to execute the methods on 286 */ 287 protected Object getObject() { 288 289 return m_object; 290 } 291 292 /** 293 * Builds a method lookup String.<p> 294 * 295 * @param methodName the name of the method 296 * @param paramCount the parameter count of the method 297 * 298 * @return a method lookup String 299 */ 300 private String buildMethodLookup(String methodName, int paramCount) { 301 302 StringBuffer buf = new StringBuffer(32); 303 buf.append(methodName.toLowerCase()); 304 buf.append(" ["); 305 buf.append(paramCount); 306 buf.append("]"); 307 return buf.toString(); 308 } 309 310 /** 311 * Initializes the map of accessible methods.<p> 312 */ 313 private void initShellMethods() { 314 315 Map<String, List<Method>> result = new TreeMap<String, List<Method>>(); 316 317 Method[] methods = m_object.getClass().getMethods(); 318 for (int i = 0; i < methods.length; i++) { 319 // only public methods directly declared in the base class can be used in the shell 320 if ((methods[i].getDeclaringClass() == m_object.getClass()) 321 && (methods[i].getModifiers() == Modifier.PUBLIC)) { 322 323 // check if the method signature only uses primitive data types 324 boolean onlyPrimitive = true; 325 Class<?>[] clazz = methods[i].getParameterTypes(); 326 for (int j = 0; j < clazz.length; j++) { 327 if (!CmsDataTypeUtil.isParseable(clazz[j])) { 328 // complex data type methods can not be called from the shell 329 onlyPrimitive = false; 330 break; 331 } 332 } 333 334 if (onlyPrimitive) { 335 // add this method to the set of methods that can be called from the shell 336 String lookup = buildMethodLookup(methods[i].getName(), methods[i].getParameterTypes().length); 337 List<Method> l; 338 if (result.containsKey(lookup)) { 339 l = result.get(lookup); 340 } else { 341 l = new ArrayList<Method>(1); 342 } 343 l.add(methods[i]); 344 result.put(lookup, l); 345 } 346 } 347 } 348 m_methods = result; 349 } 350 } 351 352 /** Prefix for "additional" parameter. */ 353 public static final String SHELL_PARAM_ADDITIONAL_COMMANDS = "-additional="; 354 355 /** Prefix for "base" parameter. */ 356 public static final String SHELL_PARAM_BASE = "-base="; 357 358 /** Prefix for "servletMapping" parameter. */ 359 public static final String SHELL_PARAM_DEFAULT_WEB_APP = "-defaultWebApp="; 360 361 /** Prefix for "script" parameter. */ 362 public static final String SHELL_PARAM_SCRIPT = "-script="; 363 364 /** Prefix for "servletMapping" parameter. */ 365 public static final String SHELL_PARAM_SERVLET_MAPPING = "-servletMapping="; 366 367 /** The OpenCms context object. */ 368 protected CmsObject m_cms; 369 370 /** Additional shell commands object. */ 371 private I_CmsShellCommands m_additionalShellCommands; 372 373 /** All shell callable objects. */ 374 private List<CmsCommandObject> m_commandObjects; 375 376 /** If set to true, all commands are echoed. */ 377 private boolean m_echo; 378 379 /** Indicates if the 'exit' command has been called. */ 380 private boolean m_exitCalled; 381 382 /** The messages object. */ 383 private CmsMessages m_messages; 384 385 /** The OpenCms system object. */ 386 private OpenCmsCore m_opencms; 387 388 /** The shell prompt format. */ 389 private String m_prompt; 390 391 /** The current users settings. */ 392 private CmsUserSettings m_settings; 393 394 /** Internal shell command object. */ 395 private I_CmsShellCommands m_shellCommands; 396 397 /** Stream to write the regular output messages to. */ 398 protected PrintStream m_out; 399 400 /** Stream to write the error messages output to. */ 401 protected PrintStream m_err; 402 403 /** Indicates if this is an interactive session with a user sitting on a console. */ 404 private boolean m_interactive; 405 406 /** 407 * Creates a new CmsShell.<p> 408 * 409 * @param cms the user context to run the shell from 410 * @param prompt the prompt format to set 411 * @param additionalShellCommands optional object for additional shell commands, or null 412 * @param out stream to write the regular output messages to 413 * @param err stream to write the error messages output to 414 */ 415 public CmsShell( 416 CmsObject cms, 417 String prompt, 418 I_CmsShellCommands additionalShellCommands, 419 PrintStream out, 420 PrintStream err) { 421 422 setPrompt(prompt); 423 try { 424 // has to be initialized already if this constructor is used 425 m_opencms = null; 426 Locale locale = getLocale(); 427 m_messages = Messages.get().getBundle(locale); 428 m_cms = cms; 429 430 // initialize the shell 431 initShell(additionalShellCommands, out, err); 432 } catch (Throwable t) { 433 t.printStackTrace(m_err); 434 } 435 } 436 437 /** 438 * Creates a new CmsShell using System.out and System.err for output of the messages.<p> 439 * 440 * @param webInfPath the path to the 'WEB-INF' folder of the OpenCms installation 441 * @param servletMapping the mapping of the servlet (or <code>null</code> to use the default <code>"/opencms/*"</code>) 442 * @param defaultWebAppName the name of the default web application (or <code>null</code> to use the default <code>"ROOT"</code>) 443 * @param prompt the prompt format to set 444 * @param additionalShellCommands optional object for additional shell commands, or null 445 */ 446 public CmsShell( 447 String webInfPath, 448 String servletMapping, 449 String defaultWebAppName, 450 String prompt, 451 I_CmsShellCommands additionalShellCommands) { 452 453 this( 454 webInfPath, 455 servletMapping, 456 defaultWebAppName, 457 prompt, 458 additionalShellCommands, 459 System.out, 460 System.err, 461 false); 462 } 463 464 /** 465 * Creates a new CmsShell.<p> 466 * 467 * @param webInfPath the path to the 'WEB-INF' folder of the OpenCms installation 468 * @param servletMapping the mapping of the servlet (or <code>null</code> to use the default <code>"/opencms/*"</code>) 469 * @param defaultWebAppName the name of the default web application (or <code>null</code> to use the default <code>"ROOT"</code>) 470 * @param prompt the prompt format to set 471 * @param additionalShellCommands optional object for additional shell commands, or null 472 * @param out stream to write the regular output messages to 473 * @param err stream to write the error messages output to 474 * @param interactive if <code>true</code> this is an interactive session with a user sitting on a console 475 */ 476 public CmsShell( 477 String webInfPath, 478 String servletMapping, 479 String defaultWebAppName, 480 String prompt, 481 I_CmsShellCommands additionalShellCommands, 482 PrintStream out, 483 PrintStream err, 484 boolean interactive) { 485 486 setPrompt(prompt); 487 setInteractive(interactive); 488 if (CmsStringUtil.isEmpty(servletMapping)) { 489 servletMapping = "/opencms/*"; 490 } 491 if (CmsStringUtil.isEmpty(defaultWebAppName)) { 492 defaultWebAppName = "ROOT"; 493 } 494 try { 495 // first initialize runlevel 1 496 m_opencms = OpenCmsCore.getInstance(); 497 // Externalization: get Locale: will be the System default since no CmsObject is up before 498 // runlevel 2 499 Locale locale = getLocale(); 500 m_messages = Messages.get().getBundle(locale); 501 // search for the WEB-INF folder 502 if (CmsStringUtil.isEmpty(webInfPath)) { 503 out.println(m_messages.key(Messages.GUI_SHELL_NO_HOME_FOLDER_SPECIFIED_0)); 504 out.println(); 505 webInfPath = CmsFileUtil.searchWebInfFolder(System.getProperty("user.dir")); 506 if (CmsStringUtil.isEmpty(webInfPath)) { 507 err.println(m_messages.key(Messages.GUI_SHELL_HR_0)); 508 err.println(m_messages.key(Messages.GUI_SHELL_NO_HOME_FOLDER_FOUND_0)); 509 err.println(); 510 err.println(m_messages.key(Messages.GUI_SHELL_START_DIR_LINE1_0)); 511 err.println(m_messages.key(Messages.GUI_SHELL_START_DIR_LINE2_0)); 512 err.println(m_messages.key(Messages.GUI_SHELL_HR_0)); 513 return; 514 } 515 } 516 out.println(Messages.get().getBundle(locale).key(Messages.GUI_SHELL_WEB_INF_PATH_1, webInfPath)); 517 // set the path to the WEB-INF folder (the 2nd and 3rd parameters are just reasonable dummies) 518 CmsServletContainerSettings settings = new CmsServletContainerSettings( 519 webInfPath, 520 defaultWebAppName, 521 servletMapping, 522 null, 523 null); 524 m_opencms.getSystemInfo().init(settings); 525 // now read the configuration properties 526 String propertyPath = m_opencms.getSystemInfo().getConfigurationFileRfsPath(); 527 out.println(m_messages.key(Messages.GUI_SHELL_CONFIG_FILE_1, propertyPath)); 528 out.println(); 529 CmsParameterConfiguration configuration = new CmsParameterConfiguration(propertyPath); 530 531 // now upgrade to runlevel 2 532 m_opencms = m_opencms.upgradeRunlevel(configuration); 533 534 // create a context object with 'Guest' permissions 535 m_cms = m_opencms.initCmsObject(m_opencms.getDefaultUsers().getUserGuest()); 536 537 // initialize the shell 538 initShell(additionalShellCommands, out, err); 539 } catch (Throwable t) { 540 t.printStackTrace(err); 541 } 542 } 543 544 /** 545 * Main program entry point when started via the command line.<p> 546 * 547 * @param args parameters passed to the application via the command line 548 */ 549 public static void main(String[] args) { 550 551 boolean wrongUsage = false; 552 String webInfPath = null; 553 String script = null; 554 String servletMapping = null; 555 String defaultWebApp = null; 556 String additional = null; 557 558 if (args.length > 4) { 559 wrongUsage = true; 560 } else { 561 for (int i = 0; i < args.length; i++) { 562 String arg = args[i]; 563 if (arg.startsWith(SHELL_PARAM_BASE)) { 564 webInfPath = arg.substring(SHELL_PARAM_BASE.length()); 565 } else if (arg.startsWith(SHELL_PARAM_SCRIPT)) { 566 script = arg.substring(SHELL_PARAM_SCRIPT.length()); 567 } else if (arg.startsWith(SHELL_PARAM_SERVLET_MAPPING)) { 568 servletMapping = arg.substring(SHELL_PARAM_SERVLET_MAPPING.length()); 569 } else if (arg.startsWith(SHELL_PARAM_DEFAULT_WEB_APP)) { 570 defaultWebApp = arg.substring(SHELL_PARAM_DEFAULT_WEB_APP.length()); 571 } else if (arg.startsWith(SHELL_PARAM_ADDITIONAL_COMMANDS)) { 572 additional = arg.substring(SHELL_PARAM_ADDITIONAL_COMMANDS.length()); 573 } else { 574 System.out.println(Messages.get().getBundle().key(Messages.GUI_SHELL_WRONG_USAGE_0)); 575 wrongUsage = true; 576 } 577 } 578 } 579 if (wrongUsage) { 580 System.out.println(Messages.get().getBundle().key(Messages.GUI_SHELL_USAGE_1, CmsShell.class.getName())); 581 } else { 582 583 I_CmsShellCommands additionalCommands = null; 584 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(additional)) { 585 try { 586 Class<?> commandClass = Class.forName(additional); 587 additionalCommands = (I_CmsShellCommands)commandClass.newInstance(); 588 } catch (Exception e) { 589 System.out.println( 590 Messages.get().getBundle().key(Messages.GUI_SHELL_ERR_ADDITIONAL_COMMANDS_1, additional)); 591 e.printStackTrace(); 592 return; 593 } 594 } 595 boolean interactive = true; 596 FileInputStream stream = null; 597 if (script != null) { 598 try { 599 stream = new FileInputStream(script); 600 } catch (IOException exc) { 601 System.out.println(Messages.get().getBundle().key(Messages.GUI_SHELL_ERR_SCRIPTFILE_1, script)); 602 } 603 } 604 if (stream == null) { 605 // no script-file, use standard input stream 606 stream = new FileInputStream(FileDescriptor.in); 607 interactive = true; 608 } 609 CmsShell shell = new CmsShell( 610 webInfPath, 611 servletMapping, 612 defaultWebApp, 613 "${user}@${project}:${siteroot}|${uri}>", 614 additionalCommands, 615 System.out, 616 System.err, 617 interactive); 618 shell.execute(stream); 619 try { 620 stream.close(); 621 } catch (IOException e) { 622 e.printStackTrace(); 623 } 624 } 625 } 626 627 /** 628 * Executes the commands from the given input stream in this shell.<p> 629 * 630 * <ul> 631 * <li>Commands in the must be separated with a line break '\n'. 632 * <li>Only one command per line is allowed. 633 * <li>String parameters must be quoted like this: <code>'string value'</code>. 634 * </ul> 635 * 636 * @param inputStream the input stream from which the commands are read 637 */ 638 public void execute(InputStream inputStream) { 639 640 execute(new InputStreamReader(inputStream)); 641 } 642 643 /** 644 * Executes the commands from the given reader in this shell.<p> 645 * 646 * <ul> 647 * <li>Commands in the must be separated with a line break '\n'. 648 * <li>Only one command per line is allowed. 649 * <li>String parameters must be quoted like this: <code>'string value'</code>. 650 * </ul> 651 * 652 * @param reader the reader from which the commands are read 653 */ 654 public void execute(Reader reader) { 655 656 try { 657 LineNumberReader lnr = new LineNumberReader(reader); 658 while (!m_exitCalled) { 659 if (m_interactive || m_echo) { 660 // print the prompt in front of the commands to process only when 'interactive' 661 printPrompt(); 662 } 663 String line = lnr.readLine(); 664 if (line == null) { 665 // if null the file has been read to the end 666 try { 667 Thread.sleep(500); 668 } catch (Throwable t) { 669 // noop 670 } 671 // end the while loop 672 break; 673 } 674 if (line.trim().startsWith("#")) { 675 m_out.println(line); 676 continue; 677 } 678 // In linux, the up and down arrows generate escape sequences that cannot be properly handled. 679 // If a escape sequence is detected, OpenCms prints a warning message 680 if (line.indexOf(KeyEvent.VK_ESCAPE) != -1) { 681 m_out.println( 682 m_messages.key(Messages.GUI_SHELL_ESCAPE_SEQUENCES_NOT_SUPPORTED_0)); 683 continue; 684 } 685 StringReader lineReader = new StringReader(line); 686 StreamTokenizer st = new StreamTokenizer(lineReader); 687 st.eolIsSignificant(true); 688 st.wordChars('*', '*'); 689 // put all tokens into a List 690 List<String> parameters = new ArrayList<String>(); 691 while (st.nextToken() != StreamTokenizer.TT_EOF) { 692 if (st.ttype == StreamTokenizer.TT_NUMBER) { 693 parameters.add(Integer.toString(new Double(st.nval).intValue())); 694 } else { 695 if (null != st.sval) { 696 parameters.add(st.sval); 697 } 698 } 699 } 700 lineReader.close(); 701 702 if (parameters.size() == 0) { 703 // empty line, just need to check if echo is on 704 if (m_echo) { 705 m_out.println(); 706 } 707 continue; 708 } 709 710 // extract command and arguments 711 String command = parameters.get(0); 712 List<String> arguments = parameters.subList(1, parameters.size()); 713 714 // execute the command with the given arguments 715 executeCommand(command, arguments); 716 } 717 } catch (Throwable t) { 718 t.printStackTrace(m_err); 719 } 720 } 721 722 /** 723 * Executes the commands from the given string in this shell.<p> 724 * 725 * <ul> 726 * <li>Commands in the must be separated with a line break '\n'. 727 * <li>Only one command per line is allowed. 728 * <li>String parameters must be quoted like this: <code>'string value'</code>. 729 * </ul> 730 * 731 * @param commands the string from which the commands are read 732 */ 733 public void execute(String commands) { 734 735 execute(new StringReader(commands)); 736 } 737 738 /** 739 * Exits this shell and destroys the OpenCms instance.<p> 740 */ 741 public void exit() { 742 743 if (m_exitCalled) { 744 return; 745 } 746 m_exitCalled = true; 747 try { 748 if (m_additionalShellCommands != null) { 749 m_additionalShellCommands.shellExit(); 750 } else { 751 m_shellCommands.shellExit(); 752 } 753 } catch (Throwable t) { 754 t.printStackTrace(); 755 } 756 if (m_opencms != null) { 757 // if called by an in line script we don't want to kill the whole instance 758 try { 759 m_opencms.shutDown(); 760 } catch (Throwable t) { 761 t.printStackTrace(); 762 } 763 } 764 } 765 766 /** 767 * Returns the stream this shell writes its error messages to.<p> 768 * 769 * @return the stream this shell writes its error messages to 770 */ 771 public PrintStream getErr() { 772 773 return m_err; 774 } 775 776 /** 777 * Private internal helper for localization to the current user's locale 778 * within OpenCms. <p> 779 * 780 * @return the current user's <code>Locale</code>. 781 */ 782 public Locale getLocale() { 783 784 if (getSettings() == null) { 785 return CmsLocaleManager.getDefaultLocale(); 786 } 787 return getSettings().getLocale(); 788 } 789 790 /** 791 * Returns the localized messages object for the current user.<p> 792 * 793 * @return the localized messages object for the current user 794 */ 795 public CmsMessages getMessages() { 796 797 return m_messages; 798 } 799 800 /** 801 * Returns the stream this shell writes its regular messages to.<p> 802 * 803 * @return the stream this shell writes its regular messages to 804 */ 805 public PrintStream getOut() { 806 807 return m_out; 808 } 809 810 /** 811 * Obtain the additional settings related to the current user. 812 * 813 * @return the additional settings related to the current user. 814 */ 815 public CmsUserSettings getSettings() { 816 817 return m_settings; 818 } 819 820 /** 821 * Initializes the CmsShell.<p> 822 * 823 * @param additionalShellCommands optional object for additional shell commands, or null 824 * @param out stream to write the regular output messages to 825 * @param err stream to write the error messages output to 826 */ 827 public void initShell(I_CmsShellCommands additionalShellCommands, PrintStream out, PrintStream err) { 828 829 // set the output streams 830 m_out = out; 831 m_err = err; 832 833 // initialize the settings of the user 834 m_settings = initSettings(); 835 836 // initialize shell command object 837 m_shellCommands = new CmsShellCommands(); 838 m_shellCommands.initShellCmsObject(m_cms, this); 839 840 // initialize additional shell command object 841 if (additionalShellCommands != null) { 842 m_additionalShellCommands = additionalShellCommands; 843 m_additionalShellCommands.initShellCmsObject(m_cms, this); 844 m_additionalShellCommands.shellStart(); 845 } else { 846 m_shellCommands.shellStart(); 847 } 848 849 m_commandObjects = new ArrayList<CmsCommandObject>(); 850 if (m_additionalShellCommands != null) { 851 // get all shell callable methods from the additional shell command object 852 m_commandObjects.add(new CmsCommandObject(m_additionalShellCommands)); 853 } 854 // get all shell callable methods from the CmsShellCommands 855 m_commandObjects.add(new CmsCommandObject(m_shellCommands)); 856 // get all shell callable methods from the CmsRequestContext 857 m_commandObjects.add(new CmsCommandObject(m_cms.getRequestContext())); 858 // get all shell callable methods from the CmsObject 859 m_commandObjects.add(new CmsCommandObject(m_cms)); 860 } 861 862 /** 863 * If <code>true</code> this is an interactive session with a user sitting on a console.<p> 864 * 865 * @return <code>true</code> if this is an interactive session with a user sitting on a console 866 */ 867 public boolean isInteractive() { 868 869 return m_interactive; 870 } 871 872 /** 873 * Prints the shell prompt.<p> 874 */ 875 public void printPrompt() { 876 877 String prompt = m_prompt; 878 try { 879 prompt = CmsStringUtil.substitute(prompt, "${user}", m_cms.getRequestContext().getCurrentUser().getName()); 880 prompt = CmsStringUtil.substitute(prompt, "${siteroot}", m_cms.getRequestContext().getSiteRoot()); 881 prompt = CmsStringUtil.substitute( 882 prompt, 883 "${project}", 884 m_cms.getRequestContext().getCurrentProject().getName()); 885 prompt = CmsStringUtil.substitute(prompt, "${uri}", m_cms.getRequestContext().getUri()); 886 } catch (Throwable t) { 887 // ignore 888 } 889 m_out.print(prompt); 890 m_out.flush(); 891 } 892 893 /** 894 * Set <code>true</code> if this is an interactive session with a user sitting on a console.<p> 895 * 896 * This controls the output of the prompt and some other info that is valuable 897 * on the console, but not required in an automatic session.<p> 898 * 899 * @param interactive if <code>true</code> this is an interactive session with a user sitting on a console 900 */ 901 public void setInteractive(boolean interactive) { 902 903 m_interactive = interactive; 904 } 905 906 /** 907 * Sets the locale of the current user.<p> 908 * 909 * @param locale the locale to set 910 * 911 * @throws CmsException in case the locale of the current user can not be stored 912 */ 913 public void setLocale(Locale locale) throws CmsException { 914 915 CmsUserSettings settings = getSettings(); 916 if (settings != null) { 917 settings.setLocale(locale); 918 settings.save(m_cms); 919 m_messages = Messages.get().getBundle(locale); 920 } 921 } 922 923 /** 924 * Reads the given stream and executes the commands in this shell.<p> 925 * 926 * @param inputStream an input stream from which commands are read 927 * @deprecated use {@link #execute(InputStream)} instead 928 */ 929 @Deprecated 930 public void start(FileInputStream inputStream) { 931 932 // in the old behavior 'interactive' was always true 933 setInteractive(true); 934 execute(inputStream); 935 } 936 937 /** 938 * Validates the given user and password and checks if the user has the requested role.<p> 939 * 940 * @param userName the user name 941 * @param password the password 942 * @param requiredRole the required role 943 * 944 * @return <code>true</code> if the user is valid 945 */ 946 public boolean validateUser(String userName, String password, CmsRole requiredRole) { 947 948 boolean result = false; 949 950 try { 951 CmsUser user = m_cms.readUser(userName, password); 952 result = OpenCms.getRoleManager().hasRole(m_cms, user.getName(), requiredRole); 953 } catch (@SuppressWarnings("unused") CmsException e) { 954 // nothing to do 955 } 956 return result; 957 } 958 959 /** 960 * Shows the signature of all methods containing the given search String.<p> 961 * 962 * @param searchString the String to search for in the methods, if null all methods are shown 963 */ 964 protected void help(String searchString) { 965 966 String commandList; 967 boolean foundSomething = false; 968 m_out.println(); 969 970 Iterator<CmsCommandObject> i = m_commandObjects.iterator(); 971 while (i.hasNext()) { 972 CmsCommandObject cmdObj = i.next(); 973 commandList = cmdObj.getMethodHelp(searchString); 974 if (!CmsStringUtil.isEmpty(commandList)) { 975 m_out.println( 976 m_messages.key(Messages.GUI_SHELL_AVAILABLE_METHODS_1, cmdObj.getObject().getClass().getName())); 977 m_out.println(commandList); 978 foundSomething = true; 979 } 980 } 981 982 if (!foundSomething) { 983 m_out.println(m_messages.key(Messages.GUI_SHELL_MATCH_SEARCHSTRING_1, searchString)); 984 } 985 } 986 987 /** 988 * Initializes the internal <code>CmsWorkplaceSettings</code> that contain (amongst other 989 * information) important information additional information about the current user 990 * (an instance of {@link CmsUserSettings}).<p> 991 * 992 * This step is performed within the <code>CmsShell</code> constructor directly after 993 * switching to run-level 2 and obtaining the <code>CmsObject</code> for the guest user as 994 * well as when invoking the CmsShell command <code>login</code>.<p> 995 * 996 * @return the user settings for the current user. 997 */ 998 protected CmsUserSettings initSettings() { 999 1000 m_settings = new CmsUserSettings(m_cms); 1001 return m_settings; 1002 } 1003 1004 /** 1005 * Sets the echo status.<p> 1006 * 1007 * @param echo the echo status to set 1008 */ 1009 protected void setEcho(boolean echo) { 1010 1011 m_echo = echo; 1012 } 1013 1014 /** 1015 * Sets the current shell prompt.<p> 1016 * 1017 * To set the prompt, the following variables are available:<p> 1018 * 1019 * <code>$u</code> the current user name<br> 1020 * <code>$s</code> the current site root<br> 1021 * <code>$p</code> the current project name<p> 1022 * 1023 * @param prompt the prompt to set 1024 */ 1025 protected void setPrompt(String prompt) { 1026 1027 m_prompt = prompt; 1028 } 1029 1030 /** 1031 * Executes a shell command with a list of parameters.<p> 1032 * 1033 * @param command the command to execute 1034 * @param parameters the list of parameters for the command 1035 */ 1036 private void executeCommand(String command, List<String> parameters) { 1037 if (null == command) { 1038 return; 1039 } 1040 1041 if (m_echo) { 1042 // echo the command to STDOUT 1043 m_out.print(command); 1044 for (int i = 0; i < parameters.size(); i++) { 1045 m_out.print(" '"); 1046 m_out.print(parameters.get(i)); 1047 m_out.print("'"); 1048 } 1049 m_out.println(); 1050 } 1051 1052 // prepare to lookup a method in CmsObject or CmsShellCommands 1053 boolean executed = false; 1054 Iterator<CmsCommandObject> i = m_commandObjects.iterator(); 1055 while (!executed && i.hasNext()) { 1056 CmsCommandObject cmdObj = i.next(); 1057 executed = cmdObj.executeMethod(command, parameters); 1058 } 1059 1060 if (!executed) { 1061 // method not found 1062 m_out.println(); 1063 StringBuffer commandMsg = new StringBuffer(command).append("("); 1064 for (int j = 0; j < parameters.size(); j++) { 1065 commandMsg.append("value"); 1066 if (j < (parameters.size() - 1)) { 1067 commandMsg.append(", "); 1068 } 1069 } 1070 commandMsg.append(")"); 1071 1072 m_out.println(m_messages.key(Messages.GUI_SHELL_METHOD_NOT_FOUND_1, commandMsg.toString())); 1073 m_out.println(m_messages.key(Messages.GUI_SHELL_HR_0)); 1074 ((CmsShellCommands)m_shellCommands).help(); 1075 } 1076 } 1077}