Coverage Report - org.mule.transport.http.HttpMessageReceiver
 
Classes in this File Line Coverage Branch Coverage Complexity
HttpMessageReceiver
93%
65/70
78%
31/40
2.826
HttpMessageReceiver$HttpWorker
83%
106/127
76%
44/58
2.826
 
 1  
 /*
 2  
  * $Id: HttpMessageReceiver.java 12117 2008-06-20 09:40:51Z dirk.olmes $
 3  
  * --------------------------------------------------------------------------------------
 4  
  * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.com
 5  
  *
 6  
  * The software in this package is published under the terms of the CPAL v1.0
 7  
  * license, a copy of which has been included with this distribution in the
 8  
  * LICENSE.txt file.
 9  
  */
 10  
 
 11  
 package org.mule.transport.http;
 12  
 
 13  
 import org.mule.DefaultMuleEvent;
 14  
 import org.mule.DefaultMuleMessage;
 15  
 import org.mule.DefaultMuleSession;
 16  
 import org.mule.MuleServer;
 17  
 import org.mule.NullSessionHandler;
 18  
 import org.mule.OptimizedRequestContext;
 19  
 import org.mule.RequestContext;
 20  
 import org.mule.api.MessagingException;
 21  
 import org.mule.api.MuleEvent;
 22  
 import org.mule.api.MuleException;
 23  
 import org.mule.api.MuleMessage;
 24  
 import org.mule.api.config.MuleProperties;
 25  
 import org.mule.api.endpoint.EndpointURI;
 26  
 import org.mule.api.endpoint.ImmutableEndpoint;
 27  
 import org.mule.api.endpoint.InboundEndpoint;
 28  
 import org.mule.api.lifecycle.CreateException;
 29  
 import org.mule.api.service.Service;
 30  
 import org.mule.api.transformer.TransformerException;
 31  
 import org.mule.api.transport.Connector;
 32  
 import org.mule.api.transport.MessageAdapter;
 33  
 import org.mule.api.transport.MessageReceiver;
 34  
 import org.mule.transport.ConnectException;
 35  
 import org.mule.transport.NullPayload;
 36  
 import org.mule.transport.http.i18n.HttpMessages;
 37  
 import org.mule.transport.tcp.TcpConnector;
 38  
 import org.mule.transport.tcp.TcpMessageReceiver;
 39  
 import org.mule.util.MapUtils;
 40  
 import org.mule.util.ObjectUtils;
 41  
 import org.mule.util.monitor.Expirable;
 42  
 
 43  
 import java.io.IOException;
 44  
 import java.net.Socket;
 45  
 import java.net.SocketAddress;
 46  
 import java.util.HashMap;
 47  
 import java.util.Iterator;
 48  
 import java.util.Map;
 49  
 
 50  
 import javax.resource.spi.work.Work;
 51  
 
 52  
 import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
 53  
 
 54  
 import org.apache.commons.httpclient.Cookie;
 55  
 import org.apache.commons.httpclient.Header;
 56  
 import org.apache.commons.httpclient.cookie.MalformedCookieException;
 57  
 import org.apache.commons.logging.Log;
 58  
 import org.apache.commons.logging.LogFactory;
 59  
 
 60  
 /**
 61  
  * <code>HttpMessageReceiver</code> is a simple http server that can be used to
 62  
  * listen for HTTP requests on a particular port.
 63  
  */
 64  1975
 public class HttpMessageReceiver extends TcpMessageReceiver
 65  
 {
 66  130
     protected final Log logger = LogFactory.getLog(getClass());
 67  
 
 68  
     public HttpMessageReceiver(Connector connector, Service service, InboundEndpoint endpoint)
 69  
             throws CreateException
 70  
     {
 71  130
         super(connector, service, endpoint);
 72  130
     }
 73  
 
 74  
     // @Override
 75  
     protected Work createWork(Socket socket) throws IOException
 76  
     {
 77  152
         return new HttpWorker(socket);
 78  
     }
 79  
 
 80  
     // @Override
 81  
     protected void doConnect() throws ConnectException
 82  
     {
 83  
         // If we already have an endpoint listening on this socket don't try and
 84  
         // start another serversocket
 85  120
         if (this.shouldConnect())
 86  
         {
 87  116
             super.doConnect();
 88  
         }
 89  120
     }
 90  
 
 91  
     protected boolean shouldConnect()
 92  
     {
 93  120
         StringBuffer requestUri = new StringBuffer(80);
 94  120
         requestUri.append(endpoint.getProtocol()).append("://");
 95  120
         requestUri.append(endpoint.getEndpointURI().getHost());
 96  120
         requestUri.append(':').append(endpoint.getEndpointURI().getPort());
 97  120
         requestUri.append('*');
 98  
 
 99  120
         MessageReceiver[] receivers = connector.getReceivers(requestUri.toString());
 100  239
         for (int i = 0; i < receivers.length; i++)
 101  
         {
 102  123
             if (receivers[i].isConnected())
 103  
             {
 104  4
                 return false;
 105  
             }
 106  
         }
 107  
 
 108  116
         return true;
 109  
     }
 110  
 
 111  
 
 112  
     // @Override
 113  
     protected MuleMessage handleUnacceptedFilter(MuleMessage message)
 114  
     {
 115  2
         if (logger.isDebugEnabled())
 116  
         {
 117  0
             logger.debug("Message request '" + message.getProperty(HttpConnector.HTTP_REQUEST_PROPERTY)
 118  
                     + "' is being rejected since it does not match the filter on this endpoint: " + endpoint);
 119  
         }
 120  2
         message.setProperty(HttpConnector.HTTP_STATUS_PROPERTY, String.valueOf(HttpConstants.SC_NOT_ACCEPTABLE));
 121  2
         return message;
 122  
     }
 123  
 
 124  
     protected class HttpWorker implements Work, Expirable
 125  
     {
 126  
         private HttpServerConnection conn;
 127  
         private String cookieSpec;
 128  
         private boolean enableCookies;
 129  
         private String remoteClientAddress;
 130  
 
 131  
         public HttpWorker(Socket socket) throws IOException
 132  152
         {
 133  152
             String encoding = endpoint.getEncoding();
 134  152
             if (encoding == null)
 135  
             {
 136  0
                 encoding = MuleServer.getMuleContext().getConfiguration().getDefaultEncoding();
 137  
             }
 138  
 
 139  152
             conn = new HttpServerConnection(socket, encoding, (HttpConnector) connector);
 140  
 
 141  152
             cookieSpec =
 142  
                     MapUtils.getString(endpoint.getProperties(), HttpConnector.HTTP_COOKIE_SPEC_PROPERTY,
 143  
                             ((HttpConnector) connector).getCookieSpec());
 144  152
             enableCookies =
 145  
                     MapUtils.getBooleanValue(endpoint.getProperties(), HttpConnector.HTTP_ENABLE_COOKIES_PROPERTY,
 146  
                             ((HttpConnector) connector).isEnableCookies());
 147  
 
 148  152
             final SocketAddress clientAddress = socket.getRemoteSocketAddress();
 149  152
             if (clientAddress != null)
 150  
             {
 151  152
                 remoteClientAddress = clientAddress.toString();
 152  
             }
 153  152
         }
 154  
         
 155  
         public void expired()
 156  
         {
 157  0
             if (conn.isOpen())
 158  
             {
 159  0
                 conn.close();
 160  
             }
 161  0
         }
 162  
 
 163  
         public void run()
 164  
         {
 165  152
             long keepAliveTimeout = ((TcpConnector) connector).getKeepAliveTimeout();
 166  
             
 167  
             try
 168  
             {
 169  
                 do
 170  
                 {
 171  158
                     conn.setKeepAlive(false);
 172  
                     
 173  
                     // Only add a monitor if the timeout has been set
 174  158
                     if(keepAliveTimeout > 0)
 175  
                     {
 176  0
                         ((HttpConnector) connector).getKeepAliveMonitor().addExpirable(
 177  
                             keepAliveTimeout, TimeUnit.MILLISECONDS, this);
 178  
                     }
 179  
                     
 180  158
                     HttpRequest request = conn.readRequest();
 181  155
                     if (request == null)
 182  
                     {
 183  3
                         break;
 184  
                     }
 185  
                     
 186  
                     // Ensure that we drop any monitors, we'll add again for the next request
 187  152
                     ((HttpConnector) connector).getKeepAliveMonitor().removeExpirable(this);
 188  
                     
 189  152
                     conn.writeResponse(processRequest(request));
 190  
                 }
 191  152
                 while (conn.isKeepAlive());
 192  
             }
 193  3
             catch (Exception e)
 194  
             {
 195  3
                 handleException(e);
 196  
             }
 197  
             finally
 198  
             {
 199  152
                 logger.info("Closing HTTP connection.");
 200  
 
 201  152
                 if (conn.isOpen())
 202  
                 {
 203  149
                     conn.close();
 204  149
                     conn = null;
 205  
                     
 206  
                     // Ensure that we drop any monitors
 207  149
                     ((HttpConnector) connector).getKeepAliveMonitor().removeExpirable(this);
 208  
                 }
 209  
             }
 210  150
         }
 211  
 
 212  
         protected HttpResponse processRequest(HttpRequest request) throws MuleException, IOException
 213  
         {
 214  152
             RequestLine requestLine = request.getRequestLine();
 215  152
             String method = requestLine.getMethod();
 216  
 
 217  152
             if (method.equals(HttpConstants.METHOD_HEAD))
 218  
             {
 219  2
                 return doHead(requestLine);
 220  
             }
 221  150
             else if (method.equals(HttpConstants.METHOD_GET)
 222  
                     || method.equals(HttpConstants.METHOD_POST)
 223  
                     || method.equals(HttpConstants.METHOD_OPTIONS)
 224  
                     || method.equals(HttpConstants.METHOD_PUT)
 225  
                     || method.equals(HttpConstants.METHOD_DELETE)
 226  
                     || method.equals(HttpConstants.METHOD_TRACE)
 227  
                     || method.equals(HttpConstants.METHOD_CONNECT))
 228  
             {
 229  148
                 return doRequest(request, requestLine);
 230  
             }
 231  
             else
 232  
             {
 233  2
                 return doBad(requestLine);
 234  
             }
 235  
         }
 236  
 
 237  
         protected HttpResponse doHead(RequestLine requestLine) throws MuleException
 238  
         {
 239  2
             MuleMessage message = new DefaultMuleMessage(NullPayload.getInstance());
 240  2
             MuleEvent event = new DefaultMuleEvent(message, endpoint, new DefaultMuleSession(message, new NullSessionHandler(), connector.getMuleContext()), true);
 241  2
             OptimizedRequestContext.unsafeSetEvent(event);
 242  2
             HttpResponse response = new HttpResponse();
 243  2
             response.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_OK);
 244  2
             return transformResponse(response);
 245  
         }
 246  
 
 247  
         protected HttpResponse doRequest(HttpRequest request,
 248  
                                          RequestLine requestLine) throws IOException, MuleException
 249  
         {
 250  148
             Map headers = parseHeaders(request);
 251  
 
 252  
             // TODO Mule 2.0 generic way to set stream message adapter
 253  148
             MessageAdapter adapter = buildStandardAdapter(request, headers);
 254  
 
 255  148
             MuleMessage message = new DefaultMuleMessage(adapter);
 256  
 
 257  148
             if (logger.isDebugEnabled())
 258  
             {
 259  0
                 logger.debug(message.getProperty(HttpConnector.HTTP_REQUEST_PROPERTY));
 260  
             }
 261  
 
 262  
             // determine if the request path on this request denotes a different receiver
 263  148
             MessageReceiver receiver = getTargetReceiver(message, endpoint);
 264  
 
 265  
             HttpResponse response;
 266  
             // the response only needs to be transformed explicitly if
 267  
             // A) the request was not served or B) a null result was returned
 268  148
             if (receiver != null)
 269  
             {
 270  146
                 preRouteMessage(message);
 271  146
                 MuleMessage returnMessage = receiver.routeMessage(message, endpoint.isSynchronous(), null);
 272  
 
 273  
                 Object tempResponse;
 274  146
                 if (returnMessage != null)
 275  
                 {
 276  138
                     tempResponse = returnMessage.getPayload();
 277  
                 }
 278  
                 else
 279  
                 {
 280  8
                     tempResponse = NullPayload.getInstance();
 281  
                 }
 282  
                 // This removes the need for users to explicitly adding the response transformer
 283  
                 // ObjectToHttpResponse in their config
 284  146
                 if (tempResponse instanceof HttpResponse)
 285  
                 {
 286  128
                     response = (HttpResponse)tempResponse;
 287  
                 }
 288  
                 else
 289  
                 {
 290  18
                     response = transformResponse(returnMessage);
 291  
                 }
 292  146
                 response.disableKeepAlive(!((HttpConnector)connector).isKeepAlive());
 293  146
             }
 294  
             else
 295  
             {
 296  2
                 response = buildFailureResponse(requestLine, message);
 297  
             }
 298  148
             return response;
 299  
         }
 300  
 
 301  
         protected HttpResponse doOtherValid(RequestLine requestLine, String method) throws MuleException
 302  
         {
 303  0
             MuleMessage message = new DefaultMuleMessage(NullPayload.getInstance());
 304  0
             MuleEvent event = new DefaultMuleEvent(message, endpoint, new DefaultMuleSession(message, new NullSessionHandler(), connector.getMuleContext()), true);
 305  0
             OptimizedRequestContext.unsafeSetEvent(event);
 306  0
             HttpResponse response = new HttpResponse();
 307  0
             response.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_METHOD_NOT_ALLOWED);
 308  0
             response.setBody(HttpMessages.methodNotAllowed(method).toString() + HttpConstants.CRLF);
 309  0
             return transformResponse(response);
 310  
         }
 311  
 
 312  
         protected HttpResponse doBad(RequestLine requestLine) throws MuleException
 313  
         {
 314  2
             MuleMessage message = new DefaultMuleMessage(NullPayload.getInstance());
 315  2
             MuleEvent event = new DefaultMuleEvent(message, endpoint, new DefaultMuleSession(message, new NullSessionHandler(), connector.getMuleContext()), true);
 316  2
             OptimizedRequestContext.unsafeSetEvent(event);
 317  2
             HttpResponse response = new HttpResponse();
 318  2
             response.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_BAD_REQUEST);
 319  2
             response.setBody(HttpMessages.malformedSyntax().toString() + HttpConstants.CRLF);
 320  2
             return transformResponse(response);
 321  
         }
 322  
 
 323  
         protected MessageAdapter buildStandardAdapter(final HttpRequest request,
 324  
                                                          final Map headers) throws MessagingException, TransformerException, IOException
 325  
         {
 326  148
             final RequestLine requestLine = request.getRequestLine();
 327  
 
 328  148
             sendExpect100(headers, requestLine);
 329  
 
 330  148
             Object body = request.getBody();
 331  148
             if (body == null)
 332  
             {
 333  22
                 body = requestLine.getUri();
 334  
             }
 335  
 
 336  148
             return connector.getMessageAdapter(new Object[]{body, headers});
 337  
         }
 338  
 
 339  
         private void sendExpect100(Map headers, RequestLine requestLine)
 340  
             throws TransformerException, IOException
 341  
         {
 342  
             // respond with status code 100, for Expect handshake
 343  
             // according to rfc 2616 and http 1.1
 344  
             // the processing will continue and the request will be fully
 345  
             // read immediately after
 346  148
             if (HttpConstants.HTTP11.equals(headers.get(HttpConnector.HTTP_VERSION_PROPERTY)))
 347  
             {
 348  
                 // just in case we have something other than String in
 349  
                 // the headers map
 350  148
                 String expectHeaderValue = ObjectUtils.toString(
 351  
                         headers.get(HttpConstants.HEADER_EXPECT)).toLowerCase();
 352  148
                 if (HttpConstants.HEADER_EXPECT_CONTINUE_REQUEST_VALUE.equals(expectHeaderValue))
 353  
                 {
 354  2
                     HttpResponse expected = new HttpResponse();
 355  2
                     expected.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_CONTINUE);
 356  2
                     final DefaultMuleEvent event = new DefaultMuleEvent(new DefaultMuleMessage(expected), endpoint,
 357  
                             new DefaultMuleSession(service, connector.getMuleContext()), true);
 358  2
                     RequestContext.setEvent(event);
 359  2
                     conn.writeResponse(transformResponse(expected));
 360  
                 }
 361  
             }
 362  148
         }
 363  
 
 364  
         protected HttpResponse buildFailureResponse(RequestLine requestLine, MuleMessage message) throws TransformerException
 365  
         {
 366  2
             EndpointURI uri = endpoint.getEndpointURI();
 367  2
             String failedPath = uri.getScheme() + "://" + uri.getHost() + ":" + uri.getPort()
 368  
                     + getRequestPath(message);
 369  
 
 370  2
             if (logger.isDebugEnabled())
 371  
             {
 372  0
                 logger.debug("Failed to bind to " + failedPath);
 373  
             }
 374  
 
 375  2
             HttpResponse response = new HttpResponse();
 376  2
             response.setStatusLine(requestLine.getHttpVersion(), HttpConstants.SC_NOT_FOUND);
 377  2
             response.setBody(HttpMessages.cannotBindToAddress(failedPath).toString());
 378  2
             RequestContext.setEvent(new DefaultMuleEvent(new DefaultMuleMessage(response), endpoint,
 379  
                     new DefaultMuleSession(service, connector.getMuleContext()), true));
 380  
             // The DefaultResponseTransformer will set the necessary headers
 381  2
             return transformResponse(response);
 382  
         }
 383  
 
 384  
         protected Map parseHeaders(HttpRequest request) throws MalformedCookieException
 385  
         {
 386  148
             RequestLine requestLine = request.getRequestLine();
 387  148
             Map headers = new HashMap();
 388  
 
 389  148
             for (Iterator rhi = request.getHeaderIterator(); rhi.hasNext();)
 390  
             {
 391  836
                 Header header = (Header)rhi.next();
 392  836
                 String headerName = header.getName();
 393  836
                 Object headerValue = header.getValue();
 394  
 
 395  
                 // fix Mule headers?
 396  836
                 if (headerName.startsWith("X-MULE"))
 397  
                 {
 398  260
                     headerName = headerName.substring(2);
 399  
                 }
 400  
                 // Parse cookies?
 401  576
                 else if (headerName.equals(HttpConnector.HTTP_COOKIES_PROPERTY))
 402  
                 {
 403  0
                     if (enableCookies)
 404  
                     {
 405  0
                         Cookie[] cookies = CookieHelper.parseCookies(header, cookieSpec);
 406  0
                         if (cookies.length > 0)
 407  
                         {
 408  
                             // yum!
 409  0
                             headerValue = cookies;
 410  
                         }
 411  
                         else
 412  
                         {
 413  
                             // bad cookies?!
 414  
                             continue;
 415  
                         }
 416  
                     }
 417  
                     else
 418  
                     {
 419  
                         // no cookies for you!
 420  
                         continue;
 421  
                     }
 422  
                 }
 423  
 
 424  
                 // accept header & value
 425  836
                 headers.put(headerName, headerValue);
 426  836
             }
 427  
 
 428  148
             headers.put(HttpConnector.HTTP_METHOD_PROPERTY, requestLine.getMethod());
 429  148
             headers.put(HttpConnector.HTTP_REQUEST_PROPERTY, requestLine.getUri());
 430  148
             headers.put(HttpConnector.HTTP_VERSION_PROPERTY, requestLine.getHttpVersion().toString());
 431  148
             headers.put(HttpConnector.HTTP_COOKIE_SPEC_PROPERTY, cookieSpec);
 432  148
             return headers;
 433  
         }
 434  
 
 435  
         protected void preRouteMessage(MuleMessage message)
 436  
         {
 437  146
             message.setProperty(MuleProperties.MULE_REMOTE_CLIENT_ADDRESS, remoteClientAddress);
 438  146
         }
 439  
 
 440  
         public void release()
 441  
         {
 442  0
             conn.close();
 443  0
             conn = null;
 444  0
         }
 445  
     }
 446  
 
 447  
     protected String getRequestPath(MuleMessage message)
 448  
     {
 449  2
         String path = (String) message.getProperty(HttpConnector.HTTP_REQUEST_PROPERTY);
 450  2
         int i = path.indexOf('?');
 451  2
         if (i > -1)
 452  
         {
 453  0
             path = path.substring(0, i);
 454  
         }
 455  2
         return path;
 456  
     }
 457  
 
 458  
     protected MessageReceiver getTargetReceiver(MuleMessage message, ImmutableEndpoint endpoint)
 459  
             throws ConnectException
 460  
     {
 461  148
         String path = (String) message.getProperty(HttpConnector.HTTP_REQUEST_PROPERTY);
 462  148
         int i = path.indexOf('?');
 463  148
         if (i > -1)
 464  
         {
 465  4
             path = path.substring(0, i);
 466  
         }
 467  
 
 468  148
         StringBuffer requestUri = new StringBuffer(80);
 469  148
         if (path.indexOf("://")==-1)
 470  
         {
 471  148
             requestUri.append(endpoint.getProtocol()).append("://");
 472  148
             requestUri.append(endpoint.getEndpointURI().getHost());
 473  148
             requestUri.append(':').append(endpoint.getEndpointURI().getPort());
 474  
         }
 475  
         // first check that there is a receiver on the root address
 476  148
         if (logger.isTraceEnabled())
 477  
         {
 478  0
             logger.trace("Looking up receiver on connector: " + connector.getName() + " with URI key: "
 479  
                     + requestUri.toString());
 480  
         }
 481  
 
 482  148
         MessageReceiver receiver = connector.lookupReceiver(requestUri.toString());
 483  
 
 484  
         // If no receiver on the root and there is a request path, look up the
 485  
         // received based on the root plus request path
 486  148
         if (receiver == null && !"/".equals(path))
 487  
         {
 488  
             // remove anything after the last '/'
 489  94
             int x = path.lastIndexOf('/');
 490  94
             if (x > 1 && path.indexOf('.') > x)
 491  
             {
 492  0
                 requestUri.append(path.substring(0, x));
 493  
             }
 494  
             else
 495  
             {
 496  94
                 requestUri.append(path);
 497  
             }
 498  
 
 499  94
             if (logger.isDebugEnabled())
 500  
             {
 501  0
                 logger.debug("Secondary lookup of receiver on connector: " + connector.getName()
 502  
                         + " with URI key: " + requestUri.toString());
 503  
             }
 504  
 
 505  
             // try again
 506  94
             String uriStr = requestUri.toString();
 507  94
             receiver = connector.lookupReceiver(uriStr);
 508  
 
 509  94
             if (receiver == null)
 510  
             {
 511  10
                                 receiver = findReceiverByStem(connector.getReceivers(), uriStr);
 512  
                         }
 513  
 
 514  94
             if (receiver == null && logger.isWarnEnabled())
 515  
             {
 516  2
                 logger.warn("No receiver found with secondary lookup on connector: " + connector.getName()
 517  
                         + " with URI key: " + requestUri.toString());
 518  2
                 logger.warn("Receivers on connector are: "
 519  
                         + MapUtils.toString(connector.getReceivers(), true));
 520  
             }
 521  
         }
 522  
 
 523  148
         return receiver;
 524  
     }
 525  
 
 526  
     protected HttpResponse transformResponse(Object response) throws TransformerException
 527  
     {
 528  
         MuleMessage message;
 529  26
         if(response instanceof MuleMessage)
 530  
         {
 531  10
             message = (MuleMessage)response;
 532  
         }
 533  
         else
 534  
         {
 535  16
             message = new DefaultMuleMessage(response);
 536  
         }
 537  
         //TODO RM*: Maybe we can have a generic Transformer wrapper rather that using DefaultMuleMessage (or another static utility
 538  
         //class
 539  26
         message.applyTransformers(connector.getDefaultResponseTransformers(), HttpResponse.class);
 540  26
         return (HttpResponse) message.getPayload();
 541  
     }
 542  
 
 543  
     public static MessageReceiver findReceiverByStem(Map receivers, String uriStr)
 544  
     {
 545  10
         int match = 0;
 546  10
         MessageReceiver receiver = null;
 547  10
         for (Iterator itr = receivers.entrySet().iterator(); itr.hasNext();)
 548  
         {
 549  16
             Map.Entry e = (Map.Entry)itr.next();
 550  16
             String key = (String)e.getKey();
 551  16
             MessageReceiver candidate = (MessageReceiver)e.getValue();
 552  16
             if (uriStr.startsWith(key) && match < key.length())
 553  
             {
 554  8
                 match = key.length();
 555  8
                 receiver = candidate;
 556  
             }
 557  16
         }
 558  10
         return receiver;
 559  
     }
 560  
 
 561  
 }