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 @SuppressWarnings("synthetic-access") 125 protected boolean executeMethod(String command, List<String> parameters) { 126 127 m_hasReportError = false; 128 // build the method lookup 129 String lookup = buildMethodLookup(command, parameters.size()); 130 131 // try to look up the methods of this command object 132 List<Method> possibleMethods = m_methods.get(lookup); 133 if (possibleMethods == null) { 134 return false; 135 } 136 137 // a match for the method name was found, now try to figure out if the parameters are ok 138 Method onlyStringMethod = null; 139 Method foundMethod = null; 140 Object[] params = null; 141 Iterator<Method> i; 142 143 // first check if there is one method with only has String parameters, make this the fall back 144 i = possibleMethods.iterator(); 145 while (i.hasNext()) { 146 Method method = i.next(); 147 Class<?>[] clazz = method.getParameterTypes(); 148 boolean onlyString = true; 149 for (int j = 0; j < clazz.length; j++) { 150 if (!(clazz[j].equals(String.class))) { 151 onlyString = false; 152 break; 153 } 154 } 155 if (onlyString) { 156 onlyStringMethod = method; 157 break; 158 } 159 } 160 161 // now check a method matches the provided parameters 162 // if so, use this method, else continue searching 163 i = possibleMethods.iterator(); 164 while (i.hasNext()) { 165 Method method = i.next(); 166 if (method == onlyStringMethod) { 167 // skip the String only signature because this would always match 168 continue; 169 } 170 // now try to convert the parameters to the required types 171 Class<?>[] clazz = method.getParameterTypes(); 172 Object[] converted = new Object[clazz.length]; 173 boolean match = true; 174 for (int j = 0; j < clazz.length; j++) { 175 String value = parameters.get(j); 176 try { 177 converted[j] = CmsDataTypeUtil.parse(value, clazz[j]); 178 } catch (Throwable t) { 179 match = false; 180 break; 181 } 182 } 183 if (match) { 184 // we found a matching method signature 185 params = converted; 186 foundMethod = method; 187 break; 188 } 189 190 } 191 192 if ((foundMethod == null) && (onlyStringMethod != null)) { 193 // no match found but String only signature available, use this 194 params = parameters.toArray(); 195 foundMethod = onlyStringMethod; 196 } 197 198 if ((params == null) || (foundMethod == null)) { 199 // no match found at all 200 return false; 201 } 202 203 // now try to invoke the method 204 try { 205 Object result = foundMethod.invoke(m_object, params); 206 if (result != null) { 207 if (result instanceof Collection<?>) { 208 Collection<?> c = (Collection<?>)result; 209 m_out.println(c.getClass().getName() + " (size: " + c.size() + ")"); 210 int count = 0; 211 if (result instanceof Map<?, ?>) { 212 Map<?, ?> m = (Map<?, ?>)result; 213 Iterator<?> j = m.entrySet().iterator(); 214 while (j.hasNext()) { 215 Map.Entry<?, ?> entry = (Map.Entry<?, ?>)j.next(); 216 m_out.println(count++ + ": " + entry.getKey() + "= " + entry.getValue()); 217 } 218 } else { 219 Iterator<?> j = c.iterator(); 220 while (j.hasNext()) { 221 m_out.println(count++ + ": " + j.next()); 222 } 223 } 224 } else { 225 m_out.println(result.toString()); 226 } 227 } 228 } catch (InvocationTargetException ite) { 229 m_out.println( 230 Messages.get().getBundle(getLocale()).key( 231 Messages.GUI_SHELL_EXEC_METHOD_1, 232 new Object[] {foundMethod.getName()})); 233 ite.getTargetException().printStackTrace(m_out); 234 if (m_errorCode != -1) { 235 throw new CmsShellCommandException(ite.getCause()); 236 } 237 } catch (Throwable t) { 238 m_out.println( 239 Messages.get().getBundle(getLocale()).key( 240 Messages.GUI_SHELL_EXEC_METHOD_1, 241 new Object[] {foundMethod.getName()})); 242 t.printStackTrace(m_out); 243 if (m_errorCode != -1) { 244 throw new CmsShellCommandException(t); 245 } 246 } 247 if (m_hasReportError && (m_errorCode != -1)) { 248 throw new CmsShellCommandException(true); 249 } 250 return true; 251 } 252 253 /** 254 * Returns a signature overview of all methods containing the given search String.<p> 255 * 256 * If no method name matches the given search String, the empty String is returned.<p> 257 * 258 * @param searchString the String to search for, if null all methods are shown 259 * 260 * @return a signature overview of all methods containing the given search String 261 */ 262 protected String getMethodHelp(String searchString) { 263 264 StringBuffer buf = new StringBuffer(512); 265 Iterator<String> i = m_methods.keySet().iterator(); 266 while (i.hasNext()) { 267 List<Method> l = m_methods.get(i.next()); 268 Iterator<Method> j = l.iterator(); 269 while (j.hasNext()) { 270 Method method = j.next(); 271 if ((searchString == null) 272 || (method.getName().toLowerCase().indexOf(searchString.toLowerCase()) > -1)) { 273 buf.append("* "); 274 buf.append(method.getName()); 275 buf.append("("); 276 Class<?>[] params = method.getParameterTypes(); 277 for (int k = 0; k < params.length; k++) { 278 String par = params[k].getName(); 279 par = par.substring(par.lastIndexOf('.') + 1); 280 if (k != 0) { 281 buf.append(", "); 282 } 283 buf.append(par); 284 } 285 buf.append(")\n"); 286 } 287 } 288 } 289 return buf.toString(); 290 } 291 292 /** 293 * Returns the object to execute the methods on.<p> 294 * 295 * @return the object to execute the methods on 296 */ 297 protected Object getObject() { 298 299 return m_object; 300 } 301 302 /** 303 * Builds a method lookup String.<p> 304 * 305 * @param methodName the name of the method 306 * @param paramCount the parameter count of the method 307 * 308 * @return a method lookup String 309 */ 310 private String buildMethodLookup(String methodName, int paramCount) { 311 312 StringBuffer buf = new StringBuffer(32); 313 buf.append(methodName.toLowerCase()); 314 buf.append(" ["); 315 buf.append(paramCount); 316 buf.append("]"); 317 return buf.toString(); 318 } 319 320 /** 321 * Initializes the map of accessible methods.<p> 322 */ 323 private void initShellMethods() { 324 325 Map<String, List<Method>> result = new TreeMap<String, List<Method>>(); 326 327 Method[] methods = m_object.getClass().getMethods(); 328 for (int i = 0; i < methods.length; i++) { 329 // only public methods directly declared in the base class can be used in the shell 330 if ((methods[i].getDeclaringClass() == m_object.getClass()) 331 && (methods[i].getModifiers() == Modifier.PUBLIC)) { 332 333 // check if the method signature only uses primitive data types 334 boolean onlyPrimitive = true; 335 Class<?>[] clazz = methods[i].getParameterTypes(); 336 for (int j = 0; j < clazz.length; j++) { 337 if (!CmsDataTypeUtil.isParseable(clazz[j])) { 338 // complex data type methods can not be called from the shell 339 onlyPrimitive = false; 340 break; 341 } 342 } 343 344 if (onlyPrimitive) { 345 // add this method to the set of methods that can be called from the shell 346 String lookup = buildMethodLookup(methods[i].getName(), methods[i].getParameterTypes().length); 347 List<Method> l; 348 if (result.containsKey(lookup)) { 349 l = result.get(lookup); 350 } else { 351 l = new ArrayList<Method>(1); 352 } 353 l.add(methods[i]); 354 result.put(lookup, l); 355 } 356 } 357 } 358 m_methods = result; 359 } 360 } 361 362 /** Prefix for "additional" parameter. */ 363 public static final String SHELL_PARAM_ADDITIONAL_COMMANDS = "-additional="; 364 365 /** Prefix for "base" parameter. */ 366 public static final String SHELL_PARAM_BASE = "-base="; 367 368 /** Prefix for "servletMapping" parameter. */ 369 public static final String SHELL_PARAM_DEFAULT_WEB_APP = "-defaultWebApp="; 370 371 /** Prefix for errorCode parameter. */ 372 public static final String SHELL_PARAM_ERROR_CODE = "-errorCode="; 373 374 /** Command line parameter to prevent disabling of JLAN. */ 375 public static final String SHELL_PARAM_JLAN = "-jlan"; 376 377 /** Prefix for "script" parameter. */ 378 public static final String SHELL_PARAM_SCRIPT = "-script="; 379 380 /** Prefix for "servletMapping" parameter. */ 381 public static final String SHELL_PARAM_SERVLET_MAPPING = "-servletMapping="; 382 383 /** 384 * Thread local which stores the currently active shell instance. 385 * 386 * <p>We need multiple ones because shell commands may cause another nested shell to be launched (e.g. for module import scripts). 387 */ 388 public static final ThreadLocal<ArrayList<CmsShell>> SHELL_STACK = ThreadLocal.withInitial(() -> new ArrayList<>()); 389 390 /** Boolean variable to disable JLAN. */ 391 private static boolean JLAN_DISABLED; 392 393 /** The OpenCms context object. */ 394 protected CmsObject m_cms; 395 396 /** Stream to write the error messages output to. */ 397 protected PrintStream m_err; 398 399 /** The code which the process should exit with in case of errors; -1 means exit is not called. */ 400 protected int m_errorCode = -1; 401 402 /** Stream to write the regular output messages to. */ 403 protected PrintStream m_out; 404 405 /** Additional shell commands object. */ 406 private I_CmsShellCommands m_additionalShellCommands; 407 408 /** All shell callable objects. */ 409 private List<CmsCommandObject> m_commandObjects; 410 411 /** If set to true, all commands are echoed. */ 412 private boolean m_echo; 413 414 /** Indicates if the 'exit' command has been called. */ 415 private boolean m_exitCalled; 416 417 /** Flag to indicate whether an error was added to a shell report during the last command execution. */ 418 private boolean m_hasReportError; 419 420 /** Indicates if this is an interactive session with a user sitting on a console. */ 421 private boolean m_interactive; 422 423 /** The messages object. */ 424 private CmsMessages m_messages; 425 426 /** The OpenCms system object. */ 427 private OpenCmsCore m_opencms; 428 429 /** The shell prompt format. */ 430 private String m_prompt; 431 432 /** The current users settings. */ 433 private CmsUserSettings m_settings; 434 435 /** Internal shell command object. */ 436 private I_CmsShellCommands m_shellCommands; 437 438 /** 439 * Creates a new CmsShell.<p> 440 * 441 * @param cms the user context to run the shell from 442 * @param prompt the prompt format to set 443 * @param additionalShellCommands optional object for additional shell commands, or null 444 * @param out stream to write the regular output messages to 445 * @param err stream to write the error messages output to 446 */ 447 public CmsShell( 448 CmsObject cms, 449 String prompt, 450 I_CmsShellCommands additionalShellCommands, 451 PrintStream out, 452 PrintStream err) { 453 454 setPrompt(prompt); 455 try { 456 // has to be initialized already if this constructor is used 457 m_opencms = null; 458 Locale locale = getLocale(); 459 m_messages = Messages.get().getBundle(locale); 460 m_cms = cms; 461 462 // initialize the shell 463 initShell(additionalShellCommands, out, err); 464 } catch (Throwable t) { 465 t.printStackTrace(m_err); 466 } 467 } 468 469 /** 470 * Creates a new CmsShell using System.out and System.err for output of the messages.<p> 471 * 472 * @param webInfPath the path to the 'WEB-INF' folder of the OpenCms installation 473 * @param servletMapping the mapping of the servlet (or <code>null</code> to use the default <code>"/opencms/*"</code>) 474 * @param defaultWebAppName the name of the default web application (or <code>null</code> to use the default <code>"ROOT"</code>) 475 * @param prompt the prompt format to set 476 * @param additionalShellCommands optional object for additional shell commands, or null 477 */ 478 public CmsShell( 479 String webInfPath, 480 String servletMapping, 481 String defaultWebAppName, 482 String prompt, 483 I_CmsShellCommands additionalShellCommands) { 484 485 this( 486 webInfPath, 487 servletMapping, 488 defaultWebAppName, 489 prompt, 490 additionalShellCommands, 491 System.out, 492 System.err, 493 false); 494 } 495 496 /** 497 * Creates a new CmsShell.<p> 498 * 499 * @param webInfPath the path to the 'WEB-INF' folder of the OpenCms installation 500 * @param servletMapping the mapping of the servlet (or <code>null</code> to use the default <code>"/opencms/*"</code>) 501 * @param defaultWebAppName the name of the default web application (or <code>null</code> to use the default <code>"ROOT"</code>) 502 * @param prompt the prompt format to set 503 * @param additionalShellCommands optional object for additional shell commands, or null 504 * @param out stream to write the regular output messages to 505 * @param err stream to write the error messages output to 506 * @param interactive if <code>true</code> this is an interactive session with a user sitting on a console 507 */ 508 public CmsShell( 509 String webInfPath, 510 String servletMapping, 511 String defaultWebAppName, 512 String prompt, 513 I_CmsShellCommands additionalShellCommands, 514 PrintStream out, 515 PrintStream err, 516 boolean interactive) { 517 518 setPrompt(prompt); 519 setInteractive(interactive); 520 if (CmsStringUtil.isEmpty(servletMapping)) { 521 servletMapping = "/opencms/*"; 522 } 523 if (CmsStringUtil.isEmpty(defaultWebAppName)) { 524 defaultWebAppName = "ROOT"; 525 } 526 try { 527 // first initialize runlevel 1 528 m_opencms = OpenCmsCore.getInstance(); 529 // Externalization: get Locale: will be the System default since no CmsObject is up before 530 // runlevel 2 531 Locale locale = getLocale(); 532 m_messages = Messages.get().getBundle(locale); 533 // search for the WEB-INF folder 534 if (CmsStringUtil.isEmpty(webInfPath)) { 535 out.println(m_messages.key(Messages.GUI_SHELL_NO_HOME_FOLDER_SPECIFIED_0)); 536 out.println(); 537 webInfPath = CmsFileUtil.searchWebInfFolder(System.getProperty("user.dir")); 538 if (CmsStringUtil.isEmpty(webInfPath)) { 539 err.println(m_messages.key(Messages.GUI_SHELL_HR_0)); 540 err.println(m_messages.key(Messages.GUI_SHELL_NO_HOME_FOLDER_FOUND_0)); 541 err.println(); 542 err.println(m_messages.key(Messages.GUI_SHELL_START_DIR_LINE1_0)); 543 err.println(m_messages.key(Messages.GUI_SHELL_START_DIR_LINE2_0)); 544 err.println(m_messages.key(Messages.GUI_SHELL_HR_0)); 545 return; 546 } 547 } 548 out.println(Messages.get().getBundle(locale).key(Messages.GUI_SHELL_WEB_INF_PATH_1, webInfPath)); 549 // set the path to the WEB-INF folder (the 2nd and 3rd parameters are just reasonable dummies) 550 CmsServletContainerSettings settings = new CmsServletContainerSettings( 551 webInfPath, 552 defaultWebAppName, 553 servletMapping, 554 null, 555 null); 556 m_opencms.getSystemInfo().init(settings); 557 // now read the configuration properties 558 String propertyPath = m_opencms.getSystemInfo().getConfigurationFileRfsPath(); 559 out.println(m_messages.key(Messages.GUI_SHELL_CONFIG_FILE_1, propertyPath)); 560 out.println(); 561 CmsParameterConfiguration configuration = new CmsParameterConfiguration(propertyPath); 562 563 // now upgrade to runlevel 2 564 m_opencms = m_opencms.upgradeRunlevel(configuration); 565 566 // create a context object with 'Guest' permissions 567 m_cms = m_opencms.initCmsObject(m_opencms.getDefaultUsers().getUserGuest()); 568 569 // initialize the shell 570 initShell(additionalShellCommands, out, err); 571 } catch (Throwable t) { 572 t.printStackTrace(err); 573 } 574 } 575 576 /** 577 * Gets the top of thread-local shell stack, or null if it is empty. 578 * 579 * @return the top of the shell stack 580 */ 581 public static CmsShell getTopShell() { 582 583 ArrayList<CmsShell> shells = SHELL_STACK.get(); 584 if (shells.isEmpty()) { 585 return null; 586 } 587 return shells.get(shells.size() - 1); 588 589 } 590 591 /** 592 * Check if JLAN should be disabled.<p> 593 * 594 * @return true if JLAN should be disabled 595 */ 596 public static boolean isJlanDisabled() { 597 598 return JLAN_DISABLED; 599 } 600 601 /** 602 * Main program entry point when started via the command line.<p> 603 * 604 * @param args parameters passed to the application via the command line 605 */ 606 public static void main(String[] args) { 607 608 JLAN_DISABLED = true; 609 boolean wrongUsage = false; 610 String webInfPath = null; 611 String script = null; 612 String servletMapping = null; 613 String defaultWebApp = null; 614 String additional = null; 615 int errorCode = -1; 616 if (args.length > 4) { 617 wrongUsage = true; 618 } else { 619 for (int i = 0; i < args.length; i++) { 620 String arg = args[i]; 621 if (arg.startsWith(SHELL_PARAM_BASE)) { 622 webInfPath = arg.substring(SHELL_PARAM_BASE.length()); 623 } else if (arg.startsWith(SHELL_PARAM_SCRIPT)) { 624 script = arg.substring(SHELL_PARAM_SCRIPT.length()); 625 } else if (arg.startsWith(SHELL_PARAM_SERVLET_MAPPING)) { 626 servletMapping = arg.substring(SHELL_PARAM_SERVLET_MAPPING.length()); 627 } else if (arg.startsWith(SHELL_PARAM_DEFAULT_WEB_APP)) { 628 defaultWebApp = arg.substring(SHELL_PARAM_DEFAULT_WEB_APP.length()); 629 } else if (arg.startsWith(SHELL_PARAM_ADDITIONAL_COMMANDS)) { 630 additional = arg.substring(SHELL_PARAM_ADDITIONAL_COMMANDS.length()); 631 } else if (arg.startsWith(SHELL_PARAM_ERROR_CODE)) { 632 errorCode = Integer.valueOf(arg.substring(SHELL_PARAM_ERROR_CODE.length())).intValue(); 633 } else if (arg.startsWith(SHELL_PARAM_JLAN)) { 634 JLAN_DISABLED = false; 635 } else { 636 System.out.println(Messages.get().getBundle().key(Messages.GUI_SHELL_WRONG_USAGE_0)); 637 wrongUsage = true; 638 } 639 } 640 } 641 if (wrongUsage) { 642 System.out.println(Messages.get().getBundle().key(Messages.GUI_SHELL_USAGE_1, CmsShell.class.getName())); 643 } else { 644 645 I_CmsShellCommands additionalCommands = null; 646 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(additional)) { 647 try { 648 Class<?> commandClass = Class.forName(additional); 649 additionalCommands = (I_CmsShellCommands)commandClass.newInstance(); 650 } catch (Exception e) { 651 System.out.println( 652 Messages.get().getBundle().key(Messages.GUI_SHELL_ERR_ADDITIONAL_COMMANDS_1, additional)); 653 e.printStackTrace(); 654 return; 655 } 656 } 657 boolean interactive = true; 658 FileInputStream stream = null; 659 if (script != null) { 660 try { 661 stream = new FileInputStream(script); 662 } catch (IOException exc) { 663 System.out.println(Messages.get().getBundle().key(Messages.GUI_SHELL_ERR_SCRIPTFILE_1, script)); 664 } 665 } 666 if (stream == null) { 667 // no script-file, use standard input stream 668 stream = new FileInputStream(FileDescriptor.in); 669 interactive = true; 670 } 671 CmsShell shell = new CmsShell( 672 webInfPath, 673 servletMapping, 674 defaultWebApp, 675 "${user}@${project}:${siteroot}|${uri}>", 676 additionalCommands, 677 System.out, 678 System.err, 679 interactive); 680 shell.m_errorCode = errorCode; 681 shell.execute(stream); 682 try { 683 stream.close(); 684 } catch (IOException e) { 685 e.printStackTrace(); 686 } 687 } 688 } 689 690 /** 691 * Removes top of thread-local shell stack. 692 */ 693 public static void popShell() { 694 695 ArrayList<CmsShell> shells = SHELL_STACK.get(); 696 if (shells.size() > 0) { 697 shells.remove(shells.size() - 1); 698 } 699 700 } 701 702 /** 703 * Pushes shell instance on thread-local stack. 704 * 705 * @param shell the shell to push 706 */ 707 public static void pushShell(CmsShell shell) { 708 709 SHELL_STACK.get().add(shell); 710 } 711 712 /** 713 * If running in the context of a CmsShell, this method notifies the running shell instance that an error has occured in a report.<p> 714 */ 715 public static void setReportError() { 716 717 CmsShell instance = getTopShell(); 718 if (instance != null) { 719 instance.m_hasReportError = true; 720 } 721 } 722 723 /** 724 * Executes the commands from the given input stream in this shell.<p> 725 * 726 * <ul> 727 * <li>Commands in the must be separated with a line break '\n'. 728 * <li>Only one command per line is allowed. 729 * <li>String parameters must be quoted like this: <code>'string value'</code>. 730 * </ul> 731 * 732 * @param inputStream the input stream from which the commands are read 733 */ 734 public void execute(InputStream inputStream) { 735 736 execute(new InputStreamReader(inputStream)); 737 } 738 739 /** 740 * Executes the commands from the given reader in this shell.<p> 741 * 742 * <ul> 743 * <li>Commands in the must be separated with a line break '\n'. 744 * <li>Only one command per line is allowed. 745 * <li>String parameters must be quoted like this: <code>'string value'</code>. 746 * </ul> 747 * 748 * @param reader the reader from which the commands are read 749 */ 750 public void execute(Reader reader) { 751 752 try { 753 pushShell(this); 754 LineNumberReader lnr = new LineNumberReader(reader); 755 while (!m_exitCalled) { 756 String line = lnr.readLine(); 757 if (m_interactive || m_echo) { 758 // print the prompt in front of the commands to process only when 'interactive' 759 if ((line != null) | m_interactive) { 760 printPrompt(); 761 } 762 } 763 764 if (line == null) { 765 // if null the file has been read to the end 766 if (m_interactive) { 767 try { 768 Thread.sleep(500); 769 } catch (Throwable t) { 770 // noop 771 } 772 } 773 // end the while loop 774 break; 775 } 776 if (line.trim().startsWith("#")) { 777 m_out.println(line); 778 continue; 779 } 780 // In linux, the up and down arrows generate escape sequences that cannot be properly handled. 781 // If a escape sequence is detected, OpenCms prints a warning message 782 if (line.indexOf(KeyEvent.VK_ESCAPE) != -1) { 783 m_out.println(m_messages.key(Messages.GUI_SHELL_ESCAPE_SEQUENCES_NOT_SUPPORTED_0)); 784 continue; 785 } 786 StringReader lineReader = new StringReader(line); 787 StreamTokenizer st = new StreamTokenizer(lineReader); 788 st.eolIsSignificant(true); 789 st.wordChars('*', '*'); 790 // put all tokens into a List 791 List<String> parameters = new ArrayList<String>(); 792 while (st.nextToken() != StreamTokenizer.TT_EOF) { 793 if (st.ttype == StreamTokenizer.TT_NUMBER) { 794 parameters.add(Integer.toString(new Double(st.nval).intValue())); 795 } else { 796 if (null != st.sval) { 797 parameters.add(st.sval); 798 } 799 } 800 } 801 lineReader.close(); 802 803 if (parameters.size() == 0) { 804 // empty line, just need to check if echo is on 805 if (m_echo) { 806 m_out.println(); 807 } 808 continue; 809 } 810 811 // extract command and arguments 812 String command = parameters.get(0); 813 List<String> arguments = parameters.subList(1, parameters.size()); 814 815 // execute the command with the given arguments 816 executeCommand(command, arguments); 817 } 818 } catch (Throwable t) { 819 if (!(t instanceof CmsShellCommandException)) { 820 // in case it's a shell command exception, the stack trace has already been written 821 t.printStackTrace(m_err); 822 } 823 if (m_errorCode != -1) { 824 System.exit(m_errorCode); 825 } 826 } finally { 827 popShell(); 828 } 829 } 830 831 /** 832 * Executes the commands from the given string in this shell.<p> 833 * 834 * <ul> 835 * <li>Commands in the must be separated with a line break '\n'. 836 * <li>Only one command per line is allowed. 837 * <li>String parameters must be quoted like this: <code>'string value'</code>. 838 * </ul> 839 * 840 * @param commands the string from which the commands are read 841 */ 842 public void execute(String commands) { 843 844 execute(new StringReader(commands)); 845 } 846 847 /** 848 * Executes a shell command with a list of parameters.<p> 849 * 850 * @param command the command to execute 851 * @param parameters the list of parameters for the command 852 */ 853 public void executeCommand(String command, List<String> parameters) { 854 855 if (null == command) { 856 return; 857 } 858 859 if (m_echo) { 860 // echo the command to STDOUT 861 m_out.print(command); 862 for (int i = 0; i < parameters.size(); i++) { 863 m_out.print(" '"); 864 m_out.print(parameters.get(i)); 865 m_out.print("'"); 866 } 867 m_out.println(); 868 } 869 870 // prepare to lookup a method in CmsObject or CmsShellCommands 871 boolean executed = false; 872 Iterator<CmsCommandObject> i = m_commandObjects.iterator(); 873 while (!executed && i.hasNext()) { 874 CmsCommandObject cmdObj = i.next(); 875 executed = cmdObj.executeMethod(command, parameters); 876 } 877 878 if (!executed) { 879 // method not found 880 m_out.println(); 881 StringBuffer commandMsg = new StringBuffer(command).append("("); 882 for (int j = 0; j < parameters.size(); j++) { 883 commandMsg.append("value"); 884 if (j < (parameters.size() - 1)) { 885 commandMsg.append(", "); 886 } 887 } 888 commandMsg.append(")"); 889 890 m_out.println(m_messages.key(Messages.GUI_SHELL_METHOD_NOT_FOUND_1, commandMsg.toString())); 891 m_out.println(m_messages.key(Messages.GUI_SHELL_HR_0)); 892 ((CmsShellCommands)m_shellCommands).help(); 893 } 894 } 895 896 /** 897 * Exits this shell and destroys the OpenCms instance.<p> 898 */ 899 public void exit() { 900 901 if (m_exitCalled) { 902 return; 903 } 904 m_exitCalled = true; 905 try { 906 if (m_additionalShellCommands != null) { 907 m_additionalShellCommands.shellExit(); 908 } else if (null != m_shellCommands) { 909 m_shellCommands.shellExit(); 910 } 911 } catch (Throwable t) { 912 t.printStackTrace(); 913 } 914 if (m_opencms != null) { 915 // if called by an in line script we don't want to kill the whole instance 916 try { 917 m_opencms.shutDown(); 918 } catch (Throwable t) { 919 t.printStackTrace(); 920 } 921 } 922 } 923 924 /** 925 * Returns the stream this shell writes its error messages to.<p> 926 * 927 * @return the stream this shell writes its error messages to 928 */ 929 public PrintStream getErr() { 930 931 return m_err; 932 } 933 934 /** 935 * Gets the error code.<p> 936 * 937 * @return the error code 938 */ 939 public int getErrorCode() { 940 941 return m_errorCode; 942 } 943 944 /** 945 * Private internal helper for localization to the current user's locale 946 * within OpenCms. <p> 947 * 948 * @return the current user's <code>Locale</code>. 949 */ 950 public Locale getLocale() { 951 952 if (getSettings() == null) { 953 return CmsLocaleManager.getDefaultLocale(); 954 } 955 return getSettings().getLocale(); 956 } 957 958 /** 959 * Returns the localized messages object for the current user.<p> 960 * 961 * @return the localized messages object for the current user 962 */ 963 public CmsMessages getMessages() { 964 965 return m_messages; 966 } 967 968 /** 969 * Returns the stream this shell writes its regular messages to.<p> 970 * 971 * @return the stream this shell writes its regular messages to 972 */ 973 public PrintStream getOut() { 974 975 return m_out; 976 } 977 978 /** 979 * Gets the prompt.<p> 980 * 981 * @return the prompt 982 */ 983 public String getPrompt() { 984 985 String prompt = m_prompt; 986 try { 987 prompt = CmsStringUtil.substitute(prompt, "${user}", m_cms.getRequestContext().getCurrentUser().getName()); 988 prompt = CmsStringUtil.substitute(prompt, "${siteroot}", m_cms.getRequestContext().getSiteRoot()); 989 prompt = CmsStringUtil.substitute( 990 prompt, 991 "${project}", 992 m_cms.getRequestContext().getCurrentProject().getName()); 993 prompt = CmsStringUtil.substitute(prompt, "${uri}", m_cms.getRequestContext().getUri()); 994 } catch (Throwable t) { 995 // ignore 996 } 997 return prompt; 998 } 999 1000 /** 1001 * Obtain the additional settings related to the current user. 1002 * 1003 * @return the additional settings related to the current user. 1004 */ 1005 public CmsUserSettings getSettings() { 1006 1007 return m_settings; 1008 } 1009 1010 /** 1011 * Returns true if echo mode is on.<p> 1012 * 1013 * @return true if echo mode is on 1014 */ 1015 public boolean hasEcho() { 1016 1017 return m_echo; 1018 } 1019 1020 /** 1021 * Checks whether a report error occurred during execution of the last command.<p> 1022 * 1023 * @return true if a report error occurred 1024 */ 1025 public boolean hasReportError() { 1026 1027 return m_hasReportError; 1028 } 1029 1030 /** 1031 * Initializes the CmsShell.<p> 1032 * 1033 * @param additionalShellCommands optional object for additional shell commands, or null 1034 * @param out stream to write the regular output messages to 1035 * @param err stream to write the error messages output to 1036 */ 1037 public void initShell(I_CmsShellCommands additionalShellCommands, PrintStream out, PrintStream err) { 1038 1039 // set the output streams 1040 m_out = out; 1041 m_err = err; 1042 1043 // initialize the settings of the user 1044 m_settings = initSettings(); 1045 1046 // initialize shell command object 1047 m_shellCommands = new CmsShellCommands(); 1048 m_shellCommands.initShellCmsObject(m_cms, this); 1049 1050 // initialize additional shell command object 1051 if (additionalShellCommands != null) { 1052 m_additionalShellCommands = additionalShellCommands; 1053 m_additionalShellCommands.initShellCmsObject(m_cms, this); 1054 m_additionalShellCommands.shellStart(); 1055 } else { 1056 m_shellCommands.shellStart(); 1057 } 1058 1059 m_commandObjects = new ArrayList<CmsCommandObject>(); 1060 if (m_additionalShellCommands != null) { 1061 // get all shell callable methods from the additional shell command object 1062 m_commandObjects.add(new CmsCommandObject(m_additionalShellCommands)); 1063 } 1064 // get all shell callable methods from the CmsShellCommands 1065 m_commandObjects.add(new CmsCommandObject(m_shellCommands)); 1066 // get all shell callable methods from the CmsRequestContext 1067 m_commandObjects.add(new CmsCommandObject(m_cms.getRequestContext())); 1068 // get all shell callable methods from the CmsObject 1069 m_commandObjects.add(new CmsCommandObject(m_cms)); 1070 } 1071 1072 /** 1073 * Returns true if exit was called.<p> 1074 * 1075 * @return true if exit was called 1076 */ 1077 public boolean isExitCalled() { 1078 1079 return m_exitCalled; 1080 } 1081 1082 /** 1083 * If <code>true</code> this is an interactive session with a user sitting on a console.<p> 1084 * 1085 * @return <code>true</code> if this is an interactive session with a user sitting on a console 1086 */ 1087 public boolean isInteractive() { 1088 1089 return m_interactive; 1090 } 1091 1092 /** 1093 * Prints the shell prompt.<p> 1094 */ 1095 public void printPrompt() { 1096 1097 String prompt = getPrompt(); 1098 m_out.print(prompt); 1099 m_out.flush(); 1100 } 1101 1102 /** 1103 * Set <code>true</code> if this is an interactive session with a user sitting on a console.<p> 1104 * 1105 * This controls the output of the prompt and some other info that is valuable 1106 * on the console, but not required in an automatic session.<p> 1107 * 1108 * @param interactive if <code>true</code> this is an interactive session with a user sitting on a console 1109 */ 1110 public void setInteractive(boolean interactive) { 1111 1112 m_interactive = interactive; 1113 } 1114 1115 /** 1116 * Sets the locale of the current user.<p> 1117 * 1118 * @param locale the locale to set 1119 * 1120 * @throws CmsException in case the locale of the current user can not be stored 1121 */ 1122 public void setLocale(Locale locale) throws CmsException { 1123 1124 CmsUserSettings settings = getSettings(); 1125 if (settings != null) { 1126 settings.setLocale(locale); 1127 settings.save(m_cms); 1128 m_messages = Messages.get().getBundle(locale); 1129 } 1130 } 1131 1132 /** 1133 * Reads the given stream and executes the commands in this shell.<p> 1134 * 1135 * @param inputStream an input stream from which commands are read 1136 * @deprecated use {@link #execute(InputStream)} instead 1137 */ 1138 @Deprecated 1139 public void start(FileInputStream inputStream) { 1140 1141 setInteractive(true); 1142 execute(inputStream); 1143 } 1144 1145 /** 1146 * Validates the given user and password and checks if the user has the requested role.<p> 1147 * 1148 * @param userName the user name 1149 * @param password the password 1150 * @param requiredRole the required role 1151 * 1152 * @return <code>true</code> if the user is valid 1153 */ 1154 public boolean validateUser(String userName, String password, CmsRole requiredRole) { 1155 1156 boolean result = false; 1157 try { 1158 CmsUser user = m_cms.readUser(userName, password); 1159 result = OpenCms.getRoleManager().hasRole(m_cms, user.getName(), requiredRole); 1160 } catch (CmsException e) { 1161 // nothing to do 1162 } 1163 return result; 1164 } 1165 1166 /** 1167 * Shows the signature of all methods containing the given search String.<p> 1168 * 1169 * @param searchString the String to search for in the methods, if null all methods are shown 1170 */ 1171 protected void help(String searchString) { 1172 1173 String commandList; 1174 boolean foundSomething = false; 1175 m_out.println(); 1176 1177 Iterator<CmsCommandObject> i = m_commandObjects.iterator(); 1178 while (i.hasNext()) { 1179 CmsCommandObject cmdObj = i.next(); 1180 commandList = cmdObj.getMethodHelp(searchString); 1181 if (!CmsStringUtil.isEmpty(commandList)) { 1182 1183 m_out.println( 1184 m_messages.key(Messages.GUI_SHELL_AVAILABLE_METHODS_1, cmdObj.getObject().getClass().getName())); 1185 m_out.println(commandList); 1186 foundSomething = true; 1187 } 1188 } 1189 1190 if (!foundSomething) { 1191 m_out.println(m_messages.key(Messages.GUI_SHELL_MATCH_SEARCHSTRING_1, searchString)); 1192 } 1193 } 1194 1195 /** 1196 * Initializes the internal <code>CmsWorkplaceSettings</code> that contain (amongst other 1197 * information) important information additional information about the current user 1198 * (an instance of {@link CmsUserSettings}).<p> 1199 * 1200 * This step is performed within the <code>CmsShell</code> constructor directly after 1201 * switching to run-level 2 and obtaining the <code>CmsObject</code> for the guest user as 1202 * well as when invoking the CmsShell command <code>login</code>.<p> 1203 * 1204 * @return the user settings for the current user. 1205 */ 1206 protected CmsUserSettings initSettings() { 1207 1208 m_settings = new CmsUserSettings(m_cms); 1209 return m_settings; 1210 } 1211 1212 /** 1213 * Executes all commands read from the given reader.<p> 1214 * 1215 * @param reader a Reader from which the commands are read 1216 */ 1217 1218 /** 1219 * Sets the echo status.<p> 1220 * 1221 * @param echo the echo status to set 1222 */ 1223 protected void setEcho(boolean echo) { 1224 1225 m_echo = echo; 1226 } 1227 1228 /** 1229 * Sets the current shell prompt.<p> 1230 * 1231 * To set the prompt, the following variables are available:<p> 1232 * 1233 * <code>$u</code> the current user name<br> 1234 * <code>$s</code> the current site root<br> 1235 * <code>$p</code> the current project name<p> 1236 * 1237 * @param prompt the prompt to set 1238 */ 1239 protected void setPrompt(String prompt) { 1240 1241 m_prompt = prompt; 1242 } 1243}