/* * SoapUI, Copyright (C) 2004-2016 SmartBear Software * * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent * versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * * http://ec.europa.eu/idabc/eupl * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the Licence for the specific language governing permissions and limitations * under the Licence. */ package com.eviware.soapui.plugins; import com.eviware.soapui.SoapUI; import com.eviware.soapui.model.iface.SoapUIListener; import com.eviware.soapui.plugins.auto.AutoFactory; import com.eviware.soapui.support.StringUtils; import com.eviware.soapui.support.action.SoapUIAction; import com.eviware.soapui.support.action.SoapUIActionGroup; import com.eviware.soapui.support.action.SoapUIActionRegistry; import com.eviware.soapui.support.action.support.DefaultActionMapping; import com.eviware.soapui.support.action.support.DefaultSoapUIActionGroup; import com.eviware.soapui.support.action.support.StandaloneActionMapping; import com.eviware.soapui.support.action.support.WrapperSoapUIAction; import com.eviware.soapui.support.factory.SoapUIFactoryRegistry; import com.eviware.soapui.support.listener.ListenerRegistry; import org.apache.commons.lang.ObjectUtils; import org.reflections.Reflections; import org.reflections.adapters.JavaReflectionAdapter; import org.reflections.scanners.SubTypesScanner; import org.reflections.util.ClasspathHelper; import org.reflections.util.ConfigurationBuilder; import org.reflections.vfs.Vfs; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.Action; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; public class LoaderBase { private static Logger logger = LoggerFactory.getLogger(LoaderBase.class); protected SoapUIFactoryRegistry factoryRegistry; protected SoapUIActionRegistry actionRegistry; protected ListenerRegistry listenerRegistry; public LoaderBase(ListenerRegistry listenerRegistry, SoapUIActionRegistry actionRegistry, SoapUIFactoryRegistry factoryRegistry) { this.listenerRegistry = listenerRegistry; this.actionRegistry = actionRegistry; this.factoryRegistry = factoryRegistry; } protected Collection<? extends SoapUIFactory> loadFactories(Reflections jarFileScanner) throws IllegalAccessException, InstantiationException { Collection<SoapUIFactory> factories = new HashSet<SoapUIFactory>(); Set<Class<?>> factoryClasses = jarFileScanner.getTypesAnnotatedWith(FactoryConfiguration.class); for (Class<?> factoryClass : factoryClasses) { if (!SoapUIFactory.class.isAssignableFrom(factoryClass)) { logger.warn("Class " + factoryClass + " is annotated with @FactoryConfiguration " + "but does not implement SoapUIFactory"); } else factories.add(createFactory((Class<SoapUIFactory>) factoryClass)); } loadAutoFactories(jarFileScanner, factories); return registerFactories(factories); } protected SoapUIFactory createFactory(Class<SoapUIFactory> factoryClass) throws InstantiationException, IllegalAccessException { return createObject(factoryClass); } protected <T extends Object> T createObject(Class<T> objectClass) throws IllegalAccessException, InstantiationException { return PluginProxies.proxyIfApplicable(objectClass.newInstance()); } protected Collection<? extends SoapUIFactory> registerFactories(Collection<? extends SoapUIFactory> factories) { for (SoapUIFactory factory : factories) { factoryRegistry.addFactory(factory.getFactoryType(), factory); } return factories; } protected void loadAutoFactories(Reflections jarFileScanner, Collection<SoapUIFactory> factories) { ConfigurationBuilder builder = new ConfigurationBuilder(); builder.addUrls(ClasspathHelper.forClass(AutoFactory.class)); builder.setScanners(new SubTypesScanner(), new TypeAnnotationsScanner()); builder.addClassLoader(Thread.currentThread().getContextClassLoader()); Reflections autoAnnotationFinder = new Reflections(builder); for (Class clazz : autoAnnotationFinder.getTypesAnnotatedWith(AutoFactory.class)) { if (clazz.isAnnotation() && clazz.getSimpleName().startsWith("Plugin")) { try { String className = "Auto" + clazz.getSimpleName().substring(6) + "Factory"; Class<? extends SoapUIFactory> factoryClass = (Class<? extends SoapUIFactory>) Class.forName(clazz.getPackage().getName() + ".factories." + className); factories.addAll(findAutoFactoryObjects(jarFileScanner, clazz, factoryClass)); } catch (ClassNotFoundException e) { SoapUI.logError(e); } } } } protected Collection<SoapUIFactory> findAutoFactoryObjects(Reflections jarFileScanner, Class<? extends Annotation> annotationType, Class<? extends SoapUIFactory> factoryClass) { Collection<SoapUIFactory> factories = new HashSet<SoapUIFactory>(); Set<Class<?>> objectClasses = jarFileScanner.getTypesAnnotatedWith(annotationType); for (Class<?> clazz : objectClasses) { try { Annotation annotation = clazz.getAnnotation(annotationType); factories.add(createAutoFactory(annotationType, factoryClass, clazz, annotation)); SoapUI.log("Added AutoFactory for [" + annotationType.getSimpleName() + "]"); } catch (Exception e) { SoapUI.logError(e); } } return factories; } protected SoapUIFactory createAutoFactory(Class<? extends Annotation> annotationType, Class<? extends SoapUIFactory> factoryClass, Class<?> clazz, Annotation annotation) throws InstantiationException, IllegalAccessException, java.lang.reflect.InvocationTargetException, NoSuchMethodException { return factoryClass.getConstructor(annotationType, clazz.getClass()).newInstance(annotation, clazz); } protected List<Class<? extends SoapUIListener>> registerListeners(List<Class<? extends SoapUIListener>> listeners) { for (Class<?> listenerClass : listeners) { Class currentListenerClass = listenerClass; while (currentListenerClass != null) { for (Class<?> implementedInterface : currentListenerClass.getInterfaces()) { if (SoapUIListener.class.isAssignableFrom(implementedInterface)) { listenerRegistry.addListener(implementedInterface, listenerClass, null); } } currentListenerClass = currentListenerClass.getSuperclass(); } } return listeners; } protected List<Class<? extends SoapUIListener>> loadListeners(Reflections jarFileScanner) throws IllegalAccessException, InstantiationException { List<Class<? extends SoapUIListener>> listeners = new ArrayList<Class<? extends SoapUIListener>>(); Set<Class<?>> listenerClasses = jarFileScanner.getTypesAnnotatedWith(ListenerConfiguration.class); for (Class<?> listenerClass : listenerClasses) { if (!SoapUIListener.class.isAssignableFrom(listenerClass)) { logger.warn("Class " + listenerClass + " is annotated with @ListenerConfiguration " + "but does not implement SoapUIListener"); } else { listeners.add(((Class<SoapUIListener>) listenerClass)); } } return registerListeners(listeners); } protected List<? extends SoapUIAction> registerActions(List<? extends SoapUIAction> actions) { // sort actions so references work consistently Collections.sort(actions, new Comparator<SoapUIAction>() { @Override public int compare(SoapUIAction o1, SoapUIAction o2) { return o1.getId().compareTo(o2.getId()); } }); for (SoapUIAction action : actions) { actionRegistry.addAction(action.getId(), action); ActionConfiguration configuration = PluginProxies.getAnnotation(action, ActionConfiguration.class); if (configuration != null) { configureAction(action, configuration); } ActionConfigurations configurations = PluginProxies.getAnnotation(action, ActionConfigurations.class); if (configurations != null) { for (ActionConfiguration config : configurations.configurations()) { configureAction(action, config); } } } return actions; } protected List<? extends SoapUIActionGroup> registerActionGroups(List<SoapUIActionGroup> actionGroups) { for (SoapUIActionGroup actionGroup : actionGroups) actionRegistry.addActionGroup(actionGroup); return actionGroups; } protected List<? extends SoapUIActionGroup> loadActionGroups(Reflections jarFileScanner) throws InstantiationException, IllegalAccessException { List<SoapUIActionGroup> actionGroups = new ArrayList<SoapUIActionGroup>(); Set<Class<?>> actionGroupClasses = jarFileScanner.getTypesAnnotatedWith(ActionGroup.class); for (Class<?> actionGroupClass : actionGroupClasses) { if (!SoapUIActionGroup.class.isAssignableFrom(actionGroupClass)) { logger.warn("Class " + actionGroupClass + " is annotated with @ActionGroup " + "but does not implement SoapUIActionGroup"); } else { ActionGroup annotation = actionGroupClass.getAnnotation(ActionGroup.class); SoapUIActionGroup actionGroup = createActionGroup((Class<SoapUIActionGroup>) actionGroupClass); for (ActionMapping mapping : annotation.actions()) { try { if (mapping.type() == ActionMapping.Type.SEPARATOR) { actionGroup.addMapping( SoapUIActionRegistry.SeperatorAction.SOAPUI_ACTION_ID, SoapUIActionRegistry.SeperatorAction.getDefaultMapping()); } else if (mapping.type() == ActionMapping.Type.GROUP || mapping.type() == ActionMapping.Type.INSERT) { SoapUIActionRegistry.SoapUIActionGroupAction actionListAction = new SoapUIActionRegistry.SoapUIActionGroupAction(mapping.name(), mapping.description(), mapping.groupId()); actionListAction.setInsert(mapping.type() == ActionMapping.Type.INSERT); StandaloneActionMapping actionMapping = new StandaloneActionMapping(actionListAction); actionGroup.addMapping(mapping.groupId(), actionMapping); } else if (mapping.type() == ActionMapping.Type.ACTION) { Class<?> actionClass = mapping.actionClass(); String actionId = mapping.actionId(); if (actionClass != ObjectUtils.Null.class) { if (SoapUIAction.class.isAssignableFrom(actionClass)) { SoapUIAction action = createAction((Class<SoapUIAction>) actionClass); actionRegistry.addAction(action.getId(), action); actionId = action.getId(); } else if (Action.class.isAssignableFrom(actionClass)) { Action swingAction = createObject((Class<Action>) actionClass); SoapUIAction action = new WrapperSoapUIAction(swingAction); actionRegistry.addAction(action.getId(), action); actionId = action.getId(); } } DefaultActionMapping actionMapping = new DefaultActionMapping(actionId, mapping.keyStroke(), mapping.iconPath(), actionId.equals( annotation.defaultAction()), mapping.param()); actionMapping.setToolbarAction(mapping.isToolbarAction()); actionMapping.setToolbarIndex(mapping.toolbarIndex()); if (!mapping.name().equals("")) { actionMapping.setName(mapping.name()); } if (!mapping.description().equals("")) { actionMapping.setDescription(mapping.description()); } actionGroup.addMapping(actionId, actionMapping); } } catch (Throwable e) { logger.error("Error adding actionMapping", e); } } actionGroups.add(actionGroup); } } return registerActionGroups(actionGroups); } protected List<? extends SoapUIAction> loadActions(Reflections jarFileScanner) throws InstantiationException, IllegalAccessException { List<SoapUIAction> actions = new ArrayList<SoapUIAction>(); Set<Class<?>> actionClasses = jarFileScanner.getTypesAnnotatedWith(ActionConfiguration.class); for (Class<?> actionClass : actionClasses) { if (!SoapUIAction.class.isAssignableFrom(actionClass)) { logger.error("Class " + actionClass + " is annotated with @ActionConfiguration " + "but does not implement SoapUIAction"); } else { actions.add(createAction((Class<SoapUIAction>) actionClass)); } } actionClasses = jarFileScanner.getTypesAnnotatedWith(ActionConfigurations.class); for (Class<?> actionClass : actionClasses) { if (!SoapUIAction.class.isAssignableFrom(actionClass)) { logger.error("Class " + actionClass + " is annotated with @ActionConfigurations " + "but does not implement SoapUIAction"); } else { actions.add(createAction((Class<SoapUIAction>) actionClass)); } } return registerActions(actions); } protected SoapUIAction createAction(Class<SoapUIAction> actionClass) throws InstantiationException, IllegalAccessException { return createObject(actionClass); } protected SoapUIActionGroup createActionGroup(Class<SoapUIActionGroup> actionGroupClass) throws InstantiationException, IllegalAccessException { return createObject(actionGroupClass); } protected void configureAction(final SoapUIAction action, ActionConfiguration configuration) { String groupId = configuration.actionGroup(); SoapUIActionGroup targetGroup = actionRegistry.getActionGroup(groupId); if (targetGroup == null) { targetGroup = new DefaultSoapUIActionGroup(groupId, groupId); actionRegistry.addActionGroup(targetGroup); } DefaultActionMapping mapping = new DefaultActionMapping(action.getId(), configuration.keyStroke(), configuration.iconPath(), configuration.defaultAction(), null); mapping.setName(action.getName()); mapping.setDescription(configuration.description()); mapping.setToolbarAction(configuration.isToolbarAction()); int insertIndex = -1; if (StringUtils.hasContent(configuration.beforeAction())) { insertIndex = targetGroup.getMappingIndex(configuration.beforeAction()); } else if (StringUtils.hasContent(configuration.afterAction())) { insertIndex = targetGroup.getMappingIndex(configuration.afterAction()) + 1; } if (configuration.separatorBefore()) { targetGroup.addMapping(SoapUIActionRegistry.SeperatorAction.SOAPUI_ACTION_ID, insertIndex++, SoapUIActionRegistry.SeperatorAction.getDefaultMapping()); } targetGroup.addMapping(action.getId(), insertIndex, mapping); if (configuration.separatorAfter()) { targetGroup.addMapping(SoapUIActionRegistry.SeperatorAction.SOAPUI_ACTION_ID, ++insertIndex, SoapUIActionRegistry.SeperatorAction.getDefaultMapping()); } } protected void unregisterListeners(List<Class<? extends SoapUIListener>> listeners) { for (Class<? extends SoapUIListener> listenerClass : listeners) { for (Class<?> implementedInterface : listenerClass.getInterfaces()) { if (SoapUIListener.class.isAssignableFrom(implementedInterface)) { listenerRegistry.removeListener(implementedInterface, listenerClass); listenerRegistry.removeSingletonListener(implementedInterface, listenerClass); } } } } protected void unregisterFactories(Collection<? extends SoapUIFactory> factories) { for (SoapUIFactory soapUIFactory : factories) { factoryRegistry.removeFactory(soapUIFactory.getFactoryType(), soapUIFactory); } } protected void unregisterActions(List<? extends SoapUIAction> actions) { for (SoapUIAction soapUIAction : actions) { actionRegistry.removeAction(soapUIAction.getId()); } } private boolean isToolbarAction(SoapUIAction soapUIAction) { ActionConfiguration annotation = PluginProxies.getAnnotation(soapUIAction, ActionConfiguration.class); return annotation != null && StringUtils.hasContent(annotation.toolbarIcon()); } // due to Reflections internals (or my misunderstanding of them) this class has to be // named as its superclass protected static class TypeAnnotationsScanner extends org.reflections.scanners.TypeAnnotationsScanner { @Override public boolean acceptsInput(String file) { if (file.endsWith(".groovy")) { return true; } else { return super.acceptsInput(file); } } } // loads both groovy and java classes for Reflections package protected static class GroovyAndJavaReflectionAdapter extends JavaReflectionAdapter { private final JarClassLoader jarClassLoader; public GroovyAndJavaReflectionAdapter(JarClassLoader jarClassLoader) { this.jarClassLoader = jarClassLoader; } @Override public Class getOfCreateClassObject(Vfs.File file) throws Exception { if (file.getName().endsWith(".groovy")) { return jarClassLoader.loadScriptClass(file.getRelativePath()); } else { return super.getOfCreateClassObject(file, jarClassLoader); } } } }