/******************************************************************************* * Copyright (c) 2001, 2005 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jem.internal.proxy.remote; /* */ import java.io.*; import java.net.Socket; import java.util.logging.Level; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jem.internal.proxy.common.remote.Commands; import org.eclipse.jem.internal.proxy.common.remote.TransmitableArray; import org.eclipse.jem.internal.proxy.core.*; /** * This thread handles the actual callback. * It is package protected because no one else should access it. */ class REMCallbackThread extends Thread { final REMConnection fConnection; // A connection to use final REMCallbackRegistry fServer; final REMStandardBeanProxyFactory fFactory; final REMStandardBeanTypeProxyFactory fTypeFactory; final REMProxyFactoryRegistry registry; protected boolean shuttingDown; protected boolean inTransaction; // Is this thread currently participating in a transaction, (i.e. reading/writing), if so then we can't use it for another transaction. /** * Is this callback thread currently participating in a transaction (reading/writing). If so then it can't be used for an * independent new transaction. In other words you can't write/read data using this callback thread's * connection because it is being used by someone else expecting to have exclusive read/write on this connection. * * @return * * @since 1.1.0 */ public boolean inTransaction() { return inTransaction; } /** * Set whether this callback thread is in a transaction or not. If it is in a transaction it cannot be used * for an independent new transaction. In other words you can't write/read data using this callback thread's * connection because it is being used by someone else expecting to have exclusive read/write on this connection. * @param inTransaction * * @since 1.1.0 */ public void setIntransaction(boolean inTransaction) { this.inTransaction = inTransaction; } // Kludge: Bug in Linux 1.3.xxx of JVM. Closing a socket while the socket is being read/accept will not interrupt the // wait. Need to timeout to the socket read/accept before the socket close will be noticed. This has been fixed // in Linux 1.4. So on Linux 1.3 need to put timeouts in on those sockets that can be separately closed while reading/accepting. static boolean LINUX_1_3 = "linux".equalsIgnoreCase(System.getProperty("os.name")) && System.getProperty("java.version","").startsWith("1.3"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ public REMCallbackThread(Socket socket, REMCallbackRegistry server, String name, REMProxyFactoryRegistry registry, boolean noTimeouts) { super(name); fConnection = new REMConnection(socket, true); // No timeouts since this is a server thread. this.registry = registry; fServer = server; fFactory = (REMStandardBeanProxyFactory) registry.getBeanProxyFactory(); fTypeFactory = (REMStandardBeanTypeProxyFactory) registry.getBeanTypeProxyFactory(); } /** * Request the thread to close down. */ public void close() { try { shuttingDown = true; // So that if this is a forced close then it will know not to print error msgs. if (fConnection.fSocket != null) fConnection.fSocket.close(); } catch (Exception e) { } } /* * Return the IREMConnection associated with this callback. Used by * the connection server to return this connection for any requests * made on this thread. * <package-protected> so that only remote proxy can access. */ IREMConnection getConnection() { return fConnection; } public void run() { DataInputStream in = fConnection.in; DataOutputStream out = fConnection.out; InputStream ins = null; boolean shutdown = false; Commands.ValueObject valueObject = new Commands.ValueObject(); // Working value object so not continually recreated. BeanProxyValueSender valueSender = new BeanProxyValueSender(this.fFactory); // Working valuesender so not continually recreated. try { boolean doLoop = true; /** * Note: In the cases below you will see a lot of finally clauses that null variables out. * This is because this is a long running loop, and variables declared within blocks are not * garbage collected until the method is terminated, so these variables once set would never * be GC'd. The nulling at the end of the case makes sure that any of those objects set are * now available for garbage collection when necessary. */ while(doLoop) { byte cmd = 0; try { if (LINUX_1_3) fConnection.fSocket.setSoTimeout(1000); // Linux 1.3 bug, see static LINUX_1_3 above cmd = in.readByte(); if (LINUX_1_3) fConnection.fSocket.setSoTimeout(0); // Linux 1.3 bug, see static LINUX_1_3 above } catch (InterruptedIOException e) { continue; // Timed out, try again } switch (cmd) { case Commands.QUIT_CONNECTION: doLoop = false; break; // Close this connection case Commands.TERMINATE_SERVER: doLoop = false; shutdown = true; // Shutdown everything break; case Commands.CALLBACK: int callID = in.readInt(); // The id of the registered callback to call. int msgID = in.readInt(); // The id of the message to call. Object parm = null; Object result = null; ICallback cb = null; try { // The register callback handler will know how to handle the parm, // it will know if it is an array of proxies, or an object of some kind. fFactory.startTransaction(); // Start a transaction. setIntransaction(true); // Also tell ourselves that we are in a transaction. boolean isProxies = true; try { Commands.readValue(in, valueObject); if (valueObject.type == Commands.ARRAY_IDS) { // It is an array containing IDs, as it normally would be. // However it will become IBeanProxy[]. That is because if ID's // they must be proxies over here. valueSender.initialize(valueObject); Commands.readArray(in, valueObject.anInt, valueSender, valueObject, false); if (valueSender.getException() != null) { close(); // Something wrong, close the thread so next time we get a new one. } parm = valueSender.getArray(); } else { // It is object or null. It could be an array of objects, or one object. isProxies = false; parm = valueObject.getAsObject(); } } finally { setIntransaction(false); fFactory.stopTransaction(); } // Now perform the callback. cb = fServer.getRegisteredCallback(callID); if (cb != null) { // Callback still registered. If proxies, then if first entry is just a proxy, // then we are sending only one. A convienence factor for callbacks. // If not a single entry of IBeanProxy, then send whole array. try { if (isProxies) if (((Object[]) parm).length == 1 && (((Object[]) parm)[0] == null || ((Object[]) parm)[0] instanceof IBeanProxy)) result = cb.calledBack(msgID, (IBeanProxy) ((Object[]) parm)[0]); else result = cb.calledBack(msgID, (Object[]) parm); else result = cb.calledBack(msgID, parm); // We only accept null, IREMBeanProxies, and Object[], where // contents of Object[] are bean proxies. valueObject.set(); if (result instanceof IREMBeanProxy) ((IREMBeanProxy) result).renderBean(valueObject); else if (result instanceof Object[]) { class Retriever implements Commands.ValueRetrieve { int index = 0; Object[] array; Commands.ValueObject worker = new Commands.ValueObject(); public Retriever(Object[] anArray) { array = anArray; } public Commands.ValueObject nextValue() { Object retParm = array[index++]; if (retParm != null) if (retParm instanceof IREMBeanProxy) ((IREMBeanProxy) retParm).renderBean(worker); else if (retParm instanceof TransmitableArray) { // It is another array, create a new // retriever. worker.setArrayIDS( new Retriever(((TransmitableArray) retParm).array), ((TransmitableArray) retParm).array.length, ((TransmitableArray) retParm).componentTypeID); } else { // It's an object. Need to get bean // type so that we can send it. IREMBeanProxy type = (IREMBeanProxy) fTypeFactory.getBeanTypeProxy(retParm.getClass().getName()); if (type == null) throw new IllegalArgumentException(); int classID = type.getID().intValue(); worker.setAsObject(retParm, classID); } else worker.set(); return worker; } }; valueObject.setArrayIDS( new Retriever((Object[]) result), ((Object[]) result).length, Commands.OBJECT_CLASS); } Commands.sendCallbackDoneCommand(out, valueObject, Commands.NO_ERROR); } catch (RuntimeException e) { // Something happened, turn it into an error object // to send back. valueObject.set(e.getClass().getName() + ':' + e.getLocalizedMessage()); Commands.sendCallbackDoneCommand(out, valueObject, Commands.CALLBACK_RUNTIME_EXCEPTION); ProxyPlugin.getPlugin().getLogger().log(e, Level.INFO); // Just log it, but assume safe enough to just go back and wait for next callback request. } } else { valueObject.set(); Commands.sendCallbackDoneCommand(out, valueObject, Commands.CALLBACK_NOT_REGISTERED); // Send error msg back to indicate we weren't registered. } } finally { parm = null; // Clear out for GC to work result = null; cb = null; valueObject.set(); valueSender.clear(); } break; case Commands.CALLBACK_STREAM: // A request for a stream callID = in.readInt(); // The id of the registered callback to call. msgID = in.readInt(); // The id of the message to call. cb = null; try { // Now perform the callback. cb = fServer.getRegisteredCallback(callID); if (cb != null) { // Callback still registered valueObject.set(); Commands.sendCallbackDoneCommand(out, valueObject, Commands.NO_ERROR); // Send null to let it know we've accepted the stream ins = new REMCallbackInputStream(in, out); try { cb.calledBackStream(msgID, ins); } finally { try { ins.close(); // Make sure it's closed. } catch (IOException e) { // Exception while closing, just log it and then mark to end the loop so we close connection too. doLoop = false; ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.ERROR, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "In REMCallbackThread", e)); //$NON-NLS-1$ } } } else { valueObject.set(); Commands.sendCallbackDoneCommand(out, valueObject, Commands.CALLBACK_NOT_REGISTERED); // Send error msg back to indicate we weren't registered. } } finally { cb = null; // Clear out for GC to work ins = null; valueObject.set(); } break; default: // Unknown command. We don't know how long the command is, so we need to close the connection. ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.ERROR, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "REMCallback: Invalid cmd sent="+cmd, null)); //$NON-NLS-1$ doLoop = false; break; } } } catch (EOFException e) { // This is ok. It means that the connection on the other side was terminated. // So just accept this and go down. } catch (Exception e) { if (!shuttingDown) ProxyPlugin.getPlugin().getLogger().log(new Status(IStatus.ERROR, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, "In REMCallbackThread", e)); //$NON-NLS-1$ } finally { if (in != null) try { in.close(); } catch (Exception e) { } if (out != null) try { out.close(); } catch (Exception e) { } close(); } fServer.removeCallbackThread(this); if (shutdown) fServer.requestShutdown(); } }