package org.ovirt.engine.core.bll; import java.lang.reflect.Constructor; import java.text.MessageFormat; import java.util.Arrays; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; 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.compat.LogCompat; import org.ovirt.engine.core.compat.LogFactoryCompat; import org.ovirt.engine.core.compat.RefObject; import org.ovirt.engine.core.utils.ReflectionUtils; public final class CommandsFactory { private static final String CommandSuffix = "Command"; private static final String QueryPrefix = "Query"; private static final String CanDoCommandPrefix = "Can"; 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 {0} with parameter types: {1}"; private static final String[] COMMAND_PACKAGES = new String[] { "org.ovirt.engine.core.bll", "org.ovirt.engine.core.bll.storage", "org.ovirt.engine.core.bll.gluster" }; private static Map<String, Class<CommandBase<? extends VdcActionParametersBase>>> commandsCache = new ConcurrentHashMap<String, Class<CommandBase<? extends VdcActionParametersBase>>>(VdcActionType.values().length); public static boolean canDoActionWithParameters(VdcActionType action, Object id, RefObject<java.util.ArrayList<String>> reasons, Object... additionalParameters) { boolean returnValue = true; reasons.argvalue = null; try { Class<?> actionType = getCommandClass(action.name(), CommandSuffix); /** * if action type not exist - operation valid */ if (actionType != null) { String canDoActionName = String.format("%1$s%2$s", CanDoCommandPrefix, action); reasons.argvalue = new java.util.ArrayList<String>(); Object[] args; if (additionalParameters != null) { args = new Object[2 + additionalParameters.length]; for (int i = 0; i < additionalParameters.length; i++) { args[2 + i] = additionalParameters[i]; } } else { args = new Object[2]; } args[0] = id; args[1] = reasons.argvalue; /** * Each command must implement static public function in order * to possibility to check operation validity */ java.lang.reflect.Method method = actionType.getMethod(canDoActionName); /** * By default all operations valid */ if (method != null) { returnValue = (Boolean) method.invoke(null, args); } } } catch (Exception e) { log.error("Failed to check Action ", e); } return returnValue; } @SuppressWarnings("unchecked") public static <P extends VdcActionParametersBase> CommandBase<P> CreateCommand(VdcActionType action, P parameters) { try { Constructor<CommandBase<? extends VdcActionParametersBase>> constructor = findCommandConstructor(getCommandClass(action.name(), CommandSuffix), parameters.getClass()); return (CommandBase<P>) constructor.newInstance(new Object[] { parameters }); } catch (java.lang.Exception e) { log.error( "CommandsFactory [parameter: VdcActionParametersBase]: Failed to get type information using " + "reflection for Action: " + action, e); 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. */ @SuppressWarnings("unchecked") public static CommandBase<?> CreateCommand(String className, Guid commandId) { Constructor<?> constructor = null; Boolean isAcessible = null; try { constructor = Class.forName(className).getDeclaredConstructor(Guid.class); // since this constructor is defined as protected, we must modify accessability and restore it afterwards if (!constructor.isAccessible()) { isAcessible = constructor.isAccessible(); constructor.setAccessible(true); } return (CommandBase<?>) constructor.newInstance(new Object[] { commandId }); } catch (java.lang.Exception e) { log.error( "CommandsFactory : Failed to get type information using " + "reflection for Class : " + className + ", Command Id:" + commandId, e); return null; } finally { if (isAcessible != null) { constructor.setAccessible(isAcessible); } } } public static QueriesCommandBase CreateQueryCommand(VdcQueryType query, VdcQueryParametersBase parameters) { java.lang.Class<?> type = null; try { type = getCommandClass(query.name(), QueryPrefix); java.lang.Class<?>[] types = new java.lang.Class[1]; types[0] = parameters.getClass(); java.lang.reflect.Constructor<?> info = findCommandConstructor(type, types); Object[] vdcParameters = new Object[1]; vdcParameters[0] = parameters; Object tempVar = info.newInstance(vdcParameters); return (QueriesCommandBase) ((tempVar instanceof QueriesCommandBase) ? tempVar : null); } catch (Exception e) { log.errorFormat("Command Factory: Failed to create command {0} using reflection\n. {1}", type, e); throw new RuntimeException(e); } } private static Class<CommandBase<? extends VdcActionParametersBase>> getCommandClass(String name, String suffix) { // try the cache first if (commandsCache.get(name + suffix) != null) return commandsCache.get(name + suffix); for (String commandPackage : COMMAND_PACKAGES) { String className = String.format("%1$s.%2$s%3$s", commandPackage, name, suffix); Class<CommandBase<?>> type = loadClass(className); if (type != null) { commandsCache.put(name + suffix, type); // update cache return type; } } // nothing found log.warn("Unable to find class for action: " + name + suffix); 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.errorFormat(CTOR_NOT_FOUND_FOR_PARAMETERS, type.getName(), Arrays.toString(expectedParams)); throw new RuntimeException(MessageFormat.format(CTOR_MISMATCH, type)); } return constructor; } private static LogCompat log = LogFactoryCompat.getLog(CommandsFactory.class); }