001: // jTDS JDBC Driver for Microsoft SQL Server and Sybase
002: // Copyright (C) 2004 The jTDS Project
003: //
004: // This library is free software; you can redistribute it and/or
005: // modify it under the terms of the GNU Lesser General Public
006: // License as published by the Free Software Foundation; either
007: // version 2.1 of the License, or (at your option) any later version.
008: //
009: // This library is distributed in the hope that it will be useful,
010: // but WITHOUT ANY WARRANTY; without even the implied warranty of
011: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: // Lesser General Public License for more details.
013: //
014: // You should have received a copy of the GNU Lesser General Public
015: // License along with this library; if not, write to the Free Software
016: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: //
018: package net.sourceforge.jtds.jdbc;
019:
020: import java.io.IOException;
021: import java.io.UnsupportedEncodingException;
022: import java.io.InputStream;
023: import java.io.Reader;
024: import java.math.BigDecimal;
025: import java.math.BigInteger;
026: import net.sourceforge.jtds.util.*;
027:
028: /**
029: * Class to implement an output stream for the server request.
030: * <p>
031: * Implementation note:
032: * <ol>
033: * <li>This class contains methods to write different types of data to the
034: * server request stream in TDS format.
035: * <li>Character translation of String items is carried out.
036: * </ol>
037: *
038: * @author Mike Hutchinson.
039: * @version $Id: RequestStream.java,v 1.18 2005/09/21 21:50:34 ddkilzer Exp $
040: */
041: public class RequestStream {
042: /** The shared network socket. */
043: private final SharedSocket socket;
044: /** The output packet buffer. */
045: private byte[] buffer;
046: /** The offset of the next byte to write. */
047: private int bufferPtr;
048: /** The request packet type. */
049: private byte pktType;
050: /** The unique stream id. */
051: private final int streamId;
052: /** True if stream is closed. */
053: private boolean isClosed;
054: /** The current output buffer size*/
055: private int bufferSize;
056: /** The maximum decimal precision. */
057: private int maxPrecision;
058:
059: /**
060: * Construct a RequestStream object.
061: *
062: * @param socket the shared socket object to write to
063: * @param streamId the unique id for this stream
064: * @param bufferSize the initial buffer size to use (the current network
065: * packet size)
066: * @param maxPrecision the maximum precision for numeric/decimal types
067: */
068: RequestStream(SharedSocket socket, int streamId, int bufferSize,
069: int maxPrecision) {
070: this .streamId = streamId;
071: this .socket = socket;
072: this .bufferSize = bufferSize;
073: this .buffer = new byte[bufferSize];
074: this .bufferPtr = TdsCore.PKT_HDR_LEN;
075: this .maxPrecision = maxPrecision;
076: }
077:
078: /**
079: * Set the output buffer size
080: *
081: * @param size The new buffer size (>= {@link TdsCore#MIN_PKT_SIZE} <= {@link TdsCore#MAX_PKT_SIZE}).
082: */
083: void setBufferSize(int size) {
084: if (size < bufferPtr || size == bufferSize) {
085: return; // Can't shrink buffer size;
086: }
087:
088: if (size < TdsCore.MIN_PKT_SIZE || size > TdsCore.MAX_PKT_SIZE) {
089: throw new IllegalArgumentException(
090: "Invalid buffer size parameter " + size);
091: }
092:
093: byte[] tmp = new byte[size];
094: System.arraycopy(buffer, 0, tmp, 0, bufferPtr);
095: buffer = tmp;
096: }
097:
098: /**
099: * Retrieve the current output packet size.
100: *
101: * @return the packet size as an <code>int</code>.
102: */
103: int getBufferSize() {
104: return bufferSize;
105: }
106:
107: /**
108: * Retrive the maximum decimal precision.
109: *
110: * @return The precision as an <code>int</code>.
111: */
112: int getMaxPrecision() {
113: return this .maxPrecision;
114: }
115:
116: /**
117: * Returns the maximum number of bytes required to output a decimal
118: * given the current {@link #maxPrecision}.
119: *
120: * @return the maximum number of bytes required to output a decimal.
121: */
122: byte getMaxDecimalBytes() {
123: return (byte) ((maxPrecision <= TdsData.DEFAULT_PRECISION_28) ? 13
124: : 17);
125: }
126:
127: /**
128: * Retrieve the unique stream id.
129: *
130: * @return the unique stream id as an <code>int</code>.
131: */
132: int getStreamId() {
133: return this .streamId;
134: }
135:
136: /**
137: * Set the current output packet type.
138: *
139: * @param pktType The packet type eg TdsCore.QUERY_PKT.
140: */
141: void setPacketType(byte pktType) {
142: this .pktType = pktType;
143: }
144:
145: /**
146: * Write a byte to the output stream.
147: *
148: * @param b The byte value to write.
149: * @throws IOException
150: */
151: void write(byte b) throws IOException {
152: if (bufferPtr == buffer.length) {
153: putPacket(0);
154: }
155:
156: buffer[bufferPtr++] = b;
157: }
158:
159: /**
160: * Write an array of bytes to the output stream.
161: *
162: * @param b The byte array to write.
163: * @throws IOException
164: */
165: void write(byte[] b) throws IOException {
166: int bytesToWrite = b.length;
167: int off = 0;
168:
169: while (bytesToWrite > 0) {
170: int available = buffer.length - bufferPtr;
171:
172: if (available == 0) {
173: putPacket(0);
174: continue;
175: }
176:
177: int bc = (available > bytesToWrite) ? bytesToWrite
178: : available;
179: System.arraycopy(b, off, buffer, bufferPtr, bc);
180: off += bc;
181: bufferPtr += bc;
182: bytesToWrite -= bc;
183: }
184: }
185:
186: /**
187: * Write a partial byte buffer to the output stream.
188: *
189: * @param b The byte array buffer.
190: * @param off The offset into the byte array.
191: * @param len The number of bytes to write.
192: * @throws IOException
193: */
194: void write(byte[] b, int off, int len) throws IOException {
195: int limit = (off + len) > b.length ? b.length : off + len;
196: int bytesToWrite = limit - off;
197: int i = len - bytesToWrite;
198:
199: while (bytesToWrite > 0) {
200: int available = buffer.length - bufferPtr;
201:
202: if (available == 0) {
203: putPacket(0);
204: continue;
205: }
206:
207: int bc = (available > bytesToWrite) ? bytesToWrite
208: : available;
209: System.arraycopy(b, off, buffer, bufferPtr, bc);
210: off += bc;
211: bufferPtr += bc;
212: bytesToWrite -= bc;
213: }
214:
215: for (; i > 0; i--) {
216: write((byte) 0);
217: }
218: }
219:
220: /**
221: * Write an int value to the output stream.
222: *
223: * @param i The int value to write.
224: * @throws IOException
225: */
226: void write(int i) throws IOException {
227: write((byte) i);
228: write((byte) (i >> 8));
229: write((byte) (i >> 16));
230: write((byte) (i >> 24));
231: }
232:
233: /**
234: * Write a short value to the output stream.
235: *
236: * @param s The short value to write.
237: * @throws IOException
238: */
239: void write(short s) throws IOException {
240: write((byte) s);
241: write((byte) (s >> 8));
242: }
243:
244: /**
245: * Write a long value to the output stream.
246: *
247: * @param l The long value to write.
248: * @throws IOException
249: */
250: void write(long l) throws IOException {
251: write((byte) l);
252: write((byte) (l >> 8));
253: write((byte) (l >> 16));
254: write((byte) (l >> 24));
255: write((byte) (l >> 32));
256: write((byte) (l >> 40));
257: write((byte) (l >> 48));
258: write((byte) (l >> 56));
259: }
260:
261: /**
262: * Write a double value to the output stream.
263: *
264: * @param f The double value to write.
265: * @throws IOException
266: */
267: void write(double f) throws IOException {
268: long l = Double.doubleToLongBits(f);
269:
270: write((byte) l);
271: write((byte) (l >> 8));
272: write((byte) (l >> 16));
273: write((byte) (l >> 24));
274: write((byte) (l >> 32));
275: write((byte) (l >> 40));
276: write((byte) (l >> 48));
277: write((byte) (l >> 56));
278: }
279:
280: /**
281: * Write a float value to the output stream.
282: *
283: * @param f The float value to write.
284: * @throws IOException
285: */
286: void write(float f) throws IOException {
287: int l = Float.floatToIntBits(f);
288:
289: write((byte) l);
290: write((byte) (l >> 8));
291: write((byte) (l >> 16));
292: write((byte) (l >> 24));
293: }
294:
295: /**
296: * Write a String object to the output stream.
297: * If the TDS version is >= 7.0 write a UNICODE string otherwise
298: * wrote a translated byte stream.
299: *
300: * @param s The String to write.
301: * @throws IOException
302: */
303: void write(String s) throws IOException {
304: if (socket.getTdsVersion() >= Driver.TDS70) {
305: int len = s.length();
306:
307: for (int i = 0; i < len; ++i) {
308: int c = s.charAt(i);
309:
310: if (bufferPtr == buffer.length) {
311: putPacket(0);
312: }
313:
314: buffer[bufferPtr++] = (byte) c;
315:
316: if (bufferPtr == buffer.length) {
317: putPacket(0);
318: }
319:
320: buffer[bufferPtr++] = (byte) (c >> 8);
321: }
322: } else {
323: writeAscii(s);
324: }
325: }
326:
327: /**
328: * Write a char array object to the output stream.
329: *
330: * @param s The char[] to write.
331: * @throws IOException
332: */
333: void write(char s[], int off, int len) throws IOException {
334: int i = off;
335: int limit = (off + len) > s.length ? s.length : off + len;
336:
337: for (; i < limit; i++) {
338: char c = s[i];
339:
340: if (bufferPtr == buffer.length) {
341: putPacket(0);
342: }
343:
344: buffer[bufferPtr++] = (byte) c;
345:
346: if (bufferPtr == buffer.length) {
347: putPacket(0);
348: }
349:
350: buffer[bufferPtr++] = (byte) (c >> 8);
351: }
352: }
353:
354: /**
355: * Write a String to the output stream as translated bytes.
356: *
357: * @param s The String to write.
358: * @throws IOException
359: */
360: void writeAscii(String s) throws IOException {
361: String charsetName = socket.getCharset();
362:
363: if (charsetName != null) {
364: try {
365: write(s.getBytes(charsetName));
366: } catch (UnsupportedEncodingException e) {
367: write(s.getBytes());
368: }
369: } else {
370: write(s.getBytes());
371: }
372: }
373:
374: /**
375: * Copy the contents of an InputStream to the server.
376: *
377: * @param in The InputStream to read.
378: * @param length The length of the stream.
379: * @throws IOException
380: */
381: void writeStreamBytes(InputStream in, int length)
382: throws IOException {
383: byte buffer[] = new byte[1024];
384:
385: while (length > 0) {
386: int res = in.read(buffer);
387:
388: if (res < 0) {
389: throw new java.io.IOException(
390: "Data in stream less than specified by length");
391: }
392:
393: write(buffer, 0, res);
394: length -= res;
395: }
396:
397: // XXX Not sure that this is actually an error
398: if (length < 0 || in.read() >= 0) {
399: throw new java.io.IOException(
400: "More data in stream than specified by length");
401: }
402: }
403:
404: /**
405: * Copy the contents of a Reader stream to the server.
406: *
407: * @param in The Reader object with the data.
408: * @param length The length of the data in characters.
409: * @throws IOException
410: */
411: void writeReaderChars(Reader in, int length) throws IOException {
412: char cbuffer[] = new char[512];
413: byte bbuffer[] = new byte[1024];
414:
415: while (length > 0) {
416: int res = in.read(cbuffer);
417:
418: if (res < 0) {
419: throw new java.io.IOException(
420: "Data in stream less than specified by length");
421: }
422:
423: for (int i = 0, j = -1; i < res; i++) {
424: bbuffer[++j] = (byte) cbuffer[i];
425: bbuffer[++j] = (byte) (cbuffer[i] >> 8);
426: }
427:
428: write(bbuffer, 0, res * 2);
429: length -= res;
430: }
431:
432: // XXX Not sure that this is actually an error
433: if (length < 0 || in.read() >= 0) {
434: throw new java.io.IOException(
435: "More data in stream than specified by length");
436: }
437: }
438:
439: /**
440: * Copy the contents of a Reader stream to the server as bytes.
441: * <p>
442: * NB. Only reliable where the charset is single byte.
443: *
444: * @param in The Reader object with the data.
445: * @param length The length of the data in bytes.
446: * @throws IOException
447: */
448: void writeReaderBytes(Reader in, int length) throws IOException {
449: char buffer[] = new char[1024];
450:
451: for (int i = 0; i < length;) {
452: int result = in.read(buffer);
453:
454: if (result == -1) {
455: throw new java.io.IOException(
456: "Data in stream less than specified by length");
457: } else if (i + result > length) {
458: throw new java.io.IOException(
459: "More data in stream than specified by length");
460: }
461:
462: write(Support.encodeString(socket.getCharset(), new String(
463: buffer, 0, result)));
464: i += result;
465: }
466: }
467:
468: /**
469: * Write a BigDecimal value to the output stream.
470: *
471: * @param value The BigDecimal value to write.
472: * @throws IOException
473: */
474: void write(BigDecimal value) throws IOException {
475:
476: if (value == null) {
477: write((byte) 0);
478: } else {
479: byte signum = (byte) (value.signum() < 0 ? 0 : 1);
480: BigInteger bi = value.unscaledValue();
481: byte mantisse[] = bi.abs().toByteArray();
482: byte len = (byte) (mantisse.length + 1);
483:
484: if (len > getMaxDecimalBytes()) {
485: // Should never happen now as value is normalized elsewhere
486: throw new IOException("BigDecimal to big to send");
487: }
488:
489: if (socket.serverType == Driver.SYBASE) {
490: write((byte) len);
491: // Sybase TDS5 stores MSB first opposite sign!
492: // length, prec, scale already sent in parameter descriptor.
493: write((byte) ((signum == 0) ? 1 : 0));
494:
495: for (int i = 0; i < mantisse.length; i++) {
496: write((byte) mantisse[i]);
497: }
498: } else {
499: write((byte) len);
500: write((byte) signum);
501:
502: for (int i = mantisse.length - 1; i >= 0; i--) {
503: write((byte) mantisse[i]);
504: }
505: }
506: }
507: }
508:
509: /**
510: * Flush the packet to the output stream setting the last packet flag.
511: *
512: * @throws IOException
513: */
514: void flush() throws IOException {
515: putPacket(1);
516: }
517:
518: /**
519: * Close the output stream.
520: */
521: void close() {
522: isClosed = true;
523: }
524:
525: /**
526: * Retrieve the TDS version number.
527: *
528: * @return The TDS version as an <code>int</code>.
529: */
530: int getTdsVersion() {
531: return socket.getTdsVersion();
532: }
533:
534: /**
535: * Retrieve the Server type.
536: *
537: * @return The Server type as an <code>int</code>.
538: */
539: int getServerType() {
540: return socket.serverType;
541: }
542:
543: /**
544: * Write the TDS packet to the network.
545: *
546: * @param last Set to 1 if this is the last packet else 0.
547: * @throws IOException
548: */
549: private void putPacket(int last) throws IOException {
550: if (isClosed) {
551: throw new IOException("RequestStream is closed");
552: }
553:
554: buffer[0] = pktType;
555: buffer[1] = (byte) last; // last segment indicator
556: buffer[2] = (byte) (bufferPtr >> 8);
557: buffer[3] = (byte) bufferPtr;
558: buffer[4] = 0;
559: buffer[5] = 0;
560: buffer[6] = (byte) ((socket.getTdsVersion() >= Driver.TDS70) ? 1
561: : 0);
562: buffer[7] = 0;
563:
564: if (Logger.isActive()) {
565: Logger.logPacket(streamId, false, buffer);
566: }
567:
568: buffer = socket.sendNetPacket(streamId, buffer);
569: bufferPtr = TdsCore.PKT_HDR_LEN;
570: }
571: }
|