View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.frontend.cli;
20  
21  import java.lang.reflect.Modifier;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.Comparator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Optional;
29  import java.util.TreeMap;
30  import java.util.stream.Collectors;
31  import java.util.stream.Stream;
32  
33  import org.apache.logging.log4j.LogManager;
34  import org.apache.logging.log4j.Logger;
35  import org.mycore.common.MCRClassTools;
36  import org.mycore.common.config.MCRConfiguration2;
37  import org.mycore.common.config.MCRConfigurationException;
38  import org.mycore.frontend.cli.annotation.MCRCommandGroup;
39  
40  /**
41   * Manages all commands for the Command Line Interface and WebCLI.
42   *
43   * @author Frank L\u00FCtzenkirchen
44   * @author Robert Stephan
45   */
46  public class MCRCommandManager {
47      private static final Logger LOGGER = LogManager.getLogger(MCRCommandManager.class);
48  
49      protected static TreeMap<String, List<MCRCommand>> knownCommands = new TreeMap<>();
50  
51      public MCRCommandManager() {
52          try {
53              initBuiltInCommands();
54              initCommands();
55          } catch (Exception ex) {
56              handleInitException(ex);
57          }
58      }
59  
60      @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("DM_EXIT")
61      protected void handleInitException(Exception ex) {
62          MCRCLIExceptionHandler.handleException(ex);
63          System.exit(1);
64      }
65  
66      public static TreeMap<String, List<MCRCommand>> getKnownCommands() {
67          return knownCommands;
68      }
69  
70      protected void initBuiltInCommands() {
71          addAnnotatedCLIClass(MCRBasicCommands.class);
72      }
73  
74      protected void initCommands() {
75          // load build-in commands
76          initConfiguredCommands("Internal");
77          initConfiguredCommands("External");
78      }
79  
80      /** Read internal and/or external commands */
81      protected void initConfiguredCommands(String type) {
82          String prefix = "MCR.CLI.Classes." + type;
83          Stream<Map.Entry<String, String>> propsWithPrefix = MCRConfiguration2.getPropertiesMap()
84              .entrySet()
85              .stream()
86              .filter(e -> e.getKey().startsWith(prefix));
87          Stream<String> classNames = propsWithPrefix
88              .map(Map.Entry::getValue)
89              .flatMap(MCRConfiguration2::splitValue)
90              .filter(s -> !s.isEmpty());
91          classNames.forEach(this::loadCommandClass);
92      }
93  
94      private void loadCommandClass(String commandClassName) {
95          LOGGER.debug("Will load commands from the {} class {}", commandClassName, commandClassName);
96          try {
97              Class<?> cliClass = MCRClassTools.forName(commandClassName);
98              if (cliClass.isAnnotationPresent(MCRCommandGroup.class)) {
99                  addAnnotatedCLIClass(cliClass);
100             } else {
101                 addDefaultCLIClass(commandClassName);
102             }
103 
104         } catch (ClassNotFoundException cnfe) {
105             LOGGER.error("MyCoRe Command Class {} not found.", commandClassName);
106         }
107     }
108 
109     protected void addAnnotatedCLIClass(Class<?> cliClass) {
110         String groupName = Optional.ofNullable(cliClass.getAnnotation(MCRCommandGroup.class))
111             .map(MCRCommandGroup::name)
112             .orElse(cliClass.getSimpleName());
113         final Class<org.mycore.frontend.cli.annotation.MCRCommand> mcrCommandAnnotation;
114         mcrCommandAnnotation = org.mycore.frontend.cli.annotation.MCRCommand.class;
115         ArrayList<MCRCommand> commands = Arrays.stream(cliClass.getMethods())
116             .filter(method -> method.getDeclaringClass().equals(cliClass))
117             .filter(method -> Modifier.isStatic(method.getModifiers()) && Modifier.isPublic(method.getModifiers()))
118             .filter(method -> method.isAnnotationPresent(mcrCommandAnnotation))
119             .sorted(Comparator.comparingInt(m -> m.getAnnotation(mcrCommandAnnotation).order()))
120             .map(MCRCommand::new)
121             .collect(Collectors.toCollection(ArrayList::new));
122         addCommandGroup(groupName, commands);
123     }
124 
125     //fixes MCR-1594 deep in the code
126     private List<MCRCommand> addCommandGroup(String groupName, ArrayList<MCRCommand> commands) {
127         return knownCommands.put(groupName, Collections.unmodifiableList(commands));
128     }
129 
130     protected void addDefaultCLIClass(String className) {
131         Object obj = buildInstanceOfClass(className);
132         MCRExternalCommandInterface commandInterface = (MCRExternalCommandInterface) obj;
133         ArrayList<MCRCommand> commandsToAdd = commandInterface.getPossibleCommands();
134         addCommandGroup(commandInterface.getDisplayName(), commandsToAdd);
135     }
136 
137     private Object buildInstanceOfClass(String className) {
138         try {
139             return MCRClassTools.forName(className).getDeclaredConstructor().newInstance();
140         } catch (Exception ex) {
141             String msg = "Could not instantiate class " + className;
142             throw new MCRConfigurationException(msg, ex);
143         }
144     }
145 
146     public List<String> invokeCommand(String command) throws Exception {
147         if (command.trim().startsWith("#")) {
148             //ignore comment
149             return new ArrayList<String>();
150         }
151         for (List<MCRCommand> commands : knownCommands.values()) {
152             for (MCRCommand currentCommand : commands) {
153                 long start = System.currentTimeMillis();
154                 List<String> commandsReturned = currentCommand.invoke(command);
155                 long end = System.currentTimeMillis();
156 
157                 if (commandsReturned != null) {
158                     long timeNeeded = end - start;
159                     MCRCommandLineInterface.output("Command processed (" + timeNeeded + " ms)");
160                     MCRCommandStatistics.commandInvoked(currentCommand, timeNeeded);
161 
162                     return commandsReturned;
163                 }
164             }
165         }
166 
167         MCRCommandLineInterface.output("Command not understood:" + command);
168         MCRCommandLineInterface.output("Enter 'help' to get a list of commands.");
169         return new ArrayList<>();
170     }
171 }