package org.jacorb.orb; /* * JacORB - a free Java ORB * * Copyright (C) 1997-2014 Gerald Brose. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ import java.util.Set; import org.jacorb.config.Configurable; import org.jacorb.config.Configuration; import org.jacorb.config.ConfigurationException; import org.jacorb.orb.giop.MessageInputStream; import org.jacorb.orb.giop.ReplyInputStream; import org.jacorb.orb.giop.ReplyPlaceholder; import org.jacorb.orb.portableInterceptor.ClientInterceptorHandler; import org.jacorb.util.SelectorManager; import org.jacorb.util.SelectorRequest; import org.jacorb.util.SelectorRequestCallback; import org.jacorb.util.Time; import org.omg.CORBA.CompletionStatus; import org.omg.CORBA.MARSHAL; import org.omg.CORBA.SystemException; import org.omg.CORBA.TIMEOUT; import org.omg.CORBA.portable.ApplicationException; import org.omg.CORBA.portable.InvokeHandler; import org.omg.CORBA.portable.RemarshalException; import org.omg.CORBA.portable.ServantObject; import org.omg.GIOP.ReplyStatusType_1_2; import org.omg.Messaging.ExceptionHolder; import org.omg.PortableInterceptor.ForwardRequest; import org.omg.TimeBase.UtcT; import org.slf4j.Logger; /** * A special ReplyPlaceholder that receives replies to normal requests, * either synchronously or asynchronously. A ReplyReceiver * handles all ORB-internal work that needs to be done for the reply, * such as checking for exceptions and invoking the interceptors. * The client stub can either do a blocking wait on the ReplyReceiver * (via getReply()), or a ReplyHandler can be supplied when the * ReplyReceiver is created; then the reply is delivered to that * ReplyHandler. * * @author Andre Spiegel {@literal <spiegel@gnu.org>} */ public final class ReplyReceiver extends ReplyPlaceholder implements Configurable { private final org.jacorb.orb.Delegate delegate; private final ClientInterceptorHandler interceptors; private final org.omg.Messaging.ReplyHandler replyHandler; private final String operation; private final Timer timer; private final SelectorTimer selectorTimer; private final SelectorRequest timeoutRequest; private final SelectorManager selectorManager; private UtcT replyEndTime = null; private Logger logger; private ReplyGroup group; /** configuration properties */ private boolean retry_on_failure = false; public ReplyReceiver( org.jacorb.orb.Delegate delegate, ReplyGroup group, String operation, org.omg.TimeBase.UtcT replyEndTime, ClientInterceptorHandler interceptors, org.omg.Messaging.ReplyHandler replyHandler, SelectorManager selectorManager) { this.group = group; this.delegate = delegate; this.operation = operation; this.interceptors = interceptors; this.replyHandler = replyHandler; this.replyEndTime = replyEndTime; this.selectorManager = selectorManager; if (replyEndTime != null) { if (selectorManager == null) { selectorTimer = null; timeoutRequest = null; timer = new Timer(replyEndTime); timer.setName("ReplyReceiver Timer" ); timer.start(); } else { timer = null; selectorTimer = new SelectorTimer (); long duration = org.jacorb.util.Time.millisTo (replyEndTime); timeoutRequest = new SelectorRequest (selectorTimer, System.nanoTime() + duration*1000000); selectorManager.add (timeoutRequest); } } else { timer = null; selectorTimer = null; timeoutRequest = null; } } @Override public void configure(Configuration configuration) throws ConfigurationException { super.configure (configuration); logger = configuration.getLogger("org.jacorb.orb.rep_recv"); retry_on_failure = configuration.getAttributeAsBoolean("jacorb.connection.client.retry_on_failure", false); } @Override public void replyReceived( MessageInputStream in ) { if (timeoutException) { return; // discard reply } if (replyEndTime != null) { if (selectorTimer != null) { selectorManager.remove(timeoutRequest); selectorTimer.wakeup (); } else { timer.wakeup(); } } if (group != null) { Set<ReplyPlaceholder> pending = group.getReplies(); // grab pending_replies lock BEFORE my own, // then I will already have it in the replyDone call below. synchronized ( pending ) { // This internal synchronization prevents a deadlock // when a timeout and a reply coincide, suggested // by Jimmy Wilson, 2005-01. It is only a temporary // work-around though, until I can simplify this entire // logic much more thoroughly, AS. synchronized (lock) { if (timeoutException) { return; // discard reply } this.in = in; pending.remove (this); if (replyHandler != null) { // asynchronous delivery performCallback ((ReplyInputStream)in); } else { // synchronous delivery ready = true; lock.notifyAll(); } } } } else { synchronized (lock) { if (timeoutException) { return; // discard reply } this.in = in; if (replyHandler != null) { // asynchronous delivery performCallback ((ReplyInputStream)in); } else { // synchronous delivery ready = true; lock.notifyAll(); } } } } private void performCallback ( ReplyInputStream reply ) { /** * Calls to interceptors are now done in the servant_preinvoke * method. When handling local calls using the servant_preinvoke * and servant_postinvoke method the pre invocation interceptor * calls are done in servant_preinvoke, the invocation is then * made and the server side response interception calls are done * in the normalCompletion/exceptionalCompletion methods in the * ServantObject. These methods are normally called by the stubs * but in this case there is no stub so the calls must be made * here. The servant_postinvoke method will call the client * interception points according to the reply e.g. successful, * exception etc. */ org.omg.CORBA.portable.Delegate replyHandlerDelegate = ( ( org.omg.CORBA.portable.ObjectImpl ) replyHandler ) ._get_delegate(); ServantObject so = replyHandlerDelegate.servant_preinvoke( replyHandler, operation, InvokeHandler.class ); try { switch ( reply.getStatus().value() ) { case ReplyStatusType_1_2._NO_EXCEPTION: { ((InvokeHandler)so.servant) ._invoke( operation, reply, new DummyResponseHandler() ); break; } case ReplyStatusType_1_2._USER_EXCEPTION: case ReplyStatusType_1_2._SYSTEM_EXCEPTION: { ExceptionHolderImpl holder = new ExceptionHolderImpl((ORB) delegate.orb(null), reply ); org.omg.CORBA_2_3.ORB orb = ( org.omg.CORBA_2_3.ORB )replyHandlerDelegate.orb( null ); orb.register_value_factory ( "IDL:omg.org/Messaging/ExceptionHolder:1.0", new ExceptionHolderFactory((ORB)orb) ); CDRInputStream input = new CDRInputStream( orb, holder.marshal() ); ((InvokeHandler)so.servant) ._invoke( operation + "_excep", input, new DummyResponseHandler() ); break; } } if (so instanceof org.omg.CORBA.portable.ServantObjectExt) { ( (org.omg.CORBA.portable.ServantObjectExt)so).normalCompletion(); } } catch ( Exception e ) { logger.warn("Exception during callback", e); if (so instanceof org.omg.CORBA.portable.ServantObjectExt) { ( (org.omg.CORBA.portable.ServantObjectExt) so).exceptionalCompletion (e); } } finally { replyHandlerDelegate.servant_postinvoke( replyHandler, so ); } } /** * There's a lot of code duplication in this method right now. * This should be merged with performCallback() above. */ private void performExceptionCallback (ExceptionHolderImpl holder) { /** * Calls to interceptors are now done in the servant_preinvoke * method. When handling local calls using the servant_preinvoke * and servant_postinvoke method the pre invocation interceptor * calls are done in servant_preinvoke, the invocation is then * made and the server side response interception calls are done * in the normalCompletion/exceptionalCompletion methods in the * ServantObject. These methods are normally called by the stubs * but in this case there is no stub so the calls must be made * here. The servant_postinvoke method will call the client * interception points according to the reply e.g. successful, * exception etc. */ org.omg.CORBA.portable.Delegate replyHandlerDelegate = ( ( org.omg.CORBA.portable.ObjectImpl ) replyHandler ) ._get_delegate(); ServantObject so = replyHandlerDelegate.servant_preinvoke( replyHandler, operation, InvokeHandler.class ); try { org.omg.CORBA_2_3.ORB orb = ( org.omg.CORBA_2_3.ORB )replyHandlerDelegate .orb( null ); orb.register_value_factory ( "IDL:omg.org/Messaging/ExceptionHolder:1.0", new ExceptionHolderFactory((ORB) orb)); CDRInputStream input = new CDRInputStream( orb, holder.marshal() ); ((InvokeHandler)so.servant) ._invoke( operation + "_excep", input, new DummyResponseHandler() ); if (so instanceof org.omg.CORBA.portable.ServantObjectExt) { ( (org.omg.CORBA.portable.ServantObjectExt)so).normalCompletion(); } } catch ( Exception e ) { if (logger.isWarnEnabled()) { logger.warn("Exception during callback: " + e.toString() ); } if (so instanceof org.omg.CORBA.portable.ServantObjectExt) { ( (org.omg.CORBA.portable.ServantObjectExt) so).exceptionalCompletion (e); } } finally { replyHandlerDelegate.servant_postinvoke( replyHandler, so ); } } /** * This method blocks until a reply becomes available. * If the reply contains any exceptions, they are rethrown. */ public synchronized ReplyInputStream getReply() throws RemarshalException, ApplicationException { try { // On NT connection closure due to service shutdown is not // detected until this point, resulting in a COMM_FAILURE. // Map to RemarshalException to force rebind attempt. try { getInputStream (replyEndTime != null); // block until reply is available } catch (org.omg.CORBA.COMM_FAILURE ex) { if (retry_on_failure) { throw new RemarshalException(); } //rethrow throw ex; } } catch ( SystemException se ) { try { interceptors.handle_receive_exception( se ); } catch (ForwardRequest fwd) { //should not happen with a remote request } throw se; } catch ( RemarshalException re ) { // Wait until the thread that received the actual // forward request rebound the Delegate group.waitOnBarrier(); throw new RemarshalException(); } final ReplyInputStream reply = ( ReplyInputStream ) in; final ReplyStatusType_1_2 status = reply.getStatus(); switch ( status.value() ) { case ReplyStatusType_1_2._NO_EXCEPTION: { try { interceptors.handle_receive_reply ( reply ); } catch (ForwardRequest fwd) { // should not happen with a remote request } checkTimeout(); return reply; } case ReplyStatusType_1_2._USER_EXCEPTION: { ApplicationException ae = getApplicationException ( reply ); try { interceptors.handle_receive_exception( ae, reply ); } catch (ForwardRequest fwd) { // should not happen with a remote request } checkTimeout(); throw ae; } case ReplyStatusType_1_2._SYSTEM_EXCEPTION: { SystemException se = SystemExceptionHelper.read ( reply ); try { interceptors.handle_receive_exception( se, reply ); } catch (ForwardRequest fwd) { // should not happen with a remote request } checkTimeout(); throw se; } case ReplyStatusType_1_2._LOCATION_FORWARD: case ReplyStatusType_1_2._LOCATION_FORWARD_PERM: { org.omg.CORBA.Object forward_reference = reply.read_Object(); try { interceptors.handle_location_forward( reply, forward_reference ); } catch (ForwardRequest fwd) { // should not happen with a remote request } checkTimeout(); doRebind( forward_reference ); throw new RemarshalException(); } case ReplyStatusType_1_2._NEEDS_ADDRESSING_MODE: { throw new org.omg.CORBA.NO_IMPLEMENT( "WARNING: Got reply status NEEDS_ADDRESSING_MODE " + "(not implemented)." ); } default: { throw new MARSHAL ("Received unexpected reply status: " + status.value() ); } } } /** * This method is used to check that if there is a timeout set for this request that is * has elapsed while an interceptor was invoked. */ private void checkTimeout() { if (replyEndTime != null && Time.hasPassed (replyEndTime)) { throw new TIMEOUT("Reply End Time exceeded", 3, CompletionStatus.COMPLETED_NO); } } private void doRebind ( org.omg.CORBA.Object forward_reference ) { // make other threads that have unreturned replies wait group.lockBarrier(); try { // tell every pending request to remarshal // they will be blocked on the barrier group.retry(); // do the actual rebind delegate.forwardToObj ( forward_reference ); } finally { // now other threads can safely remarshal group.openBarrier(); } } private ApplicationException getApplicationException ( ReplyInputStream reply ) { reply.mark( 0 ); String id = reply.read_string(); try { reply.reset(); } catch ( java.io.IOException ioe ) { logger.error("unexpected Exception in reset()", ioe ); } return new ApplicationException( id, reply ); } /** * A ResponseHandler that is passed to the ReplyHandler's POA * when we invoke it. Since ReplyHandler operations never generate * replies, this ResponseHandler does nothing to this effect. * The createReply() method, however, is the last method that * is called before control goes to the ReplyHandler servant, * so we use it to check for timing constraints. */ private class DummyResponseHandler implements org.omg.CORBA.portable.ResponseHandler { @Override public org.omg.CORBA.portable.OutputStream createReply() { // the latest possible time at which we can do this Time.waitFor (delegate.getReplyStartTime()); return null; } @Override public org.omg.CORBA.portable.OutputStream createExceptionReply() { return null; } } private static class ExceptionHolderFactory implements org.omg.CORBA.portable.ValueFactory { private final ORB orb; public ExceptionHolderFactory(ORB orb) { this.orb = orb; } @Override public java.io.Serializable read_value ( org.omg.CORBA_2_3.portable.InputStream is ) { ExceptionHolder result = new ExceptionHolderImpl(orb); result._read( is ); return result; } } /** * This class implements timeouts while we are waiting for * replies. When it is instantiated, it takes a CORBA UtcT * constructor parameter that specifies the timeout expiration * time. The timer starts running as soon as the Thread is * started. When the timeout goes off, this Timer makes sure * that the enclosing ReplyReceiver is deactivated, and that * everybody associated with it is notified appropriately. * The timeout can be cancelled by calling wakeup() on a Timer. */ private class Timer extends Thread { private final UtcT endTime; private boolean awakened = false; public Timer (UtcT endTime) { super("ReplyReceiverTimer"); this.endTime = endTime; } @Override public void run() { synchronized (lock) { timeoutException = false; if (!awakened) { long time = org.jacorb.util.Time.millisTo (endTime); if (time > 0) { try { lock.wait (time); } catch (InterruptedException ex) { logger.info("Interrupted while waiting for timeout"); } } if (!awakened) { timeoutException = true; if (replyHandler != null) { ExceptionHolderImpl exHolder = new ExceptionHolderImpl((ORB)delegate.orb(null), new org.omg.CORBA.TIMEOUT()); performExceptionCallback(exHolder); } ready = true; lock.notifyAll(); } } } } public void wakeup() { synchronized (lock) { awakened = true; timeoutException = false; lock.notifyAll(); } } } /** * And alternative timer helper. This one integrates with the * SelectorManager framework, extending the SelectorRequestCallback. * The timer is computed as a wait limit for the selector when the * timeout event is registered. * When the timeout goes off, this Timer makes sure * that the enclosing ReplyReceiver is deactivated, and that * everybody associated with it is notified appropriately. * The timeout can be cancelled by calling wakeup() on a Timer. */ class SelectorTimer extends SelectorRequestCallback { private boolean awakened = false; @Override public boolean call (SelectorRequest request) { if (logger.isDebugEnabled()) { logger.debug ("Request callback. Request type: " + request.type.toString() + ", request status: " + request.status.toString()); } synchronized (lock) { if (request.status == SelectorRequest.Status.EXPIRED) { if (!awakened) { timeoutException = true; if (replyHandler != null) { ExceptionHolderImpl exHolder = new ExceptionHolderImpl((ORB)delegate.orb(null), new org.omg.CORBA.TIMEOUT()); performExceptionCallback(exHolder); } } } else { // something bad happened (SHUTDOWN, FAILED throw a COM_FAILURE) communicationException = true; } ready = true; lock.notifyAll(); } return false; } private void wakeup () { synchronized (lock) { awakened = true; timeoutException = false; lock.notifyAll(); } } } }