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