001/*
002 * Copyright (C) 2012 eXo Platform SAS.
003 *
004 * This is free software; you can redistribute it and/or modify it
005 * under the terms of the GNU Lesser General Public License as
006 * published by the Free Software Foundation; either version 2.1 of
007 * the License, or (at your option) any later version.
008 *
009 * This software is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public
015 * License along with this software; if not, write to the Free
016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018 */
019
020package org.crsh.shell.impl.command;
021
022import org.crsh.cli.descriptor.Format;
023import org.crsh.command.BaseCommand;
024import org.crsh.lang.java.ShellCommandImpl;
025import org.crsh.shell.impl.command.spi.CommandCreationException;
026import org.crsh.shell.impl.command.spi.ShellCommand;
027import org.crsh.plugin.PluginContext;
028import org.crsh.plugin.ResourceKind;
029import org.crsh.shell.impl.command.spi.CommandManager;
030import org.crsh.shell.impl.command.spi.CommandResolution;
031import org.crsh.shell.impl.command.system.help;
032import org.crsh.shell.impl.command.system.repl;
033import org.crsh.util.TimestampedObject;
034import org.crsh.vfs.Resource;
035
036import java.security.Principal;
037import java.util.HashMap;
038import java.util.LinkedHashSet;
039import java.util.Map;
040import java.util.concurrent.ConcurrentHashMap;
041
042public class CRaSH {
043
044  /** . */
045  private static final HashMap<String, Class<? extends BaseCommand>> systemCommands = new HashMap<String, Class<? extends BaseCommand>>();
046
047  static {
048    systemCommands.put("help", help.class);
049    systemCommands.put("repl", repl.class);
050  }
051
052  /** . */
053  final PluginContext context;
054
055  /** . */
056  final HashMap<String, CommandManager> activeManagers;
057
058  /** . */
059  private final Map<String, TimestampedObject<CommandResolution>> commandCache = new ConcurrentHashMap<String, TimestampedObject<CommandResolution>>();
060
061  /**
062   * Create a new CRaSH.
063   *
064   * @param context the plugin context
065   * @throws NullPointerException if the context argument is null
066   */
067  public CRaSH(PluginContext context) throws NullPointerException {
068
069    //
070    HashMap<String, CommandManager> activeManagers = new HashMap<String, CommandManager>();
071    for (CommandManager manager : context.getPlugins(CommandManager.class)) {
072      if (manager.isActive()) {
073        for (String ext : manager.getExtensions()) {
074          activeManagers.put(ext, manager);
075        }
076      }
077    }
078
079
080    this.context = context;
081    this.activeManagers = activeManagers;
082  }
083
084  public CRaSHSession createSession(Principal user) {
085    return new CRaSHSession(this, user);
086  }
087
088  /**
089   * Returns the plugin context.
090   *
091   * @return the plugin context
092   */
093  public PluginContext getContext() {
094    return context;
095  }
096
097  /**
098   * Attempt to obtain a command description. Null is returned when such command does not exist.
099   *
100   * @param name the command name
101   * @return a command description
102   * @throws org.crsh.shell.impl.command.spi.CommandCreationException if an error occured preventing the command creation
103   * @throws NullPointerException if the name argument is null
104   */
105  public String getCommandDescription(String name) throws CommandCreationException, NullPointerException {
106    CommandResolution resolution = resolveCommand(name);
107    return resolution != null ? resolution.getDescription() : null;
108  }
109
110  /**
111   * Attempt to obtain a command instance. Null is returned when such command does not exist.
112   *
113   * @param name the command name
114   * @return a command instance
115   * @throws org.crsh.shell.impl.command.spi.CommandCreationException if an error occured preventing the command creation
116   * @throws NullPointerException if the name argument is null
117   */
118  public ShellCommand<?> getCommand(String name) throws CommandCreationException, NullPointerException {
119    CommandResolution resolution = resolveCommand(name);
120    return resolution != null ? resolution.getCommand() : null;
121  }
122
123  /**
124   * Attempt to obtain a command instance. Null is returned when such command does not exist.
125   *
126   * @param name the command name
127   * @return a command instance
128   * @throws org.crsh.shell.impl.command.spi.CommandCreationException if an error occured preventing the command creation
129   * @throws NullPointerException if the name argument is null
130   */
131  public CommandResolution resolveCommand(final String name) throws CommandCreationException, NullPointerException {
132    if (name == null) {
133      throw new NullPointerException("No null name accepted");
134    }
135    final Class<? extends BaseCommand> systemCommand = systemCommands.get(name);
136    if (systemCommand != null) {
137      return createCommand(systemCommand);
138    } else {
139      for (CommandManager manager : activeManagers.values()) {
140        if (manager.isActive()) {
141          for (String ext : manager.getExtensions()) {
142            Iterable<Resource> resources = context.loadResources(name + "." + ext, ResourceKind.COMMAND);
143            for (Resource resource : resources) {
144              CommandResolution resolution = resolveCommand(manager, name, resource);
145              if (resolution != null) {
146                return resolution;
147              }
148            }
149          }
150        }
151      }
152      return null;
153    }
154  }
155
156  public Iterable<String> getCommandNames() {
157    LinkedHashSet<String> names = new LinkedHashSet<String>(systemCommands.keySet());
158    for (String resourceName : context.listResources(ResourceKind.COMMAND)) {
159      int index = resourceName.indexOf('.');
160      String name = resourceName.substring(0, index);
161      String ext = resourceName.substring(index + 1);
162      if (activeManagers.containsKey(ext)) {
163        names.add(name);
164      }
165    }
166    return names;
167  }
168
169  private CommandResolution resolveCommand(CommandManager manager, String name, Resource script) throws CommandCreationException {
170    TimestampedObject<CommandResolution> ref = commandCache.get(name);
171    if (ref != null) {
172      if (script.getTimestamp() != ref.getTimestamp()) {
173        ref = null;
174      }
175    }
176    CommandResolution command;
177    if (ref == null) {
178      command = manager.resolveCommand(name, script.getContent());
179      if (command != null) {
180        commandCache.put(name, new TimestampedObject<CommandResolution>(script.getTimestamp(), command));
181      }
182    } else {
183      command = ref.getObject();
184    }
185    return command;
186  }
187
188  private <C extends BaseCommand> CommandResolution createCommand(final Class<C> commandClass) {
189    return new CommandResolution() {
190      final ShellCommandImpl<C> shellCommand = new ShellCommandImpl<C>(commandClass);
191      @Override
192      public String getDescription() {
193        return shellCommand.describe(commandClass.getSimpleName(), Format.DESCRIBE);
194      }
195      @Override
196      public ShellCommand<?> getCommand() throws CommandCreationException {
197        return shellCommand;
198      }
199    };
200  }
201}