| /******************************************************************************* |
| * 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; |
| /* |
| * $RCSfile: REMCallbackThread.java,v $ |
| * $Revision: 1.16 $ $Date: 2005/08/24 20:39:06 $ |
| */ |
| |
| 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 (Throwable 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(); |
| } |
| } |