You are viewing a plain text version of this content. The canonical link for it is here.
Posted to axis-cvs@ws.apache.org by ba...@apache.org on 2009/03/27 18:08:52 UTC

svn commit: r759272 - in /webservices/axis2/trunk/java/modules: jaxws/src/org/apache/axis2/jaxws/ jaxws/src/org/apache/axis2/jaxws/core/controller/impl/ jaxws/src/org/apache/axis2/jaxws/handler/ jaxws/src/org/apache/axis2/jaxws/spi/ jaxws/test/org/apac...

Author: barrettj
Date: Fri Mar 27 17:08:51 2009
New Revision: 759272

URL: http://svn.apache.org/viewvc?rev=759272&view=rev
Log:
Cleanup dynamic ports added to a service when the associated service goes out of scope and is finalized.
Fix logic so that if a service delegate is closed and other delegates are using the same service, any dynamic ports that aren't in use by other delegates are released
Add associated tests.

Added:
    webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/util/WeakKey.java
Modified:
    webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/BindingProvider.java
    webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/core/controller/impl/InvocationControllerImpl.java
    webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/handler/HandlerResolverImpl.java
    webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/spi/ServiceDelegate.java
    webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/client/ReleaseServiceTests.java
    webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/description/DescriptionTestUtils2.java
    webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/spi/ClientMetadataTest.java
    webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/impl/ServiceDescriptionImpl.java

Modified: webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/BindingProvider.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/BindingProvider.java?rev=759272&r1=759271&r2=759272&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/BindingProvider.java (original)
+++ webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/BindingProvider.java Fri Mar 27 17:08:51 2009
@@ -54,6 +54,9 @@
 
     protected EndpointDescription endpointDesc;
 
+    // NOTE this reference to the ServiceDelegate MUST be a strong reference to keep the delegate
+    // from being GC'd when the Service instance in the client goes out of scope but ports under
+    // that service are still in use.
     protected ServiceDelegate serviceDelegate;
 
     private org.apache.axis2.jaxws.spi.Binding binding;

Modified: webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/core/controller/impl/InvocationControllerImpl.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/core/controller/impl/InvocationControllerImpl.java?rev=759272&r1=759271&r2=759272&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/core/controller/impl/InvocationControllerImpl.java (original)
+++ webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/core/controller/impl/InvocationControllerImpl.java Fri Mar 27 17:08:51 2009
@@ -336,7 +336,7 @@
         
         // Put the understood header qnames on the request context where it can
         // be found during response processing.
-        if (understoodHeaders != null & understoodHeaders.size() > 0) {
+        if (understoodHeaders != null && understoodHeaders.size() > 0) {
           if (log.isDebugEnabled()) {
             log.debug("Adding understood header QName collection to message context " + understoodHeaders);
           }

Modified: webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/handler/HandlerResolverImpl.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/handler/HandlerResolverImpl.java?rev=759272&r1=759271&r2=759272&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/handler/HandlerResolverImpl.java (original)
+++ webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/handler/HandlerResolverImpl.java Fri Mar 27 17:08:51 2009
@@ -43,6 +43,8 @@
 import javax.xml.ws.handler.LogicalHandler;
 import javax.xml.ws.handler.PortInfo;
 import javax.xml.ws.handler.soap.SOAPHandler;
+
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -68,7 +70,7 @@
       */
 
     private ServiceDescription serviceDesc;
-    private Object serviceDelegateKey;
+    private WeakReference<Object> serviceDelegateKeyReference;
     
     public HandlerResolverImpl(ServiceDescription sd) {
         this(sd, null);
@@ -76,7 +78,9 @@
 
     public HandlerResolverImpl(ServiceDescription sd, Object serviceDelegateKey) { 
         this.serviceDesc = sd;
-        this.serviceDelegateKey = serviceDelegateKey;
+        if (serviceDelegateKey != null) {
+            this.serviceDelegateKeyReference = new WeakReference<Object>(serviceDelegateKey);
+        }
     }
     
     private ResolvedHandlersDescription getResolvedHandlersDescription(PortInfo portInfo) {
@@ -84,17 +88,26 @@
         // On the client the handler information can be changed via service-delegate-specific
         // deployment information.  So, only look into the service-side cache only if the 
         // service delegate key is null.
-        if (serviceDelegateKey == null) {
+        if (getServiceDelegateKey() == null) {
             resolvedHandlersDesc = serviceDesc.getResolvedHandlersDescription(portInfo);
         }
         return resolvedHandlersDesc;
     }
+
+    private Object getServiceDelegateKey() {
+        Object returnKey = null;
+        if (serviceDelegateKeyReference != null) {
+            returnKey = serviceDelegateKeyReference.get();
+        }
+        return returnKey;
+    }
+
     private ResolvedHandlersDescription getOrCreateResolvedHandlersDescription(PortInfo portInfo) {
         ResolvedHandlersDescription resolvedHandlersDesc = null;
         // On the client the handler information can be changed via service-delegate-specific
         // deployment information.  So, only look into the service-side cache only if the 
         // service delegate key is null.
-        if (serviceDelegateKey == null) {
+        if (getServiceDelegateKey() == null) {
             resolvedHandlersDesc = serviceDesc.getResolvedHandlersDescription(portInfo);
             if (resolvedHandlersDesc == null) {
                 resolvedHandlersDesc = DescriptionFactory.createResolvedHandlersDescription();
@@ -281,7 +294,7 @@
         
         // Get the HandlerChains specified on the Endpoint (service-provider) or on the Service
         // (service-requester).
-        handlerChainsType = serviceDesc.getHandlerChain(serviceDelegateKey);  
+        handlerChainsType = serviceDesc.getHandlerChain(getServiceDelegateKey());  
 
         // HandlerChains apply to specific Port Compoments (service-provider) or Ports (
         // (service-requesters) so find the appropriate one.
@@ -303,7 +316,7 @@
             // overrides the annotations as described in the long-winded comment above.
             // -- THEN --
             // Use this handler chains information
-            HandlerChainsType hct_includingComposite = ed.getHandlerChain(serviceDelegateKey);
+            HandlerChainsType hct_includingComposite = ed.getHandlerChain(getServiceDelegateKey());
             HandlerChainsType hct_noComposite = ed.getHandlerChain();
             if (handlerChainsType == null || (hct_includingComposite != hct_noComposite)) {
                 handlerChainsType = hct_includingComposite;

Modified: webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/spi/ServiceDelegate.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/spi/ServiceDelegate.java?rev=759272&r1=759271&r2=759272&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/spi/ServiceDelegate.java (original)
+++ webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/spi/ServiceDelegate.java Fri Mar 27 17:08:51 2009
@@ -901,12 +901,12 @@
         ServiceDelegate returnServiceDelegate = null;
         try {
             try {
-                Field serviceDelgateField = service.getClass().getDeclaredFields()[0];
+                Field serviceDelgateField = service.getClass().getDeclaredField("delegate");
                 serviceDelgateField.setAccessible(true);
                 returnServiceDelegate = (ServiceDelegate) serviceDelgateField.get(service);
-            } catch (ArrayIndexOutOfBoundsException e) {
+            } catch (NoSuchFieldException e) {
                 // This may be a generated service subclass, so get the delegate from the superclass
-                Field serviceDelegateField = service.getClass().getSuperclass().getDeclaredFields()[0];
+                Field serviceDelegateField = service.getClass().getSuperclass().getDeclaredField("delegate");
                 serviceDelegateField.setAccessible(true);
                 returnServiceDelegate = (ServiceDelegate) serviceDelegateField.get(service);
             } 
@@ -920,6 +920,11 @@
                 log.debug("Attempt to get service delegate for service caught exception.", e);
             }
             throw ExceptionFactory.makeWebServiceException(e);
+        } catch (NoSuchFieldException e) {
+            if (log.isDebugEnabled()) {
+                log.debug("Attempt to get service delegate for service caught exception.", e);
+            }
+            throw ExceptionFactory.makeWebServiceException(e);
         }
         return returnServiceDelegate;
 
@@ -957,6 +962,9 @@
     }
     
     protected void finalize() throws Throwable {
+        if (log.isDebugEnabled()) {
+            log.debug("ServiceDelegate.finalize entered for " + this);
+        }
         try {
             releaseServiceResources();
         } catch (Exception e) {
@@ -966,5 +974,8 @@
         } finally {
             super.finalize();
         }
+        if (log.isDebugEnabled()) {
+            log.debug("ServiceDelegate.finalize exited");
+        }
     }   
 }

Modified: webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/client/ReleaseServiceTests.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/client/ReleaseServiceTests.java?rev=759272&r1=759271&r2=759272&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/client/ReleaseServiceTests.java (original)
+++ webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/client/ReleaseServiceTests.java Fri Mar 27 17:08:51 2009
@@ -28,11 +28,13 @@
 
 import javax.jws.WebService;
 import javax.xml.namespace.QName;
+import javax.xml.ws.Dispatch;
 import javax.xml.ws.Service;
 import javax.xml.ws.WebServiceClient;
 import javax.xml.ws.WebServiceException;
 
 import java.io.File;
+import java.lang.reflect.Field;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.HashMap;
@@ -53,7 +55,7 @@
     static final String dynamicPort1 = "dynamicPort1";
     static final String bindingID1 = null;
     static final String epr1 = null;
-
+    
     /**
      * When a ServiceDelegate will not be used anymore, a close call on it should release
      * the AxisServices and such it holds. Verify this for dynamic ports.
@@ -141,16 +143,256 @@
             ClientMetadataTest.restoreOriginalFactory();
         }
     }
+
+    /**
+     * Verify that a service with no BindingProviders created under it will result in the 
+     * associated service delegate being finalized.  BindingProviders are created when a port
+     * is added.  
+     * 
+     * This test only uses a single Service, so the delegate is not shared, 
+     * so the entire Service Description will be released.
+     * 
+     * NOTE: This test is disabled because forcing garbage collection is an inexact science
+     * at best.  You can only ask the JVM to consider doing GC, and that behaves differently
+     * on different JVMS.  So, there's no reliable way to make sure this test runs on various
+     * JVMs.  So, it is disabled.  Note that the test ran successfully on IBM 
+     * java version "1.6.0" and failed on Sun java version "1.5.0_17"
+     */
+    public void _DISABLED_testGarbageCollection() {
+        QName svcQN = new QName(namespaceURI, svcLocalPart);
+        try {
+            ClientMetadataTest.installCachingFactory();
+
+            TestFinalizerService.finalizerCalled = 0;
+            TestFinalizerService svc1 = new TestFinalizerService(svcQN);
+
+            QName portQN1 = new QName(namespaceURI, dynamicPort1);
+            QName portQN2 = new QName(namespaceURI, dynamicPort1 + "_2");
+
+            svc1.addPort(portQN1,bindingID1, epr1);
+            svc1.addPort(portQN2,bindingID1, epr1);
+
+            // Verify that all is as expected in the runtime
+            ServiceDelegate sd1 = DescriptionTestUtils2.getServiceDelegate(svc1);
+            
+            ServiceDescription svcDesc1 = sd1.getServiceDescription();
+            AxisConfiguration axisConfig = svcDesc1.getAxisConfigContext().getAxisConfiguration();
+            
+            EndpointDescription epDesc1_port1 = svcDesc1.getEndpointDescription(portQN1, sd1);
+            AxisService axisSvc1_port1 = epDesc1_port1.getAxisService();
+            EndpointDescription epDesc1_port2 = svcDesc1.getEndpointDescription(portQN2, sd1);
+            AxisService axisSvc1_port2 = epDesc1_port2.getAxisService();
+
+            // Make sure all the AxisServices we expect exist.  When the resources associated
+            // with a port are release below when the service is GC'd, the AxisServices are released.
+            assertTrue(axisConfig.getServiceGroup(axisSvc1_port1.getAxisServiceGroup().getServiceGroupName()) != null);
+            assertTrue(axisConfig.getServiceGroup(axisSvc1_port2.getAxisServiceGroup().getServiceGroupName()) != null);
+
+            // De-reference the Service instance so it can be GC'd
+            svc1 = null;
+
+            // Loop asking the sytem to GC until the service finalizer is called
+            int loop = 0;
+            while (TestFinalizerService.finalizerCalled == 0 && loop++ < 1000) {
+                System.gc();
+            }
+            assertTrue("GC did not occur", loop < 1000);
+            
+            // Make sure the ports were released, which should be driven by the finalizer logic.  
+            // The AxisServices are removed as part of the ports being released. 
+            assertTrue("GC did not occur on delegate on port 1", axisConfig.getServiceGroup(axisSvc1_port1.getAxisServiceGroup().getServiceGroupName()) == null);
+            assertTrue("GC did not occur on delegate on port 2", axisConfig.getServiceGroup(axisSvc1_port2.getAxisServiceGroup().getServiceGroupName()) == null);
+
+        } finally {
+            ClientMetadataTest.restoreOriginalFactory();
+        }
+    }
     
     /**
+     * Verify that service with no active Binding Providers under it results in the 
+     * associated service delegate is finalized.  This test uses multiple identical Services, so 
+     * the delegate is shared.  That means the entire ServiceDescription can't be released when the 
+     * first instance goes out of scope; the specific delegate associated with the de-scoped service 
+     * must be released individually. 
+     * 
+     * NOTE: This test is disabled because forcing garbage collection is an inexact science
+     * at best.  You can only ask the JVM to consider doing GC, and that behaves differently
+     * on different JVMS.  So, there's no reliable way to make sure this test runs on various
+     * JVMs.  So, it is disabled.  Note that the test ran successfully on IBM 
+     * java version "1.6.0" and failed on Sun java version "1.5.0_17"
+     */
+    public void _DISABLED_testGarbageCollectionMultipleService() {
+        QName svcQN = new QName(namespaceURI, svcLocalPart);
+        try {
+            ClientMetadataTest.installCachingFactory();
+
+            TestFinalizerService.finalizerCalled = 0;
+            // We need 2 services we can de-reference and 2 we hold on, to which prevents the
+            // entire ServiceDescription from being released.
+            TestFinalizerService svc1 = new TestFinalizerService(svcQN);
+            TestFinalizerService svc2 = new TestFinalizerService(svcQN);
+            TestFinalizerService svc3 = new TestFinalizerService(svcQN);
+            TestFinalizerService svc4 = new TestFinalizerService(svcQN);
+
+            QName portQN1 = new QName(namespaceURI, dynamicPort1);
+            QName portQN2 = new QName(namespaceURI, dynamicPort1 + "_2");
+            QName portQN3 = new QName(namespaceURI, dynamicPort1 + "_3");
+            QName portQN4 = new QName(namespaceURI, dynamicPort1 + "_4");
+            QName portQN5 = new QName(namespaceURI, dynamicPort1 + "_5");
+            QName portQN6 = new QName(namespaceURI, dynamicPort1 + "_6");
+
+            // The services don't share any ports, so the ports can be released when the service
+            // is de-referenced and finalized.
+            svc1.addPort(portQN1, bindingID1, epr1);
+            svc1.addPort(portQN2, bindingID1, epr1);
+            svc2.addPort(portQN3, bindingID1, epr1);
+            svc2.addPort(portQN4, bindingID1, epr1);
+            svc3.addPort(portQN5, bindingID1, epr1);
+            svc3.addPort(portQN6, bindingID1, epr1);
+
+            ServiceDelegate sd1 = DescriptionTestUtils2.getServiceDelegate(svc1);
+            ServiceDelegate sd2 = DescriptionTestUtils2.getServiceDelegate(svc2);
+            ServiceDelegate sd3 = DescriptionTestUtils2.getServiceDelegate(svc3);
+            
+            // Note that all the delegates will share the same service description
+            ServiceDescription svcDesc = sd1.getServiceDescription();
+            AxisConfiguration axisConfig = svcDesc.getAxisConfigContext().getAxisConfiguration();
+
+            EndpointDescription epDesc1_port1 = svcDesc.getEndpointDescription(portQN1, sd1);
+            AxisService axisSvc1_port1 = epDesc1_port1.getAxisService();
+            EndpointDescription epDesc1_port2 = svcDesc.getEndpointDescription(portQN2, sd1);
+            AxisService axisSvc1_port2 = epDesc1_port2.getAxisService();
+
+            EndpointDescription epDesc2_port3 = svcDesc.getEndpointDescription(portQN3, sd2);
+            AxisService axisSvc2_port3 = epDesc2_port3.getAxisService();
+            EndpointDescription epDesc2_port4 = svcDesc.getEndpointDescription(portQN4, sd2);
+            AxisService axisSvc2_port4 = epDesc2_port4.getAxisService();
+            
+            EndpointDescription epDesc3_port5 = svcDesc.getEndpointDescription(portQN5, sd3);
+            AxisService axisSvc3_port5 = epDesc3_port5.getAxisService();
+            EndpointDescription epDesc3_port6 = svcDesc.getEndpointDescription(portQN6, sd3);
+            AxisService axisSvc3_port6 = epDesc3_port6.getAxisService();
+
+
+            // Make sure all the AxisServices we expect exist.  When the resources associated
+            // with a port are released below after gc, the AxisServices are released.
+            assertTrue(axisConfig.getServiceGroup(axisSvc1_port1.getAxisServiceGroup().getServiceGroupName()) != null);
+            assertTrue(axisConfig.getServiceGroup(axisSvc1_port2.getAxisServiceGroup().getServiceGroupName()) != null);
+
+            // De-scope 2 of the 4 services, which should allow them to be GC'd
+            // Note that this assert keeps Java from optimizing the finalization of these early
+            assertTrue(svc1 != null && svc2 != null && svc3 != null && svc4 != null);
+            svc1 = null;
+            svc2 = null;
+
+            int loop = 0;
+            while (TestFinalizerService.finalizerCalled < 2  && loop++ < 1000) {
+                System.gc();
+            }
+            assertTrue("GC did not occur", loop < 1000);
+            
+            // Make sure the ports were released for services 1 & 2, which should be driven by the finalizer logic.  
+            // The AxisServices are removed as part of the ports being released.
+            assertTrue("GC did not occur on delegate 1 on port 1", axisConfig.getServiceGroup(axisSvc1_port1.getAxisServiceGroup().getServiceGroupName()) == null);
+            assertTrue("GC did not occur on delegate 1 on port 2", axisConfig.getServiceGroup(axisSvc1_port2.getAxisServiceGroup().getServiceGroupName()) == null);
+            assertTrue("GC did not occur on delegate 2 on port 3", axisConfig.getServiceGroup(axisSvc2_port3.getAxisServiceGroup().getServiceGroupName()) == null);
+            assertTrue("GC did not occur on delegate 2 on port 4", axisConfig.getServiceGroup(axisSvc2_port4.getAxisServiceGroup().getServiceGroupName()) == null);
+            // Make sure the ports for service 3 was not released
+            assertTrue("GC should not occur on delegate 3 on port 5", axisConfig.getServiceGroup(axisSvc3_port5.getAxisServiceGroup().getServiceGroupName()) != null);
+            assertTrue("GC should not occur on delegate 3 on port 6", axisConfig.getServiceGroup(axisSvc3_port6.getAxisServiceGroup().getServiceGroupName()) != null);
+            // Need to reference the value of svc3 & svc4 to keep Java from optimizing it and releasing it early
+            assertNotNull(svc3);
+            assertNotNull(svc4);
+        } finally {
+            ClientMetadataTest.restoreOriginalFactory();
+        }
+    }
+
+    /**
+     * Verify that a service with BindingProviders created under it does not result in the 
+     * associated service delegate being finalized until those providers are no longer referenced 
+     * and can be GC'd themselves.
+     * 
+     * This validates the scenario where a client creates a service, then uses that service
+     * to create a port (either a dispatch or a proxy), and then de-references the service while
+     * still retaining a reference to the ports.  One example of this is where a method is called
+     * to create a port, and the service is created within that method, used to create the port
+     * and then goes out of scope when the method returns the port.
+     * 
+     * NOTE: This test is disabled because forcing garbage collection is an inexact science
+     * at best.  You can only ask the JVM to consider doing GC, and that behaves differently
+     * on different JVMS.  So, there's no reliable way to make sure this test runs on various
+     * JVMs.  So, it is disabled.  Note that the test ran successfully on IBM 
+     * java version "1.6.0" and failed on Sun java version "1.5.0_17"
+     */
+    public void _DISABLED_testGarbageCollectionWithProvider() {
+        QName svcQN = new QName(namespaceURI, svcLocalPart);
+        Dispatch<String> port1Dispatch = null;
+        Dispatch<String> port2Dispatch = null;
+        for (int i = 0; i < 3; i++) {
+            System.out.println("Start testGarbageCollectionWithProvider " + i);
+        try {
+            ClientMetadataTest.installCachingFactory();
+
+            TestFinalizerService.finalizerCalled = 0;
+            TestFinalizerService svc1 = new TestFinalizerService(svcQN);
+
+            QName portQN1 = new QName(namespaceURI, dynamicPort1);
+            QName portQN2 = new QName(namespaceURI, dynamicPort1 + "_2");
+
+            svc1.addPort(portQN1,bindingID1, epr1);
+            port1Dispatch = svc1.createDispatch(portQN1, String.class, Service.Mode.PAYLOAD);
+            svc1.addPort(portQN2,bindingID1, epr1);
+            port2Dispatch = svc1.createDispatch(portQN2, String.class, Service.Mode.PAYLOAD);
+            
+            ServiceDelegate sd1 = DescriptionTestUtils2.getServiceDelegate(svc1);
+            
+            ServiceDescription svcDesc1 = sd1.getServiceDescription();
+            AxisConfiguration axisConfig = svcDesc1.getAxisConfigContext().getAxisConfiguration();
+            
+            EndpointDescription epDesc1_port1 = svcDesc1.getEndpointDescription(portQN1, sd1);
+            AxisService axisSvc1_port1 = epDesc1_port1.getAxisService();
+            
+            EndpointDescription epDesc1_port2 = svcDesc1.getEndpointDescription(portQN2, sd1);
+            AxisService axisSvc1_port2 = epDesc1_port2.getAxisService();
+
+            // Make sure all the AxisServices we expect exist.  When the resources associated
+            // with a port are release below after the closes, the AxisServices are released.
+            assertTrue(axisConfig.getServiceGroup(axisSvc1_port1.getAxisServiceGroup().getServiceGroupName()) != null);
+            assertTrue(axisConfig.getServiceGroup(axisSvc1_port2.getAxisServiceGroup().getServiceGroupName()) != null);
+
+            svc1 = null;
+
+            int loop = 0;
+            while (TestFinalizerService.finalizerCalled == 0 && loop++ < 1000) {
+                System.gc();
+            }
+            assertTrue("GC did not occur", loop < 1000);
+            
+            // After the service was released, make sure the ports were NOT released since
+            // there is still an active reference to the ports.
+            assertTrue("GC should not occur on delegate on port 1", axisConfig.getServiceGroup(axisSvc1_port1.getAxisServiceGroup().getServiceGroupName()) != null);
+            assertTrue("GC should not occur on delegate on port 2", axisConfig.getServiceGroup(axisSvc1_port2.getAxisServiceGroup().getServiceGroupName()) != null);
+            
+            // The asserts keep the Java compiler from optimizing the null assignment from 
+            // occurring too early
+            assertNotNull(port1Dispatch);
+            assertNotNull(port2Dispatch);
+        } finally {
+            ClientMetadataTest.restoreOriginalFactory();
+        }
+        }
+
+    }
+    /**
      * Test that creating a large number of services and ports, and then having them released by
      * the finalizers during garbage collections does not produce on Out of Memory Error.
      * 
      * NOTE: This test is disabled because forcing garbage collection is an inexact science
-     * at best.  You can only ask the JVM to consider doing GC, and that behaves differntly
+     * at best.  You can only ask the JVM to consider doing GC, and that behaves differently
      * on different JVMS.  So, there's no reliable way to make sure this test runs on various
-     * JVMs.  So, it is disabled.  See the test that runns in a similar loop creating lots of 
-     * services and ports, and then explicitly calls the relese method.  That test should 
+     * JVMs.  So, it is disabled.  See the test that runs in a similar loop creating lots of 
+     * services and ports, and then explicitly calls the release method.  That test should 
      * reliably and predictably not produce an OOM because of the explicit release call.  
      */
     public void _DISABLED_testServiceReleaseServiceDescriptionFinalizer() {
@@ -324,6 +566,11 @@
             // First close should NOT cleanup the endpoints since the other service is
             // still using them.
             org.apache.axis2.jaxws.spi.ServiceDelegate.releaseService(svc1);
+            // But it should remove the dynamic enpdoits for the closed service delegate
+            EndpointDescription epDesc1_port1_after_close = svcDesc1.getEndpointDescription(portQN1, sd1);
+            assertNull(epDesc1_port1_after_close);
+            EndpointDescription epDesc1_port2_after_close = svcDesc1.getEndpointDescription(portQN2, sd1);
+            assertNull(epDesc1_port2_after_close);
             
             ServiceDescription svcDesc2_afterClose = sd2.getServiceDescription();
             assertSame(svcDesc2, svcDesc2_afterClose);
@@ -371,6 +618,7 @@
         }
     }
     
+
     public void testSeviceUseAfterClose() {
         QName svcQN = new QName(namespaceURI, svcLocalPart);
         try {
@@ -470,7 +718,7 @@
             EndpointDescription epDesc3_port2 = svcDesc3.getEndpointDescription(portQN2, sd3);
             assertSame(epDesc3_port2, epDesc2_port2_afterClose);
 
-            // Close the 2nd delegate and make sure cahced objects are still there
+            // Close the 2nd delegate and make sure cached objects are still there
             // since there's a 3rd delegate now
             org.apache.axis2.jaxws.spi.ServiceDelegate.releaseService(svc2);
 
@@ -547,6 +795,158 @@
             ClientMetadataTest.restoreOriginalFactory();
         }
     }
+    
+    /**
+     * Verify that if multiple service are sharing a service description, the release of a shared
+     * dynamic port happens when there are no more delegates using that port, even if the service
+     * can not be released yet.
+     */
+    public void testMultipleServiceMultipleDynamicPortRelease() {
+        QName svcQN = new QName(namespaceURI, svcLocalPart);
+        try {
+            ClientMetadataTest.installCachingFactory();
+            Service svc1 = Service.create(svcQN);
+            Service svc2 = Service.create(svcQN);
+            Service svc3 = Service.create(svcQN);
+
+            // Verify that when a service is closed, dynamic ports that are being shared with other
+            // still-open services are not released, while ones that are not shared are released.
+            // The ports are used by the services as follows:
+            //
+            //         Service
+            //          svc1    svc2    svc3
+            //  Port
+            //   QN1    x       x
+            //   QN2    x       x
+            //   QN3    x       
+            //   QN4            x       x
+            //   QN5                    x
+            //
+            // Closing the services in the following order should have the following effect:
+            // - close(svc1) should release QN3 only, not QN1 & QN2
+            // - close(svc2) should release QN1 and QN2, not QN4
+            // - close(svc3) should release everything since it is the last service sharing the
+            //   service description.
+            
+            QName portQN1 = new QName(namespaceURI, dynamicPort1);
+            QName portQN2 = new QName(namespaceURI, dynamicPort1 + "_2");
+            QName portQN3 = new QName(namespaceURI, dynamicPort1 + "_3");
+            QName portQN4 = new QName(namespaceURI, dynamicPort1 + "_4");
+            QName portQN5 = new QName(namespaceURI, dynamicPort1 + "_4");
+
+            svc1.addPort(portQN1,bindingID1, epr1);
+            svc1.addPort(portQN2,bindingID1, epr1);
+            svc1.addPort(portQN3,bindingID1, epr1);
+            
+            svc2.addPort(portQN1,bindingID1, epr1);
+            svc2.addPort(portQN2,bindingID1, epr1);
+            svc2.addPort(portQN4,bindingID1, epr1);
+
+            svc3.addPort(portQN4, bindingID1, epr1);
+            svc3.addPort(portQN5, bindingID1, epr1);
+            
+            // Verify that things are as expected and save off information for later asserts
+            // after the closes.
+            
+            // Make sure all the Service Delegates are unique.
+            ServiceDelegate sd1 = DescriptionTestUtils2.getServiceDelegate(svc1);
+            ServiceDelegate sd2 = DescriptionTestUtils2.getServiceDelegate(svc2);
+            ServiceDelegate sd3 = DescriptionTestUtils2.getServiceDelegate(svc3);
+            assertNotSame(sd1, sd2);
+            assertNotSame(sd2, sd3);
+            assertNotSame(sd1, sd3);
+            
+            // Make sure the ServiceDescription is shared across the delegate instances.
+            ServiceDescription svcDesc1 = sd1.getServiceDescription();
+            ServiceDescription svcDesc2 = sd2.getServiceDescription();
+            ServiceDescription svcDesc3 = sd3.getServiceDescription();
+            AxisConfiguration axisConfig = svcDesc1.getAxisConfigContext().getAxisConfiguration();
+            assertSame(svcDesc1, svcDesc2);
+            assertSame(svcDesc1, svcDesc3);
+            // Since the services descriptions are shared, use this in the rest of the test for clarity
+            ServiceDescription svcDesc = svcDesc1;
+            
+            // Make sure the endpoint descriptions for the same ports are shared across the 
+            // delegate instances
+            
+            EndpointDescription epDesc1_port1 = svcDesc.getEndpointDescription(portQN1, sd1);
+            EndpointDescription epDesc2_port1 = svcDesc.getEndpointDescription(portQN1, sd2);
+            assertSame(epDesc1_port1, epDesc2_port1);
+            AxisService axisSvc1_port1 = epDesc1_port1.getAxisService();
+            AxisService axisSvc2_port1 = epDesc2_port1.getAxisService();
+            assertSame(axisSvc1_port1, axisSvc2_port1);
+            AxisService portQN1_AxisService = axisSvc1_port1;
+            AxisService portQN3_AxisService = svcDesc.getEndpointDescription(portQN3, sd1).getAxisService();
+            assertNull(svcDesc.getEndpointDescription(portQN1, sd3));
+            
+            EndpointDescription epDesc1_port2 = svcDesc.getEndpointDescription(portQN2, sd1);
+            EndpointDescription epDesc2_port2 = svcDesc.getEndpointDescription(portQN2, sd2);
+            assertSame(epDesc1_port2, epDesc2_port2);
+            AxisService axisSvc1_port2 = epDesc1_port2.getAxisService();
+            AxisService axisSvc2_port2 = epDesc2_port2.getAxisService();
+            assertSame(axisSvc1_port2, axisSvc2_port2);
+            AxisService portQN2_AxisService = axisSvc1_port2;
+            
+            EndpointDescription epDesc3_port4 = svcDesc.getEndpointDescription(portQN4, sd3);
+            assertNotNull(epDesc3_port4);
+            AxisService portQN4_AxisService = epDesc3_port4.getAxisService();
+            
+            // Make sure all the AxisServices we expect exist.  When the resoureces associated
+            // with a port are release below after the closes, the AxisServices are released.
+            assertTrue(axisConfig.getServiceGroup(portQN1_AxisService.getAxisServiceGroup().getServiceGroupName()) != null);
+            assertTrue(axisConfig.getServiceGroup(portQN2_AxisService.getAxisServiceGroup().getServiceGroupName()) != null);
+            assertTrue(axisConfig.getServiceGroup(portQN3_AxisService.getAxisServiceGroup().getServiceGroupName()) != null);
+            assertTrue(axisConfig.getServiceGroup(portQN4_AxisService.getAxisServiceGroup().getServiceGroupName()) != null);
+
+            // First close
+            org.apache.axis2.jaxws.spi.ServiceDelegate.releaseService(svc1);
+            // Should remove the entries for this delegate
+            assertNull(svcDesc.getEndpointDescription(portQN1, sd1));
+            assertNull(svcDesc.getEndpointDescription(portQN2, sd1));
+            // Should only release port 3 since 1 and 2 are shared and 4 wasn't added to this delegate
+            assertTrue(axisConfig.getServiceGroup(portQN1_AxisService.getAxisServiceGroup().getServiceGroupName()) != null);
+            assertTrue(axisConfig.getServiceGroup(portQN2_AxisService.getAxisServiceGroup().getServiceGroupName()) != null);
+            assertTrue(axisConfig.getServiceGroup(portQN3_AxisService.getAxisServiceGroup().getServiceGroupName()) == null);
+            assertTrue(axisConfig.getServiceGroup(portQN4_AxisService.getAxisServiceGroup().getServiceGroupName()) != null);
+            
+            // Make sure the close didn't change anything unexpected
+            ServiceDescription svcDesc2_afterClose = sd2.getServiceDescription();
+            assertSame(svcDesc2, svcDesc2_afterClose);
+            EndpointDescription epDesc2_port1_afterClose = 
+                svcDesc2_afterClose.getEndpointDescription(portQN1, sd2);
+            assertSame(epDesc2_port1, epDesc2_port1_afterClose);
+            EndpointDescription epDesc2_port2_afterClose = 
+                svcDesc2_afterClose.getEndpointDescription(portQN2, sd2);
+            assertSame(epDesc2_port2, epDesc2_port2_afterClose);
+            
+            // Second close.
+            // This should remove all entries for this delegate
+            // This should cause ports 1 and 2 to be released as both service 1 and 2 that were
+            // using them are now closed.  Service 4 should be unaffected
+            org.apache.axis2.jaxws.spi.ServiceDelegate.releaseService(svc2);
+            assertNull(svcDesc.getEndpointDescription(portQN1, sd2));
+            assertNull(svcDesc.getEndpointDescription(portQN2, sd2));
+            assertNull(svcDesc.getEndpointDescription(portQN4, sd2));
+
+            assertTrue(axisConfig.getServiceGroup(portQN1_AxisService.getAxisServiceGroup().getServiceGroupName()) == null);
+            assertTrue(axisConfig.getServiceGroup(portQN2_AxisService.getAxisServiceGroup().getServiceGroupName()) == null);
+            assertTrue(axisConfig.getServiceGroup(portQN3_AxisService.getAxisServiceGroup().getServiceGroupName()) == null);
+            assertTrue(axisConfig.getServiceGroup(portQN4_AxisService.getAxisServiceGroup().getServiceGroupName()) != null);
+
+            // Make sure the close didn't change anything unexpected
+            ServiceDescription svcDesc3_afterClose = sd3.getServiceDescription();
+            assertSame(svcDesc3_afterClose, svcDesc3);
+
+            // Close the last delegate then verify all the services have been removed 
+            // from the AxisConfiguration 
+            org.apache.axis2.jaxws.spi.ServiceDelegate.releaseService(svc3);
+            HashMap axisServices = axisConfig.getServices();
+            assertEquals(0, axisServices.size());
+            
+        } finally {
+            ClientMetadataTest.restoreOriginalFactory();
+        }
+    }
 
     // =============================================================================================
     // Utility methods
@@ -599,3 +999,24 @@
         super(wsdlLocation, serviceName);
     }
 }
+
+
+/**
+ * Subclasses Service to track how many times the finalize() method is called.  This allows the
+ * tests to tell when the Service instance is garbage collected.   
+ */
+class TestFinalizerService extends javax.xml.ws.Service {
+    static int finalizerCalled = 0;
+    public TestFinalizerService(QName qn) {
+        super(null, qn);
+    }
+
+    protected TestFinalizerService(java.net.URL wsdlDocumentLocation, QName serviceName) {
+        super(wsdlDocumentLocation, serviceName);
+    }
+
+    public void finalize() {
+        finalizerCalled++;
+        
+    }
+}

Modified: webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/description/DescriptionTestUtils2.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/description/DescriptionTestUtils2.java?rev=759272&r1=759271&r2=759272&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/description/DescriptionTestUtils2.java (original)
+++ webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/description/DescriptionTestUtils2.java Fri Mar 27 17:08:51 2009
@@ -91,12 +91,13 @@
         ServiceDelegate returnServiceDelegate = null;
         try {
             try {
-                Field serviceDelgateField = service.getClass().getDeclaredFields()[0];
+//                Field serviceDelgateField = service.getClass().getDeclaredFields()[0];
+                Field serviceDelgateField = service.getClass().getDeclaredField("delegate");
                 serviceDelgateField.setAccessible(true);
                 returnServiceDelegate = (ServiceDelegate) serviceDelgateField.get(service);
-            } catch (ArrayIndexOutOfBoundsException e) {
+            } catch (NoSuchFieldException e) {
                 // This may be a generated service subclass, so get the delegate from the superclass
-                Field serviceDelegateField = service.getClass().getSuperclass().getDeclaredFields()[0];
+                Field serviceDelegateField = service.getClass().getSuperclass().getDeclaredField("delegate");
                 serviceDelegateField.setAccessible(true);
                 returnServiceDelegate = (ServiceDelegate) serviceDelegateField.get(service);
             } 
@@ -106,6 +107,9 @@
         } catch (IllegalAccessException e) {
             // TODO Auto-generated catch block
             e.printStackTrace();
+        } catch (NoSuchFieldException e) { 
+            // TODO Auto-generated catch block
+            e.printStackTrace();
         }
         return returnServiceDelegate;
     }

Modified: webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/spi/ClientMetadataTest.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/spi/ClientMetadataTest.java?rev=759272&r1=759271&r2=759272&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/spi/ClientMetadataTest.java (original)
+++ webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/spi/ClientMetadataTest.java Fri Mar 27 17:08:51 2009
@@ -1098,7 +1098,7 @@
         if (context == null) {
             context = super.getClientConfigurationContext();
         }
-        System.out.println("Test version of MetadataTestCachingClientContextFactory: " + context);
+//        System.out.println("Test version of MetadataTestCachingClientContextFactory: " + context);
         return context;
     }
     

Modified: webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/impl/ServiceDescriptionImpl.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/impl/ServiceDescriptionImpl.java?rev=759272&r1=759271&r2=759272&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/impl/ServiceDescriptionImpl.java (original)
+++ webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/impl/ServiceDescriptionImpl.java Fri Mar 27 17:08:51 2009
@@ -48,6 +48,7 @@
 import org.apache.axis2.jaxws.i18n.Messages;
 import org.apache.axis2.jaxws.util.WSDL4JWrapper;
 import org.apache.axis2.jaxws.util.WSDLWrapper;
+import org.apache.axis2.jaxws.util.WeakKey;
 import org.apache.axis2.AxisFault;
 import org.apache.axis2.engine.AxisConfiguration;
 import org.apache.commons.logging.Log;
@@ -70,6 +71,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.annotation.Annotation;
+import java.lang.ref.ReferenceQueue;
 import java.lang.ref.SoftReference;
 import java.lang.ref.WeakReference;
 import java.net.ConnectException;
@@ -114,11 +116,13 @@
                 new HashMap<QName, EndpointDescription>();
 
     // Endpoints for dynamic ports
-    // This needs to be a strong reference (not Weak or Soft) so that the resource release logic
-    // in the finalizer still has a reference to the EndpointDescriptions so they can be
-    // released.  Otherwise, this collection will be null when the finalizer is called.
-    private Map<Object, Map<QName, EndpointDescriptionImpl>> dynamicEndpointDescriptions =
-                new HashMap<Object, Map<QName, EndpointDescriptionImpl>>();
+    // The WeakKey is an instance of a ServiceDelegate.  It is a Weak Reference so it does not
+    // prevent the service delegate from being GC'd
+    private Map<WeakKey, Map<QName, EndpointDescriptionImpl>> dynamicEndpointDescriptions =
+                new HashMap<WeakKey, Map<QName, EndpointDescriptionImpl>>();
+    // Used to cleanup entries in the dynamic port collection when the ServiceDelegate is
+    // GC'd
+    private ReferenceQueue dynamicPortRefQueue = new ReferenceQueue<WeakKey>();
 
     // Cache classes for the info for resolved handlers
     private SoftReference<Map<PortInfo, ResolvedHandlersDescription>> resolvedHandlersDescription =
@@ -712,8 +716,8 @@
     public Collection<EndpointDescriptionImpl> getDynamicEndpointDescriptions_AsCollection(Object serviceDelegateKey) {
         Collection <EndpointDescriptionImpl> dynamicEndpoints = null;
     	if (serviceDelegateKey != null ) {
-    		if (dynamicEndpointDescriptions.get(serviceDelegateKey) != null)
-    			dynamicEndpoints = dynamicEndpointDescriptions.get(serviceDelegateKey).values();
+    		if (dynamicEndpointDescriptions.get(WeakKey.comparisonKey(serviceDelegateKey)) != null)
+    			dynamicEndpoints = dynamicEndpointDescriptions.get(WeakKey.comparisonKey(serviceDelegateKey)).values();
         }
     	return dynamicEndpoints;
     }
@@ -2323,7 +2327,7 @@
     private EndpointDescriptionImpl getDynamicEndpointDescriptionImpl(QName portQName, Object key) {
         Map<QName, EndpointDescriptionImpl> innerMap = null;
         synchronized(dynamicEndpointDescriptions) {
-        	innerMap = dynamicEndpointDescriptions.get(key);
+        	innerMap = dynamicEndpointDescriptions.get(WeakKey.comparisonKey(key));
             if (innerMap != null) {
             	return innerMap.get(portQName);
             }
@@ -2335,10 +2339,10 @@
     												Object key) {
         Map<QName, EndpointDescriptionImpl> innerMap = null;
         synchronized(dynamicEndpointDescriptions) {
-            innerMap = dynamicEndpointDescriptions.get(key);
+            innerMap = dynamicEndpointDescriptions.get(WeakKey.comparisonKey(key));
             if (innerMap == null) {
                innerMap = new HashMap<QName, EndpointDescriptionImpl>();
-               dynamicEndpointDescriptions.put(key, innerMap);
+               dynamicEndpointDescriptions.put(new WeakKey(key, dynamicPortRefQueue), innerMap);
             }
             innerMap.put(endpointDescriptionImpl.getPortQName(), endpointDescriptionImpl);
         }
@@ -2565,23 +2569,40 @@
 
     public void releaseResources(Object delegate) {
         try {
-        if (log.isDebugEnabled()) {
-            log.debug("ServiceDescription release resources called with delegate " + delegate);
-        }
-        // If the service desc can be removed from the cache, which means no other service delegates
-        // are using it, then we will release the resources associated with it.  If it can't be 
-        // removed because it is still in use, then just return.
-        if (!DescriptionFactoryImpl.removeFromCache(this)) {
             if (log.isDebugEnabled()) {
-                log.debug("ServiceDesc was not removed from cache, so it will not be released");
+                log.debug("ServiceDescription release resources called with delegate " + delegate);
             }
-            return;
-        }
-        
-        if (log.isDebugEnabled()) {
-            log.debug("ServiceDesc was removed from cache, so releasing associated resources");
+
+            // If the entire service desc can be removed from the cache, which means no other service delegates
+            // are using it, then we will release the resources associated with it.  If it can't be 
+            // removed because it is still in use, then release resources it contains associated with
+            // this particular delegate
+            if (DescriptionFactoryImpl.removeFromCache(this)) {
+                if (log.isDebugEnabled()) {
+                    log.debug("ServiceDesc not in use so it was removed from cache.  Releasing all resources for this ServiceDesc");
+                }
+                releaseAllResourcesForServiceDescription();
+            } else {
+                if (log.isDebugEnabled()) {
+                    log.debug("ServiceDesc still in use so not removed from cache.  Releasing resources for delegate");
+                }
+                releaseResourcesForDelegate(delegate);
+            }
+        } catch (Throwable t) {
+            if (log.isDebugEnabled()) {
+                log.debug("Release resorces in ServiceDesc caught throwable ", t);
+            }
+            throw ExceptionFactory.makeWebServiceException(t);
         }
-                
+        return;
+    }    
+    
+    /**
+     * Release all resources for the ServiceDescription. This is done when there are no 
+     * active service delegates which are using the ServiceDescription.  Note that multiple
+     * service delegates can share a single ServiceDescription.
+     */
+    private void releaseAllResourcesForServiceDescription() {
         
         // Close all the endpoint descs, both declared and dynamic
         Collection<EndpointDescription> definedEndpoints = definedEndpointDescriptions.values(); 
@@ -2606,22 +2627,144 @@
             Collection<EndpointDescriptionImpl> dynamicEndpoints = mapEntry.values();
             if (dynamicEndpoints != null && dynamicEndpoints.size() > 0) {
                 for (EndpointDescription endpointDesc : dynamicEndpoints) {
-                    ((EndpointDescriptionImpl) endpointDesc).releaseResources(getAxisConfigContext());
-                    // Remove this endpoint from the list on axis config 
-                    removeFromDynamicEndpointCache(endpointDesc);
+                    releaseEndpoint(endpointDesc);
                 }
             }
         }
         dynamicEndpointDescriptions.clear();
+    }
+
+    /**
+     * Release all the resources associated with a particular endpoint description. 
+     * @param endpointDesc The endpoint for which the resources are to be released.
+     */
+    private void releaseEndpoint(EndpointDescription endpointDesc) {
+        ((EndpointDescriptionImpl) endpointDesc).releaseResources(getAxisConfigContext());
+        // Remove this endpoint from the list on axis config 
+        removeFromDynamicEndpointCache(endpointDesc);
+    }
+    
+    /**
+     * Release resources associated with a specific service delegate.  When a service delegate is
+     * closed, if the entire ServiceDescription can not be released because other services are
+     * still using it, then attempt to release any resources associated with this closed service
+     * delegate.  Currently, the resources which are released are:
+     * - Any unreferenced dynamic ports
+     * 
+     * @param delegate
+     */
+    private void releaseResourcesForDelegate(Object delegate) {
+        // Get the dynamic endpoint entries tied to this delegate and remove the entry.
+        // If no other delegates refer to this port, then it can be released also.
+        synchronized(dynamicEndpointDescriptions) {
+            // Note that if the delegate has already been GC'd, then there will be no entry
+            // matching the delegate in the table, since the WeakKey delegate referent will have
+            // been set to null.
+            Map<QName, EndpointDescriptionImpl> delegateEntry = 
+                dynamicEndpointDescriptions.remove(WeakKey.comparisonKey(delegate));
+            if (delegateEntry != null && delegateEntry.size() > 0) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Removed delegate from dynamic ports: " + delegate);
+                }
+                // There were dynamic ports for this delegate.  Release all ports this delegate was
+                // using that no other delegates still references 
+                releaseUnreferencedDynamicPorts(delegateEntry);
+            }
+            // Remove any entries that were GC'd.    The instance of the WeakKey containing the 
+            // ServiceDelegate that was GC'd is placed on the reference queue, so we use that exact 
+            // key value to remove the entry from the collection.
+            Object gcKey = null;
+            while ((gcKey = dynamicPortRefQueue.poll()) != null) {
+                WeakKey removeKey = (WeakKey) gcKey;
+                if (log.isDebugEnabled()) {
+                    log.debug("Removing GC'd key from dynamic ports: " + removeKey);
+                }
+                Map<QName, EndpointDescriptionImpl> removeEntry = 
+                    dynamicEndpointDescriptions.remove(removeKey);
+                if (removeEntry != null && removeEntry.size() > 0) {
+                    // There were dynamic ports for this delegate.  Release all ports this delegate was
+                    // using that no other delegates still references 
+                    if (log.isDebugEnabled()) {
+                        log.debug("Releasing dynamic ports referenced by GC'd key : " + removeEntry);
+                    }
+                    releaseUnreferencedDynamicPorts(removeEntry);
+                }
+            }
+        }
+    }
+
+    /**
+     * Release any dynamic ports in the collection argument that are not referenced by any
+     * currently-active service delegates.  When a service delegate is closed, it is deleted
+     * from the list of currently-active delegates.  The dynamic ports associated with that service
+     * delegate are passed to this method, which will check all the remaining service delegates
+     * to see if any of the dynamic ports are no longer in use.  Any which are no longer in use
+     * will have the resources held by the EndpointDescriptoinImpl released.
+     * 
+     * IMPORTANT:  This method MUST be called with the within a  
+     * synchronized(dynamicEndpointDescriptions) block to prevent ConcurrentModificationExceptions
+     * 
+     * @param delegateEntry Map containing the dynamic endpoints referenced by a now-deleted
+     * service delegate.
+     */
+    private void releaseUnreferencedDynamicPorts(Map<QName, EndpointDescriptionImpl> delegateEntry) {
         
-        } catch (Throwable t) {
-            if (log.isDebugEnabled()) {
-                log.debug("Release resorces in ServiceDesc caught throwable ", t);
+        // The argument is a colleciton of dynamic port QNames (the key) and the associated 
+        // EndpointDescription for the dynamic port (the value).  For each one of these, we need 
+        // to check the remaining active service delegates to see of the dynamic port is no longer
+        // in use.  If it is not, it can be relesed.
+
+        // Get a list of the (QName, EndpointDescriptionImpl) entries for the delegate that has
+        // been deleted.
+        Set<Map.Entry<QName, EndpointDescriptionImpl>> delegatePortEntries = delegateEntry.entrySet();
+        Iterator<Map.Entry<QName, EndpointDescriptionImpl>> delegatePortEntriesIteator = 
+            delegatePortEntries.iterator();
+
+        // Loop through the (QName, EndpointDescriptionImpl) entries for the deleted delegate
+        // to see if the dynamic port is still being used by any of the remining service 
+        // delegates.
+        while (delegatePortEntriesIteator.hasNext()) {
+            
+            Map.Entry<QName, EndpointDescriptionImpl> delegatePortEntry = 
+                delegatePortEntriesIteator.next();
+            boolean deletePort = true;
+
+            // For this entry, loop through all the still-active service delegates and see 
+            // if this (QName, EndpointDescriptionImpl) still exists.  If not, it can released.
+            Collection<Map<QName, EndpointDescriptionImpl>> activeDelegatePortEntries = 
+                dynamicEndpointDescriptions.values();
+            Iterator<Map<QName, EndpointDescriptionImpl>> activeDelegatePortEntriesIterator =
+                activeDelegatePortEntries.iterator();
+            while (deletePort && activeDelegatePortEntriesIterator.hasNext()) {
+                
+                // Check the dynamic ports for this currently-active service delegate to see
+                // if the it shared the deleted delegate's (QName, EndpointDescriptionImpl)
+                Map<QName, EndpointDescriptionImpl> activeDelegatePorts = 
+                    activeDelegatePortEntriesIterator.next();
+                Set<Map.Entry<QName, EndpointDescriptionImpl>> checkActivePorts = 
+                    activeDelegatePorts.entrySet();
+                Iterator<Map.Entry<QName, EndpointDescriptionImpl>> checkActivePortsIterator =
+                    checkActivePorts.iterator();
+                while (deletePort && checkActivePortsIterator.hasNext()) {
+                    Map.Entry<QName, EndpointDescriptionImpl> checkActivePort = 
+                        checkActivePortsIterator.next();
+                    if (checkActivePort.getKey().equals(delegatePortEntry.getKey())
+                        && checkActivePort.getValue().equals(delegatePortEntry.getValue())) {
+                        deletePort = false;
+                    }
+                }
+            }
+            
+            // The port can be deleted because no other delegates refer to it
+            if (deletePort) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Releasing resources for dynamic port " + delegatePortEntry.getKey());
+                }
+                releaseEndpoint(delegatePortEntry.getValue());
             }
-            throw ExceptionFactory.makeWebServiceException(t);
         }
     }
-    
+
     /**
      * Remove the endpointDescription from the list of dynamic ports held on the
      * AxisConfiguration object.

Added: webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/util/WeakKey.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/util/WeakKey.java?rev=759272&view=auto
==============================================================================
--- webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/util/WeakKey.java (added)
+++ webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/util/WeakKey.java Fri Mar 27 17:08:51 2009
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *      
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.axis2.jaxws.util;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+
+/**
+ * Implement a WeakReference key to be used in a collection.  Being a WeakReference, it will not
+ * prevent the key from being Garbage Collected.  The key can only be created with a reference queue
+ * so that users of this class provide cleanup logic which uses the items in the reference queue to
+ * cleanup entries in the collection.
+ * 
+ * Note that the ReferenceQueue will contain the WeakKey instance that assocaited with the
+ * referent that was GC'd.  So, the elements on the ReferenceQueue can be used to directly access
+ * and remove entries in the collection it is a key for.  For example, one could do something
+ * like the following in cleanup logic:
+ *     Object gcKey = null;
+ *     while ((gcKey = q.poll()) != null) {
+ *         WeakKey wk = (WeakKey) gcKey;
+ *         <collection value type> removedEntry = collection.remove(wk);
+ *         ...
+ *     }
+ */
+public class WeakKey extends WeakReference<Object> {
+    // The hashcode for this object will be based on the key object it is created with 
+    private int keyHashCode = 0;
+
+    // This constructor is private to prevent creating a key without a ReferenceQueue
+    private WeakKey(Object key) {
+        super(key);
+        keyHashCode = calculateHashCode(key);
+    }
+
+    public WeakKey(Object key, ReferenceQueue q) {
+        super(key, q);
+        keyHashCode = calculateHashCode(key);
+    }
+    
+    private static int calculateHashCode(Object r) {
+        return r != null ? r.hashCode() : 0;
+    }
+
+    public boolean equals(Object o) {
+        if (o == null) {
+            return false;
+        } else if (this == o) {
+            return true;
+        } else if (!(o instanceof WeakKey)) {
+            return false;
+        } else {
+            WeakKey checkIt = (WeakKey) o;
+            if (checkIt.get() != null && checkIt.get().equals(this.get())) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    public int hashCode() {
+        return keyHashCode;
+    }
+    
+    /**
+     * Return an instance of WeakKey that can be used in comparsion operations.  For example, it
+     * can be used to lookup a key in a collection that has a WeakKey as the key.
+     * @param checkKey the key value
+     * @return an instance of WeakKey for the value
+     */
+    public static WeakKey comparisonKey(Object checkKey) {
+        return new WeakKey(checkKey);
+    }
+}