package org.ovirt.engine.core.bll; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.text.MessageFormat; import java.util.Arrays; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.context.EngineContext; import org.ovirt.engine.core.common.action.VdcActionParametersBase; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.queries.VdcQueryParametersBase; import org.ovirt.engine.core.common.queries.VdcQueryType; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.di.Injector; import org.ovirt.engine.core.utils.ReflectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class CommandsFactory { private static final Logger log = LoggerFactory.getLogger(CommandsFactory.class); private static final String CLASS_NAME_FORMAT = "%1$s.%2$s%3$s"; private static final String COMMAND_SUFFIX = "Command"; private static final String QUERY_SUFFIX = "Query"; private static final String CTOR_MISMATCH = "could not find matching constructor for Command class {0}"; private static final String CTOR_NOT_FOUND_FOR_PARAMETERS = "Can't find constructor for type {} with parameter types: {}"; private static final String[] COMMAND_PACKAGES = new String[] { "org.ovirt.engine.core.bll", "org.ovirt.engine.core.bll.aaa", "org.ovirt.engine.core.bll.exportimport", "org.ovirt.engine.core.bll.gluster", "org.ovirt.engine.core.bll.hostdeploy", "org.ovirt.engine.core.bll.hostdev", "org.ovirt.engine.core.bll.network", "org.ovirt.engine.core.bll.network.cluster", "org.ovirt.engine.core.bll.network.dc", "org.ovirt.engine.core.bll.network.host", "org.ovirt.engine.core.bll.network.template", "org.ovirt.engine.core.bll.network.vm", "org.ovirt.engine.core.bll.numa.host", "org.ovirt.engine.core.bll.numa.vm", "org.ovirt.engine.core.bll.pm", "org.ovirt.engine.core.bll.profiles", "org.ovirt.engine.core.bll.provider", "org.ovirt.engine.core.bll.provider.network", "org.ovirt.engine.core.bll.provider.storage", "org.ovirt.engine.core.bll.qos", "org.ovirt.engine.core.bll.scheduling.commands", "org.ovirt.engine.core.bll.scheduling.queries", "org.ovirt.engine.core.bll.snapshots", "org.ovirt.engine.core.bll.storage", "org.ovirt.engine.core.bll.storage.connection", "org.ovirt.engine.core.bll.storage.connection.iscsibond", "org.ovirt.engine.core.bll.storage.disk", "org.ovirt.engine.core.bll.storage.disk.cinder", "org.ovirt.engine.core.bll.storage.disk.image", "org.ovirt.engine.core.bll.storage.disk.lun", "org.ovirt.engine.core.bll.storage.domain", "org.ovirt.engine.core.bll.storage.dr", "org.ovirt.engine.core.bll.storage.export", "org.ovirt.engine.core.bll.storage.lease", "org.ovirt.engine.core.bll.storage.lsm", "org.ovirt.engine.core.bll.storage.ovfstore", "org.ovirt.engine.core.bll.storage.pool", "org.ovirt.engine.core.bll.storage.repoimage" }; protected static String[] getCommandPackages() { return COMMAND_PACKAGES; } private static ConcurrentMap<String, Class<CommandBase<? extends VdcActionParametersBase>>> commandsCache = new ConcurrentHashMap<>(VdcActionType.values().length); public static <P extends VdcActionParametersBase> CommandBase<P> createCommand(VdcActionType action, P parameters) { return createCommand(action, parameters, null); } public static <P extends VdcActionParametersBase> CommandBase<P> createCommand(VdcActionType action, P parameters, CommandContext commandContext) { try { Constructor<CommandBase<? extends VdcActionParametersBase>> commandConstructor = findCommandConstructor(getCommandClass(action.name()), parameters.getClass(), CommandContext.class); if (commandContext == null) { commandContext = CommandContext.createContext(parameters.getSessionId()); } else if (commandContext.getEngineContext().getSessionId() == null) { // Needed for SEAT mechanism - session ID is available only on parameters // upon command re-instantiation (when moving between task handlers). commandContext.getEngineContext().withSessionId(parameters.getSessionId()); } @SuppressWarnings("unchecked") CommandBase<P> command = (CommandBase<P>) commandConstructor.newInstance(parameters, commandContext); return Injector.injectMembers(command); } catch (InvocationTargetException ex) { log.error("Error in invocating CTOR of command '{}' with parameters '{}': {}", action.name(), parameters, ex.getMessage()); log.debug("Exception", ex); return null; } catch (Exception ex) { log.error("An exception has occurred while trying to create a command object for command '{}' with parameters '{}': {}", action.name(), parameters, ex.getMessage()); log.debug("Exception", ex); return null; } } /** * Creates an instance of the given command class and passed the command id to it's constructor * * @param className * command class name to be created * @param commandId * the command id used by the compensation. * @return command instance or null if exception occurred. */ public static CommandBase<?> createCommand(String className, Guid commandId) { try { Constructor<?> constructor = Class.forName(className).getDeclaredConstructor(Guid.class); CommandBase<?> cmd = (CommandBase<?>) constructor.newInstance(commandId); return Injector.injectMembers(cmd); } catch (Exception e) { log.error("CommandsFactory : Failed to get type information using reflection for Class '{}', Command Id '{}': {}", className, commandId, e.getMessage()); log.error("Exception", e); return null; } } public static QueriesCommandBase<?> createQueryCommand(VdcQueryType query, VdcQueryParametersBase parameters, EngineContext engineContext) { Class<?> type = null; try { type = getQueryClass(query.name()); QueriesCommandBase<?> result; if (engineContext == null) { result = (QueriesCommandBase<?>) findCommandConstructor(type, parameters.getClass()).newInstance(parameters); } else { result = (QueriesCommandBase<?>) findCommandConstructor(type, parameters.getClass(), EngineContext.class).newInstance(parameters, engineContext); } return Injector.injectMembers(result); } catch (Exception e) { log.error("Command Factory: Failed to create command '{}' using reflection: {}", type, e.getMessage()); log.error("Exception", e); throw new RuntimeException(e); } } public static Class<CommandBase<? extends VdcActionParametersBase>> getCommandClass(String name) { return getCommandClass(name, COMMAND_SUFFIX); } public static Class<CommandBase<? extends VdcActionParametersBase>> getQueryClass(String name) { return getCommandClass(name, QUERY_SUFFIX); } private static Class<CommandBase<? extends VdcActionParametersBase>> getCommandClass(String name, String suffix) { // try the cache first String key = name + suffix; Class<CommandBase<? extends VdcActionParametersBase>> clazz = commandsCache.get(key); if (clazz != null) { return clazz; } for (String commandPackage : COMMAND_PACKAGES) { String className = String.format(CLASS_NAME_FORMAT, commandPackage, name, suffix); Class<CommandBase<? extends VdcActionParametersBase>> type = loadClass(className); if (type != null) { Class<CommandBase<? extends VdcActionParametersBase>> cachedType = commandsCache.putIfAbsent(key, type); // update cache return cachedType == null ? type : cachedType; } } // nothing found log.warn("Unable to find class for action '{}'", key); return null; } @SuppressWarnings("unchecked") private static Class<CommandBase<? extends VdcActionParametersBase>> loadClass(String className) { try { return (Class<CommandBase<? extends VdcActionParametersBase>>) Class.forName(className); } catch (ClassNotFoundException e) { return null; } } /** * Return the constructor for the command. * * @param <T> * The command type to look for. * @param type * A class representing the command type to look for. * @param expectedParams * The parameters which the constructor is expected to have (can * be empty). * * @return The first matching constructor for the command. * @throws RuntimeException * If a matching constructor can't be found. * * @see ReflectionUtils#findConstructor(Class, Class...) */ private static <T> Constructor<T> findCommandConstructor(Class<T> type, Class<?>... expectedParams) { Constructor<T> constructor = ReflectionUtils.findConstructor(type, expectedParams); if (constructor == null) { log.error(CTOR_NOT_FOUND_FOR_PARAMETERS, type.getName(), Arrays.toString(expectedParams)); throw new RuntimeException(MessageFormat.format(CTOR_MISMATCH, type)); } return constructor; } }