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    
020    package org.crsh.shell.impl.command;
021    
022    import org.crsh.cli.descriptor.Format;
023    import org.crsh.command.BaseCommand;
024    import org.crsh.lang.java.ShellCommandImpl;
025    import org.crsh.shell.impl.command.spi.CommandCreationException;
026    import org.crsh.shell.impl.command.spi.ShellCommand;
027    import org.crsh.plugin.PluginContext;
028    import org.crsh.plugin.ResourceKind;
029    import org.crsh.shell.impl.command.spi.CommandManager;
030    import org.crsh.shell.impl.command.spi.CommandResolution;
031    import org.crsh.shell.impl.command.system.help;
032    import org.crsh.shell.impl.command.system.repl;
033    import org.crsh.util.TimestampedObject;
034    import org.crsh.vfs.Resource;
035    
036    import java.security.Principal;
037    import java.util.HashMap;
038    import java.util.LinkedHashSet;
039    import java.util.Map;
040    import java.util.concurrent.ConcurrentHashMap;
041    
042    public 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    }