package de.zib.gndms.infra.system; /* * Copyright 2008-2011 Zuse Institute Berlin (ZIB) * * 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. */ import com.google.common.base.Function; import com.google.common.collect.MapMaker; import com.google.common.collect.Sets; import de.zib.gndms.infra.action.*; import de.zib.gndms.kit.config.ParameterTools; import de.zib.gndms.logic.action.Action; import de.zib.gndms.logic.action.NoSuchActionException; import de.zib.gndms.logic.action.SkipActionInitializationException; import de.zib.gndms.logic.model.DefaultBatchUpdateAction; import de.zib.gndms.logic.model.NoWSDontNeedModelUpdateListener; import de.zib.gndms.logic.model.config.AvailableActionsAction; import de.zib.gndms.logic.model.config.ConfigAction; import de.zib.gndms.logic.model.config.EchoOptionsAction; import de.zib.gndms.logic.model.config.HelpOverviewAction; import de.zib.gndms.logic.model.dspace.AssignSliceKindAction; import de.zib.gndms.logic.model.dspace.SetupSliceKindAction; import de.zib.gndms.logic.model.dspace.SetupSubspaceAction; import de.zib.gndms.logic.model.gorfx.ConfigTaskFlowTypeAction; import de.zib.gndms.logic.model.gorfx.SetupTaskFlowAction; import de.zib.gndms.model.common.GridResource; import de.zib.gndms.model.common.ModelUUIDGen; import de.zib.gndms.stuff.GNDMSInjector; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.PrintWriter; import java.util.Map; import java.util.Set; import java.util.UUID; /** * Default implementation of the {@code WSActionCaller} interface. * * A ConfigActionCaller is used to dynamically instantiate and call new {@code ConfigAction}s. * The main method of this class is {@code callPublicAction(String, String, java.io.PrintWriter)} which is used for * this purpose. See {@link WSActionCaller#callPublicAction(String, String, java.io.PrintWriter)} * for a description. * * @see ConfigAction * @see WSActionCaller * @author try ste fan pla nti kow zib * @version $Id$ * * User: stepn Date: 03.09.2008 Time: 16:43:46 */ public final class ConfigActionCaller implements WSActionCaller { private @NotNull final Logger logger = LoggerFactory.getLogger(ConfigActionCaller.class); private final @NotNull ModelUUIDGen actionUUIDGen = new ModelUUIDGen() { public @NotNull String nextUUID() { return UUID.randomUUID().toString(); } }; private final MapMaker mapMaker = new MapMaker(); private final Map<Class<? extends ConfigAction<?>>, Boolean> configActionMap = mapMaker.makeMap(); /** * A set of ConfigAction.{@link HelpOverviewAction} is excluded, as this set used in the {@code HelpOverviewAction} * to print help about all available ConfigActions. */ private Set<Class<? extends ConfigAction<?>>> configActions = Sets.newSetFromMap(configActionMap); private final Function<String, String> classToActionNameMapper = new Function<String, String>() { public String apply(final String s) { if (s.startsWith("de.zib.gndms.infra.action") && s.endsWith("Action")) return ".sys" + s.substring("de.zib.gndms.infra.action".length(), s.length()-6); if (s.startsWith("de.zib.gndms.logic.model") && s.endsWith("Action")) return s.substring("de.zib.gndms.logic.model".length(), s.length()-6); return s; } }; private GNDMSystem system; private GNDMSInjector injector; /** * Constructs a new instance of the configActionCaller. * * @Note {@link #init} must be called to have a usable instance of this class. */ public ConfigActionCaller( ) { } /** * Constructs and initializes the configActionCaller. * * @see ConfigActionCaller#init(GNDMSystem) init * @param systemParam the GNDMSystem */ @SuppressWarnings({ "ThisEscapedInObjectConstruction", "OverlyCoupledMethod" }) public ConfigActionCaller(final @NotNull GNDMSystem systemParam) { init( systemParam ); } /** * Initializes this action * * Fills {@link #configActions} with all implemented {@link ConfigAction}s, except the {@link HelpOverviewAction}. * Load the GNDMSystem's injector into {@link #injector} as a new {@code ChildInjector} of this. * * @param systemParam the GNDMSystem */ public void init( final @NotNull GNDMSystem systemParam ) { system = systemParam; configActions.add(SetupSubspaceAction.class); configActions.add(EchoOptionsAction.class); configActions.add(SetupTaskFlowAction.class); configActions.add(ConfigTaskFlowTypeAction.class); configActions.add(SetupSliceKindAction.class); configActions.add(AssignSliceKindAction.class); configActions.add(RefreshSystemAction.class); configActions.add(SetupDefaultConfigletAction.class); configActions.add(SetupUpdatingConfigletAction.class); configActions.add(ReadC3CatalogAction.class); configActions.add(ReadContainerLogAction.class); configActions.add(ReadGNDMSVersionAction.class); injector = system.getInstanceDir().getSystemAccessInjector(); } // public void configure(final @NotNull Binder binder) { // binder.skipSources(GNDMSystem.class, // EntityManager.class, // ModelUUIDGen.class, // UUIDGen.class, // EntityUpdateListener.class, // BatchUpdateAction.class); // binder.bind(ConfigActionCaller.class).toInstance(this); // } /** * Instantiates a new config action or a specific subclass of it according to the String <tt>name</tt>, * sets its configurations map to the values set in <tt>params</tt> and returns the action. * <p>If the instance should be public accessible (see {@link de.zib.gndms.infra.action.PublicAccessible}) set <tt>pub</tt> * to <tt>true</tt>. * * * @param name A String giving a description about the action class or a help request. * See {@link #findActionClass(String)} ) * @param params A String containing the configuration for the Config Action. * See {@link ConfigAction#parseLocalOptions(String)} * @param pub a boolean to flag that the instance is public accessible or not. * See {@link PublicAccessible}. * * @return a ConfigAction instance with a given configuration * * @throws ClassNotFoundException if the class given by <tt>name</tt> could not be found. * @throws IllegalAccessException * @throws InstantiationException * @throws ParameterTools.ParameterParseException if <tt>params</tt> is not valid according to defined Syntax. */ @SuppressWarnings({ "RawUseOfParameterizedType", "unchecked" }) public ConfigAction<?> instantiateConfigAction(final @NotNull String name, final @NotNull String params, final boolean pub ) throws ClassNotFoundException, IllegalAccessException, InstantiationException, ParameterTools.ParameterParseException { final Class<? extends ConfigAction<?>> clazz = findActionClass(name); if( pub && !PublicAccessible.class.isAssignableFrom(clazz) ) throw new IllegalArgumentException( name + " is not public accessible." ); if (ConfigAction.class.isAssignableFrom(clazz)) { final ConfigAction configAction = clazz.newInstance(); configAction.parseLocalOptions(params); return configAction; } else throw new NoSuchActionException( name ); } /** * Finds a specific ConfigAction class. * * If <tt>name</tt> * <ul> * <li> * is denoted as "help", "-help" or "--help" a {@link HelpOverviewAction} class instance will be returned. * </li> * <li> * starts with ".sys", the class 'de.zib.gndms.infra.action'+name.substring(4)+'Action' will be returned. * </li> * * <li> * starts with ".", the class 'de.zib.gndms.logic.model'+name+'Action' will be returned. * </li> * </ul> * * @param name a String containing the name of a config action class or a help request. * @return a ConfigAction class corresponding the given String. * @throws ClassNotFoundException if <tt>name</tt> is null or the class could not be found */ @SuppressWarnings({ "MethodMayBeStatic" }) private Class<? extends ConfigAction<?>> findActionClass( final @NotNull String name) throws ClassNotFoundException { if (name.length() > 0) { if ( matchParam( "help", name )) return toConfigActionClass(HelpOverviewAction.class); if ( matchParam( "list", name ) ) return toConfigActionClass(AvailableActionsAction.class); try { if (name.startsWith(".sys")) return toConfigActionClass(Class.forName( "de.zib.gndms.infra.action" + name.substring(4) + "Action")); } catch (ClassNotFoundException e) { // continue trying } if (name.charAt(0) == '.') return toConfigActionClass( Class.forName("de.zib.gndms.logic.model" + name + "Action")); } return toConfigActionClass(Class.forName(name)); } private boolean matchParam( String expected, String actual ) { final String nameToLower = actual.toLowerCase(); return expected.equals(nameToLower) || ("-" + expected).equals(nameToLower) || ("--" + expected).equals(nameToLower); } /** * Checks if <tt>helpOverviewActionClassParam</tt> is a <tt>ConfigAction</tt> class or a subclass of it. * In this case the class object will be returned with a cast, as denoted in the signature. * Otherwise an IllegalArgumentException will be thrown. * * @param helpOverviewActionClassParam the class which will be casted and returned * @return <tt>helpOverviewActionClassParam</tt> if it is a <tt>ConfigAction</tt> class or a subclass of it. */ @SuppressWarnings({ "unchecked" }) private static Class<? extends ConfigAction<?>> toConfigActionClass( final Class<?> helpOverviewActionClassParam) { if (ConfigAction.class.isAssignableFrom(helpOverviewActionClassParam)) return (Class<? extends ConfigAction<?>>) helpOverviewActionClassParam; else throw new IllegalArgumentException("Given class is not a ConfigAction"); } @SuppressWarnings({ "FeatureEnvy" }) public Object callAction( final @NotNull String className, final @NotNull String opts, final @NotNull PrintWriter writer) throws Exception { ConfigAction<?> action = instantiateConfigAction(className, opts.trim(), false); return realCallAction( action, className, opts, writer ); } public Object callPublicAction( final @NotNull String className, final @NotNull String opts, final @NotNull PrintWriter writer ) throws Exception { ConfigAction<?> action = instantiateConfigAction(className, opts.trim(), true); return realCallAction( action, className, opts, writer ); } /** * Sets all necessary fields of the newly created ConfigAction instance, * invokes {@code call()} on the ConfigAction instance and its postponed actions * and returns the result of the ConfigAction's computation. * * Note: the options of the action ({@code opts}) must be already be set before this method is invoked. * * @param action the ConfigAction which shall be invoked * @param className the classname of the ConfigAction which shall be created * @param opts A String containing the configuration for the ConfigAction * @param writer a printwriter where all output, generated by the ConfigAction, will be written to * @return the result of the ConfigAction's computation * @throws Exception */ protected Object realCallAction( ConfigAction<?> action, final @NotNull String className, final @NotNull String opts, final @NotNull PrintWriter writer ) throws Exception { action.setOwnEntityManager(system.getEntityManagerFactory().createEntityManager()); action.setPrintWriter(writer); action.setClosingWriterOnCleanUp(false); action.setWriteResult(true); action.setUUIDGen(actionUUIDGen); action.setOwnPostponedEntityActions( new DefaultBatchUpdateAction<GridResource>( new NoWSDontNeedModelUpdateListener() ) ); if (action instanceof SystemAction) ((SystemAction<?>)action).setSystem(system); // Help Action required dynamic casting due to compiler bug in older 1.5 javac // regarding generics and instanceof if (HelpOverviewAction.class.isInstance(action)) { HelpOverviewAction helpAction = HelpOverviewAction.class.cast(action); helpAction.setConfigActions(configActions); helpAction.setNameMapper(classToActionNameMapper); } injector.injectMembers(action); logger.info("Running " + className + ' ' + opts); try { Object retVal = action.call(); Action<?> postAction = action.getPostponedEntityActions(); postAction.call(); return retVal; } catch (SkipActionInitializationException sa) { // Intentionally ignored return null; } catch (Exception e) { logger.warn("Failure during " + className + ' ' + opts, e); throw e; } } public void setConfigActions( Set<Class<? extends ConfigAction<?>>> configActions ) { this.configActions = configActions; } }