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