// 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.http; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.Stack; import java.util.logging.Level; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Attr; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import de.ugoe.cs.autoquest.eventcore.Event; import de.ugoe.cs.autoquest.plugin.http.eventcore.EqualSOAPDataMap; import de.ugoe.cs.autoquest.plugin.http.eventcore.SOAPEventType; import de.ugoe.cs.autoquest.plugin.http.eventcore.SimpleSOAPEventType; import de.ugoe.cs.autoquest.plugin.http.eventcore.SimpleSOAPEventType.CallType; import de.ugoe.cs.util.console.Console; /** *

* Utilities for working with SOAP events. Their main task is to simply working with * {@link SimpleSOAPEventType} and {@link SOAPEventType}. *

* * @author Steffen Herbold */ public class SOAPUtils { /** *

* Defines how sequences are ordered: *

*

* * @author Steffen Herbold */ public enum SequenceOrder { REQUEST, RESPONSE } /** *

* Replaces events with a {@link SOAPEventType} with new events of {@link SimpleSOAPEventType} * and no target. *

* * @param sequences * sequences where the events are replaced * @param keepOnlySOAPEvents * if true, all events that are not of SOAPEventType or SimpleSOAPEventType are * removed * @return sequences with {@link SimpleSOAPEventType}s instead of {@link SOAPEventType}s */ public static Collection> convertToSimpleSOAPEvent(Collection> sequences, boolean keepOnlySOAPEvents) { Collection> newSequences = new LinkedList<>(); EqualSOAPDataMap equalSOAPDataMap = new EqualSOAPDataMap(); for (List sequence : sequences) { List newSequence = null; if (sequence != null) { newSequence = new LinkedList<>(); for (Event event : sequence) { if (event.getType() instanceof SOAPEventType) { SOAPEventType eventType = (SOAPEventType) event.getType(); newSequence.add(new Event(new SimpleSOAPEventType(eventType .getCalledMethod(), eventType.getServiceName(), eventType .getClientName(), eventType.getSoapRequestBody(), equalSOAPDataMap, CallType.REQUEST))); } else { if (!keepOnlySOAPEvents || event.getType() instanceof SimpleSOAPEventType) { newSequence.add(event); } } } } newSequences.add(newSequence); } return newSequences; } /** *

* Removes all non SOAP events from the contained sequences *

* * @param sequences * sequences where the events are replaced */ public static Collection> removeNonSOAPEvents(Collection> sequences) { Collection> soapOnlySequences = new LinkedList<>(); for (List sequence : sequences) { soapOnlySequences.add(removeNonSOAPEvents(sequence)); } return soapOnlySequences; } /** *

* Removes all non SOAP events from a sequence *

* * @param sequence * sequence where the events are replaced */ public static List removeNonSOAPEvents(List sequence) { List soapOnlySequence = new LinkedList<>(); for (Event event : sequence) { if (event.getType() instanceof SOAPEventType) { soapOnlySequence.add(event); } } return soapOnlySequence; } /** *

* Helper function to get the name of a service from a SOAP event. *

* * @param event * event for which the service name is retrieved * @return service name */ public static String getServiceNameFromEvent(Event event) { if (event.getType() instanceof SOAPEventType) { return ((SOAPEventType) event.getType()).getServiceName(); } else if (event.getType() instanceof SimpleSOAPEventType) { return ((SimpleSOAPEventType) event.getType()).getServiceName(); } else { throw new RuntimeException( "Wrong event type. Only SOAPEventType and SimpleSOAPEventType supported but was: " + event.getType().getClass().getName()); } } /** * *

* Helper function to get the called method from a SOAP event *

* * @param event * event for which the called method is retrieved * @return called method */ public static String getCalledMethodFromEvent(Event event) { if (event.getType() instanceof SOAPEventType) { return ((SOAPEventType) event.getType()).getCalledMethod(); } else if (event.getType() instanceof SimpleSOAPEventType) { return ((SimpleSOAPEventType) event.getType()).getCalledMethod(); } else { throw new RuntimeException( "Wrong event type. Only SOAPEventType and SimpleSOAPEventType supported but was: " + event.getType().getClass().getName()); } } /** *

* Helper function to get the name of a client from a SOAP event. *

* * @param event * event for which the client name is retrieved * @return service name */ public static String getClientNameFromEvent(Event event) { if (event.getType() instanceof SOAPEventType) { return ((SOAPEventType) event.getType()).getClientName(); } else if (event.getType() instanceof SimpleSOAPEventType) { return ((SimpleSOAPEventType) event.getType()).getClientName(); } else { throw new RuntimeException( "Wrong event type. Only SOAPEventType and SimpleSOAPEventType supported but was: " + event.getType().getClass().getName()); } } /** *

* Helper function to get the body of a SOAP request. *

* * @param event * event for which the SOAP request body is retrieved * @return body of the SOAP event */ public static Element getSoapBodyFromEvent(Event event) { return getSoapBodyFromEvent(event, false, CallType.REQUEST); } /** *

* Helper function to get the body of a SOAP message. *

* * @param event * event for which the SOAP message body is retrieved * @param useRandomBodies * defines if random message bodies are used or the body of the associated event * @param callType * defines if the request or response of a message is retrieved * @return body of the SOAP event */ public static Element getSoapBodyFromEvent(Event event, boolean useRandomBodies, CallType callType) { if (event.getType() instanceof SOAPEventType) { switch (callType) { case REQUEST: return ((SOAPEventType) event.getType()).getSoapRequestBody(); case RESPONSE: return ((SOAPEventType) event.getType()).getSoapResponseBody(); default: throw new RuntimeException("unsupported call type: " + callType); } } else if (event.getType() instanceof SimpleSOAPEventType) { switch (callType) { case REQUEST: if (((SimpleSOAPEventType) event.getType()).getCallType() == CallType.REQUEST) { if (useRandomBodies) { return ((SimpleSOAPEventType) event.getType()).getRandomSoapMsgBody(); } else { return ((SimpleSOAPEventType) event.getType()).getSoapMsgBody(); } } else { throw new RuntimeException( "cannot retrieve request body, is of CallType: " + ((SimpleSOAPEventType) event.getType()) .getCallType()); } case RESPONSE: if (((SimpleSOAPEventType) event.getType()).getCallType() == CallType.RESPONSE) { if (useRandomBodies) { return ((SimpleSOAPEventType) event.getType()).getRandomSoapMsgBody(); } else { return ((SimpleSOAPEventType) event.getType()).getSoapMsgBody(); } } else { throw new RuntimeException( "cannot retrieve response body, is of CallType: " + ((SimpleSOAPEventType) event.getType()) .getCallType()); } default: throw new RuntimeException("unsupported call type: " + callType); } } else { throw new RuntimeException( "unsupported event type; must be SOAPEventType or SimpleSOAPEventType but is: " + event.getType().getClass().getName()); } } /** *

* Checks if an event is a SOAP request *

* * @param event * event that is checked * @return true if SOAP request; false otherwise */ public static boolean isSOAPRequest(Event event) { if (event.getType() instanceof SOAPEventType) { return true; } else if (event.getType() instanceof SimpleSOAPEventType) { return ((SimpleSOAPEventType) event.getType()).getCallType() == CallType.REQUEST; } else { throw new RuntimeException( "unsupported event type; must be SOAPEventType or SimpleSOAPEventType but is: " + event.getType().getClass().getName()); } } /** *

* Checks if an event is a SOAP response *

* * @param event * event that is checked * @return true if SOAP response; false otherwise */ public static boolean isSOAPResponse(Event event) { if (event.getType() instanceof SOAPEventType) { return true; } else if (event.getType() instanceof SimpleSOAPEventType) { return ((SimpleSOAPEventType) event.getType()).getCallType() == CallType.RESPONSE; } else { throw new RuntimeException( "unsupported event type; must be SOAPEventType or SimpleSOAPEventType but is: " + event.getType().getClass().getName()); } } /** *

* returns the XML serialization of a DOM node; located here because it is used for SOAP request * bodies *

* * @param node * DOM node that is serialized * @return XML serialization as String; null if node is null */ public static String getSerialization(Node node) { if (node == null) { return null; } try { StringWriter writer = new StringWriter(); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.transform(new DOMSource(node), new StreamResult(writer)); return writer.toString(); } catch (TransformerFactoryConfigurationError | TransformerException e) { throw new IllegalArgumentException( "could not create serialization for SOAP request body.", e); } } /** *

* Fetches all {@link Element}s that are direct children of the parent node, whose name matches * the given name. *

* * @param typeNameRaw * name of elements that are looked for * @param parentNode * DOM node in which the elements are searched for * @return found {@link Element}s */ public static List getMatchingChildNode(String typeNameRaw, Element parentNode) { List matchingNodes = new ArrayList<>(); Node parameterNode = null; if (parentNode != null) { NodeList parameterNodes = parentNode.getChildNodes(); String[] typeNameSplit = typeNameRaw.split(":"); String typeName = typeNameSplit[typeNameSplit.length - 1]; for (int i = 0; i < parameterNodes.getLength(); i++) { parameterNode = parameterNodes.item(i); if (parameterNode.getNodeType() == Node.ELEMENT_NODE) { String[] parameterNodeSplit = parameterNode.getNodeName().split(":"); String parameterNodeName = parameterNodeSplit[parameterNodeSplit.length - 1]; if (typeName.equals(parameterNodeName)) { matchingNodes.add((Element) parameterNode); } } } } return matchingNodes; } /** *

* Fetches the names of all child elements of a node *

* * @param parentNode node for which the child names are fetched * @return names of the child nodes */ public static List getChildNodeNames(Element parentNode) { List childNames = new LinkedList<>(); Node parameterNode = null; if (parentNode != null) { NodeList parameterNodes = parentNode.getChildNodes(); for (int i = 0; i < parameterNodes.getLength(); i++) { parameterNode = parameterNodes.item(i); if (parameterNode.getNodeType() == Node.ELEMENT_NODE) { String[] parameterNodeSplit = parameterNode.getNodeName().split(":"); String parameterNodeName = parameterNodeSplit[parameterNodeSplit.length - 1]; childNames.add(parameterNodeName); } } } return childNames; } /** *

* Fetches all children of type {@link Element} from a parent node *

* * @param parentNode node for which the child elements are fetched * @return the child elements */ public static List getChildElements(Element parentNode) { List childElements = new LinkedList<>(); Node parameterNode = null; if (parentNode != null) { NodeList parameterNodes = parentNode.getChildNodes(); for (int i = 0; i < parameterNodes.getLength(); i++) { parameterNode = parameterNodes.item(i); if (parameterNode.getNodeType() == Node.ELEMENT_NODE) { childElements.add((Element) parameterNode); } } } return childElements; } /** *

* Returns the values found in a currentNode for a defined valueName. To this aim, this methods * first determines if there is an attribute with the name "valueName". If this is the case, the * value of this attribute is returned. Otherwise, the methods looks for {@link Element}s that * are direct children of the provided DOM node with the given name and returns the text content * of those nodes. *

*

* In case no values can be found, an empty list is returned * * @param valueName * name of the value that is retrieved * @param node * node for which the value is retrieved * @return list of the found values. */ public static List getValuesFromElement(String valueName, Element node) { List attributeValues = new LinkedList<>(); if (node != null) { // first check attributes of the node Attr attribute = node.getAttributeNode(valueName); if (attribute != null) { attributeValues.add(attribute.getValue()); } else { // now check elements List elements = getMatchingChildNode(valueName, node); for (Element element : elements) { attributeValues.add(element.getTextContent()); } } } return attributeValues; } /** *

* Allows the removal of pre- and suffixes from SOAP operation names in * {@link SimpleSOAPEventType}. *

* * @param sequences * sequences where the operation names are normalized */ public static Collection> normalizeOperationNames(Collection> sequences, String prefixToRemove, String suffixToRemove) { Collection> normalizedSequences = new LinkedList<>(); for (List sequence : sequences) { List normalizedSequence = new LinkedList<>(); for (Iterator eventIter = sequence.iterator(); eventIter.hasNext();) { Event event = eventIter.next(); if ((event.getType() instanceof SimpleSOAPEventType)) { SimpleSOAPEventType eventType = (SimpleSOAPEventType) event.getType(); String methodName = eventType.getCalledMethod(); if (prefixToRemove != null && methodName.startsWith(prefixToRemove)) { methodName = methodName.substring(prefixToRemove.length(), methodName.length()); // remove prefix } if (suffixToRemove != null && methodName.endsWith(suffixToRemove)) { methodName = methodName.substring(0, methodName.length() - suffixToRemove.length()); } event = new Event(new SimpleSOAPEventType(methodName, eventType.getServiceName(), eventType.getClientName(), eventType.getSoapMsgBody(), eventType.getEqualSOAPDataMap(), eventType.getCallType()), event.getTarget()); } normalizedSequence.add(event); } normalizedSequences.add(normalizedSequence); } return normalizedSequences; } /** *

* Sorts the sequences by the orderingId of the requests/responses. This function only supports * the ordering of {@link Event}s with a {@link SOAPEventType}. *

* * @param sequences * sequences to be order * @param orderType * determines if sequences are ordered by request or response * @return sorted sequences */ public static Collection> sortSequences(Collection> sequences, SequenceOrder orderType) { Collection> sortedSequences = new LinkedList<>(); for (List sequence : sequences) { // use insertion sort List sortedSequence = new LinkedList<>(); long lastOrderId = Long.MIN_VALUE; long selectedOrderId; Event selectedEvent; while (sortedSequence.size() != sequence.size()) { selectedEvent = null; selectedOrderId = Long.MAX_VALUE; for (Event event : sequence) { if (!(event.getType() instanceof SOAPEventType)) { throw new RuntimeException( "Can only order SOAPEventTypes. SimpleSOAPEvent is also not supported. Event type found: " + event.getType().getClass().getName()); } SOAPEventType soapEventType = (SOAPEventType) event.getType(); long eventOrderId; switch (orderType) { case REQUEST: eventOrderId = soapEventType.getExchange().getRequest().getOrderingId(); break; case RESPONSE: eventOrderId = soapEventType.getExchange().getResponse().getOrderingId(); break; default: throw new RuntimeException("unsupported order type: " + orderType.toString()); } if (eventOrderId > lastOrderId && eventOrderId < selectedOrderId) { selectedOrderId = eventOrderId; selectedEvent = event; } } if (selectedEvent != null) { sortedSequence.add(selectedEvent); lastOrderId = selectedOrderId; } else { throw new RuntimeException( "could not select next event; possibly the same order Id for multiple exchanges events!?"); } } sortedSequences.add(sortedSequence); } return sortedSequences; } /** *

* Sorts the sequences by the orderingId of the requests/responses. This function only supports * the ordering of {@link Event}s with a {@link SOAPEventType}. *

* * @param sequences * sequences to be order * @param orderType * determines if sequences are ordered by request or response * @return sorted sequences */ public static Collection> sortAndConvertSequences(Collection> sequences, boolean keepRequests, boolean keepResponse) { Collection> sortedSequences = new LinkedList<>(); EqualSOAPDataMap equalSOAPDataMap = new EqualSOAPDataMap(); for (List sequence : sequences) { // use insertion sort List sortedSequence = new LinkedList<>(); long lastOrderId = Long.MIN_VALUE; long selectedOrderId; Event selectedEvent; CallType selectedCallType = CallType.RESPONSE; do { selectedEvent = null; selectedOrderId = Long.MAX_VALUE; for (Event event : sequence) { if (!(event.getType() instanceof SOAPEventType)) { throw new RuntimeException( "Can only order SOAPEventTypes. SimpleSOAPEvent is also not supported. Event type found: " + event.getType().getClass().getName()); } SOAPEventType soapEventType = (SOAPEventType) event.getType(); long requestOrderId = soapEventType.getExchange().getRequest().getOrderingId(); long responseOrderId = soapEventType.getExchange().getResponse().getOrderingId(); // System.out.println(requestOrderId + " / " + responseOrderId); if (requestOrderId > lastOrderId && requestOrderId < selectedOrderId) { selectedOrderId = requestOrderId; selectedEvent = event; selectedCallType = CallType.REQUEST; } if (responseOrderId > lastOrderId && responseOrderId < selectedOrderId) { selectedOrderId = responseOrderId; selectedEvent = event; selectedCallType = CallType.RESPONSE; } } if (selectedEvent != null) { SOAPEventType eventType = (SOAPEventType) selectedEvent.getType(); Element soapMsgBody; switch (selectedCallType) { case REQUEST: soapMsgBody = eventType.getSoapRequestBody(); break; case RESPONSE: soapMsgBody = eventType.getSoapResponseBody(); break; default: throw new RuntimeException("unsupported call type: " + selectedCallType); } if ((keepRequests && selectedCallType == CallType.REQUEST) || (keepResponse && selectedCallType == CallType.RESPONSE)) { sortedSequence.add(new Event(new SimpleSOAPEventType(eventType .getCalledMethod(), eventType.getServiceName(), eventType .getClientName(), soapMsgBody, equalSOAPDataMap, selectedCallType))); } lastOrderId = selectedOrderId; } } while (selectedEvent != null); sortedSequences.add(sortedSequence); } return sortedSequences; } /** *

* Removes calls to and from all ignored services from the sequences. *

* * @param sequences * sequences where the ignored services are removed * @param ignoredServicesString * comma separted string that defines the ignored services * @return sequences without events that reference the ignored services */ public static Collection> removeCallsToIgnoredServices(Collection> sequences, String ignoredServicesString) { Set ignoredServices = new HashSet<>(); if (ignoredServicesString != null) { for (String service : ignoredServicesString.split(",")) { ignoredServices.add(service.trim()); } } return removeCallsToIgnoredServices(sequences, ignoredServices); } /** *

* Removes calls to and from all ignored services from the sequences. *

* * @param sequences * sequences where the ignored services are removed * @param ignoredServices * set with all ignored service names * @return sequences without events that reference the ignored services */ public static Collection> removeCallsToIgnoredServices(Collection> sequences, Set ignoredServices) { Collection> onlyAcceptedServicesSequences = new LinkedList<>(); for (List sequence : sequences) { List onlyAcceptedServicesSequence = new LinkedList<>(); for (Event event : sequence) { SimpleSOAPEventType eventType = (SimpleSOAPEventType) event.getType(); if (!ignoredServices.contains(eventType.getServiceName()) && !ignoredServices.contains(eventType.getClientName())) { onlyAcceptedServicesSequence.add(event); } } onlyAcceptedServicesSequences.add(onlyAcceptedServicesSequence); } return onlyAcceptedServicesSequences; } /** *

* Drops all requests without responses as well as invalid request/response orders. Only the * part of the sequence before the first invalid occurrence is kept. *

* * @param sequences * sequences where the invalid pairs are dropped * @return sequences with only valid and complete interactions */ public static Collection> dropInvalidResponseRequestPairs(Collection> sequences) { Collection> validSequences = new LinkedList<>(); int i = 0; for (List sequence : sequences) { List validSequence = dropInvalidResponseRequestPairs(sequence); if (validSequence.isEmpty() || (validSequence.size() <= 2 && validSequence.get(0) == Event.STARTEVENT)) { Console.traceln(Level.INFO, "dropped sequence " + i + ": empty after removal of invalid request/response pairs"); } validSequences.add(validSequence); i++; } return validSequences; } /** *

* Drops all requests without responses as well as invalid request/response orders. Only the * part of the sequence before the first invalid occurrence is kept. *

* * @param sequence * sequence where the invalid pairs are dropped * @return sequence with only valid and complete interactions */ public static List dropInvalidResponseRequestPairs(List sequence) { Stack unrespondedRequests = new Stack<>(); boolean hasStartEvent = false; int lastValidIndex = 0; int i = 0; for (Event event : sequence) { if (event == Event.STARTEVENT) { hasStartEvent = true; lastValidIndex = i; } else if (event.getType() instanceof SimpleSOAPEventType) { SimpleSOAPEventType currentEventType = (SimpleSOAPEventType) event.getType(); if (SOAPUtils.isSOAPRequest(event)) { unrespondedRequests.push(currentEventType); } else if (SOAPUtils.isSOAPResponse(event)) { if (unrespondedRequests.empty()) { break; // found a response without previous request; sequence invalid from // here } else { SimpleSOAPEventType lastRequest = unrespondedRequests.peek(); if (isRequestResponseMatch(lastRequest, currentEventType)) { unrespondedRequests.pop(); if (unrespondedRequests.empty()) { lastValidIndex = i; } } } } } i++; } List validSequence = new LinkedList<>(sequence.subList(0, lastValidIndex + 1)); if (hasStartEvent) { validSequence.add(Event.ENDEVENT); } return validSequence; } /** *

* Checks if two {@link SimpleSOAPEventType} types are equal except their {@link CallType}, * which must be {@value CallType#REQUEST} for the first parameter and {@link CallType#RESPONSE} * for the second parameter. *

* * @param request * request soap event * @param response * response soap event * @return true if they are a matching request/response pair */ public static boolean isRequestResponseMatch(SimpleSOAPEventType request, SimpleSOAPEventType response) { if (request == null && response == null) { return true; } if ((request != null && response == null) || (request == null && response != null)) { return false; } return request.getCallType().equals(CallType.REQUEST) && response.getCallType().equals(CallType.RESPONSE) && HTTPUtils.equals(request.getCalledMethod(), response.getCalledMethod()) && HTTPUtils.equals(request.getServiceName(), response.getServiceName()) && HTTPUtils.equals(request.getClientName(), response.getClientName()); } /** *

* prevent instantiation *

*/ private SOAPUtils() {} }