// Copyright 2012 Georg-August-Universität Göttingen, Germany // // 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. package de.ugoe.cs.autoquest.plugin.uml; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; import java.util.Set; import java.util.TreeSet; import org.eclipse.emf.common.util.EList; import org.eclipse.uml2.uml.Activity; import org.eclipse.uml2.uml.ActivityEdge; import org.eclipse.uml2.uml.ActivityNode; import org.eclipse.uml2.uml.CallConcurrencyKind; import org.eclipse.uml2.uml.CallEvent; import org.eclipse.uml2.uml.CallOperationAction; import org.eclipse.uml2.uml.Comment; import org.eclipse.uml2.uml.Component; import org.eclipse.uml2.uml.Connector; import org.eclipse.uml2.uml.ConnectorEnd; import org.eclipse.uml2.uml.DataType; import org.eclipse.uml2.uml.Element; import org.eclipse.uml2.uml.Expression; import org.eclipse.uml2.uml.InstanceSpecification; import org.eclipse.uml2.uml.InstanceValue; import org.eclipse.uml2.uml.Interaction; import org.eclipse.uml2.uml.InteractionFragment; import org.eclipse.uml2.uml.Interface; import org.eclipse.uml2.uml.Lifeline; import org.eclipse.uml2.uml.LiteralBoolean; import org.eclipse.uml2.uml.LiteralInteger; import org.eclipse.uml2.uml.LiteralReal; import org.eclipse.uml2.uml.LiteralString; import org.eclipse.uml2.uml.Message; import org.eclipse.uml2.uml.MessageOccurrenceSpecification; import org.eclipse.uml2.uml.MessageSort; import org.eclipse.uml2.uml.Model; import org.eclipse.uml2.uml.Operation; import org.eclipse.uml2.uml.Package; import org.eclipse.uml2.uml.Parameter; import org.eclipse.uml2.uml.ParameterDirectionKind; import org.eclipse.uml2.uml.Port; import org.eclipse.uml2.uml.PrimitiveType; import org.eclipse.uml2.uml.Property; import org.eclipse.uml2.uml.Region; import org.eclipse.uml2.uml.Slot; import org.eclipse.uml2.uml.StateMachine; import org.eclipse.uml2.uml.Stereotype; import org.eclipse.uml2.uml.Transition; import org.eclipse.uml2.uml.Trigger; import org.eclipse.uml2.uml.UMLPackage; import org.eclipse.uml2.uml.ValueSpecification; import org.eclipse.uml2.uml.Vertex; import de.ugoe.cs.autoquest.eventcore.Event; import de.ugoe.cs.autoquest.plugin.http.SOAPUtils; import de.ugoe.cs.autoquest.plugin.http.eventcore.SOAPEventType; import de.ugoe.cs.autoquest.plugin.http.eventcore.SimpleSOAPEventType; import de.ugoe.cs.autoquest.plugin.uml.eventcore.UMLTransitionType; import de.ugoe.cs.autoquest.usageprofiles.IStochasticProcess; import de.ugoe.cs.util.StringTools; import de.ugoe.cs.util.console.Console; /** *

* Utilities for working with UML. *

* * @author Steffen Herbold */ public class UMLUtils { /** * In case a multiplicity is defined as *, this value defines the highest one that can be picked */ final static int MAX_MULTIPLICITY = 10; /** *

* Method for checking if the information in a usage journal can be mapped to the SUT model. In * case this is not possible, the violations are reported. *

* * @param sequences * sequences of the usage journal * @param model * SUT model that is validated * @param testContextName * name of the test context to be used; if null, the first test context found is used * @return number of violations */ public static int validateModelWithLog(Collection> sequences, Model model, String testContextName) { int violationCount = 0; Component testContext = fetchTestContext(model, testContextName); if (testContext == null) { violationCount++; if (testContextName == null) { Console.traceln(Level.SEVERE, "Could not find any TestContext in the model."); } else { Console.traceln(Level.SEVERE, "Could not find TestContext in the model: " + testContextName); } Console .traceln(Level.SEVERE, "Hint: Check if you have applied the TestContext stereotype correctly in the model."); Console.traceln(Level.SEVERE, "Aborting"); return violationCount; } // Create list of unique methods calls HashMap> calledMethods = new HashMap<>(); for (List sequence : sequences) { for (Event event : sequence) { String serviceName = SOAPUtils.getServiceNameFromEvent(event); String calledMethod = SOAPUtils.getCalledMethodFromEvent(event); if (serviceName != null) { Set curCalledMethods = calledMethods.get(serviceName); if (curCalledMethods == null) { curCalledMethods = new TreeSet<>(); calledMethods.put(serviceName, curCalledMethods); } curCalledMethods.add(calledMethod); } } } Console.traceln(Level.INFO, "Found the following services and operations in the usage data: "); for (Entry> entry : calledMethods.entrySet()) { Console.traceln(Level.INFO, "\tService \"" + entry.getKey() + "\": "); for (String method : entry.getValue()) { Console.traceln(Level.INFO, "\t\t" + method); } } // fetch all SUTs and TestComponents HashMap properties = new HashMap<>(); for (Property property : fetchAllSUTProperties(testContext)) { properties.put(property.getName(), property); } for (Property property : fetchAllTestComponentProperties(testContext)) { properties.put(property.getName(), property); } Console.traceln(Level.INFO, "Found the following services in the TestConfiguration:"); for (Entry entry : properties.entrySet()) { Console.traceln(Level.INFO, "\t" + entry.getKey()); } for (Entry> entry : calledMethods.entrySet()) { String serviceName = entry.getKey(); Console.traceln(Level.INFO, "Checking service: " + serviceName); Set methodNames = entry.getValue(); Property property = properties.get(serviceName); if (property == null) { violationCount++; Console.traceln(Level.SEVERE, "\tCould not find property for service: " + serviceName); Console .traceln(Level.SEVERE, "\tHint: Check service name map and/or model if the service is present and spelled correctly."); Console .traceln(Level.SEVERE, "\tHint: Check if the SUT/TestComponent stereotype has been applied correctly in this TestContext."); } else { Set interfaces = getRealizedInterfacesFromProperty(property); if (interfaces.isEmpty()) { violationCount++; Console .traceln(Level.SEVERE, "\tCould not find any interfaces implementing the property for service: " + serviceName); Console .traceln(Level.SEVERE, "\tHint: Check if the property correctly realizes the interfaces in the model."); } else { Console.traceln(Level.INFO, "\tFound the following realized interfaces for the service \"" + serviceName + "\": "); for (Interface intface : interfaces) { Console.traceln(Level.INFO, "\t" + intface.getName()); for (Operation operation : intface.getAllOperations()) { Console.traceln(Level.INFO, "\t\t" + operation.getName()); } } for (String methodName : methodNames) { boolean methodFound = false; for (Interface intface : interfaces) { if (getOperationFromName(intface.getOperations(), methodName) != null) { // interface found Console.traceln(Level.INFO, "\tMethod " + methodName + " found in interface " + intface.getName()); methodFound = true; } } if (!methodFound) { violationCount++; Console.traceln(Level.SEVERE, "\tCould not find operation: " + methodName); } } } } } return violationCount; } /** *

* Creates a sequence of events with {@link UMLTransitionType} as event type from a given * sequence of events with the {@link SOAPEventType} or {@link SimpleSOAPEventType}, by matching * the sequences to a state machine. *

* * @param sequence * SOAP sequences * @param stateMachine * the state machine * @return create UML sequences */ public static List createUMLTransitionSequence(List sequence, StateMachine stateMachine) { List> matchingSequences = determineMatchingTransitionSequences(sequence, stateMachine); if (matchingSequences.size() != 1) { throw new RuntimeException("no unique match found; " + matchingSequences.size() + " matches"); } List umlEventSequence = new LinkedList<>(); for (Transition transition : matchingSequences.get(0)) { umlEventSequence.add(new Event(new UMLTransitionType(transition))); } return umlEventSequence; } /** *

* Uses a sequences of events with the {@link UMLTransitionType} to determine the transition * probabilities for the state machine. *

* * @param sequences * UML sequences * @param stateMachine * state machine to be converted to a usage profile */ public static void convertStateMachineToUsageProfile(Collection> sequences, StateMachine stateMachine) { // create state->outgoings hashmap Map> stateMap = new HashMap<>(); for (Region region : stateMachine.getRegions()) { for (Vertex state : region.getSubvertices()) { stateMap.put(state, new HashMap()); } } // create counters for each transition for (List sequence : sequences) { for (Event event : sequence) { if (event.getType() instanceof UMLTransitionType) { Transition transition = ((UMLTransitionType) event.getType()).getTransition(); Map transitionMap = stateMap.get(transition.getSource()); Integer value = transitionMap.get(transition); if (value == null) { value = 0; } transitionMap.put(transition, value + 1); } else { throw new RuntimeException( "Wrong event type. Only UMLTransitionType supported but was: " + event.getType().getClass().getName()); } } } // calculate probabilities for (Region region : stateMachine.getRegions()) { for (Vertex state : region.getSubvertices()) { Map transitionMap = stateMap.get(state); int totalCount = 0; for (Entry entry : transitionMap.entrySet()) { totalCount += entry.getValue(); } if (totalCount != 0) { for (Transition transition : state.getOutgoings()) { double prob = 0.0d; if (transitionMap.containsKey(transition)) { prob = ((double) transitionMap.get(transition)) / totalCount; } Comment comment = transition.createOwnedComment(); comment.setBody("" + prob); } } else { // system has never been in this state, all transitions equally likely int numOutgoings = state.getOutgoings().size(); for (Transition transition : state.getOutgoings()) { Comment comment = transition.createOwnedComment(); comment.setBody("" + (1.0d / numOutgoings)); } } } } } /** *

* Determines all matching {@link Transition} sequences in a state machine for a given sequence * of SOAP events. *

* * @param sequence * SOAP sequence * @param stateMachine * the state machine * @return all matching {@link Transition} sequences */ public static List> determineMatchingTransitionSequences(List sequence, StateMachine stateMachine) { EList regions = stateMachine.getRegions(); EList states = null; for (Region region : regions) { if (states == null) { states = region.getSubvertices(); } else { states.addAll(region.getSubvertices()); } } List allTransitions = new LinkedList<>(); for (Vertex state : states) { allTransitions.addAll(state.getOutgoings()); } List> matchingSequences = null; List currentTransitions = null; // first, we try to find a single unique transition that we can match using the method name for (Iterator eventIterator = sequence.iterator(); eventIterator.hasNext();) { Event event = eventIterator.next(); if (matchingSequences == null) { matchingSequences = new LinkedList<>(); List initialMatches = matchTransitions(allTransitions, event); for (Transition transition : initialMatches) { List candidate = new LinkedList<>(); candidate.add(transition); matchingSequences.add(candidate); } currentTransitions = initialMatches; } else { List> nextMatchingSequences = new LinkedList<>(); List nextCurrentTransitions = new LinkedList<>(); Iterator currentTransitionIterator = currentTransitions.iterator(); Iterator> currentMatchingSequencesIterator = matchingSequences.iterator(); while (currentTransitionIterator.hasNext()) { Transition currentTransition = currentTransitionIterator.next(); List currentMatch = currentMatchingSequencesIterator.next(); List matches = matchTransitions(currentTransition.getTarget().getOutgoings(), event); if (matches.isEmpty()) { throw new RuntimeException("no matches found"); } for (Transition matchingTransition : matches) { List candidate = new LinkedList<>(currentMatch); candidate.add(matchingTransition); nextMatchingSequences.add(candidate); nextCurrentTransitions.add(matchingTransition); } } matchingSequences = nextMatchingSequences; currentTransitions = nextCurrentTransitions; } } return matchingSequences; } /** *

* Extends a given model with an interaction that represents an observed sequence. *

* * @param sequence * sequence that is added as sequence diagram * @param model * UML model to which the interaction is added * @param interactionName * name of the interaction * @param testContextName * Name of the test context that should be used. If this value is null, the first * test context found is used. */ public static Interaction createInteractionFromEventSequence(List sequence, Model model, String interactionName, String testContextName) { final Component testContext = fetchTestContext(model, testContextName); if (testContext == null) { throw new RuntimeException("Could not find any test context in the model"); } final Operation operation = testContext.createOwnedOperation(interactionName, null, null); operation.applyStereotype(UTPUtils.getTestCaseStereotype(model)); final Interaction interaction = (Interaction) testContext.createPackagedElement(interactionName + "_Impl", UMLPackage.Literals.INTERACTION); operation.getMethods().add(interaction); // create lifelines Lifeline userLifeline = null; for (Property property : fetchAllSUTProperties(testContext)) { String serviceName = property.getName(); Lifeline targetLifeline = interaction.createLifeline(serviceName); targetLifeline.setRepresents(property); } for (Property property : fetchAllTestComponentProperties(testContext)) { // TODO check if this is still required if (userLifeline != null) { throw new RuntimeException( "TestContext must only have one TestComponent for the application of usage-based testing."); } userLifeline = interaction.createLifeline(property.getName()); userLifeline.setRepresents(property); } if (userLifeline == null) { throw new RuntimeException("No TestComponent found, could not create user lifeline."); } if (interaction.getLifelines().size() < 2) { throw new RuntimeException("Fewer than two lifelines created. No SUT found."); } int i = 0; for (Event event : sequence) { if (!(event.equals(Event.STARTEVENT) || event.equals(Event.ENDEVENT))) { String serviceName = SOAPUtils.getServiceNameFromEvent(event); String methodName = SOAPUtils.getCalledMethodFromEvent(event); String clientName = SOAPUtils.getClientNameFromEvent(event); String prefix = interactionName + ":" + i + ":" + methodName + "_"; // determine lifelines Lifeline msgTargetLifeline; Lifeline msgSourceLifeline; msgSourceLifeline = interaction.getLifeline(clientName); msgTargetLifeline = interaction.getLifeline(serviceName); if (msgSourceLifeline == null) { throw new RuntimeException( "Error creating message: could not determine source lifeline for component: " + clientName); } if (msgTargetLifeline == null) { throw new RuntimeException( "Error creating message: could not determine target lifeline for component: " + serviceName); } // determine correct target interface Set targetInterfaces = getRealizedInterfacesFromProperty((Property) msgTargetLifeline.getRepresents()); if (targetInterfaces.isEmpty()) { throw new RuntimeException("no interface associated with the property " + msgTargetLifeline.getRepresents().getName()); } Interface targetInterface = null; for (Interface intface : targetInterfaces) { if (getOperationFromName(intface.getOperations(), methodName) != null) { // interface found targetInterface = intface; break; } } if (targetInterface == null) { StringBuilder errStrBuilder = new StringBuilder(); errStrBuilder .append("Error creating message: operation not found in the implementing interfaces ("); Iterator iter = targetInterfaces.iterator(); while (iter.hasNext()) { String interfaceName = iter.next().getName(); errStrBuilder.append(interfaceName); if (iter.hasNext()) { errStrBuilder.append(","); } else { errStrBuilder.append("): " + methodName); } } throw new RuntimeException(errStrBuilder.toString()); } Operation calledOperation = getOperationFromName(targetInterface.getOperations(), methodName); // setup for both SYNCH and ASYNCH calls MessageOccurrenceSpecification callSendFragment = (MessageOccurrenceSpecification) interaction.createFragment(prefix + "callSendFragment", UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION); MessageOccurrenceSpecification callRecvFragment = (MessageOccurrenceSpecification) interaction.createFragment(prefix + "callRecvFragment", UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION); callSendFragment.setCovered(msgSourceLifeline); callRecvFragment.setCovered(msgTargetLifeline); // get connector Connector connector = inferConnector(msgSourceLifeline, msgTargetLifeline); // create call Message callMessage = interaction.createMessage(prefix + "call"); callMessage.setSignature(calledOperation); setCallMessageParameters(callMessage, calledOperation, event, prefix); callMessage.setConnector(connector); callMessage.setSendEvent(callSendFragment); callMessage.setReceiveEvent(callRecvFragment); callSendFragment.setMessage(callMessage); callRecvFragment.setMessage(callMessage); boolean asynch = false; if (calledOperation.getConcurrency() == CallConcurrencyKind.CONCURRENT_LITERAL) { asynch = true; } if (asynch) { // Create ASYNCH call callMessage.setMessageSort(MessageSort.ASYNCH_CALL_LITERAL); } else { // SYNCH call callMessage.setMessageSort(MessageSort.SYNCH_CALL_LITERAL); // setup reply and behavior execution specifications MessageOccurrenceSpecification replySendFragment = (MessageOccurrenceSpecification) interaction .createFragment(prefix + "replySendFragment", UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION); MessageOccurrenceSpecification replyRecvFragment = (MessageOccurrenceSpecification) interaction .createFragment(prefix + "replyRecvFragment", UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION); replySendFragment.setCovered(msgTargetLifeline); replyRecvFragment.setCovered(msgSourceLifeline); /* * BehaviorExecutionSpecification sourceBehaviorExecutionSpecification = * (BehaviorExecutionSpecification) interaction .createFragment(":" + methodName * + "_sourceBhvExecSpec", * UMLPackage.Literals.BEHAVIOR_EXECUTION_SPECIFICATION); * BehaviorExecutionSpecification targetBehaviorExecutionSpecification = * (BehaviorExecutionSpecification) interaction .createFragment(":" + methodName * + "_targetBhvExecSpec", * UMLPackage.Literals.BEHAVIOR_EXECUTION_SPECIFICATION); * * sourceBehaviorExecutionSpecification.setStart(callSendFragment); * sourceBehaviorExecutionSpecification.setFinish(replyRecvFragment); * targetBehaviorExecutionSpecification.setStart(callRecvFragment); * targetBehaviorExecutionSpecification.setFinish(replySendFragment); */ // create reply Message replyMessage = interaction.createMessage(prefix + "_reply"); replyMessage.setMessageSort(MessageSort.REPLY_LITERAL); replyMessage.setSignature(calledOperation); setReplyMessageParameters(replyMessage, calledOperation); replyMessage.setConnector(connector); replyMessage.setSendEvent(replySendFragment); replyMessage.setReceiveEvent(replyRecvFragment); replySendFragment.setMessage(replyMessage); replyRecvFragment.setMessage(replyMessage); } i++; } } return interaction; } /** *

* Calculates the usage score of an interaction as the logsum of the event probabilities * multiplied with the length of the interaction. *

* * @param interaction * interaction for which the score is calculated * @param usageProfile * usage profile used for the calculation * @return calculated usage score */ public static double calculateUsageScore(Interaction interaction, IStochasticProcess usageProfile) { double usageScore = 0.0d; EList interactionFragments = interaction.getFragments(); List eventSequence = new LinkedList<>(); eventSequence.add(Event.STARTEVENT); for (InteractionFragment interactionFragment : interactionFragments) { if (interactionFragment instanceof MessageOccurrenceSpecification) { Message message = ((MessageOccurrenceSpecification) interactionFragment).getMessage(); if (message.getReceiveEvent().equals(interactionFragment) && isCallMessage(message)) { String clientName = ((MessageOccurrenceSpecification) message.getSendEvent()).getCovereds() .get(0).getName(); String serviceName = ((MessageOccurrenceSpecification) message.getReceiveEvent()).getCovereds() .get(0).getName(); String methodName = message.getSignature().getName(); eventSequence.add(new Event(new SimpleSOAPEventType(methodName, serviceName, clientName, null))); } } } eventSequence.add(Event.ENDEVENT); double prob = usageProfile.getLogSum(eventSequence); usageScore = eventSequence.size() * prob; return usageScore; } /** *

* Extends the given model with an activity for usage-based scheduling of the test cases. *

* * @param model * model to be extended * @param usageProfile * usage profile used as foundation */ public static void createScheduling(Model model, IStochasticProcess usageProfile, String testContextName) { final Component testContext = fetchTestContext(model, testContextName); if (testContext == null) { throw new RuntimeException("Could not find any test context in the model"); } Map usageScoreMapUnsorted = new HashMap<>(); // first, we determine all test cases and calculate their usage scores final Stereotype utpTestCase = UTPUtils.getTestCaseStereotype(model); for (Operation operation : testContext.getAllOperations()) { if (operation.getAppliedStereotypes().contains(utpTestCase) && !operation.getMethods().isEmpty()) { Interaction interaction = (Interaction) operation.getMethods().get(0); usageScoreMapUnsorted .put(operation, calculateUsageScore(interaction, usageProfile)); } } Map usageScoreMapSorted = sortByValue(usageScoreMapUnsorted); // now we create the scheduling Activity schedulingActivity = (Activity) testContext.createOwnedBehavior("UsageBasedScheduling", UMLPackage.Literals.ACTIVITY); testContext.setClassifierBehavior(schedulingActivity); ActivityNode startNode = schedulingActivity.createOwnedNode("final", UMLPackage.Literals.INITIAL_NODE); ActivityNode finalNode = schedulingActivity.createOwnedNode("final", UMLPackage.Literals.ACTIVITY_FINAL_NODE); ActivityNode currentOperationNode = startNode; for (Entry entry : usageScoreMapSorted.entrySet()) { Operation operation = entry.getKey(); CallOperationAction nextOperationNode = (CallOperationAction) schedulingActivity .createOwnedNode(operation.getName(), UMLPackage.Literals.CALL_OPERATION_ACTION); nextOperationNode.setOperation(operation); ActivityEdge edge = schedulingActivity.createEdge(currentOperationNode.getName() + "_to_" + nextOperationNode.getName(), UMLPackage.Literals.CONTROL_FLOW); edge.setSource(currentOperationNode); edge.setTarget(nextOperationNode); currentOperationNode = nextOperationNode; } ActivityEdge edge = schedulingActivity .createEdge(currentOperationNode.getName() + "_to_" + finalNode.getName(), UMLPackage.Literals.CONTROL_FLOW); edge.setSource(currentOperationNode); edge.setTarget(finalNode); } /** *

* Fetches an operation using only its name from a list of operations. Returns the first match * found or null if no match is found. *

* * @param operations * list of operations * @param name * name of the operation * @return first matching operation; null if no match is found */ private static Operation getOperationFromName(EList operations, String name) { if (name == null) { throw new IllegalArgumentException("name of the operation must not be null"); } if (operations != null) { for (Operation operation : operations) { if (operation.getName() != null && operation.getName().equals(name)) { return operation; } } } return null; } /** *

* Determines which transitions match a given {@link SOAPEventType}. *

* * @param transitions * the transitions * @param eventType * the SOAP event * @return matching transitions */ private static List matchTransitions(List transitions, Event event) { String eventService = SOAPUtils.getServiceNameFromEvent(event); String eventMethod = SOAPUtils.getCalledMethodFromEvent(event); Map interfaceServiceMap = createInterfaceServiceMap(transitions.get(0).getModel()); List matching = new LinkedList<>(); for (Transition transition : transitions) { EList triggers = transition.getTriggers(); if (triggers.size() == 1) { if (triggers.get(0).getEvent() instanceof CallEvent) { CallEvent callEvent = (CallEvent) triggers.get(0).getEvent(); String transitionMethod = callEvent.getOperation().getName(); String transitionService = interfaceServiceMap.get(callEvent.getOperation().getInterface()); if (eventMethod.equals(transitionMethod) && eventService.equals(transitionService)) { matching.add(transition); } } } else { throw new RuntimeException("only one trigger of type CallEvent per transition allowed: " + transition.getName()); } } return matching; } /** *

* Fetches all realized interfaces from the type of a UML {@link Property} (i.e., * property.getType()). If no interfaces are realized, an empty set is returned. *

* * @param property * property, of the whose realized interfaces of the type are determined * @return realized interfaces */ private static Set getRealizedInterfacesFromProperty(Property property) { return getRealizedInterfaceFromComponent((Component) property.getType()); } /** *

* Fetches all realized interfaces from a UML {@link Component}. If no interfaces are realized, * an empty set is returned. *

* * @param comp * component whose realized interfaces are determined * @return realized interfaces */ private static Set getRealizedInterfaceFromComponent(Component component) { Set interfaces = new HashSet<>(); // Interface myInterface = null; for (Property property : component.getAttributes()) { if (property instanceof Port) { Port port = (Port) property; if (!port.isConjugated()) { interfaces.addAll(port.getProvideds()); } } } return interfaces; } /** *

* Determines searches within a {@link Package} for a component to which the UTP TestContext * stereotype is applied. *

    *
  • If no testContextName is provided, the first test context found is returned.
  • *
  • In case no test context is found, null is returned.
  • *

    * * @param pkg * package where the test context is being locked for * @param testContextName * name of the test context; in case no test name is specified, use null and not the * empty String. * @return {@link Component} to which the TestContext stereotype is applied */ private static Component fetchTestContext(final Package pkg, final String testContextName) { List testContexts = fetchAllTestContexts(pkg); if (testContexts.isEmpty()) { return null; } if (testContextName != null) { for (Component testContext : testContexts) { if (testContextName.equals(testContext.getName())) { return testContext; } } return null; } else { return testContexts.get(0); } } /** *

    * Retrieves all UML {@link Component}s to which the UTP TestContext stereotype is applied from * a package. This method calls itself recursively to include all components contained in * sub-packages. *

    *

    * In case no test context is found, an empty list is returned. *

    * * @param pkg * package from which the test contexts are retrieved * @return {@link List} of test contexts */ private static List fetchAllTestContexts(final Package pkg) { final Stereotype utpTestContext = UTPUtils.getTestContextStereotype(pkg.getModel()); final List testContexts = new LinkedList<>(); for (Element element : pkg.getOwnedElements()) { if (element instanceof Package) { testContexts.addAll(fetchAllTestContexts((Package) element)); } if (element instanceof Component && element.getAppliedStereotypes().contains(utpTestContext)) { testContexts.add((Component) element); } } return testContexts; } /** *

    * Retrieves all properties that represent a UTP TestComponent from a test context. *

    * * @param testContext * test context from which the properties are retrieved * @return properties that represent test components */ private static Set fetchAllTestComponentProperties(final Component testContext) { // fetch all SUTs and TestComponents final Stereotype utpTestComponent = UTPUtils.getTestComponentStereotype(testContext.getModel()); final Set properties = new HashSet<>(); for (Property property : testContext.getAllAttributes()) { if (property.getType().getAppliedStereotypes().contains(utpTestComponent)) { properties.add(property); } } return properties; } /** *

    * Retrieves all properties that represent a UTP SUT from a test context. *

    * * @param testContext * test context from which the properties are retrieved * @return properties that represent the SUTs */ private static Set fetchAllSUTProperties(final Component testContext) { // fetch all SUTs and TestComponents final Stereotype utpSUT = UTPUtils.getSUTStereotype(testContext.getModel()); final Set properties = new HashSet<>(); for (Property property : testContext.getAllAttributes()) { if (property.getAppliedStereotypes().contains(utpSUT)) { properties.add(property); } } return properties; } /** *

    * Infers connector between two lifelines. *

    *

    * TODO: Currently assumes only one connector between two lifelines possible. This assumption is * invalid as soon as there are two ports that connect the same two properties. *

    * * @param msgSourceLifeline * source lifeline of the message * @param targetAttributes * target lifeline of the message */ private static Connector inferConnector(Lifeline msgSourceLifeline, Lifeline msgTargetLifeline) { EList userAttributes = ((Component) msgSourceLifeline.getRepresents().getType()).getAttributes(); EList targetAttributes = ((Component) msgTargetLifeline.getRepresents().getType()).getAttributes(); for (Property userAttribute : userAttributes) { if (userAttribute instanceof Port) { EList userEnds = ((Port) userAttribute).getEnds(); for (ConnectorEnd userEnd : userEnds) { Connector userConnector = (Connector) userEnd.eContainer(); for (Property targetAttribute : targetAttributes) { if (targetAttribute instanceof Port) { EList targetEnds = ((Port) targetAttribute).getEnds(); for (ConnectorEnd targetEnd : targetEnds) { Connector targetConnector = (Connector) targetEnd.eContainer(); if (targetConnector == userConnector) { return targetConnector; } } } } } } } return null; } /** *

    * Creates a map that maps the interfaces to the properties, i.e., services that they are * represented by. *

    *

    * TODO: currently assumes that each interfaces is only realized by one property *

    * * @param model * model for which the interface->service map is created * @return the map */ private static Map createInterfaceServiceMap(Model model) { Map interfaceServiceMap = new HashMap<>(); List testContexts = fetchAllTestContexts(model.getModel()); for (Component testContext : testContexts) { for (Property property : fetchAllSUTProperties(testContext)) { for (Interface intface : getRealizedInterfacesFromProperty(property)) { interfaceServiceMap.put(intface, property.getName()); } } for (Property property : fetchAllTestComponentProperties(testContext)) { for (Interface intface : getRealizedInterfacesFromProperty(property)) { interfaceServiceMap.put(intface, property.getName()); } } } return interfaceServiceMap; } /** *

    * Sets values for the parameters of a call message. The values are, if possible, inferred from * the event that is provided. *

    * * @param callMessage * call message for which the parameters are set * @param calledOperation * operation that is called by the message * @param event * event that provides the parameters; in case of null, default values are assumed * @param prefix * prefix of the call message; used to create good warnings and debugging information */ private static void setCallMessageParameters(Message callMessage, Operation calledOperation, Event event, String prefix) { org.w3c.dom.Element requestBody = SOAPUtils.getSoapRequestBodyFromEvent(event); Package instSpecPkg = null; // Set parameters of operation for (Parameter param : calledOperation.getOwnedParameters()) { if (instSpecPkg == null) { instSpecPkg = getOrCreateInstanceSpecificationPackage(param.getModel(), event); } String path = calledOperation.getName() + ":" + param.getName(); Expression argument = (Expression) callMessage.createArgument(param.getName(), param.getType(), UMLPackage.Literals.EXPRESSION); if (isInParameter(param)) { if (param.getType() instanceof DataType) { List paramNodes = SOAPUtils.getMatchingChildNode(param.getType().getName(), requestBody); int multiplicityChosen = paramNodes.size(); if (multiplicityChosen == 0 && param.getLower() > 0) { Console.traceln(Level.WARNING, "required attribute not found in SOAP message: " + path); Console .traceln(Level.WARNING, "setting default values for this attribute and all its children"); Console.traceln(Level.FINE, "XML structure of path:" + StringTools.ENDLINE + SOAPUtils.getSerialization(requestBody)); multiplicityChosen = param.getLower(); } for (int i = 0; i < multiplicityChosen; i++) { org.w3c.dom.Element paramNode = null; if (!paramNodes.isEmpty()) { paramNode = paramNodes.get(i); } InstanceSpecification instSpec = createInstanceSpecification((DataType) param.getType(), instSpecPkg, prefix, paramNode, path); InstanceValue value = (InstanceValue) argument .createOperand(null, param.getType(), UMLPackage.Literals.INSTANCE_VALUE); value.setInstance(instSpec); } } else if (param.getType() instanceof PrimitiveType) { createOperandPrimitiveType(param, argument, requestBody, path); } } else { // set literalNull for out and return parameters argument.createOperand(null, param.getType(), UMLPackage.Literals.LITERAL_NULL); } } } /** *

    * Creates an {@link InstanceSpecification} for a data type in the given package. The values are * inferred, if possible, from the DOM node. The prefix and the path are used for naming the * instance specification and to provide good warnings and debug information in case of * problems. *

    * * @param type * DataType for which the {@link InstanceSpecification} is created * @param pkg * package in which the {@link InstanceSpecification} is created * @param prefix * prefix used for naming the {@link InstanceSpecification} * @param currentNode * node of a DOM from which values are inferred * @param path * used for warnings and debug information * @return {@link InstanceSpecification} for the given type */ private static InstanceSpecification createInstanceSpecification(DataType type, Package pkg, String prefix, org.w3c.dom.Element currentNode, String path) { if ("".equals(path)) { path = type.getName(); } InstanceSpecification instSpec = (InstanceSpecification) pkg .createPackagedElement(prefix + "instspec_" + type.getName(), UMLPackage.Literals.INSTANCE_SPECIFICATION); instSpec.getClassifiers().add(type); for (Property prop : type.getAllAttributes()) { if (prop.getType() instanceof PrimitiveType) { createSlotPrimitiveType(instSpec, prop, currentNode, path); } else if (prop.getType() instanceof DataType) { List attributeNodes = null; int multiplicityChosen = 0; if (currentNode != null) { attributeNodes = SOAPUtils.getMatchingChildNode(prop.getName(), currentNode); multiplicityChosen = attributeNodes.size(); } if (multiplicityChosen == 0 && prop.getLower() > 0) { if (currentNode != null) { Console.traceln(Level.WARNING, "required attribute not found in SOAP message: " + path + "." + prop.getName()); Console .traceln(Level.WARNING, "setting default values for this attribute and all its children"); Console.traceln(Level.FINE, "XML structure of path:" + StringTools.ENDLINE + SOAPUtils.getSerialization(currentNode)); } multiplicityChosen = prop.getLower(); } for (int i = 0; i < multiplicityChosen; i++) { org.w3c.dom.Element attributeNode = null; if (attributeNodes != null && !attributeNodes.isEmpty()) { attributeNode = attributeNodes.get(i); } Slot slot = instSpec.createSlot(); slot.setDefiningFeature(prop); InstanceValue value = (InstanceValue) slot.createValue(prop.getName() + "_" + i, prop.getType(), UMLPackage.Literals.INSTANCE_VALUE); value.setInstance(createInstanceSpecification((DataType) prop.getType(), pkg, prefix, attributeNode, path + "." + prop.getName())); } } else { Console.traceln(Level.SEVERE, "property neither DataType nor PrimitiveType: " + prop.getType()); // TODO abort? } } return instSpec; } /** *

    * Gets or creates a {@link Package} for {@link InstanceSpecification} created by the * usage-based testing. Each service gets its own sub-package within a package called * UBT_InstanceSpecifications. " *

    * * @param model * model in which the package is generated * @param event * event from which the service name is inferred * @return package for the {@link InstanceSpecification}s */ private static Package getOrCreateInstanceSpecificationPackage(Model model, Event event) { String pkgUBTInstSpecs = "UBT_InstanceSpecifications"; Package ubtInstSpecPkg = (Package) model.getOwnedMember(pkgUBTInstSpecs); if (ubtInstSpecPkg == null) { ubtInstSpecPkg = (Package) model.createPackagedElement(pkgUBTInstSpecs, UMLPackage.Literals.PACKAGE); } String serviceName = SOAPUtils.getServiceNameFromEvent(event); Package serviceInstSpecPkg = (Package) ubtInstSpecPkg.getOwnedMember(serviceName); if (serviceInstSpecPkg == null) { serviceInstSpecPkg = (Package) ubtInstSpecPkg.createPackagedElement(serviceName, UMLPackage.Literals.PACKAGE); } return serviceInstSpecPkg; } /** *

    * Creates an operand that defines a {@link PrimitiveType}. *

    *

    * TODO: Currently does nothing in case of multiplicity 0. I am not sure if, in that case, one * has to define LiteralNull instead. *

    * * @param param * parameter for which the operand is created * @param argument * argument to which the operand is added * @param currentNode * DOM node from which is value for the operand is inferred * @param path * used for warnings and debug information */ private static void createOperandPrimitiveType(Parameter param, Expression argument, org.w3c.dom.Element currentNode, String path) { List attributeValues = SOAPUtils.getValuesFromElement(param.getName(), currentNode); if (attributeValues.isEmpty()) { if (param.getLower() == 0) { // ignoring optional attribute return; } else { if (currentNode != null) { Console.traceln(Level.WARNING, "required attribute not found in SOAP message: " + path + "." + param.getName()); Console.traceln(Level.WARNING, "setting default values for this attribute"); Console.traceln(Level.FINE, "XML structure of path:" + StringTools.ENDLINE + SOAPUtils.getSerialization(currentNode)); } attributeValues.add(null); } } for (String attributeValue : attributeValues) { if ("String".equals(param.getType().getName())) { LiteralString spec = (LiteralString) argument.createOperand(param.getName(), null, UMLPackage.Literals.LITERAL_STRING); if (attributeValue != null) { spec.setValue(attributeValue); } else { spec.setValue("foobar"); } } else if ("Integer".equals(param.getType().getName())) { LiteralInteger spec = (LiteralInteger) argument.createOperand(param.getName(), null, UMLPackage.Literals.LITERAL_INTEGER); if (attributeValue != null) { spec.setValue(Integer.parseInt(attributeValue)); } else { spec.setValue(42); } } else if ("Boolean".equals(param.getType().getName())) { LiteralBoolean spec = (LiteralBoolean) argument.createOperand(param.getName(), null, UMLPackage.Literals.LITERAL_BOOLEAN); if (attributeValue != null) { spec.setValue(Boolean.parseBoolean(attributeValue)); } else { spec.setValue(true); } } else if ("Real".equals(param.getType().getName())) { LiteralReal spec = (LiteralReal) argument.createOperand(param.getName(), null, UMLPackage.Literals.LITERAL_REAL); if (attributeValue != null) { spec.setValue(Double.parseDouble(attributeValue)); } else { spec.setValue(3.14); } } } } /** *

    * Creates a {@link Slot} in an {@link InstanceSpecification} for a primitive type. *

    * * @param instSpec * instance specification to which the slot is added * @param prop * property that describes the slot * @param currentNode * DOM node from which is value for the slot is inferred * @param path * used for warnings and debug information */ private static void createSlotPrimitiveType(InstanceSpecification instSpec, Property prop, org.w3c.dom.Element currentNode, String path) { List attributeValues = SOAPUtils.getValuesFromElement(prop.getName(), currentNode); if (attributeValues.isEmpty()) { if (prop.getLower() == 0) { // ignoring optional attribute return; } else { if (currentNode != null) { Console.traceln(Level.WARNING, "required attribute not found in SOAP message: " + path + "." + prop.getName()); Console.traceln(Level.WARNING, "setting default values for this attribute"); Console.traceln(Level.FINE, "XML structure of path:" + StringTools.ENDLINE + SOAPUtils.getSerialization(currentNode)); } attributeValues.add(null); } } for (String attributeValue : attributeValues) { Slot slot = instSpec.createSlot(); slot.setDefiningFeature(prop); if ("String".equals(prop.getType().getName())) { LiteralString value = (LiteralString) slot.createValue(prop.getName(), null, UMLPackage.Literals.LITERAL_STRING); if (attributeValue != null) { value.setValue(attributeValue); } else { value.setValue("foobar"); } } else if ("Integer".equals(prop.getType().getName())) { LiteralInteger value = (LiteralInteger) slot.createValue(prop.getName(), null, UMLPackage.Literals.LITERAL_INTEGER); if (attributeValue != null) { value.setValue(Integer.parseInt(attributeValue)); } else { value.setValue(42); } } else if ("Boolean".equals(prop.getType().getName())) { LiteralBoolean value = (LiteralBoolean) slot.createValue(prop.getName(), null, UMLPackage.Literals.LITERAL_BOOLEAN); if (attributeValue != null) { value.setValue(Boolean.parseBoolean(attributeValue)); } else { value.setValue(true); } } else if ("Real".equals(prop.getType().getName())) { LiteralReal value = (LiteralReal) slot.createValue(prop.getName(), null, UMLPackage.Literals.LITERAL_REAL); if (attributeValue != null) { value.setValue(Double.parseDouble(attributeValue)); } else { value.setValue(3.14); } } else { Console.traceln(Level.SEVERE, "could not create literal for primitive type: " + prop.getType().getName()); // TODO abort? } } } /** *

    * Sets values for the parameters of a reply message. The values are, all LiterealNull and to * the INOUT, OUT and REPLY parameters, the UTP stereotype LiteralAny is applied. *

    * * @param replyMessage * reply message for which the parameters are set * @param calledOperation * operation that is replied for by the message */ private static void setReplyMessageParameters(Message replyMessage, Operation calledOperation) { for (Parameter param : calledOperation.getOwnedParameters()) { Expression argument = (Expression) replyMessage.createArgument(param.getName(), param.getType(), UMLPackage.Literals.EXPRESSION); ValueSpecification operand = argument.createOperand(null, param.getType(), UMLPackage.Literals.LITERAL_NULL); if (isOutParameter(param)) { operand.applyStereotype(UTPUtils.getLiteralAnyStereotype(param.getModel())); } } } /** *

    * Checks if a parameter has the direction IN or INOUT *

    * * @param parameter * parameter that is checked * @return true if the direction is IN or INOUT; false otherwise */ private static boolean isInParameter(Parameter parameter) { return parameter.getDirection() == ParameterDirectionKind.IN_LITERAL || parameter.getDirection() == ParameterDirectionKind.INOUT_LITERAL; } /** *

    * Checks if a parameter has the direction RETURN, OUT or INOUT *

    * * @param parameter * parameter that is checked * @return true if the direction is RETURN, OUT, or INOUT; false otherwise */ private static boolean isOutParameter(Parameter parameter) { return parameter.getDirection() == ParameterDirectionKind.RETURN_LITERAL || parameter.getDirection() == ParameterDirectionKind.OUT_LITERAL || parameter.getDirection() == ParameterDirectionKind.INOUT_LITERAL; } /** *

    * Checks if the {@link MessageSort} of a message is a call message, i.e., ASYNCH_CALL or * SYNCH_CALL. *

    * * @param message * message that is checked * @return true if the message is a call message; false otherwise */ private static boolean isCallMessage(Message message) { if (message == null) { return false; } MessageSort msgSort = message.getMessageSort(); return msgSort == MessageSort.ASYNCH_CALL_LITERAL || msgSort == MessageSort.SYNCH_CALL_LITERAL; } /** *

    * inverse-sorts the values of a map. Has been adapted from this StackOverflow post. *

    * * @param map * map whose values are sorted * @return sorted version of the map */ private static > Map sortByValue(Map map) { // TODO possibly move to another class List> list = new LinkedList<>(map.entrySet()); Collections.sort(list, new Comparator>() { @Override public int compare(Map.Entry o1, Map.Entry o2) { return -1 * (o1.getValue()).compareTo(o2.getValue()); } }); Map result = new LinkedHashMap<>(); for (Map.Entry entry : list) { result.put(entry.getKey(), entry.getValue()); } return result; } }