source: trunk/autoquest-plugin-http/src/main/java/de/ugoe/cs/autoquest/plugin/http/SOAPUtils.java @ 1994

Last change on this file since 1994 was 1994, checked in by sherbold, 9 years ago
  • fixed bug in the serialization of SimpleSOAPEventType. It is a workaround around a JDK bug ... who would have thought that HashMaps? have a serialization bug?!
  • Property svn:mime-type set to text/plain
File size: 28.7 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15package de.ugoe.cs.autoquest.plugin.http;
16
17import java.io.StringWriter;
18import java.util.ArrayList;
19import java.util.Collection;
20import java.util.HashSet;
21import java.util.Iterator;
22import java.util.LinkedList;
23import java.util.List;
24import java.util.Set;
25
26import javax.xml.transform.Transformer;
27import javax.xml.transform.TransformerException;
28import javax.xml.transform.TransformerFactory;
29import javax.xml.transform.TransformerFactoryConfigurationError;
30import javax.xml.transform.dom.DOMSource;
31import javax.xml.transform.stream.StreamResult;
32
33import org.w3c.dom.Attr;
34import org.w3c.dom.Element;
35import org.w3c.dom.Node;
36import org.w3c.dom.NodeList;
37
38import de.ugoe.cs.autoquest.eventcore.Event;
39import de.ugoe.cs.autoquest.plugin.http.eventcore.EqualSOAPDataMap;
40import de.ugoe.cs.autoquest.plugin.http.eventcore.SOAPEventType;
41import de.ugoe.cs.autoquest.plugin.http.eventcore.SimpleSOAPEventType;
42import de.ugoe.cs.autoquest.plugin.http.eventcore.SimpleSOAPEventType.CallType;
43
44/**
45 * <p>
46 * Utilities for working with SOAP events. Their main task is to simply working with
47 * {@link SimpleSOAPEventType} and {@link SOAPEventType}.
48 * </p>
49 *
50 * @author Steffen Herbold
51 */
52public class SOAPUtils {
53
54    /**
55     * <p>
56     * Defines how sequences are ordered:
57     * <ul>
58     * <li>REQUEST: by the request order id</li>
59     * <li>RESPONSE: by the response order id</li>
60     * </ul>
61     * </p>
62     *
63     * @author Steffen Herbold
64     */
65    public enum SequenceOrder {
66        REQUEST, RESPONSE
67    }
68
69    /**
70     * <p>
71     * Replaces events with a {@link SOAPEventType} with new events of {@link SimpleSOAPEventType}
72     * and no target.
73     * </p>
74     *
75     * @param sequences
76     *            sequences where the events are replaced
77     * @param keepOnlySOAPEvents
78     *            if true, all events that are not of SOAPEventType or SimpleSOAPEventType are
79     *            removed
80     * @return sequences with {@link SimpleSOAPEventType}s instead of {@link SOAPEventType}s
81     */
82    public static Collection<List<Event>> convertToSimpleSOAPEvent(Collection<List<Event>> sequences,
83                                                                   boolean keepOnlySOAPEvents)
84    {
85        Collection<List<Event>> newSequences = new LinkedList<>();
86        EqualSOAPDataMap equalSOAPDataMap = new EqualSOAPDataMap();
87        for (List<Event> sequence : sequences) {
88            List<Event> newSequence = null;
89            if (sequence != null) {
90                newSequence = new LinkedList<>();
91                for (Event event : sequence) {
92                    if (event.getType() instanceof SOAPEventType) {
93                        SOAPEventType eventType = (SOAPEventType) event.getType();
94                        newSequence.add(new Event(new SimpleSOAPEventType(eventType
95                            .getCalledMethod(), eventType.getServiceName(), eventType
96                            .getClientName(), eventType.getSoapRequestBody(), equalSOAPDataMap,
97                                                                          CallType.REQUEST)));
98                    }
99                    else {
100                        if (!keepOnlySOAPEvents || event.getType() instanceof SimpleSOAPEventType) {
101                            newSequence.add(event);
102                        }
103                    }
104                }
105            }
106            newSequences.add(newSequence);
107        }
108        return newSequences;
109    }
110
111    /**
112     * <p>
113     * Removes all non SOAP events from the contained sequences
114     * </p>
115     *
116     * @param sequences
117     *            sequences where the events are replaced
118     */
119    public static Collection<List<Event>> removeNonSOAPEvents(Collection<List<Event>> sequences) {
120        Collection<List<Event>> soapOnlySequences = new LinkedList<>();
121        for (List<Event> sequence : sequences) {
122            soapOnlySequences.add(removeNonSOAPEvents(sequence));
123        }
124        return soapOnlySequences;
125    }
126
127    /**
128     * <p>
129     * Removes all non SOAP events from a sequence
130     * </p>
131     *
132     * @param sequence
133     *            sequence where the events are replaced
134     */
135    public static List<Event> removeNonSOAPEvents(List<Event> sequence) {
136        List<Event> soapOnlySequence = new LinkedList<>();
137        for (Event event : sequence) {
138            if (event.getType() instanceof SOAPEventType) {
139                soapOnlySequence.add(event);
140            }
141        }
142        return soapOnlySequence;
143    }
144
145    /**
146     * <p>
147     * Helper function to get the name of a service from a SOAP event.
148     * </p>
149     *
150     * @param event
151     *            event for which the service name is retrieved
152     * @return service name
153     */
154    public static String getServiceNameFromEvent(Event event) {
155        if (event.getType() instanceof SOAPEventType) {
156            return ((SOAPEventType) event.getType()).getServiceName();
157        }
158        else if (event.getType() instanceof SimpleSOAPEventType) {
159            return ((SimpleSOAPEventType) event.getType()).getServiceName();
160        }
161        else {
162            throw new RuntimeException(
163                                       "Wrong event type. Only SOAPEventType and SimpleSOAPEventType supported but was: " +
164                                           event.getType().getClass().getName());
165        }
166    }
167
168    /**
169     *
170     * <p>
171     * Helper function to get the called method from a SOAP event
172     * </p>
173     *
174     * @param event
175     *            event for which the called method is retrieved
176     * @return called method
177     */
178    public static String getCalledMethodFromEvent(Event event) {
179        if (event.getType() instanceof SOAPEventType) {
180            return ((SOAPEventType) event.getType()).getCalledMethod();
181        }
182        else if (event.getType() instanceof SimpleSOAPEventType) {
183            return ((SimpleSOAPEventType) event.getType()).getCalledMethod();
184        }
185        else {
186            throw new RuntimeException(
187                                       "Wrong event type. Only SOAPEventType and SimpleSOAPEventType supported but was: " +
188                                           event.getType().getClass().getName());
189        }
190    }
191
192    /**
193     * <p>
194     * Helper function to get the name of a client from a SOAP event.
195     * </p>
196     *
197     * @param event
198     *            event for which the client name is retrieved
199     * @return service name
200     */
201    public static String getClientNameFromEvent(Event event) {
202        if (event.getType() instanceof SOAPEventType) {
203            return ((SOAPEventType) event.getType()).getClientName();
204        }
205        else if (event.getType() instanceof SimpleSOAPEventType) {
206            return ((SimpleSOAPEventType) event.getType()).getClientName();
207        }
208        else {
209            throw new RuntimeException(
210                                       "Wrong event type. Only SOAPEventType and SimpleSOAPEventType supported but was: " +
211                                           event.getType().getClass().getName());
212        }
213    }
214
215    /**
216     * <p>
217     * Helper function to get the body of a SOAP request.
218     * </p>
219     *
220     * @param event
221     *            event for which the SOAP request body is retrieved
222     * @return body of the SOAP event
223     */
224    public static Element getSoapBodyFromEvent(Event event) {
225        return getSoapBodyFromEvent(event, false, CallType.REQUEST);
226    }
227
228    /**
229     * <p>
230     * Helper function to get the body of a SOAP message.
231     * </p>
232     *
233     * @param event
234     *            event for which the SOAP message body is retrieved
235     * @param useRandomBodies
236     *            defines if random message bodies are used or the body of the associated event
237     * @param callType
238     *            defines if the request or response of a message is retrieved
239     * @return body of the SOAP event
240     */
241    public static Element getSoapBodyFromEvent(Event event,
242                                               boolean useRandomBodies,
243                                               CallType callType)
244    {
245        if (event.getType() instanceof SOAPEventType) {
246            switch (callType)
247            {
248                case REQUEST:
249                    return ((SOAPEventType) event.getType()).getSoapRequestBody();
250                case RESPONSE:
251                    return ((SOAPEventType) event.getType()).getSoapResponseBody();
252                default:
253                    throw new RuntimeException("unsupported call type: " + callType);
254            }
255        }
256        else if (event.getType() instanceof SimpleSOAPEventType) {
257            switch (callType)
258            {
259                case REQUEST:
260                    if (((SimpleSOAPEventType) event.getType()).getCallType() == CallType.REQUEST) {
261                        if (useRandomBodies) {
262                            return ((SimpleSOAPEventType) event.getType()).getRandomSoapMsgBody();
263                        }
264                        else {
265                            return ((SimpleSOAPEventType) event.getType()).getSoapMsgBody();
266                        }
267                    }
268                    else {
269                        throw new RuntimeException(
270                                                   "cannot retrieve request body, is of CallType: " +
271                                                       ((SimpleSOAPEventType) event.getType())
272                                                           .getCallType());
273                    }
274                case RESPONSE:
275                    if (((SimpleSOAPEventType) event.getType()).getCallType() == CallType.RESPONSE)
276                    {
277                        if (useRandomBodies) {
278                            return ((SimpleSOAPEventType) event.getType()).getRandomSoapMsgBody();
279                        }
280                        else {
281                            return ((SimpleSOAPEventType) event.getType()).getSoapMsgBody();
282                        }
283                    }
284                    else {
285                        throw new RuntimeException(
286                                                   "cannot retrieve response body, is of CallType: " +
287                                                       ((SimpleSOAPEventType) event.getType())
288                                                           .getCallType());
289                    }
290                default:
291                    throw new RuntimeException("unsupported call type: " + callType);
292            }
293        }
294        else {
295            throw new RuntimeException(
296                                       "unsupported event type; must be SOAPEventType or SimpleSOAPEventType but is: " +
297                                           event.getType().getClass().getName());
298        }
299    }
300
301    /**
302     * <p>
303     * Checks if an event is a SOAP request
304     * </p>
305     *
306     * @param event
307     *            event that is checked
308     * @return true if SOAP request; false otherwise
309     */
310    public static boolean isSOAPRequest(Event event) {
311        if (event.getType() instanceof SOAPEventType) {
312            return true;
313        }
314        else if (event.getType() instanceof SimpleSOAPEventType) {
315            return ((SimpleSOAPEventType) event.getType()).getCallType() == CallType.REQUEST;
316        }
317        else {
318            throw new RuntimeException(
319                                       "unsupported event type; must be SOAPEventType or SimpleSOAPEventType but is: " +
320                                           event.getType().getClass().getName());
321        }
322    }
323
324    /**
325     * <p>
326     * Checks if an event is a SOAP response
327     * </p>
328     *
329     * @param event
330     *            event that is checked
331     * @return true if SOAP response; false otherwise
332     */
333    public static boolean isSOAPResponse(Event event) {
334        if (event.getType() instanceof SOAPEventType) {
335            return true;
336        }
337        else if (event.getType() instanceof SimpleSOAPEventType) {
338            return ((SimpleSOAPEventType) event.getType()).getCallType() == CallType.RESPONSE;
339        }
340        else {
341            throw new RuntimeException(
342                                       "unsupported event type; must be SOAPEventType or SimpleSOAPEventType but is: " +
343                                           event.getType().getClass().getName());
344        }
345    }
346
347    /**
348     * <p>
349     * returns the XML serialization of a DOM node; located here because it is used for SOAP request
350     * bodies
351     * </p>
352     *
353     * @param node
354     *            DOM node that is serialized
355     * @return XML serialization as String; null if node is null
356     */
357    public static String getSerialization(Node node) {
358        if (node == null) {
359            return null;
360        }
361        try {
362            StringWriter writer = new StringWriter();
363            Transformer transformer = TransformerFactory.newInstance().newTransformer();
364            transformer.transform(new DOMSource(node), new StreamResult(writer));
365            return writer.toString();
366        }
367        catch (TransformerFactoryConfigurationError | TransformerException e) {
368            throw new IllegalArgumentException(
369                                               "could not create serialization for SOAP request body.",
370                                               e);
371        }
372    }
373
374    /**
375     * <p>
376     * Fetches all {@link Element}s that are direct children of the parent node, whose name matches
377     * the given name.
378     * </p>
379     *
380     * @param typeNameRaw
381     *            name of elements that are looked for
382     * @param parentNode
383     *            DOM node in which the elements are searched for
384     * @return found {@link Element}s
385     */
386    public static List<Element> getMatchingChildNode(String typeNameRaw, Element parentNode) {
387        List<Element> matchingNodes = new ArrayList<>();
388        Node parameterNode = null;
389        if (parentNode != null) {
390            NodeList parameterNodes = parentNode.getChildNodes();
391            String[] typeNameSplit = typeNameRaw.split(":");
392            String typeName = typeNameSplit[typeNameSplit.length - 1];
393            for (int i = 0; i < parameterNodes.getLength(); i++) {
394                parameterNode = parameterNodes.item(i);
395                if (parameterNode.getNodeType() == Node.ELEMENT_NODE) {
396                    String[] parameterNodeSplit = parameterNode.getNodeName().split(":");
397                    String parameterNodeName = parameterNodeSplit[parameterNodeSplit.length - 1];
398                    if (typeName.equals(parameterNodeName)) {
399                        matchingNodes.add((Element) parameterNode);
400                    }
401                }
402            }
403        }
404        return matchingNodes;
405    }
406
407    /**
408     * <p>
409     * Returns the values found in a currentNode for a defined valueName. To this aim, this methods
410     * first determines if there is an attribute with the name "valueName". If this is the case, the
411     * value of this attribute is returned. Otherwise, the methods looks for {@link Element}s that
412     * are direct children of the provided DOM node with the given name and returns the text content
413     * of those nodes.
414     * </p>
415     * <p>
416     * In case no values can be found, an empty list is returned
417     *
418     * @param valueName
419     *            name of the value that is retrieved
420     * @param node
421     *            node for which the value is retrieved
422     * @return list of the found values.
423     */
424    public static List<String> getValuesFromElement(String valueName, Element node) {
425        List<String> attributeValues = new LinkedList<>();
426
427        if (node != null) {
428            // first check attributes of the node
429            Attr attribute = node.getAttributeNode(valueName);
430            if (attribute != null) {
431                attributeValues.add(attribute.getValue());
432            }
433            else {
434                // now check elements
435                List<Element> elements = getMatchingChildNode(valueName, node);
436                for (Element element : elements) {
437                    attributeValues.add(element.getTextContent());
438                }
439            }
440        }
441
442        return attributeValues;
443    }
444
445    /**
446     * <p>
447     * Allows the removal of pre- and suffixes from SOAP operation names in
448     * {@link SimpleSOAPEventType}.
449     * </p>
450     *
451     * @param sequences
452     *            sequences where the operation names are normalized
453     */
454    public static Collection<List<Event>> normalizeOperationNames(Collection<List<Event>> sequences,
455                                                                  String prefixToRemove,
456                                                                  String suffixToRemove)
457    {
458        Collection<List<Event>> normalizedSequences = new LinkedList<>();
459        for (List<Event> sequence : sequences) {
460            List<Event> normalizedSequence = new LinkedList<>();
461            for (Iterator<Event> eventIter = sequence.iterator(); eventIter.hasNext();) {
462                Event event = eventIter.next();
463                if ((event.getType() instanceof SimpleSOAPEventType)) {
464                    SimpleSOAPEventType eventType = (SimpleSOAPEventType) event.getType();
465                    String methodName = eventType.getCalledMethod();
466                    if (prefixToRemove != null && methodName.startsWith(prefixToRemove)) {
467                        methodName =
468                            methodName.substring(prefixToRemove.length(), methodName.length());
469                        // remove prefix
470                    }
471                    if (suffixToRemove != null && methodName.endsWith(suffixToRemove)) {
472                        methodName =
473                            methodName.substring(0, methodName.length() - suffixToRemove.length());
474                    }
475                    event =
476                        new Event(new SimpleSOAPEventType(methodName, eventType.getServiceName(),
477                                                          eventType.getClientName(),
478                                                          eventType.getSoapMsgBody(),
479                                                          eventType.getEqualSOAPDataMap(),
480                                                          eventType.getCallType()),
481                                  event.getTarget());
482                }
483                normalizedSequence.add(event);
484            }
485            normalizedSequences.add(normalizedSequence);
486        }
487        return normalizedSequences;
488    }
489
490    /**
491     * <p>
492     * Sorts the sequences by the orderingId of the requests/responses. This function only supports
493     * the ordering of {@link Event}s with a {@link SOAPEventType}.
494     * </p>
495     *
496     * @param sequences
497     *            sequences to be order
498     * @param orderType
499     *            determines if sequences are ordered by request or response
500     * @return sorted sequences
501     */
502    public static Collection<List<Event>> sortSequences(Collection<List<Event>> sequences,
503                                                        SequenceOrder orderType)
504    {
505        Collection<List<Event>> sortedSequences = new LinkedList<>();
506        for (List<Event> sequence : sequences) {
507            // use insertion sort
508            List<Event> sortedSequence = new LinkedList<>();
509            long lastOrderId = Long.MIN_VALUE;
510            long selectedOrderId;
511            Event selectedEvent;
512            while (sortedSequence.size() != sequence.size()) {
513                selectedEvent = null;
514                selectedOrderId = Long.MAX_VALUE;
515                for (Event event : sequence) {
516                    if (!(event.getType() instanceof SOAPEventType)) {
517                        throw new RuntimeException(
518                                                   "Can only order SOAPEventTypes. SimpleSOAPEvent is also not supported. Event type found: " +
519                                                       event.getType().getClass().getName());
520                    }
521                    SOAPEventType soapEventType = (SOAPEventType) event.getType();
522                    long eventOrderId;
523                    switch (orderType)
524                    {
525                        case REQUEST:
526                            eventOrderId = soapEventType.getExchange().getRequest().getOrderingId();
527                            break;
528                        case RESPONSE:
529                            eventOrderId =
530                                soapEventType.getExchange().getResponse().getOrderingId();
531                            break;
532                        default:
533                            throw new RuntimeException("unsupported order type: " +
534                                orderType.toString());
535                    }
536                    if (eventOrderId > lastOrderId && eventOrderId < selectedOrderId) {
537                        selectedOrderId = eventOrderId;
538                        selectedEvent = event;
539                    }
540                }
541                if (selectedEvent != null) {
542                    sortedSequence.add(selectedEvent);
543                    lastOrderId = selectedOrderId;
544                }
545                else {
546                    throw new RuntimeException(
547                                               "could not select next event; possibly the same order Id for multiple exchanges events!?");
548                }
549            }
550            sortedSequences.add(sortedSequence);
551        }
552        return sortedSequences;
553    }
554
555    /**
556     * <p>
557     * Sorts the sequences by the orderingId of the requests/responses. This function only supports
558     * the ordering of {@link Event}s with a {@link SOAPEventType}.
559     * </p>
560     *
561     * @param sequences
562     *            sequences to be order
563     * @param orderType
564     *            determines if sequences are ordered by request or response
565     * @return sorted sequences
566     */
567    public static Collection<List<Event>> sortAndConvertSequences(Collection<List<Event>> sequences,
568                                                                  boolean keepRequests,
569                                                                  boolean keepResponse)
570    {
571        Collection<List<Event>> sortedSequences = new LinkedList<>();
572        EqualSOAPDataMap equalSOAPDataMap = new EqualSOAPDataMap();
573        for (List<Event> sequence : sequences) {
574            // use insertion sort
575            List<Event> sortedSequence = new LinkedList<>();
576            long lastOrderId = Long.MIN_VALUE;
577            long selectedOrderId;
578            Event selectedEvent;
579            CallType selectedCallType = CallType.RESPONSE;
580            do {
581                selectedEvent = null;
582                selectedOrderId = Long.MAX_VALUE;
583                for (Event event : sequence) {
584                    if (!(event.getType() instanceof SOAPEventType)) {
585                        throw new RuntimeException(
586                                                   "Can only order SOAPEventTypes. SimpleSOAPEvent is also not supported. Event type found: " +
587                                                       event.getType().getClass().getName());
588                    }
589                    SOAPEventType soapEventType = (SOAPEventType) event.getType();
590                    long requestOrderId = soapEventType.getExchange().getRequest().getOrderingId();
591                    long responseOrderId =
592                        soapEventType.getExchange().getResponse().getOrderingId();
593                    // System.out.println(requestOrderId + " / " + responseOrderId);
594
595                    if (requestOrderId > lastOrderId && requestOrderId < selectedOrderId) {
596                        selectedOrderId = requestOrderId;
597                        selectedEvent = event;
598                        selectedCallType = CallType.REQUEST;
599                    }
600                    if (responseOrderId > lastOrderId && responseOrderId < selectedOrderId) {
601                        selectedOrderId = responseOrderId;
602                        selectedEvent = event;
603                        selectedCallType = CallType.RESPONSE;
604                    }
605                }
606                if (selectedEvent != null) {
607                    SOAPEventType eventType = (SOAPEventType) selectedEvent.getType();
608                    Element soapMsgBody;
609                    switch (selectedCallType)
610                    {
611                        case REQUEST:
612                            soapMsgBody = eventType.getSoapRequestBody();
613                            break;
614                        case RESPONSE:
615                            soapMsgBody = eventType.getSoapResponseBody();
616                            break;
617                        default:
618                            throw new RuntimeException("unsupported call type: " + selectedCallType);
619                    }
620                    if ((keepRequests && selectedCallType == CallType.REQUEST) ||
621                        (keepResponse && selectedCallType == CallType.RESPONSE))
622                    {
623                        sortedSequence.add(new Event(new SimpleSOAPEventType(eventType
624                            .getCalledMethod(), eventType.getServiceName(), eventType
625                            .getClientName(), soapMsgBody, equalSOAPDataMap, selectedCallType)));
626                    }
627                    lastOrderId = selectedOrderId;
628                }
629
630            }
631            while (selectedEvent != null);
632            sortedSequences.add(sortedSequence);
633        }
634        return sortedSequences;
635    }
636
637    /**
638     * <p>
639     * Removes calls to and from all ignored services from the sequences.
640     * </p>
641     *
642     * @param sequences
643     *            sequences where the ignored services are removed
644     * @param ignoredServicesString
645     *            comma separted string that defines the ignored services
646     * @return sequences without events that reference the ignored services
647     */
648    public static Collection<List<Event>> removeCallsToIgnoredServices(Collection<List<Event>> sequences,
649                                                                       String ignoredServicesString)
650    {
651        Set<String> ignoredServices = new HashSet<>();
652        if (ignoredServicesString != null) {
653            for (String service : ignoredServicesString.split(",")) {
654                ignoredServices.add(service.trim());
655            }
656        }
657        return removeCallsToIgnoredServices(sequences, ignoredServices);
658    }
659
660    /**
661     * <p>
662     * Removes calls to and from all ignored services from the sequences.
663     * </p>
664     *
665     * @param sequences
666     *            sequences where the ignored services are removed
667     * @param ignoredServices
668     *            set with all ignored service names
669     * @return sequences without events that reference the ignored services
670     */
671    public static Collection<List<Event>> removeCallsToIgnoredServices(Collection<List<Event>> sequences,
672                                                                       Set<String> ignoredServices)
673    {
674        Collection<List<Event>> onlyAcceptedServicesSequences = new LinkedList<>();
675        for (List<Event> sequence : sequences) {
676            List<Event> onlyAcceptedServicesSequence = new LinkedList<>();
677            for (Event event : sequence) {
678                SimpleSOAPEventType eventType = (SimpleSOAPEventType) event.getType();
679                if (!ignoredServices.contains(eventType.getServiceName()) &&
680                    !ignoredServices.contains(eventType.getClientName()))
681                {
682                    onlyAcceptedServicesSequence.add(event);
683                }
684            }
685            onlyAcceptedServicesSequences.add(onlyAcceptedServicesSequence);
686        }
687        return onlyAcceptedServicesSequences;
688    }
689
690    /**
691     * <p>
692     * prevent instantiation
693     * </p>
694     */
695    private SOAPUtils() {}
696}
Note: See TracBrowser for help on using the repository browser.