// 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.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import org.apache.commons.lang.mutable.MutableInt;
import org.eclipse.uml2.uml.CallConcurrencyKind;
import org.eclipse.uml2.uml.Component;
import org.eclipse.uml2.uml.Connector;
import org.eclipse.uml2.uml.DataType;
import org.eclipse.uml2.uml.InstanceSpecification;
import org.eclipse.uml2.uml.InstanceValue;
import org.eclipse.uml2.uml.Interaction;
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.PrimitiveType;
import org.eclipse.uml2.uml.Property;
import org.eclipse.uml2.uml.Slot;
import org.eclipse.uml2.uml.UMLPackage;
import de.ugoe.cs.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.plugin.http.SOAPUtils;
import de.ugoe.cs.autoquest.plugin.http.eventcore.SimpleSOAPEventType.CallType;
import de.ugoe.cs.util.StringTools;
import de.ugoe.cs.util.console.Console;
/**
*
* Utility class to create UML Interactions from event sequences
*
*
* @author Steffen Herbold
*/
class UMLInteractionCreator {
/**
* model for which the interactions are created
*/
private final Model model;
/**
* name of the test context in which the interactions are created
*/
private final String testContextName;
/**
* defines whether random message bodies are used or the ones associated with the SOAP events
*/
private final boolean useRandomMsgBodies;
/**
*
* Creates a new UMLInteractionCreator
*
*
* @param model
* UML model to which the interaction is added
* @param testContextName
* Name of the test context that should be used. If this value is null, the first
* test context found is used.
* @param useRandomRequestBodies
* defines is random request bodies are used or the body of the associated event
*/
public UMLInteractionCreator(Model model, String testContextName, boolean useRandomMsgBodies) {
this.model = model;
this.testContextName = testContextName;
this.useRandomMsgBodies = useRandomMsgBodies;
}
/**
*
* Creates the UML Interaction for the given sequence
*
*
* @param sequence
* for which the interaction is created
* @param interactionName
* name of the interaction
* @return created interaction
*/
public Interaction createInteraction(List sequence, String interactionName) {
final Component testContext = UMLUtils.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 : UMLUtils.fetchAllSUTProperties(testContext)) {
String serviceName = property.getName();
Lifeline targetLifeline = interaction.createLifeline(serviceName);
targetLifeline.setRepresents(property);
}
for (Property property : UMLUtils.fetchAllTestComponentProperties(testContext)) {
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 =
UMLUtils.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 (UMLUtils.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 =
UMLUtils.getOperationFromName(targetInterface.getOperations(), methodName);
// get connector
Connector connector =
UMLUtils.inferConnector(msgSourceLifeline, msgTargetLifeline, targetInterface);
if (connector == null) {
throw new RuntimeException(
"Error creating message: could not find connector between the two life lines that supports the target interface at the target lifeline");
}
boolean asynch = false;
if (calledOperation.getConcurrency() == CallConcurrencyKind.CONCURRENT_LITERAL) {
asynch = true;
}
if (SOAPUtils.isSOAPRequest(event)) {
// 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);
// create call
Message callMessage = interaction.createMessage(prefix + "call");
callMessage.setSignature(calledOperation);
setMessageParameters(callMessage, calledOperation, event, prefix);
callMessage.setConnector(connector);
callMessage.setSendEvent(callSendFragment);
callMessage.setReceiveEvent(callRecvFragment);
callSendFragment.setMessage(callMessage);
callRecvFragment.setMessage(callMessage);
if (asynch) {
// Create ASYNCH call
callMessage.setMessageSort(MessageSort.ASYNCH_CALL_LITERAL);
}
else {
// SYNCH call
callMessage.setMessageSort(MessageSort.SYNCH_CALL_LITERAL);
}
}
if (!asynch && SOAPUtils.isSOAPResponse(event)) {
// 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);
setMessageParameters(replyMessage, calledOperation, event, prefix);
replyMessage.setConnector(connector);
replyMessage.setSendEvent(replySendFragment);
replyMessage.setReceiveEvent(replyRecvFragment);
replySendFragment.setMessage(replyMessage);
replyRecvFragment.setMessage(replyMessage);
}
i++;
}
}
return interaction;
}
/**
*
* Sets values for the parameters of a call message. The values are, if possible, inferred from
* the event that is provided.
*
*
* @param message
* 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 void setMessageParameters(Message message,
Operation calledOperation,
Event event,
String prefix)
{
org.w3c.dom.Element requestBody;
if (SOAPUtils.isSOAPRequest(event)) {
requestBody =
SOAPUtils.getSoapBodyFromEvent(event, useRandomMsgBodies, CallType.REQUEST);
}
else {
requestBody =
SOAPUtils.getSoapBodyFromEvent(event, useRandomMsgBodies, CallType.RESPONSE);
}
Package instSpecPkg = null;
MutableInt instSpecNumber = new MutableInt(0);
// Set parameters of operation
for (Parameter param : calledOperation.getOwnedParameters()) {
if (instSpecPkg == null) {
instSpecPkg = getOrCreateInstanceSpecificationPackage(event);
}
String path = calledOperation.getName() + ":" + param.getType().getName();
if ((UMLUtils.isInParameter(param) && SOAPUtils.isSOAPRequest(event)) ||
(UMLUtils.isOutParameter(param) && SOAPUtils.isSOAPResponse(event)))
{
// create parameters node
if (!(param.getType() instanceof DataType)) {
throw new RuntimeException("TODO error handling; parameters missing");
}
DataType parametersNode = (DataType) param.getType();
InstanceSpecification instSpecParameters =
(InstanceSpecification) instSpecPkg
.createPackagedElement(prefix + "instspec" + instSpecNumber.intValue() +
"_" + param.getType().getName(),
UMLPackage.Literals.INSTANCE_SPECIFICATION);
instSpecParameters.getClassifiers().add((DataType) param.getType());
instSpecNumber.setValue(instSpecNumber.intValue() + 1);
InstanceValue instanceValue =
(InstanceValue) message.createArgument(param.getName(), param.getType(),
UMLPackage.Literals.INSTANCE_VALUE);
instanceValue.setInstance(instSpecParameters);
for (Property internalParameter : parametersNode.getAllAttributes()) {
if (internalParameter.getType() instanceof DataType) {
List paramNodes =
SOAPUtils.getMatchingChildNode(internalParameter.getType().getName(),
requestBody);
int multiplicityChosen = paramNodes.size();
if (multiplicityChosen == 0 && internalParameter.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 = internalParameter.getLower();
}
for (int i = 0; i < multiplicityChosen; i++) {
org.w3c.dom.Element paramNode = null;
if (!paramNodes.isEmpty()) {
paramNode = paramNodes.get(i);
}
Slot slot = instSpecParameters.createSlot();
slot.setDefiningFeature(internalParameter);
InstanceValue value =
(InstanceValue) slot
.createValue(internalParameter.getName() + "_" + i,
internalParameter.getType(),
UMLPackage.Literals.INSTANCE_VALUE);
value
.setInstance(createInstanceSpecification((DataType) internalParameter
.getType(),
instSpecPkg, prefix,
instSpecNumber, paramNode,
path));
/*
* InstanceValue value = (InstanceValue) argument .createOperand(null,
* internalParameter.getType(), UMLPackage.Literals.INSTANCE_VALUE);
* value.setInstance(instSpec);
*/
}
}
else if (internalParameter.getType() instanceof PrimitiveType) {
createSlotPrimitiveType(instSpecParameters, internalParameter, requestBody,
path);
}
}
}
else {
// set literalNull for out and return parameters
// argument.createOperand(null, param.getType(), UMLPackage.Literals.LITERAL_NULL);
message.createArgument(param.getName(), 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 InstanceSpecification createInstanceSpecification(DataType type,
Package pkg,
String prefix,
MutableInt instSpecNumber,
org.w3c.dom.Element currentNode,
String path)
{
if ("".equals(path)) {
path = type.getName();
}
InstanceSpecification instSpec =
(InstanceSpecification) pkg
.createPackagedElement(prefix + "instspec" + instSpecNumber.intValue() + "_" +
type.getName(),
UMLPackage.Literals.INSTANCE_SPECIFICATION);
instSpec.getClassifiers().add(type);
instSpecNumber.setValue(instSpecNumber.intValue() + 1);
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, instSpecNumber,
attributeNode,
path + "." + prop.getName()));
}
}
else {
Console.traceln(Level.SEVERE, "property neither DataType nor PrimitiveType: " +
prop.getType());
throw new RuntimeException(
"can only handle DataType and PrimitiveType properties but was: " +
prop.getType().getClass().getName());
}
}
return instSpec;
}
/**
*
* 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());
throw new RuntimeException("unknown primitive type: " + prop.getType().getName());
}
}
}
/**
*
* 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 event
* event from which the service name is inferred
* @return package for the {@link InstanceSpecification}s
*/
private Package getOrCreateInstanceSpecificationPackage(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;
}
}