001: /*
002: Copyright (C) 2002-2007 MySQL AB
003:
004: This program is free software; you can redistribute it and/or modify
005: it under the terms of version 2 of the GNU General Public License as
006: published by the Free Software Foundation.
007:
008: There are special exceptions to the terms and conditions of the GPL
009: as it is applied to this software. View the full text of the
010: exception in file EXCEPTIONS-CONNECTOR-J in the directory of this
011: software distribution.
012:
013: This program is distributed in the hope that it will be useful,
014: but WITHOUT ANY WARRANTY; without even the implied warranty of
015: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: GNU General Public License for more details.
017:
018: You should have received a copy of the GNU General Public License
019: along with this program; if not, write to the Free Software
020: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021:
022:
023:
024: */
025: package com.mysql.jdbc;
026:
027: import java.io.IOException;
028:
029: import java.lang.reflect.Constructor;
030: import java.lang.reflect.InvocationTargetException;
031: import java.lang.reflect.Method;
032:
033: import java.net.InetAddress;
034: import java.net.Socket;
035: import java.net.SocketException;
036:
037: import java.util.Properties;
038:
039: /**
040: * Socket factory for vanilla TCP/IP sockets (the standard)
041: *
042: * @author Mark Matthews
043: */
044: public class StandardSocketFactory implements SocketFactory {
045:
046: public static final String TCP_NO_DELAY_PROPERTY_NAME = "tcpNoDelay";
047:
048: public static final String TCP_KEEP_ALIVE_DEFAULT_VALUE = "true";
049:
050: public static final String TCP_KEEP_ALIVE_PROPERTY_NAME = "tcpKeepAlive";
051:
052: public static final String TCP_RCV_BUF_PROPERTY_NAME = "tcpRcvBuf";
053:
054: public static final String TCP_SND_BUF_PROPERTY_NAME = "tcpSndBuf";
055:
056: public static final String TCP_TRAFFIC_CLASS_PROPERTY_NAME = "tcpTrafficClass";
057:
058: public static final String TCP_RCV_BUF_DEFAULT_VALUE = "0";
059:
060: public static final String TCP_SND_BUF_DEFAULT_VALUE = "0";
061:
062: public static final String TCP_TRAFFIC_CLASS_DEFAULT_VALUE = "0";
063:
064: public static final String TCP_NO_DELAY_DEFAULT_VALUE = "true";
065:
066: /** Use reflection for pre-1.4 VMs */
067:
068: private static Method setTraficClassMethod;
069:
070: static {
071: try {
072: setTraficClassMethod = Socket.class.getMethod(
073: "setTrafficClass", new Class[] { Integer.TYPE });
074: } catch (SecurityException e) {
075: setTraficClassMethod = null;
076: } catch (NoSuchMethodException e) {
077: setTraficClassMethod = null;
078: }
079: }
080:
081: /** The hostname to connect to */
082: protected String host = null;
083:
084: /** The port number to connect to */
085: protected int port = 3306;
086:
087: /** The underlying TCP/IP socket to use */
088: protected Socket rawSocket = null;
089:
090: /**
091: * Called by the driver after issuing the MySQL protocol handshake and
092: * reading the results of the handshake.
093: *
094: * @throws SocketException
095: * if a socket error occurs
096: * @throws IOException
097: * if an I/O error occurs
098: *
099: * @return The socket to use after the handshake
100: */
101: public Socket afterHandshake() throws SocketException, IOException {
102: return this .rawSocket;
103: }
104:
105: /**
106: * Called by the driver before issuing the MySQL protocol handshake. Should
107: * return the socket instance that should be used during the handshake.
108: *
109: * @throws SocketException
110: * if a socket error occurs
111: * @throws IOException
112: * if an I/O error occurs
113: *
114: * @return the socket to use before the handshake
115: */
116: public Socket beforeHandshake() throws SocketException, IOException {
117: return this .rawSocket;
118: }
119:
120: /**
121: * Configures socket properties based on properties from the connection
122: * (tcpNoDelay, snd/rcv buf, traffic class, etc).
123: *
124: * @param props
125: * @throws SocketException
126: * @throws IOException
127: */
128: private void configureSocket(Socket sock, Properties props)
129: throws SocketException, IOException {
130: try {
131: sock
132: .setTcpNoDelay(Boolean.valueOf(
133: props.getProperty(
134: TCP_NO_DELAY_PROPERTY_NAME,
135: TCP_NO_DELAY_DEFAULT_VALUE))
136: .booleanValue());
137:
138: String keepAlive = props.getProperty(
139: TCP_KEEP_ALIVE_PROPERTY_NAME,
140: TCP_KEEP_ALIVE_DEFAULT_VALUE);
141:
142: if (keepAlive != null && keepAlive.length() > 0) {
143: sock.setKeepAlive(Boolean.valueOf(keepAlive)
144: .booleanValue());
145: }
146:
147: int receiveBufferSize = Integer.parseInt(props.getProperty(
148: TCP_RCV_BUF_PROPERTY_NAME,
149: TCP_RCV_BUF_DEFAULT_VALUE));
150:
151: if (receiveBufferSize > 0) {
152: sock.setReceiveBufferSize(receiveBufferSize);
153: }
154:
155: int sendBufferSize = Integer.parseInt(props.getProperty(
156: TCP_SND_BUF_PROPERTY_NAME,
157: TCP_SND_BUF_DEFAULT_VALUE));
158:
159: if (sendBufferSize > 0) {
160: sock.setSendBufferSize(sendBufferSize);
161: }
162:
163: int trafficClass = Integer.parseInt(props.getProperty(
164: TCP_TRAFFIC_CLASS_PROPERTY_NAME,
165: TCP_TRAFFIC_CLASS_DEFAULT_VALUE));
166:
167: if (trafficClass > 0 && setTraficClassMethod != null) {
168: setTraficClassMethod.invoke(sock,
169: new Object[] { new Integer(trafficClass) });
170: }
171: } catch (Throwable t) {
172: unwrapExceptionToProperClassAndThrowIt(t);
173: }
174: }
175:
176: /**
177: * @see com.mysql.jdbc.SocketFactory#createSocket(Properties)
178: */
179: public Socket connect(String hostname, int portNumber,
180: Properties props) throws SocketException, IOException {
181:
182: if (props != null) {
183: this .host = hostname;
184:
185: this .port = portNumber;
186:
187: Method connectWithTimeoutMethod = null;
188: Method socketBindMethod = null;
189: Class socketAddressClass = null;
190:
191: String localSocketHostname = props
192: .getProperty("localSocketAddress");
193:
194: String connectTimeoutStr = props
195: .getProperty("connectTimeout");
196:
197: int connectTimeout = 0;
198:
199: boolean wantsTimeout = (connectTimeoutStr != null
200: && connectTimeoutStr.length() > 0 && !connectTimeoutStr
201: .equals("0"));
202:
203: boolean wantsLocalBind = (localSocketHostname != null && localSocketHostname
204: .length() > 0);
205:
206: boolean needsConfigurationBeforeConnect = socketNeedsConfigurationBeforeConnect(props);
207:
208: if (wantsTimeout || wantsLocalBind
209: || needsConfigurationBeforeConnect) {
210:
211: if (connectTimeoutStr != null) {
212: try {
213: connectTimeout = Integer
214: .parseInt(connectTimeoutStr);
215: } catch (NumberFormatException nfe) {
216: throw new SocketException("Illegal value '"
217: + connectTimeoutStr
218: + "' for connectTimeout");
219: }
220: }
221:
222: try {
223: // Have to do this with reflection, otherwise older JVMs
224: // croak
225: socketAddressClass = Class
226: .forName("java.net.SocketAddress");
227:
228: connectWithTimeoutMethod = Socket.class.getMethod(
229: "connect", new Class[] {
230: socketAddressClass, Integer.TYPE });
231:
232: socketBindMethod = Socket.class.getMethod("bind",
233: new Class[] { socketAddressClass });
234:
235: } catch (NoClassDefFoundError noClassDefFound) {
236: // ignore, we give a better error below if needed
237: } catch (NoSuchMethodException noSuchMethodEx) {
238: // ignore, we give a better error below if needed
239: } catch (Throwable catchAll) {
240: // ignore, we give a better error below if needed
241: }
242:
243: if (wantsLocalBind && socketBindMethod == null) {
244: throw new SocketException(
245: "Can't specify \"localSocketAddress\" on JVMs older than 1.4");
246: }
247:
248: if (wantsTimeout && connectWithTimeoutMethod == null) {
249: throw new SocketException(
250: "Can't specify \"connectTimeout\" on JVMs older than 1.4");
251: }
252: }
253:
254: if (this .host != null) {
255: if (!(wantsLocalBind || wantsTimeout || needsConfigurationBeforeConnect)) {
256: InetAddress[] possibleAddresses = InetAddress
257: .getAllByName(this .host);
258:
259: Throwable caughtWhileConnecting = null;
260:
261: // Need to loop through all possible addresses, in case
262: // someone has IPV6 configured (SuSE, for example...)
263:
264: for (int i = 0; i < possibleAddresses.length; i++) {
265: try {
266: this .rawSocket = new Socket(
267: possibleAddresses[i], port);
268:
269: configureSocket(this .rawSocket, props);
270:
271: break;
272: } catch (Exception ex) {
273: caughtWhileConnecting = ex;
274: }
275: }
276:
277: if (rawSocket == null) {
278: unwrapExceptionToProperClassAndThrowIt(caughtWhileConnecting);
279: }
280: } else {
281: // must explicitly state this due to classloader issues
282: // when running on older JVMs :(
283: try {
284:
285: InetAddress[] possibleAddresses = InetAddress
286: .getAllByName(this .host);
287:
288: Throwable caughtWhileConnecting = null;
289:
290: Object localSockAddr = null;
291:
292: Class inetSocketAddressClass = null;
293:
294: Constructor addrConstructor = null;
295:
296: try {
297: inetSocketAddressClass = Class
298: .forName("java.net.InetSocketAddress");
299:
300: addrConstructor = inetSocketAddressClass
301: .getConstructor(new Class[] {
302: InetAddress.class,
303: Integer.TYPE });
304:
305: if (wantsLocalBind) {
306: localSockAddr = addrConstructor
307: .newInstance(new Object[] {
308: InetAddress
309: .getByName(localSocketHostname),
310: new Integer(0 /*
311: * use ephemeral
312: * port
313: */) });
314:
315: }
316: } catch (Throwable ex) {
317: unwrapExceptionToProperClassAndThrowIt(ex);
318: }
319:
320: // Need to loop through all possible addresses, in case
321: // someone has IPV6 configured (SuSE, for example...)
322:
323: for (int i = 0; i < possibleAddresses.length; i++) {
324:
325: try {
326: this .rawSocket = new Socket();
327:
328: configureSocket(this .rawSocket, props);
329:
330: Object sockAddr = addrConstructor
331: .newInstance(new Object[] {
332: possibleAddresses[i],
333: new Integer(port) });
334: // bind to the local port, null is 'ok', it
335: // means
336: // use the ephemeral port
337: socketBindMethod.invoke(rawSocket,
338: new Object[] { localSockAddr });
339:
340: connectWithTimeoutMethod
341: .invoke(
342: rawSocket,
343: new Object[] {
344: sockAddr,
345: new Integer(
346: connectTimeout) });
347:
348: break;
349: } catch (Exception ex) {
350: this .rawSocket = null;
351:
352: caughtWhileConnecting = ex;
353: }
354: }
355:
356: if (this .rawSocket == null) {
357: unwrapExceptionToProperClassAndThrowIt(caughtWhileConnecting);
358: }
359:
360: } catch (Throwable t) {
361: unwrapExceptionToProperClassAndThrowIt(t);
362: }
363: }
364:
365: return this .rawSocket;
366: }
367: }
368:
369: throw new SocketException("Unable to create socket");
370: }
371:
372: /**
373: * Does the configureSocket() need to be called before the socket is
374: * connect()d based on the properties supplied?
375: *
376: */
377: private boolean socketNeedsConfigurationBeforeConnect(
378: Properties props) {
379: int receiveBufferSize = Integer.parseInt(props.getProperty(
380: TCP_RCV_BUF_PROPERTY_NAME, TCP_RCV_BUF_DEFAULT_VALUE));
381:
382: if (receiveBufferSize > 0) {
383: return true;
384: }
385:
386: int sendBufferSize = Integer.parseInt(props.getProperty(
387: TCP_SND_BUF_PROPERTY_NAME, TCP_SND_BUF_DEFAULT_VALUE));
388:
389: if (sendBufferSize > 0) {
390: return true;
391: }
392:
393: int trafficClass = Integer.parseInt(props.getProperty(
394: TCP_TRAFFIC_CLASS_PROPERTY_NAME,
395: TCP_TRAFFIC_CLASS_DEFAULT_VALUE));
396:
397: if (trafficClass > 0 && setTraficClassMethod != null) {
398: return true;
399: }
400:
401: return false;
402: }
403:
404: private void unwrapExceptionToProperClassAndThrowIt(
405: Throwable caughtWhileConnecting) throws SocketException,
406: IOException {
407: if (caughtWhileConnecting instanceof InvocationTargetException) {
408:
409: // Replace it with the target, don't use 1.4 chaining as this still
410: // needs to run on older VMs
411: caughtWhileConnecting = ((InvocationTargetException) caughtWhileConnecting)
412: .getTargetException();
413: }
414:
415: if (caughtWhileConnecting instanceof SocketException) {
416: throw (SocketException) caughtWhileConnecting;
417: }
418:
419: if (caughtWhileConnecting instanceof IOException) {
420: throw (IOException) caughtWhileConnecting;
421: }
422:
423: throw new SocketException(caughtWhileConnecting.toString());
424: }
425: }
|