package org.ff4j.cli; import static org.ff4j.cli.FF4jCliDisplay.displayConf; import static org.ff4j.cli.FF4jCliDisplay.displayEnvironments; import static org.ff4j.cli.FF4jCliDisplay.displayFeatures; import static org.ff4j.cli.FF4jCliDisplay.displayHelpConnected; import static org.ff4j.cli.FF4jCliDisplay.displayHelpNotConnected; import static org.ff4j.cli.FF4jCliDisplay.displayProperties; import static org.ff4j.cli.FF4jCliOptions.addGroupOptions; import static org.ff4j.cli.FF4jCliOptions.connectOptions; import static org.ff4j.cli.FF4jCliOptions.enableFeatureOptions; import static org.ff4j.cli.FF4jCliOptions.enableGroupOptions; import static org.ff4j.cli.FF4jCliOptions.grantOptions; import static org.ff4j.cli.FF4jCliOptions.propertyOptions; import static org.ff4j.cli.ansi.AnsiTerminal.foreGroundColor; /* * #%L * ff4j-cli * %% * Copyright (C) 2013 - 2016 FF4J * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import static org.ff4j.cli.ansi.AnsiTerminal.logError; import static org.ff4j.cli.ansi.AnsiTerminal.logInfo; import static org.ff4j.cli.ansi.AnsiTerminal.logWarn; import static org.ff4j.cli.ansi.AnsiTerminal.textAttribute; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.ff4j.FF4j; import org.ff4j.cli.ansi.AnsiForegroundColor; import org.ff4j.cli.ansi.AnsiTerminal; import org.ff4j.cli.ansi.AnsiTextAttribute; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Command processor. * * @author Cedrick LUNVEN (@clunven) */ public class FF4jCliProcessor { /** Commons-cli command parser. */ private static final CommandLineParser CMD_PARSER = new DefaultParser(); /** String constants */ private static final String FEATURE = "Feature "; private static final String ERROR_DURING_CONNECT_COMMAND = "Error during connect command"; /** Environnements. */ private Map<String, FF4j> envs = new LinkedHashMap<String, FF4j>(); /** Users. */ private Map<String, String> users = new LinkedHashMap<String, String>(); /** Current environment. */ private String currentEnv = null; /** Current environment. */ private FF4j currentFF4J = null; /** * Sample Processor. * @param springConfFile */ public FF4jCliProcessor(String springConfFile) { parseSpringContext(springConfFile); } /** * Command are not the same if you have selected an environnement or not. * * @param commandLine * current command lien */ public void evaluate(String commandLine) { if (currentEnv == null) { dispatchCommandNotConnected(commandLine); } else { dispatchCommandConnected(commandLine); } } /** * Input provided by user. * * @param commandLine * current command line */ private void dispatchCommandNotConnected(String commandLine) { if (commandLine.startsWith("help") || commandLine.startsWith("?")) { displayHelpNotConnected(); } else if (commandLine.equals("list") || commandLine.equals("ls")) { displayEnvironments(envs); } else if (commandLine.startsWith("exit") || commandLine.startsWith("quit")) { exit(); } else if (commandLine.startsWith("connect")) { processCommandConnect(commandLine); } else if (!commandLine.isEmpty()) { logWarn("Invalid command, not recognized"); displayHelpNotConnected(); } } /** * Element for connected commands. * * @param commandLine * command */ private void dispatchCommandConnected(String commandLine) { String[] cmdParts = commandLine.split(" "); String cmd = cmdParts[0].trim(); if (cmd.equals("quit")) { currentEnv = null; currentFF4J = null; } else if (cmd.equals("exit") ) { exit(); } else if (cmd.equals("help") || cmd.equals("?")) { displayHelpConnected(); } else if (cmd.equals("features")) { displayFeatures(currentFF4J.getFeatureStore().readAll()); } else if (cmd.equals("properties")) { displayProperties(currentFF4J.getPropertiesStore().readAllProperties()); } else if (cmd.equals("conf")) { displayConf(currentFF4J); }else if (cmd.equals("list") || cmd.equals("ls")) { AnsiTerminal.white("\nFeatures:\n"); displayFeatures(currentFF4J.getFeatureStore().readAll()); AnsiTerminal.white("\nProperties:\n"); displayProperties(currentFF4J.getPropertiesStore().readAllProperties()); } else if (cmd.equals("enable")) { processCommandEnable(commandLine, true); System.out.println(""); displayFeatures(currentFF4J.getFeatureStore().readAll()); } else if (cmd.equals("disable")) { processCommandEnable(commandLine, false); displayFeatures(currentFF4J.getFeatureStore().readAll()); } else if (cmd.equals("enableGroup")) { processCommandEnableGroup(commandLine, true); displayFeatures(currentFF4J.getFeatureStore().readAll()); } else if (cmd.equals("disableGroup")) { processCommandEnableGroup(commandLine, false); displayFeatures(currentFF4J.getFeatureStore().readAll()); } else if (cmd.equals("addToGroup")) { processCommandAddGroup(commandLine); displayFeatures(currentFF4J.getFeatureStore().readAll()); } else if (cmd.equals("removeFromGroup")) { processCommandAddGroup(commandLine); displayFeatures(currentFF4J.getFeatureStore().readAll()); } else if (cmd.equals("grant")) { processCommandGrant(commandLine); displayFeatures(currentFF4J.getFeatureStore().readAll()); } else if (cmd.equals("revoke")) { processCommandGrant(commandLine); displayFeatures(currentFF4J.getFeatureStore().readAll()); } else if (cmd.equals("enableAudit")) { processCommandEnableEnableAudit(commandLine, true); Map <String, FF4j> mappi = new HashMap<String, FF4j>(); mappi.put(currentEnv, currentFF4J); System.out.println(""); displayEnvironments(mappi); } else if (cmd.equals("disableAudit")) { processCommandEnableEnableAudit(commandLine, false); Map <String, FF4j> mappi = new HashMap<String, FF4j>(); mappi.put(currentEnv, currentFF4J); System.out.println(""); displayEnvironments(mappi); } else if (cmd.equals("update")) { processCommandUpdateProperty(commandLine); System.out.println(""); displayProperties(currentFF4J.getPropertiesStore().readAllProperties()); } else { logWarn("Invalid command, not recognized"); FF4jCliDisplay.displayHelpConnected(); } } private void processCommandUpdateProperty(String commandLine) { try { CommandLine cmd = CMD_PARSER.parse(propertyOptions(), commandLine.split(" ")); if (cmd.getArgList().size() != 1 || !cmd.hasOption("p") || !cmd.hasOption("v")) { logError("Invalid command, expecting update -p <property> -v <value>"); } else { String property = cmd.getOptionValue('p'); String value = cmd.getOptionValue('v'); if (!currentFF4J.getPropertiesStore().existProperty(property)) { logWarn("Property " + property + " does not exist, nothing to update"); } else { currentFF4J.getPropertiesStore().updateProperty(property, value); logInfo("Property " + property + " has been updated with " + value); } } } catch (ParseException e) { error(e, "parsing error during update property command"); } catch (Exception e) { error(e, "Cannot update property"); } } private void processCommandAddGroup(String commandLine) { try { CommandLine cmd = CMD_PARSER.parse(addGroupOptions(), commandLine.split(" ")); if (cmd.getArgList().size() != 1 || !cmd.hasOption("f") || !cmd.hasOption("g")) { logError("Invalid command, expecting addToGroup[removeFromGroup] -f <featureName> -g <grouName>"); } else { String feature = cmd.getOptionValue('f'); String group = cmd.getOptionValue('g'); if (!currentFF4J.getFeatureStore().exist(feature)) { logWarn("Feature does not exist, nothing updated"); } else { if (cmd.getArgList().get(0).equals("addToGroup")) { currentFF4J.getFeatureStore().addToGroup(feature, group); logInfo(FEATURE + feature + " has been added to group " + group); } else if (cmd.getArgList().get(0).equals("removeFromGroup")) { String currentGroup = currentFF4J.getFeatureStore().read(feature).getGroup(); if (group.equals(currentGroup)) { currentFF4J.getFeatureStore().removeFromGroup(feature, group); logInfo(FEATURE + feature + " has been removed from group: " + group); } else if (currentGroup == null || currentGroup.isEmpty()){ logWarn("The groupName is invalid expected:" + currentGroup + " but was [" + group + "]"); } else { logWarn("Cannot remove group: there are no group on this feature"); } } } } } catch (ParseException e) { error(e, "Error during addToGroup/removeFromGroup command"); } } private void processCommandGrant(String commandLine) { try { CommandLine cmd = CMD_PARSER.parse(grantOptions(), commandLine.split(" ")); if (cmd.getArgList().size() != 1 || !cmd.hasOption("f") || !cmd.hasOption("r")) { logError("Invalid command, expecting grant[revoke] -r <role> -f <featureName>"); } else { String feature = cmd.getOptionValue('f'); String role = cmd.getOptionValue('r'); if (!currentFF4J.getFeatureStore().exist(feature)) { logWarn("Feature does not exist, nothing updated"); } else { if (cmd.getArgList().get(0).equals("grant")) { currentFF4J.getFeatureStore().grantRoleOnFeature(feature, role); logInfo("Role " + role + " has been added to feature " + feature); } else if (cmd.getArgList().get(0).equals("revoke")) { Set< String > permissions = currentFF4J.getFeatureStore().read(feature).getPermissions(); if (permissions == null) { logWarn("The role is invalidn there is no role on the feature " + feature); } else if (permissions.contains(role)) { currentFF4J.getFeatureStore().removeRoleFromFeature(feature, role); logInfo(FEATURE + feature + " has not more role " + role); } else { logWarn("The role is invalid expected one of " + permissions.toString()); } } } } } catch (ParseException e) { error(e, "Error during addToGroup/removeFromGroup command"); } } /** * Command to connect. * * @param commandLine * execute command line */ private void processCommandConnect(String commandLine) { try { CommandLine cmd = CMD_PARSER.parse(connectOptions(), commandLine.split(" ")); if (cmd.getArgList().size() != 2) { logError("Invalid command, expecting connect <envName> [-u user] [-p password]"); } else if (users.isEmpty()) { connectEnv(cmd.getArgList().get(1)); } else if (!cmd.hasOption("u") || !cmd.hasOption("p")) { logWarn("Connection is not setup as opened, expecting credentials"); logError("Invalid syntax expected connect <envName> -u <user> -p <password>"); } else { String user = cmd.getOptionValue('u'); String password = cmd.getOptionValue('p'); if (!users.containsKey(user) || !users.get(user).equals(password)) { logError("Invalid credentials, check users"); } else { connectEnv(cmd.getArgList().get(1)); } } } catch (ParseException e) { error(e, ERROR_DURING_CONNECT_COMMAND); } } /** * Process commandline when an environment is already selected. * * @param commandLine * current command line * @param enable * flag to disable or enable features */ private void processCommandEnable(String commandLine, boolean enable) { try { CommandLine cmd = CMD_PARSER.parse(enableFeatureOptions(), commandLine.split(" ")); if (cmd.getArgList().size() != 1 || !cmd.hasOption("f") ) { logWarn("Invalid command, expecting enable/disable -f <featureName>"); } else { String featureName = cmd.getOptionValue('f'); if (!currentFF4J.getFeatureStore().exist(featureName)) { logWarn("Feature [" + featureName + "] not found"); } else if (enable){ currentFF4J.getFeatureStore().enable(featureName); logInfo(FEATURE + featureName + " is now enabled") ; } else { currentFF4J.getFeatureStore().disable(featureName); logInfo(FEATURE + featureName + " is now disabled") ; } } } catch (ParseException moe) { error(moe, ERROR_DURING_CONNECT_COMMAND); } } private void processCommandEnableEnableAudit(String commandLine, boolean enable) { try { CMD_PARSER.parse(new Options(), commandLine.split(" ")); currentFF4J.setEnableAudit(enable); if (enable) { logInfo("Audit enabled for environment " + currentEnv); } else { logInfo("Audit disabled for environment " + currentEnv); } } catch (ParseException e) { error(e, "Error during enableAudit command"); } } private void processCommandEnableGroup(String commandLine, boolean enable) { try { CommandLine cmd = CMD_PARSER.parse(enableGroupOptions(), commandLine.split(" ")); if (cmd.getArgList().size() != 1 || !cmd.hasOption("g") ) { logWarn("Invalid command, expecting enableGroup/disableGroup -f <groupName>"); } else { String groupName = cmd.getOptionValue('g'); if (!currentFF4J.getFeatureStore().existGroup(groupName)) { logWarn("Group [" + groupName + "] not found"); } else if (enable){ currentFF4J.getFeatureStore().enableGroup(groupName); logInfo("Group " + groupName + " is now enabled") ; } else { currentFF4J.getFeatureStore().disableGroup(groupName); logInfo("Group " + groupName + " is now disabled") ; } } } catch (ParseException e) { error(e, ERROR_DURING_CONNECT_COMMAND); } } /** * Selecting environnement. * * @param envName * target environment name */ private void connectEnv(String envName) { currentEnv = envName; currentFF4J = envs.get(currentEnv); if (currentFF4J == null) { logWarn("Invalid environment name, please check"); displayEnvironments(envs); currentEnv = null; } else { logInfo("Environment [" + currentEnv + "] is now selected"); } } /** * Parse Spring context. */ @SuppressWarnings("unchecked") public void parseSpringContext(String fileName) { try { logInfo("Loading configurations from classpath file [" + fileName + "]"); ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(fileName); this.envs = ctx.getBeansOfType(FF4j.class); if (ctx.containsBean("AUTHORIZED_USERS")) { this.users = (Map<String, String>) ctx.getBean("AUTHORIZED_USERS"); } ctx.close(); } catch (RuntimeException fne) { error(fne, "Cannot parse Spring context"); } } /** * Exit */ private void exit() { logInfo("Exiting FF4j... Good Bye"); foreGroundColor(AnsiForegroundColor.WHITE); textAttribute(AnsiTextAttribute.CLEAR); System.exit(0); } /** * Error. * * @param t * current erorr * @param message */ private void error(Throwable t, String message) { logError(t.getClass().getName() + " : " + t.getMessage() + "[" + message + "]"); } /** * @return the currentEnv */ public String getCurrentEnv() { return currentEnv; } /** * @return the currentFF4J */ public FF4j getCurrentFF4J() { return currentFF4J; } }