001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.jmeter.protocol.http.sampler;
020:
021: import java.io.BufferedReader;
022: import java.io.File;
023: import java.io.FileInputStream;
024: import java.io.IOException;
025: import java.io.StringReader;
026: import java.net.URL;
027: import java.net.MalformedURLException;
028: import java.util.Enumeration;
029: import java.util.Random;
030: import java.util.Hashtable;
031:
032: import javax.xml.parsers.DocumentBuilder;
033:
034: import org.xml.sax.InputSource;
035: import org.xml.sax.SAXException;
036:
037: import org.apache.jorphan.io.TextFile;
038: import org.apache.jorphan.logging.LoggingManager;
039:
040: import org.apache.jmeter.JMeter;
041: import org.apache.jmeter.gui.JMeterFileFilter;
042: import org.apache.jmeter.protocol.http.control.AuthManager;
043: import org.apache.jmeter.protocol.http.control.Authorization;
044: import org.apache.jmeter.protocol.http.util.DOMPool;
045: import org.apache.jmeter.samplers.SampleResult;
046: import org.apache.jmeter.util.JMeterUtils;
047: import org.apache.log.Logger;
048: import org.apache.soap.Envelope;
049: import org.apache.soap.messaging.Message;
050: import org.apache.soap.transport.SOAPTransport;
051: import org.apache.soap.transport.http.SOAPHTTPConnection;
052: import org.apache.soap.util.xml.XMLParserUtils;
053: import org.apache.soap.SOAPException;
054: import org.w3c.dom.Document;
055:
056: /**
057: * Sampler to handle Web Service requests. It uses Apache SOAP drivers to
058: * perform the XML generation, connection, SOAP encoding and other SOAP
059: * functions.
060: * <p>
061: * Created on: Jun 26, 2003
062: *
063: */
064: public class WebServiceSampler extends HTTPSamplerBase {
065: private static Logger log = LoggingManager.getLoggerForClass();
066:
067: //+ JMX file attribut names - do not change!
068: private static final String XML_DATA = "HTTPSamper.xml_data"; //$NON-NLS-1$
069:
070: private static final String SOAP_ACTION = "Soap.Action"; //$NON-NLS-1$
071:
072: private static final String XML_DATA_FILE = "WebServiceSampler.xml_data_file"; //$NON-NLS-1$
073:
074: private static final String XML_PATH_LOC = "WebServiceSampler.xml_path_loc"; //$NON-NLS-1$
075:
076: private static final String MEMORY_CACHE = "WebServiceSampler.memory_cache"; //$NON-NLS-1$
077:
078: private static final String READ_RESPONSE = "WebServiceSampler.read_response"; //$NON-NLS-1$
079:
080: private static final String USE_PROXY = "WebServiceSampler.use_proxy"; //$NON-NLS-1$
081:
082: private static final String PROXY_HOST = "WebServiceSampler.proxy_host"; //$NON-NLS-1$
083:
084: private static final String PROXY_PORT = "WebServiceSampler.proxy_port"; //$NON-NLS-1$
085:
086: private static final String WSDL_URL = "WebserviceSampler.wsdl_url"; //$NON-NLS-1$
087:
088: private static final String TIMEOUT = "WebserviceSampler.timeout"; //$NON-NLS-1$
089: //- JMX file attribut names - do not change!
090:
091: private static final String PROXY_USER = JMeterUtils
092: .getPropDefault(JMeter.HTTP_PROXY_USER, ""); // $NON-NLS-1$
093:
094: private static final String PROXY_PASS = JMeterUtils
095: .getPropDefault(JMeter.HTTP_PROXY_PASS, ""); // $NON-NLS-1$
096:
097: /*
098: * Random class for generating random numbers.
099: */
100: private final Random RANDOM = new Random();
101:
102: private String fileContents = null;
103:
104: /**
105: * Set the path where XML messages are stored for random selection.
106: */
107: public void setXmlPathLoc(String path) {
108: setProperty(XML_PATH_LOC, path);
109: }
110:
111: /**
112: * Get the path where XML messages are stored. this is the directory where
113: * JMeter will randomly select a file.
114: */
115: public String getXmlPathLoc() {
116: return getPropertyAsString(XML_PATH_LOC);
117: }
118:
119: /**
120: * it's kinda obvious, but we state it anyways. Set the xml file with a
121: * string path.
122: *
123: * @param filename
124: */
125: public void setXmlFile(String filename) {
126: setProperty(XML_DATA_FILE, filename);
127: }
128:
129: /**
130: * Get the file location of the xml file.
131: *
132: * @return String file path.
133: */
134: public String getXmlFile() {
135: return getPropertyAsString(XML_DATA_FILE);
136: }
137:
138: /**
139: * Method is used internally to check if a random file should be used for
140: * the message. Messages must be valid. This is one way to load test with
141: * different messages. The limitation of this approach is parsing XML takes
142: * CPU resources, so it could affect JMeter GUI responsiveness.
143: *
144: * @return String filename
145: */
146: protected String getRandomFileName() {
147: if (this .getXmlPathLoc() != null) {
148: File src = new File(this .getXmlPathLoc());
149: if (src.isDirectory() && src.list() != null) {
150: File[] fileList = src.listFiles(new JMeterFileFilter(
151: new String[] { ".xml" }, false));
152: File one = fileList[RANDOM.nextInt(fileList.length)];
153: // return the absolutePath of the file
154: return one.getAbsolutePath();
155: } else {
156: return getXmlFile();
157: }
158: } else {
159: return getXmlFile();
160: }
161: }
162:
163: /**
164: * Set the XML data.
165: *
166: * @param data
167: */
168: public void setXmlData(String data) {
169: setProperty(XML_DATA, data);
170: }
171:
172: /**
173: * Get the XML data as a string.
174: *
175: * @return String data
176: */
177: public String getXmlData() {
178: return getPropertyAsString(XML_DATA);
179: }
180:
181: /**
182: * Set the soap action which should be in the form of an URN.
183: *
184: * @param data
185: */
186: public void setSoapAction(String data) {
187: setProperty(SOAP_ACTION, data);
188: }
189:
190: /**
191: * Return the soap action string.
192: *
193: * @return String soap action
194: */
195: public String getSoapAction() {
196: return getPropertyAsString(SOAP_ACTION);
197: }
198:
199: /**
200: * Set the memory cache.
201: *
202: * @param cache
203: */
204: public void setMemoryCache(boolean cache) {
205: setProperty(MEMORY_CACHE, String.valueOf(cache));
206: }
207:
208: /**
209: * Get the memory cache.
210: *
211: * @return boolean cache
212: */
213: public boolean getMemoryCache() {
214: return getPropertyAsBoolean(MEMORY_CACHE);
215: }
216:
217: /**
218: * Set whether the sampler should read the response or not.
219: *
220: * @param read
221: */
222: public void setReadResponse(boolean read) {
223: setProperty(READ_RESPONSE, String.valueOf(read));
224: }
225:
226: /**
227: * Return whether or not to read the response.
228: *
229: * @return boolean
230: */
231: public boolean getReadResponse() {
232: return this .getPropertyAsBoolean(READ_RESPONSE);
233: }
234:
235: /**
236: * Set whether or not to use a proxy
237: *
238: * @param proxy
239: */
240: public void setUseProxy(boolean proxy) {
241: setProperty(USE_PROXY, String.valueOf(proxy));
242: }
243:
244: /**
245: * Return whether or not to use proxy
246: *
247: * @return true if should use proxy
248: */
249: public boolean getUseProxy() {
250: return this .getPropertyAsBoolean(USE_PROXY);
251: }
252:
253: /**
254: * Set the proxy hostname
255: *
256: * @param host
257: */
258: public void setProxyHost(String host) {
259: setProperty(PROXY_HOST, host);
260: }
261:
262: /**
263: * Return the proxy hostname
264: *
265: * @return the proxy hostname
266: */
267: public String getProxyHost() {
268: this .checkProxy();
269: return this .getPropertyAsString(PROXY_HOST);
270: }
271:
272: /**
273: * Set the proxy port
274: *
275: * @param port
276: */
277: public void setProxyPort(String port) {
278: setProperty(PROXY_PORT, port);
279: }
280:
281: /**
282: * Return the proxy port
283: *
284: * @return the proxy port
285: */
286: public int getProxyPort() {
287: this .checkProxy();
288: return this .getPropertyAsInt(PROXY_PORT);
289: }
290:
291: /**
292: *
293: * @param url
294: */
295: public void setWsdlURL(String url) {
296: this .setProperty(WSDL_URL, url);
297: }
298:
299: /**
300: * method returns the WSDL URL
301: *
302: * @return the WSDL URL
303: */
304: public String getWsdlURL() {
305: return getPropertyAsString(WSDL_URL);
306: }
307:
308: /*
309: * The method will check to see if JMeter was started in NonGui mode. If it
310: * was, it will try to pick up the proxy host and port values if they were
311: * passed to JMeter.java.
312: */
313: private void checkProxy() {
314: if (System.getProperty("JMeter.NonGui") != null
315: && System.getProperty("JMeter.NonGui").equals("true")) {
316: this .setUseProxy(true);
317: // we check to see if the proxy host and port are set
318: String port = this .getPropertyAsString(PROXY_PORT);
319: String host = this .getPropertyAsString(PROXY_HOST);
320: if (host == null || host.length() == 0) {
321: // it's not set, lets check if the user passed
322: // proxy host and port from command line
323: host = System.getProperty("http.proxyHost");
324: if (host != null) {
325: this .setProxyHost(host);
326: }
327: }
328: if (port == null || port.length() == 0) {
329: // it's not set, lets check if the user passed
330: // proxy host and port from command line
331: port = System.getProperty("http.proxyPort");
332: if (port != null) {
333: this .setProxyPort(port);
334: }
335: }
336: }
337: }
338:
339: /*
340: * This method uses Apache soap util to create the proper DOM elements.
341: *
342: * @return Element
343: */
344: private org.w3c.dom.Element createDocument() throws SAXException,
345: IOException {
346: Document doc = null;
347: String next = this .getRandomFileName();//get filename or ""
348:
349: /* Note that the filename is also used as a key to the pool (if used)
350: ** Documents provided in the testplan are not currently pooled, as they may change
351: * between samples.
352: */
353:
354: if (next.length() > 0 && getMemoryCache()) {
355: doc = DOMPool.getDocument(next);
356: if (doc == null) {
357: doc = openDocument(next);
358: if (doc != null) {// we created the document
359: DOMPool.putDocument(next, doc);
360: }
361: }
362: } else { // Must be local content - or not using pool
363: doc = openDocument(next);
364: }
365:
366: if (doc == null) {
367: return null;
368: }
369: return doc.getDocumentElement();
370: }
371:
372: /**
373: * Open the file and create a Document.
374: *
375: * @param file - input filename or empty if using data from tesplan
376: * @return Document
377: * @throws IOException
378: * @throws SAXException
379: */
380: private Document openDocument(String file) throws SAXException,
381: IOException {
382: /*
383: * Consider using Apache commons pool to create a pool of document
384: * builders or make sure XMLParserUtils creates builders efficiently.
385: */
386: DocumentBuilder XDB = XMLParserUtils.getXMLDocBuilder();
387: XDB.setErrorHandler(null);//Suppress messages to stdout
388:
389: Document doc = null;
390: // if either a file or path location is given,
391: // get the file object.
392: if (file.length() > 0) {// we have a file
393: if (this .getReadResponse()) {
394: TextFile tfile = new TextFile(file);
395: fileContents = tfile.getText();
396: }
397: doc = XDB.parse(new FileInputStream(file));
398: } else {// must be a "here" document
399: fileContents = getXmlData();
400: if (fileContents != null && fileContents.length() > 0) {
401: doc = XDB.parse(new InputSource(new StringReader(
402: fileContents)));
403: } else {
404: log.warn("No post data provided!");
405: }
406: }
407: return doc;
408: }
409:
410: /*
411: * Required to satisfy HTTPSamplerBase Should not be called, as we override
412: * sample()
413: */
414:
415: protected HTTPSampleResult sample(URL u, String s, boolean b, int i) {
416: throw new RuntimeException(
417: "Not implemented - should not be called");
418: }
419:
420: /**
421: * Sample the URL using Apache SOAP driver. Implementation note for myself
422: * and those that are curious. Current logic marks the end after the
423: * response has been read. If read response is set to false, the buffered
424: * reader will read, but do nothing with it. Essentially, the stream from
425: * the server goes into the ether.
426: */
427: public SampleResult sample() {
428: SampleResult result = new SampleResult();
429: result.setSuccessful(false); // Assume it will fail
430: result.setResponseCode("000"); // ditto $NON-NLS-1$
431: result.setSampleLabel(getName());
432: try {
433: result.setURL(this .getUrl());
434: org.w3c.dom.Element rdoc = createDocument();
435: if (rdoc == null)
436: throw new SOAPException("Could not create document",
437: null);
438: Envelope msgEnv = Envelope.unmarshall(rdoc);
439: // create a new message
440: Message msg = new Message();
441: result.sampleStart();
442: SOAPHTTPConnection spconn = null;
443: // if a blank HeaderManager exists, try to
444: // get the SOAPHTTPConnection. After the first
445: // request, there should be a connection object
446: // stored with the cookie header info.
447: if (this .getHeaderManager() != null
448: && this .getHeaderManager().getSOAPHeader() != null) {
449: spconn = (SOAPHTTPConnection) this .getHeaderManager()
450: .getSOAPHeader();
451: } else {
452: spconn = new SOAPHTTPConnection();
453: }
454:
455: spconn.setTimeout(getTimeoutAsInt());
456:
457: // set the auth. thanks to KiYun Roe for contributing the patch
458: // I cleaned up the patch slightly. 5-26-05
459: if (getAuthManager() != null) {
460: if (getAuthManager().getAuthForURL(getUrl()) != null) {
461: AuthManager authmanager = getAuthManager();
462: Authorization auth = authmanager
463: .getAuthForURL(getUrl());
464: spconn.setUserName(auth.getUser());
465: spconn.setPassword(auth.getPass());
466: } else {
467: log.warn("the URL for the auth was null."
468: + " Username and password not set");
469: }
470: }
471: // check the proxy
472: String phost = "";
473: int pport = 0;
474: // if use proxy is set, we try to pick up the
475: // proxy host and port from either the text
476: // fields or from JMeterUtil if they were passed
477: // from command line
478: if (this .getUseProxy()) {
479: if (this .getProxyHost().length() > 0
480: && this .getProxyPort() > 0) {
481: phost = this .getProxyHost();
482: pport = this .getProxyPort();
483: } else {
484: if (System.getProperty("http.proxyHost") != null
485: || System.getProperty("http.proxyPort") != null) {
486: phost = System.getProperty("http.proxyHost");
487: pport = Integer.parseInt(System
488: .getProperty("http.proxyPort"));
489: }
490: }
491: // if for some reason the host is blank and the port is
492: // zero, the sampler will fail silently
493: if (phost.length() > 0 && pport > 0) {
494: spconn.setProxyHost(phost);
495: spconn.setProxyPort(pport);
496: if (PROXY_USER.length() > 0
497: && PROXY_PASS.length() > 0) {
498: spconn.setProxyUserName(PROXY_USER);
499: spconn.setProxyPassword(PROXY_PASS);
500: }
501: }
502: }
503: // by default we maintain the session.
504: spconn.setMaintainSession(true);
505: msg.setSOAPTransport(spconn);
506: msg.send(this .getUrl(), this .getSoapAction(), msgEnv);
507:
508: if (this .getHeaderManager() != null) {
509: this .getHeaderManager().setSOAPHeader(spconn);
510: }
511:
512: SOAPTransport st = msg.getSOAPTransport();
513: result.setDataType(SampleResult.TEXT);
514: BufferedReader br = null;
515: // check to see if SOAPTransport is not nul and receive is
516: // also not null. hopefully this will improve the error
517: // reporting. 5/13/05 peter lin
518: if (st != null && st.receive() != null) {
519: br = st.receive();
520: if (getReadResponse()) {
521: StringBuffer buf = new StringBuffer();
522: String line;
523: while ((line = br.readLine()) != null) {
524: buf.append(line);
525: }
526: result.sampleEnd();
527: // set the response
528: result.setResponseData(buf.toString().getBytes());
529: } else {
530: // by not reading the response
531: // for real, it improves the
532: // performance on slow clients
533: br.read();
534: result.sampleEnd();
535: result.setResponseData(JMeterUtils.getResString(
536: "read_response_message").getBytes()); //$NON-NLS-1$
537: }
538: result.setSuccessful(true);
539: result.setResponseCodeOK();
540: result.setResponseHeaders(this .convertSoapHeaders(st
541: .getHeaders()));
542: } else {
543: result.sampleEnd();
544: result.setSuccessful(false);
545: if (st != null) {
546: result.setResponseData(st.getResponseSOAPContext()
547: .getContentType().getBytes());
548: }
549: result.setResponseHeaders("error");
550: }
551: // 1-22-04 updated the sampler so that when read
552: // response is set, it also sets SamplerData with
553: // the XML message, so users can see what was
554: // sent. if read response is not checked, it will
555: // not set sampler data with the request message.
556: // peter lin.
557: // Removed URL, as that is already stored elsewere
558: result.setSamplerData(fileContents);// WARNING - could be large
559: result.setEncodingAndType(st.getResponseSOAPContext()
560: .getContentType());
561: // setting this is just a formality, since
562: // soap will return a descriptive error
563: // message, soap errors within the response
564: // are preferred.
565: if (br != null) {
566: br.close();
567: }
568: // reponse code doesn't really apply, since
569: // the soap driver doesn't provide a
570: // response code
571: } catch (IllegalArgumentException exception) {
572: String message = exception.getMessage();
573: log.warn(message);
574: result.setResponseMessage(message);
575: } catch (SAXException exception) {
576: log.warn(exception.toString());
577: result.setResponseMessage(exception.getMessage());
578: } catch (SOAPException exception) {
579: log.warn(exception.toString());
580: result.setResponseMessage(exception.getMessage());
581: } catch (MalformedURLException exception) {
582: String message = exception.getMessage();
583: log.warn(message);
584: result.setResponseMessage(message);
585: } catch (IOException exception) {
586: String message = exception.getMessage();
587: log.warn(message);
588: result.setResponseMessage(message);
589: } catch (NoClassDefFoundError error) {
590: log.error("Missing class: ", error);
591: result.setResponseMessage(error.toString());
592: } catch (Exception exception) {
593: if ("javax.mail.MessagingException".equals(exception
594: .getClass().getName())) {
595: log.warn(exception.toString());
596: result.setResponseMessage(exception.getMessage());
597: } else {
598: throw new RuntimeException(exception);
599: }
600: }
601: return result;
602: }
603:
604: /**
605: * We override this to prevent the wrong encoding and provide no
606: * implementation. We want to reuse the other parts of HTTPSampler, but not
607: * the connection. The connection is handled by the Apache SOAP driver.
608: */
609: public void addEncodedArgument(String name, String value,
610: String metaData) {
611: }
612:
613: public String convertSoapHeaders(Hashtable ht) {
614: Enumeration en = ht.keys();
615: StringBuffer buf = new StringBuffer();
616: while (en.hasMoreElements()) {
617: Object key = en.nextElement();
618: buf
619: .append((String) key)
620: .append("=").append((String) ht.get(key)).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
621: }
622: return buf.toString();
623: }
624:
625: public String getTimeout() {
626: return getPropertyAsString(TIMEOUT);
627: }
628:
629: public int getTimeoutAsInt() {
630: return getPropertyAsInt(TIMEOUT);
631: }
632:
633: public void setTimeout(String text) {
634: setProperty(TIMEOUT, text);
635: }
636: }
|