package pt.ist.struts.dispatch.servlets; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.struts.Globals; import org.apache.struts.action.Action; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionFormBean; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ExceptionHandler; import org.apache.struts.action.RequestProcessor; import org.apache.struts.config.ExceptionConfig; import org.apache.struts.config.FormBeanConfig; import org.apache.struts.config.MessageResourcesConfig; import org.apache.struts.config.ModuleConfig; import org.apache.struts.config.PlugInConfig; import org.apache.struts.config.impl.DefaultModuleConfigFactory; import org.apache.struts.util.PropertyMessageResourcesFactory; import pt.ist.struts.dispatch.annotation.ExceptionHandling; import pt.ist.struts.dispatch.annotation.Exceptions; import pt.ist.struts.dispatch.annotation.Forward; import pt.ist.struts.dispatch.annotation.Forwards; import pt.ist.struts.dispatch.annotation.Input; import pt.ist.struts.dispatch.annotation.Mapping; import pt.ist.struts.dispatch.annotation.MessageResources; import pt.ist.struts.dispatch.annotation.Property; import pt.ist.struts.dispatch.annotation.StrutsConfiguration; import pt.ist.struts.dispatch.annotation.StrutsPlugin; import pt.ist.struts.dispatch.tiles.FenixDefinitionsFactory; import pt.ist.struts.dispatch.tiles.PartialTileDefinition; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; /** * @author Shezad Anavarali (shezad@ist.utl.pt) * @author Pedro Santos (pedro.miguel.santos@ist.utl.pt) */ public class RuntimeModuleConfigFactory extends DefaultModuleConfigFactory { private static final String PAGE_FILE_EXTENSION = ".jsp"; private static final String INPUT_PAGE_AND_METHOD = ".do?page=0&method="; private static final String INPUT_DEFAULT_PAGE_AND_METHOD = ".do?page=0&method=prepare"; private static final String EXCEPTION_KEY_DEFAULT_PREFIX = "resources.Action.exceptions."; private static final List UPPER_BOUND_SUPERCLASSES = Arrays.asList("DispatchAction", "Action", "Object"); private static final String STRUTS_EMPTY = "WEB-INF/conf/struts-empty.xml"; private static Multimap runtimeConfig = HashMultimap.create(); private static Set> actions = new HashSet<>(); public static void registerConfiguration(StrutsConfiguration config) { runtimeConfig.put(config.module(), config); } public static void registerAction(Class mapping) { actions.add(mapping); } static { PartialTileDefinition.init(); } @Override public ModuleConfig createModuleConfig(String prefix) { ModuleConfig config = super.createModuleConfig(prefix); MessageResourcesConfig defaultResourceConfig = new MessageResourcesConfig(); defaultResourceConfig.setFactory(PropertyMessageResourcesFactory.class.getName()); defaultResourceConfig.setKey(Globals.MESSAGES_KEY); defaultResourceConfig.setNull(false); defaultResourceConfig.setParameter("MessageResources"); config.addMessageResourcesConfig(defaultResourceConfig); for (StrutsConfiguration strutsConfig : runtimeConfig.get(prefix)) { if (!strutsConfig.formBeanType().equals(ActionFormBean.class)) { config.setActionFormBeanClass(strutsConfig.formBeanType().getName()); } if (!strutsConfig.actionForwardType().equals(ActionForward.class)) { config.setActionForwardClass(strutsConfig.actionForwardType().getName()); } if (!strutsConfig.actionMappingType().equals(ActionMapping.class)) { config.setActionMappingClass(strutsConfig.actionMappingType().getName()); } if (!strutsConfig.controllerType().equals(RequestProcessor.class)) { config.getControllerConfig().setProcessorClass(strutsConfig.controllerType().getName()); // ControllerConfig controllerConfig = new ControllerConfig(); // controllerConfig.setProcessorClass(); // config.setControllerConfig(controllerConfig); } for (MessageResources resource : strutsConfig.resources()) { MessageResourcesConfig resourceConfig = new MessageResourcesConfig(); resourceConfig.setFactory(resource.factory().getName()); resourceConfig.setKey(resource.key()); resourceConfig.setNull(resource.nullValue()); resourceConfig.setParameter(resource.parameter()); config.addMessageResourcesConfig(resourceConfig); } for (StrutsPlugin plugin : strutsConfig.plugins()) { PlugInConfig pluginConfig = new PlugInConfig(); pluginConfig.setClassName(plugin.classname().getName()); for (Property property : plugin.properties()) { pluginConfig.addProperty(property.key(), property.value()); } config.addPlugInConfig(pluginConfig); } } for (Class actionClass : actions) { Mapping mapping = actionClass.getAnnotation(Mapping.class); if (!prefix.equals(mapping.module())) { continue; } final ActionMapping actionMapping = createCustomActionMapping(mapping); if (actionMapping == null) { continue; } actionMapping.setPath(mapping.path()); actionMapping.setType(actionClass.getName()); actionMapping.setScope(mapping.scope()); actionMapping.setParameter(mapping.parameter()); actionMapping.setAttribute(mapping.attribute()); actionMapping.setValidate(mapping.validate()); if (mapping.formBeanClass() != ActionForm.class) { final String formName = mapping.formBeanClass().getName(); createFormBeanConfigIfNecessary(config, mapping, formName); actionMapping.setName(formName); } else if (!mapping.formBean().isEmpty()) { actionMapping.setName(mapping.formBean()); } if (mapping.input().isEmpty()) { actionMapping.setInput(findInputMethod(actionClass, mapping)); } else { registerInput(actionMapping, mapping.input()); } String defaultResourcesName = getDefaultResourcesName(config); Forwards forwards = actionClass.getAnnotation(Forwards.class); if (forwards != null) { for (final Forward forward : forwards.value()) { registerForward(actionMapping, forward, forwards, mapping, defaultResourcesName); } } registerSuperclassForwards(actionMapping, actionClass.getSuperclass(), mapping, defaultResourcesName); Exceptions exceptions = actionClass.getAnnotation(Exceptions.class); if (exceptions != null) { registerExceptionHandling(actionMapping, exceptions); } initializeActionMappingProperties(actionMapping, mapping.customMappingProperties()); config.addActionConfig(actionMapping); } return config; } private static void registerExceptionHandling(final ActionMapping actionMapping, Exceptions exceptions) { for (final ExceptionHandling exception : exceptions.value()) { final ExceptionConfig exceptionConfig = new ExceptionConfig(); Class exClass = exception.type(); Class handlerClass = exception.handler(); String exceptionHandler = (handlerClass == null ? null : handlerClass.getName()); if (exceptionHandler == null) { exceptionHandler = ExceptionHandler.class.getName(); } String key = (exception.key() == null ? EXCEPTION_KEY_DEFAULT_PREFIX + exClass.getSimpleName() : exception.key()); exceptionConfig.setKey(key); exceptionConfig.setHandler(exceptionHandler); exceptionConfig.setType(exClass.getName()); if (!StringUtils.isEmpty(exception.path())) { exceptionConfig.setPath(exception.path()); } if (!StringUtils.isEmpty(exception.scope())) { exceptionConfig.setScope(exception.scope()); } actionMapping.addExceptionConfig(exceptionConfig); } } public static ActionMapping createCustomActionMapping(Mapping mapping) { try { return mapping.customMappingClass().newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return null; } private static void initializeActionMappingProperties(ActionMapping actionMapping, String[] properties) { Method[] mappingMethods = actionMapping.getClass().getMethods(); for (int i = 0; i < properties.length; i += 2) { String property = properties[i]; String value = properties[i + 1]; String setterName = "set" + property.substring(0, 1).toUpperCase() + property.substring(1); Method setterMethod = null; for (Method mappingMethod : mappingMethods) { if (mappingMethod.getName().equals(setterName) && mappingMethod.getParameterTypes().length == 1) { setterMethod = mappingMethod; break; } } if (setterMethod == null) { continue; } try { setterMethod.invoke(actionMapping, value); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } private static void registerSuperclassForwards(final ActionMapping actionMapping, Class superclass, Mapping mapping, String defaultResourcesName) { if (UPPER_BOUND_SUPERCLASSES.contains(superclass.getSimpleName())) { return; } Forwards forwards = superclass.getAnnotation(Forwards.class); if (forwards == null) { return; } for (final Forward forward : forwards.value()) { try { actionMapping.findForward(forward.name()); } catch (NullPointerException ex) { // Forward wasn't registered in any subclass, so register it. registerForward(actionMapping, forward, forwards, mapping, defaultResourcesName); } } registerSuperclassForwards(actionMapping, superclass.getSuperclass(), mapping, defaultResourcesName); } private static void registerInput(final ActionMapping actionMapping, String input) { if (isSimplePageFile(input)) { PartialTileDefinition tileDefinition = new PartialTileDefinition(input); FenixDefinitionsFactory.registerDefinition(tileDefinition); actionMapping.setInput(tileDefinition.getName()); } else { // The input is using an existing tile definition actionMapping.setInput(input); } } private static void registerForward(final ActionMapping actionMapping, final Forward forward, Forwards forwards, Mapping mapping, String defaultResourcesName) { if (forward.useTile() && isSimplePageFile(forward.path())) { PartialTileDefinition tileDefinition = new PartialTileDefinition(forward, forwards, mapping, defaultResourcesName); FenixDefinitionsFactory.registerDefinition(tileDefinition); actionMapping.addForwardConfig(new ActionForward(forward.name(), tileDefinition.getName(), forward.redirect(), mapping.module())); } else { // The forward is using an existing tile definition actionMapping .addForwardConfig(new ActionForward(forward.name(), forward.path(), forward.redirect(), mapping.module())); } } private static boolean isSimplePageFile(String str) { return (str.endsWith(PAGE_FILE_EXTENSION)) && (StringUtils.countMatches(str, PAGE_FILE_EXTENSION) == 1); } private void createFormBeanConfigIfNecessary(ModuleConfig config, Mapping mapping, final String formName) { FormBeanConfig formBeanConfig = config.findFormBeanConfig(formName); if (formBeanConfig == null) { formBeanConfig = new FormBeanConfig(); formBeanConfig.setType(mapping.formBeanClass().getName()); formBeanConfig.setName(formName); config.addFormBeanConfig(formBeanConfig); } } private static String getDefaultResourcesName(ModuleConfig config) { MessageResourcesConfig resourcesConfig = config.findMessageResourcesConfig(Globals.MESSAGES_KEY); return (resourcesConfig == null) ? null : resourcesConfig.getParameter(); } private String findInputMethod(Class actionClass, Mapping mapping) { for (Method method : actionClass.getMethods()) { final Input input = method.getAnnotation(Input.class); if (input != null) { return mapping.path() + INPUT_PAGE_AND_METHOD + method.getName(); } } return mapping.path() + INPUT_DEFAULT_PAGE_AND_METHOD; } public static Map getModuleConfigMap() { Map configMap = new HashMap(); for (String prefix : runtimeConfig.keySet()) { String path = null; for (StrutsConfiguration strutsConfig : runtimeConfig.get(prefix)) { if (StringUtils.isNotEmpty(strutsConfig.path())) { if (path != null) { throw new Error("duplicate struts static configuration file for module: " + prefix); } path = strutsConfig.path(); } } if (path == null) { path = STRUTS_EMPTY; } configMap.put(StringUtils.isEmpty(prefix) ? "config" : ("config/" + prefix), path); } return configMap; } }