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 2008/08/25 19:34:39 UTC

svn commit: r688787 - in /webservices/axis2/trunk/java/modules: jaxws/src/org/apache/axis2/jaxws/spi/ jaxws/test/org/apache/axis2/jaxws/client/ kernel/src/org/apache/axis2/engine/ metadata/src/org/apache/axis2/jaxws/description/ metadata/src/org/apache...

Author: barrettj
Date: Mon Aug 25 10:34:38 2008
New Revision: 688787

URL: http://svn.apache.org/viewvc?rev=688787&view=rev
Log:
Release client-side resources when the ServiceDelegate is finalized.  These resources include the ServiceDescription, EndpointDescriptions and associated JAX-WS objects.
Also the AxisService related to the EndpointDescriptions.  Note that the ServiceDescritions can be shared across multiple ServiceDelegate instances so the release only happens
when the last ServiceDelegate instances using the ServiceDescription is finalized.

Also added a non-JAX-WS API to the ServiceDelegate, public static void releaseService(Service service), which can be called with the JAX-WS Service instance and will attempt to perform
the release of the resources without having to wait for the finalizers to run.  Note that this API is a NON-STANDARD JAX-WS API and so it is not portable!

Also added associated tests.

Added:
    webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/client/ReleaseServiceTests.java
Modified:
    webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/spi/ServiceDelegate.java
    webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/engine/AxisConfiguration.java
    webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/ServiceDescription.java
    webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/impl/DescriptionFactoryImpl.java
    webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/impl/EndpointDescriptionImpl.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/spi/ServiceDelegate.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/jaxws/src/org/apache/axis2/jaxws/spi/ServiceDelegate.java?rev=688787&r1=688786&r2=688787&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 Mon Aug 25 10:34:38 2008
@@ -55,6 +55,8 @@
 import javax.xml.ws.WebServiceException;
 import javax.xml.ws.WebServiceFeature;
 import javax.xml.ws.handler.HandlerResolver;
+
+import java.lang.reflect.Field;
 import java.lang.reflect.Proxy;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -236,6 +238,7 @@
     // are only suitibale for creating Distpach instances.
     public void addPort(QName portName, String bindingId, String endpointAddress)
             throws WebServiceException {
+        verifyServiceDescriptionActive();
         if (log.isDebugEnabled()) {
             log.debug("Calling addPort : ("
                       + portName + "," + bindingId + "," + endpointAddress + ")");
@@ -270,6 +273,7 @@
 
     @Override
     public <T> Dispatch<T> createDispatch(EndpointReference jaxwsEPR, Class<T> type, Mode mode, WebServiceFeature... features) {
+        verifyServiceDescriptionActive();
         if (jaxwsEPR == null) {
             throw ExceptionFactory
                     .makeWebServiceException(Messages.getMessage("dispatchNoEndpointReference"));
@@ -327,6 +331,7 @@
 
     @Override
     public Dispatch<Object> createDispatch(EndpointReference jaxwsEPR, JAXBContext context, Mode mode, WebServiceFeature... features) {
+        verifyServiceDescriptionActive();
         if (jaxwsEPR == null) {
             throw ExceptionFactory
                     .makeWebServiceException(Messages.getMessage("dispatchNoEndpointReference"));
@@ -375,6 +380,7 @@
 
     @Override
     public <T> Dispatch<T> createDispatch(QName portName, Class<T> type, Mode mode, WebServiceFeature... features) {
+        verifyServiceDescriptionActive();
         if (portName == null) {
             throw ExceptionFactory
                     .makeWebServiceException(Messages.getMessage("createDispatchFail0"));
@@ -420,6 +426,7 @@
 
     @Override
     public Dispatch<Object> createDispatch(QName portName, JAXBContext context, Mode mode, WebServiceFeature... features) {
+        verifyServiceDescriptionActive();
         if (portName == null) {
             throw ExceptionFactory
                     .makeWebServiceException(Messages.getMessage("createDispatchFail0"));
@@ -517,6 +524,7 @@
 
     @Override
     public <T> T getPort(QName portName, Class<T> sei, WebServiceFeature... features) {
+        verifyServiceDescriptionActive();
         /* TODO Check to see if WSDL Location is provided.
          * if not check WebService annotation's WSDLLocation
          * if both are not provided then throw exception.
@@ -596,6 +604,7 @@
     * @see javax.xml.ws.spi.ServiceDelegate#getHandlerResolver()
     */
     public HandlerResolver getHandlerResolver() {
+        verifyServiceDescriptionActive();
         if (handlerResolver == null) {
             handlerResolver = new HandlerResolverImpl(serviceDescription, this);
         }
@@ -607,6 +616,7 @@
     * @see javax.xml.ws.spi.ServiceDelegate#getPorts()
     */
     public Iterator<QName> getPorts() {
+        verifyServiceDescriptionActive();
         return getServiceDescription().getPorts(this).iterator();
     }
 
@@ -623,6 +633,7 @@
     * @see javax.xml.ws.spi.ServiceDelegate#getWSDLDocumentLocation()
     */
     public URL getWSDLDocumentLocation() {
+        verifyServiceDescriptionActive();
         try {
             String wsdlLocation = ((ServiceDescriptionWSDL) serviceDescription).getWSDLLocation();
             if(wsdlLocation == null) {
@@ -671,10 +682,12 @@
      * 
      */
     public ServiceClient getServiceClient(QName portQName) throws WebServiceException {
+        verifyServiceDescriptionActive();
         return serviceDescription.getServiceClient(portQName, this);
     }
     
     public <T> T getPort(org.apache.axis2.addressing.EndpointReference axis2EPR, String addressingNamespace, Class<T> sei, WebServiceFeature... features) {
+        verifyServiceDescriptionActive();
         DescriptionBuilderComposite sparseComposite = getPortMetadata();
         resetPortMetadata();
         EndpointDescription endpointDesc = null;
@@ -749,6 +762,7 @@
 
     // TODO: Remove this method and put the WSDLWrapper methods on the ServiceDescriptor directly
     private WSDLWrapper getWSDLWrapper() {
+        verifyServiceDescriptionActive();
         return ((ServiceDescriptionWSDL)serviceDescription).getWSDLWrapper();
     }
 
@@ -853,4 +867,101 @@
         return classes;
     }
     
+    /**
+     * PROPRIETARY METHOD TO RELEASE RESOUCES.  USE OF THIS METHOD IS NOT JAX-WS COMPLIANT 
+     * AND IS NON-PORTABLE!  
+     * 
+     * This method can be called by client code to try to release
+     * resources associated with the Service instance parameter.  These resources include
+     * the JAX-WS metadata objects (e.g. ServiceDescription, EndpointDescription) and the
+     * associated Axis2 objects (e.g. AxisService and realted objects).  Note that these 
+     * resources can be shared across multiple service delegates, and so they will not actually 
+     * be released until the last service delegate using them is closed.
+     * 
+     * Note that it is not necessary to call this method since the service delegate finalizer
+     * will also release the resources as appropriate.  However, finalizers are not necessarily run
+     * in a timely fashion and the timing varies across JVMs.  To predictibly release resources
+     * to prevent large memory requirements and/or OutOfMemory errors, this proprietary release 
+     * method can be called.
+     * 
+     * @param service Instance of the Service for which resources may be released.
+     */
+    public static void releaseService(Service service) {
+        // Find the ServiceDelegate corresponding to the service to be closed
+        // This is the back way around since there is no close on the Service
+        ServiceDelegate serviceDelegate = getServiceDelegateForService(service);
+        serviceDelegate.releaseServiceResources();
+    }
+    
+    private static ServiceDelegate getServiceDelegateForService(Service service) {
+        // Need to get to the private Service._delegate
+        ServiceDelegate returnServiceDelegate = null;
+        try {
+            try {
+                Field serviceDelgateField = service.getClass().getDeclaredFields()[0];
+                serviceDelgateField.setAccessible(true);
+                returnServiceDelegate = (ServiceDelegate) serviceDelgateField.get(service);
+            } catch (ArrayIndexOutOfBoundsException e) {
+                // This may be a generated service subclass, so get the delegate from the superclass
+                Field serviceDelegateField = service.getClass().getSuperclass().getDeclaredFields()[0];
+                serviceDelegateField.setAccessible(true);
+                returnServiceDelegate = (ServiceDelegate) serviceDelegateField.get(service);
+            } 
+        } catch (SecurityException e) {
+            if (log.isDebugEnabled()) {
+                log.debug("Attempt to get service delegate for service caught exception.", e);
+            }
+            throw ExceptionFactory.makeWebServiceException(e);
+        } catch (IllegalAccessException e) {
+            if (log.isDebugEnabled()) {
+                log.debug("Attempt to get service delegate for service caught exception.", e);
+            }
+            throw ExceptionFactory.makeWebServiceException(e);
+        }
+        return returnServiceDelegate;
+
+    }
+    
+    /**
+     * This can be called from the proprietary static release method (which can be called via 
+     * client code), or it can be called by the finalizer.  This method tries to release resources 
+     * associated with the ServiceDelegate.  Note that since other ServiceDelegates can share these 
+     * resources (e.g. ServiceDescription, EndpointDescription, AxisService), the resources may 
+     * not be releaseed until the last ServiceDelegate using them issues a close.
+     */
+    private void releaseServiceResources() {
+        if (log.isDebugEnabled()) {
+            log.debug("ServiceDelegate.releaseServiceResouces entry");
+        }
+        // This can be called indirectly by client code or by the finalizer.  If it hasn't been
+        // called yet, have the endpointDescriptions release resources.
+        if (serviceDescription != null) {
+            serviceDescription.releaseResources(this);
+            serviceDescription = null;
+        }
+    }
+
+    /**
+     * Verify that there is an associated serviceDescription for this delegate.  If not, a
+     * webServiceException will be thrown.  A serviceDelegate may have a null serviceDescription
+     * if the client code issues the proprietary method call relealseServiceResources. 
+     */
+    private void verifyServiceDescriptionActive() {
+        if (serviceDescription == null) {
+            // TODO: This should be NLS'd
+            throw ExceptionFactory.makeWebServiceException("Attempt to use Service after it was released");
+        }
+    }
+    
+    protected void finalize() throws Throwable {
+        try {
+            releaseServiceResources();
+        } catch (Exception e) {
+            if (log.isDebugEnabled()) {
+                log.debug("ServiceDelgate Finalizer caught exception", e);
+            }
+        } finally {
+            super.finalize();
+        }
+    }   
 }

Added: 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=688787&view=auto
==============================================================================
--- webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/client/ReleaseServiceTests.java (added)
+++ webservices/axis2/trunk/java/modules/jaxws/test/org/apache/axis2/jaxws/client/ReleaseServiceTests.java Mon Aug 25 10:34:38 2008
@@ -0,0 +1,540 @@
+/*
+ * 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.client;
+
+import org.apache.axis2.description.AxisService;
+import org.apache.axis2.engine.AxisConfiguration;
+import org.apache.axis2.jaxws.description.DescriptionTestUtils2;
+import org.apache.axis2.jaxws.description.EndpointDescription;
+import org.apache.axis2.jaxws.description.ServiceDescription;
+import org.apache.axis2.jaxws.spi.ClientMetadataTest;
+import org.apache.axis2.jaxws.spi.ServiceDelegate;
+
+import javax.jws.WebService;
+import javax.xml.namespace.QName;
+import javax.xml.ws.Service;
+import javax.xml.ws.WebServiceException;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+/**
+ * Verify that when a Service (i.e. a JAXWS Service Delegate instance) is no longer needed
+ * the resources it uses are cleaned up (i.e the EndpointDescription, AxisService, etc).
+ * The release can be driven by an explicit proprietary call or via the finalizer on the
+ * ServiceDelegate that is run during garbage collection.
+ */
+public class ReleaseServiceTests extends TestCase {
+    static final String namespaceURI = "http://dispatch.client.jaxws.axis2.apache.org";
+    static final String svcLocalPart = "svcLocalPart";
+    
+    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.
+     */
+    public void testServiceReleaseDynamicPort() {
+        QName svcQN = new QName(namespaceURI, svcLocalPart);
+        try {
+            ClientMetadataTest.installCachingFactory();
+
+            Service svc1 = Service.create(svcQN);
+            QName portQN = new QName(namespaceURI, dynamicPort1);
+            svc1.addPort(portQN, bindingID1, epr1);
+
+            // User internal state to verify the port information before and after the close
+            ServiceDelegate delegate = DescriptionTestUtils2.getServiceDelegate(svc1);
+            ServiceDescription svcDesc = delegate.getServiceDescription();
+            EndpointDescription epDesc= svcDesc.getEndpointDescription(portQN, delegate);
+            assertNotNull(epDesc);
+            AxisConfiguration axisConfig = svcDesc.getAxisConfigContext().getAxisConfiguration();
+            HashMap axisServices = axisConfig.getServices();
+            assertEquals(1, axisServices.size());
+
+            org.apache.axis2.jaxws.spi.ServiceDelegate.releaseService(svc1);
+
+            axisServices = axisConfig.getServices();
+            assertEquals(0, axisServices.size());
+
+            epDesc= svcDesc.getEndpointDescription(portQN, delegate);
+            assertNull(epDesc);                
+
+        } catch (Throwable t) {
+            fail("Caught throwable " + t);
+        } finally {
+            ClientMetadataTest.restoreOriginalFactory();
+        }
+    }
+
+    /**
+     * Simple test to create a service, add quite a few dynamic ports under it, then release
+     * the service. This test is mostly for debugging the release logic outside the tests that 
+     * create a lot of services to test an OOM isn't produced.
+     */
+    public void testServiceReleaseSingleServiceDescriptionRelease() {
+        try {
+            ClientMetadataTest.installCachingFactory();
+
+            QName svcQN = new QName(namespaceURI, svcLocalPart);
+            Service svc1 = Service.create(svcQN);
+            for (int i = 0; i < 100; i++) {
+                QName portQN = new QName(namespaceURI, dynamicPort1 + "_" + i);
+                svc1.addPort(portQN, bindingID1, epr1);
+            }
+            org.apache.axis2.jaxws.spi.ServiceDelegate.releaseService(svc1);
+        } catch (Throwable t) {
+            fail("Caught throwable " + t);
+        } finally {
+            ClientMetadataTest.restoreOriginalFactory();
+        }
+
+    }
+    
+    /**
+     * Create a bunch of services with a bunch of ports under each, closing the service
+     * before the next one is created.  This should release the resources for that service.  The
+     * number of services and ports created is large enough to cause an OOM if the release isn't
+     * being done correctly.
+     */
+    public void testMultipleServiceMultiplePortReleaseLoop() {
+        // Create a bunch of different services, make sure the service desc finalizer is called
+        try {
+            ClientMetadataTest.installCachingFactory();
+
+            for (int i = 0; i < 1000; i++) {
+                QName svcQN = new QName(namespaceURI, svcLocalPart + "_" + i);
+                Service svc1 = Service.create(svcQN);
+                for (int j = 0; j < 200; j++) {
+                    QName portQN = new QName(namespaceURI, dynamicPort1 + "_svc_" + i + "_port_" + j);
+                    svc1.addPort(portQN, bindingID1, epr1);
+                }
+                org.apache.axis2.jaxws.spi.ServiceDelegate.releaseService(svc1);
+                // Give the GC time to run
+                Thread.sleep(500);
+            }
+        } catch (Throwable t) {
+            fail("Caught throwable " + t);
+        } 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
+     * 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 
+     * reliably and predictably not produce an OOM because of the explicit release call.  
+     */
+    public void _DISABLED_testServiceReleaseServiceDescriptionFinalizer() {
+        // Create a bunch of different services, make sure the service desc finalizer is called
+        try {
+            ClientMetadataTest.installCachingFactory();
+
+            final int MAX_OOM_COUNT = 5;
+            int oomCountService = 0;
+            for (int i = 0; i < 1000; i++) {
+                try {
+                    int oomCount = 0;
+                    QName svcQN = new QName(namespaceURI, svcLocalPart + "_" + i);
+                    System.out.println("Creating service " + svcQN);
+                    Service svc1 = Service.create(svcQN);
+                    for (int j = 0; j < 200; j++) {
+                        try {
+                            QName portQN = new QName(namespaceURI, dynamicPort1 + "_svc_" + i + "_port_" + j);
+                            System.out.println("Adding port " + portQN);
+                            svc1.addPort(portQN, bindingID1, epr1);
+                            // Pause every so often to give the garbage collection thread a chance to run
+                            if ((j > 0) && (j % 50) == 0) {
+                                System.out.println("Pausing port add for GC to run");
+                                Thread.sleep(500);
+                            }
+
+                        } catch (OutOfMemoryError e) {
+                            System.out.println("Caught OOM number " + ++oomCount);
+                            if (oomCount <= MAX_OOM_COUNT) {
+                                System.out.println("Sleeping to allow for GC after OOM caught");
+                                Thread.sleep(15000);
+                                System.out.println("Waking up and focing gc");
+                                System.gc();
+                                System.out.println("gc() method returned; continuing loop");
+                            } else {
+                                fail ("Maximum OOM count exceeded " + MAX_OOM_COUNT);
+                            }
+                        }
+                    }
+                    // don't call release; the finalizer should do it
+//                  org.apache.axis2.jaxws.spi.ServiceDelegate.releaseService(svc1);
+
+                    // Pause to give the garbage collection thread a chance to run
+                    System.out.println("Pausing service add for GC to run");
+                    Thread.sleep(500);
+                } catch (OutOfMemoryError e) {
+                    System.out.println("Caught Service OOM number " + ++oomCountService);
+                    if (oomCountService <= MAX_OOM_COUNT) {
+                        System.out.println("Sleeping to allow for GC after Service OOM caught");
+                        Thread.sleep(15000);
+                        System.out.println("Waking up and forcing gc");
+                        System.gc();
+                        System.out.println("Forced gc complete, continuing service loop");
+                    } else {
+                        fail ("Maximum Servcice OOM count exceeded " + MAX_OOM_COUNT);
+                    }
+                }
+            }
+
+        } catch (Throwable t) {
+            System.out.println("Test failed, caught: " + t.toString());
+            t.printStackTrace();
+            fail("Caught throwable " + t);
+        } finally {
+            ClientMetadataTest.restoreOriginalFactory();
+        }
+    }
+    
+    /**
+     * Verify that after a service is released, it can be re-used with the same dynamic ports
+     * being added 
+     */
+    public void testServiceReuseDynamicPort() {
+        QName svcQN = new QName(namespaceURI, svcLocalPart);
+        try {
+            ClientMetadataTest.installCachingFactory();
+
+            Service svc1 = Service.create(svcQN);
+            QName portQN = new QName(namespaceURI, dynamicPort1);
+            svc1.addPort(portQN, bindingID1, epr1);
+
+            // Use internal state to verify all is well
+            ServiceDelegate delegate1 = DescriptionTestUtils2.getServiceDelegate(svc1);
+            ServiceDescription svcDesc1 = delegate1.getServiceDescription();
+            assertNotNull(svcDesc1);
+            EndpointDescription epDesc1= svcDesc1.getEndpointDescription(portQN, delegate1);
+            assertNotNull(epDesc1);
+            AxisService axisService1 = epDesc1.getAxisService();
+            assertNotNull(axisService1);
+            AxisConfiguration axisConfig1 = svcDesc1.getAxisConfigContext().getAxisConfiguration();
+            HashMap axisServices1 = axisConfig1.getServices();
+            assertEquals(1, axisServices1.size());
+
+            // Close the delegate, which should release resources and remove objects from caches
+            org.apache.axis2.jaxws.spi.ServiceDelegate.releaseService(svc1);
+            
+            Service svc2 = Service.create(svcQN);
+            svc2.addPort(portQN, bindingID1, epr1);
+
+            // Use internal state to verify all is well; compare to values from the first time
+            // around to make sure the cache values for things like the ServiceDescription got 
+            // cleared out when the last ServiceDelegate (in the test the only one) relesaed
+            // the resources.
+            ServiceDelegate delegate2 = DescriptionTestUtils2.getServiceDelegate(svc2);
+            assertNotSame(delegate1, delegate2);
+            ServiceDescription svcDesc2 = delegate2.getServiceDescription();
+            assertNotNull(svcDesc2);
+            assertNotSame(svcDesc1, svcDesc2);
+            EndpointDescription epDesc2= svcDesc2.getEndpointDescription(portQN, delegate2);
+            assertNotNull(epDesc2);
+            assertNotSame(epDesc1, epDesc2);
+            AxisService axisService2 = epDesc2.getAxisService();
+            assertNotNull(axisService2);
+            assertNotSame(axisService1, axisService2);
+            AxisConfiguration axisConfig2 = svcDesc2.getAxisConfigContext().getAxisConfiguration();
+            HashMap axisServices2 = axisConfig2.getServices();
+            assertEquals(1, axisServices2.size());
+            
+            // Verify the service from the map and EndpointDesc are the same
+            // Since there's only one element in the map, we can get it directly off the iterator
+            assertSame(axisService2, ((Map.Entry) axisServices2.entrySet().iterator().next()).getValue());
+
+        } finally {
+            ClientMetadataTest.restoreOriginalFactory();
+        }
+        
+    }
+    
+    /**
+     * Verify that if multiple service are sharing a service description, the release of
+     * resources does not happen on the first close.
+     */
+    public void testMultipleServiceMultiplePortRelease() {
+        QName svcQN = new QName(namespaceURI, svcLocalPart);
+        try {
+            ClientMetadataTest.installCachingFactory();
+            Service svc1 = Service.create(svcQN);
+            Service svc2 = Service.create(svcQN);
+
+            QName portQN1 = new QName(namespaceURI, dynamicPort1);
+            QName portQN2 = new QName(namespaceURI, dynamicPort1 + "_2");
+            svc1.addPort(portQN1,bindingID1, epr1);
+            svc1.addPort(portQN2, bindingID1, epr1);
+
+            svc2.addPort(portQN1,bindingID1, epr1);
+            svc2.addPort(portQN2, bindingID1, epr1);
+            
+            ServiceDelegate sd1 = DescriptionTestUtils2.getServiceDelegate(svc1);
+            ServiceDelegate sd2 = DescriptionTestUtils2.getServiceDelegate(svc2);
+            assertNotSame(sd1, sd2);
+            
+            ServiceDescription svcDesc1 = sd1.getServiceDescription();
+            ServiceDescription svcDesc2 = sd2.getServiceDescription();
+            AxisConfiguration axisConfig = svcDesc1.getAxisConfigContext().getAxisConfiguration();
+            assertSame(svcDesc1, svcDesc2);
+            
+            EndpointDescription epDesc1_port1 = svcDesc1.getEndpointDescription(portQN1, sd1);
+            EndpointDescription epDesc2_port1 = svcDesc1.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);
+
+            EndpointDescription epDesc1_port2 = svcDesc1.getEndpointDescription(portQN2, sd1);
+            EndpointDescription epDesc2_port2 = svcDesc1.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);
+
+            // First close should NOT cleanup the endpoints since the other service is
+            // still using them.
+            org.apache.axis2.jaxws.spi.ServiceDelegate.releaseService(svc1);
+            
+            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);
+            
+            // Add a third, should use the same
+            Service svc3 = Service.create(svcQN);
+            svc3.addPort(portQN1,bindingID1, epr1);
+            svc3.addPort(portQN2, bindingID1, epr1);
+            ServiceDelegate sd3 = DescriptionTestUtils2.getServiceDelegate(svc3);
+            assertNotSame(sd2, sd3);
+            ServiceDescription svcDesc3 = sd3.getServiceDescription();
+            assertSame(svcDesc2_afterClose, svcDesc3);
+            EndpointDescription epDesc3_port1 = svcDesc3.getEndpointDescription(portQN1, sd3);
+            assertSame(epDesc3_port1, epDesc2_port1_afterClose);
+            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
+            // since there's a 3rd delegate now
+            org.apache.axis2.jaxws.spi.ServiceDelegate.releaseService(svc2);
+
+            ServiceDescription svcDesc3_afterClose = sd3.getServiceDescription();
+            assertSame(svcDesc3, svcDesc3_afterClose);
+            EndpointDescription epDesc3_port1_afterClose = 
+                svcDesc3_afterClose.getEndpointDescription(portQN1, sd3);
+            assertSame(epDesc3_port1, epDesc3_port1_afterClose);
+            EndpointDescription epDesc3_port2_afterClose = 
+                svcDesc3_afterClose.getEndpointDescription(portQN2, sd3);
+            assertSame(epDesc3_port2, epDesc3_port2_afterClose);
+            
+            // 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();
+        }
+    }
+    
+    public void testSeviceUseAfterClose() {
+        QName svcQN = new QName(namespaceURI, svcLocalPart);
+        try {
+            ClientMetadataTest.installCachingFactory();
+
+            Service svc1 = Service.create(svcQN);
+            QName portQN = new QName(namespaceURI, dynamicPort1);
+            svc1.addPort(portQN, bindingID1, epr1);
+        
+            org.apache.axis2.jaxws.spi.ServiceDelegate.releaseService(svc1);
+            
+            svc1.addPort(portQN, bindingID1, epr1);
+            fail("Should have caught an exception");
+
+        } catch (WebServiceException e) {
+            // expected path
+        } catch (Exception e) {
+            fail("Caught wrong exception " + e);
+        } finally {
+            ClientMetadataTest.restoreOriginalFactory();
+        }
+    }
+
+    static final String declared_namespaceURI = "http://description.jaxws.axis2.apache.org";
+    static final String declared_svcLocalPart = "svcLocalPart";
+    static final String multiPortWsdl = "ClientMetadataMultiPort.wsdl";
+    static final String multiPortWsdl_portLocalPart1 = "portLocalPartMulti1";
+    static final String multiPortWsdl_portLocalPart2 = "portLocalPartMulti2";
+    static final String multiPortWsdl_portLocalPart3 = "portLocalPartMulti3";
+    public void testMultipleServiceMultipeDeclaredPorts() {
+        QName serviceQName = new QName(declared_namespaceURI , declared_svcLocalPart);
+        URL wsdlUrl = getWsdlURL(multiPortWsdl);
+        QName portQN1 = new QName(namespaceURI, multiPortWsdl_portLocalPart1);
+        QName portQN2 = new QName(namespaceURI, multiPortWsdl_portLocalPart2);
+        QName portQN3 = new QName(namespaceURI, multiPortWsdl_portLocalPart3);
+
+        try {
+            ClientMetadataTest.installCachingFactory();
+            // Open 2 services
+            Service svc1 = Service.create(wsdlUrl, serviceQName);
+            Service svc2 = Service.create(wsdlUrl, serviceQName);
+
+            ClientMetadataPortSEI svc1_port1 = svc1.getPort(portQN1, ClientMetadataPortSEI.class);
+            ClientMetadataPortSEI svc1_port2 = svc1.getPort(portQN2, ClientMetadataPortSEI.class);
+            ClientMetadataPortSEI svc1_port3 = svc1.getPort(portQN3, ClientMetadataPortSEI.class);
+
+            ClientMetadataPortSEI svc2_port1 = svc2.getPort(portQN1, ClientMetadataPortSEI.class);
+            ClientMetadataPortSEI svc2_port2 = svc2.getPort(portQN2, ClientMetadataPortSEI.class);
+            ClientMetadataPortSEI svc2_port3 = svc2.getPort(portQN3, ClientMetadataPortSEI.class);
+
+            ServiceDelegate sd1 = DescriptionTestUtils2.getServiceDelegate(svc1);
+            ServiceDelegate sd2 = DescriptionTestUtils2.getServiceDelegate(svc2);
+            assertNotSame(sd1, sd2);
+
+            ServiceDescription svcDesc1 = sd1.getServiceDescription();
+            ServiceDescription svcDesc2 = sd2.getServiceDescription();
+            AxisConfiguration axisConfig = svcDesc1.getAxisConfigContext().getAxisConfiguration();
+            assertSame(svcDesc1, svcDesc2);
+            
+            EndpointDescription epDesc1_port1 = svcDesc1.getEndpointDescription(portQN1, sd1);
+            EndpointDescription epDesc2_port1 = svcDesc1.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);
+
+            EndpointDescription epDesc1_port2 = svcDesc1.getEndpointDescription(portQN2, sd1);
+            EndpointDescription epDesc2_port2 = svcDesc1.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);
+
+            // First close should NOT cleanup the endpoints since the other service is
+            // still using them.
+            org.apache.axis2.jaxws.spi.ServiceDelegate.releaseService(svc1);
+            
+            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);
+
+            // Add a third, should use the same
+            Service svc3 = Service.create(wsdlUrl, serviceQName);
+            ClientMetadataPortSEI svc3_port1 = svc3.getPort(portQN1, ClientMetadataPortSEI.class);
+            ClientMetadataPortSEI svc3_port2 = svc3.getPort(portQN2, ClientMetadataPortSEI.class);
+            ServiceDelegate sd3 = DescriptionTestUtils2.getServiceDelegate(svc3);
+            assertNotSame(sd2, sd3);
+            ServiceDescription svcDesc3 = sd3.getServiceDescription();
+            assertSame(svcDesc2_afterClose, svcDesc3);
+            EndpointDescription epDesc3_port1 = svcDesc3.getEndpointDescription(portQN1, sd3);
+            assertSame(epDesc3_port1, epDesc2_port1_afterClose);
+            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
+            // since there's a 3rd delegate now
+            org.apache.axis2.jaxws.spi.ServiceDelegate.releaseService(svc2);
+
+            ServiceDescription svcDesc3_afterClose = sd3.getServiceDescription();
+            assertSame(svcDesc3, svcDesc3_afterClose);
+            EndpointDescription epDesc3_port1_afterClose = 
+                svcDesc3_afterClose.getEndpointDescription(portQN1, sd3);
+            assertSame(epDesc3_port1, epDesc3_port1_afterClose);
+            EndpointDescription epDesc3_port2_afterClose = 
+                svcDesc3_afterClose.getEndpointDescription(portQN2, sd3);
+            assertSame(epDesc3_port2, epDesc3_port2_afterClose);
+            
+            // 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
+    // =============================================================================================
+    /**
+     * Given a simple file name (with no base dictory or path), returns a URL to the WSDL file
+     * with the base directory and path prepended.
+     * 
+     * @param wsdlFileName
+     * @return
+     */
+    static URL getWsdlURL(String wsdlFileName) {
+        URL url = null;
+        String wsdlLocation = getWsdlLocation(wsdlFileName);
+        try {
+            File file = new File(wsdlLocation);
+            url = file.toURL();
+        } catch (MalformedURLException e) {
+            e.printStackTrace();
+            fail("Exception converting WSDL file to URL: " + e.toString());
+        }
+        return url;
+    }
+
+    /**
+     * Prepends the base directory and the path where the test WSDL lives to a filename.
+     * @param wsdlFileName
+     * @return
+     */
+    static String getWsdlLocation(String wsdlFileName) {
+        String wsdlLocation = null;
+        String baseDir = System.getProperty("basedir",".");
+        wsdlLocation = baseDir + "/test-resources/wsdl/" + wsdlFileName;
+        return wsdlLocation;
+    }
+
+}
+
+@WebService(name="EchoMessagePortType", targetNamespace="http://description.jaxws.axis2.apache.org")
+interface ClientMetadataPortSEI {
+    public String echoMessage(String string);
+}
+

Modified: webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/engine/AxisConfiguration.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/engine/AxisConfiguration.java?rev=688787&r1=688786&r2=688787&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/engine/AxisConfiguration.java (original)
+++ webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/engine/AxisConfiguration.java Mon Aug 25 10:34:38 2008
@@ -49,6 +49,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Class AxisConfiguration
@@ -88,7 +89,7 @@
     private URL axis2Repository = null;
 
     private Map allServices = new Hashtable();
-    private Map allEndpoints = new Hashtable();
+    private Map allEndpoints = new ConcurrentHashMap();
 
     /**
      * Stores the module specified in the server.xml at the document parsing time.
@@ -356,8 +357,7 @@
                 }
                 if (log.isDebugEnabled()) {
                     log.debug("After adding to allEndpoints map, size is "
-                              + allEndpoints.size(), 
-                              new Exception("AxisConfiguration.addServiceGroup called from"));
+                              + allEndpoints.size());
                 }
             }
 
@@ -399,11 +399,12 @@
             throw new AxisFault(Messages.getMessage("invalidservicegroupname",
                                                     serviceGroupName));
         }
+
         Iterator services = axisServiceGroup.getServices();
         boolean isClientSide = false;
         while (services.hasNext()) {
             AxisService axisService = (AxisService) services.next();
-            allServices.remove(axisService.getName());
+            Object value = allServices.remove(axisService.getName());
             if (!axisService.isClientSide()) {
                 notifyObservers(AxisEvent.SERVICE_REMOVE, axisService);
             } else {

Modified: webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/ServiceDescription.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/ServiceDescription.java?rev=688787&r1=688786&r2=688787&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/ServiceDescription.java (original)
+++ webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/ServiceDescription.java Mon Aug 25 10:34:38 2008
@@ -172,4 +172,19 @@
      * @param resolvedHandlersInfo An object containing information for the resolved handlers
      */
     public void setResolvedHandlersDescription(PortInfo portInfo, ResolvedHandlersDescription resolvedHandlersInfo);
+    
+    /**
+     * Check into releasing resources related to this ServiceDescription.  Those resources include
+     * this ServiceDescription instance, the EndpointDescription instances it owns and their
+     * associated AxisService and related objects.  
+     * 
+     * NOTE: This should only be called on ServiceDescrpition instances that are owned by
+     * client ServiceDelegate instances; it SHOULD NOT be called on server-side 
+     * ServiceDescriptions since those are built during server start and their life-cycle is
+     * the life-cycle of the server.
+     * 
+     * @param delegate The ServiceDelegate instance that owns this ServiceDescription.
+     */
+    public void releaseResources(Object delegate);
+
 }

Modified: webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/impl/DescriptionFactoryImpl.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/impl/DescriptionFactoryImpl.java?rev=688787&r1=688786&r2=688787&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/impl/DescriptionFactoryImpl.java (original)
+++ webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/impl/DescriptionFactoryImpl.java Mon Aug 25 10:34:38 2008
@@ -44,6 +44,7 @@
 import org.apache.commons.logging.LogFactory;
 
 import javax.xml.namespace.QName;
+
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -99,10 +100,11 @@
         }
         ServiceDescription serviceDesc = null;
         synchronized(configContext) {
-            serviceDesc = cache.get(key);
             if (log.isDebugEnabled()) {
                 log.debug("Check to see if ServiceDescription is found in cache");
             }
+            serviceDesc = cache.get(key);
+
             if (serviceDesc != null) {
                 if (log.isDebugEnabled()) {
                     log.debug("ServiceDescription found in the cache");
@@ -148,6 +150,7 @@
                 ((ServiceDescriptionImpl) serviceDesc).getDescriptionBuilderComposite().
                     setSparseComposite(sparseCompositeKey, sparseComposite);
             }
+            ((ServiceDescriptionImpl) serviceDesc).registerUse();
         }
         return serviceDesc;
     }
@@ -478,4 +481,29 @@
         return new ResolvedHandlersDescriptionImpl();
     }
 
+    /**
+     * Remove the ServiceDescription instance from the client-side cache.
+     * 
+     * @param svcDesc The instance to be removed.
+     */
+    static void removeFromCache(ServiceDescription svcDesc) {
+        ConfigurationContext configContext = svcDesc.getAxisConfigContext();
+        synchronized(configContext) {
+            Set<Map.Entry<DescriptionKey, ServiceDescription>> cacheEntrySet = 
+                cache.entrySet();
+            Iterator<Map.Entry<DescriptionKey, ServiceDescription>> cacheEntryIterator =
+                cacheEntrySet.iterator();
+            while (cacheEntryIterator.hasNext()) {
+                Map.Entry<DescriptionKey, ServiceDescription> entry = 
+                    cacheEntryIterator.next();
+                ServiceDescription entrySvcDescValue = entry.getValue();
+                if (svcDesc == entrySvcDescValue) {
+                    cacheEntryIterator.remove();
+                    if (log.isDebugEnabled()) {
+                        log.debug("Removed service description from cache");
+                    }
+                }
+            }
+        }
+    }
 }

Modified: webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/impl/EndpointDescriptionImpl.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/impl/EndpointDescriptionImpl.java?rev=688787&r1=688786&r2=688787&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/impl/EndpointDescriptionImpl.java (original)
+++ webservices/axis2/trunk/java/modules/metadata/src/org/apache/axis2/jaxws/description/impl/EndpointDescriptionImpl.java Mon Aug 25 10:34:38 2008
@@ -25,6 +25,7 @@
 import org.apache.axis2.client.ServiceClient;
 import org.apache.axis2.context.ConfigurationContext;
 import org.apache.axis2.description.AxisService;
+import org.apache.axis2.description.AxisServiceGroup;
 import org.apache.axis2.description.OutInAxisOperation;
 import org.apache.axis2.description.OutOnlyAxisOperation;
 import org.apache.axis2.description.Parameter;
@@ -70,7 +71,6 @@
 import javax.xml.ws.Service;
 import javax.xml.ws.ServiceMode;
 import javax.xml.ws.WebServiceProvider;
-import javax.xml.ws.WebServiceException;
 import javax.xml.ws.handler.PortInfo;
 import javax.xml.ws.soap.MTOM;
 import javax.xml.ws.soap.MTOMFeature;
@@ -97,6 +97,7 @@
  */
 class EndpointDescriptionImpl
         implements EndpointDescription, EndpointDescriptionJava, EndpointDescriptionWSDL {
+
     private ServiceDescriptionImpl parentServiceDescription;
     private AxisService axisService;
     // In some environments some of the resources on an AxisService can lead to OOMs.
@@ -2102,6 +2103,28 @@
         }
         return currentUniqueID++;
     }
+        
+    /**
+     * Release the AxisService objects associated with this EndpointDescription.  Note that
+     * this should only be called by the ServiceDescription that owns this EndpointDescrition.
+     * 
+     * @param configurationContext  The Axis2 ConfigurationContext holding the AxisConfiguration
+     * from which the AxisServices should be removed.
+     */
+    void releaseResources(ConfigurationContext configurationContext) {
+        if (configurationContext != null) {
+            AxisConfiguration axisConfig = configurationContext.getAxisConfiguration();
+            AxisService axisService = getAxisService();
+            AxisServiceGroup axisServiceGroup = axisService.getAxisServiceGroup();
+            try {
+                axisConfig.removeServiceGroup(axisServiceGroup.getServiceGroupName());
+            } catch (AxisFault e) {
+                if (log.isDebugEnabled()) {
+                    log.debug("EndpointDescriptionImpl release resources caught exception which it is ignoring", e);
+                }
+            }
+        }
+    }
 }
 
 

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=688787&r1=688786&r2=688787&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 Mon Aug 25 10:34:38 2008
@@ -63,6 +63,7 @@
 import javax.wsdl.extensions.ExtensibilityElement;
 import javax.xml.namespace.QName;
 import javax.xml.ws.WebServiceClient;
+import javax.xml.ws.WebServiceException;
 import javax.xml.ws.handler.PortInfo;
 import javax.xml.ws.soap.SOAPBinding;
 import java.io.FileNotFoundException;
@@ -83,6 +84,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -93,6 +95,10 @@
     private ClientConfigurationFactory clientConfigFactory;
     private ConfigurationContext configContext;
 
+    // Number of times this instance is being used.  For service-providers, this is not decremented
+    // since the serice isn't un-used once it is created.  For service-requesters, the service
+    // is used by ServiceDelegates, and becomes un-used when the ServiceDelegate is released.
+    private int useCount = 0;
     private String wsdlURL;
     private QName serviceQName;
 
@@ -108,8 +114,11 @@
                 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 WeakHashMap<Object, Map<QName, EndpointDescriptionImpl>>();
+                new HashMap<Object, Map<QName, EndpointDescriptionImpl>>();
 
     // Cache classes for the info for resolved handlers
     private SoftReference<Map<PortInfo, ResolvedHandlersDescription>> resolvedHandlersDescription =
@@ -2450,5 +2459,132 @@
         }
         return wsdlLocation;
     }
+    
+    /**
+     * Increment the use count for this ServiceDescription instance.  Note the use count is
+     * only used on the client side.
+     * @return
+     */
+    private boolean isInUse() {
+        return useCount > 0;
+    }
+    
+    /**
+     * Register that this ServiceDescription is being used by a service delegate instance.  Note
+     * this is only used on the client side (since the service delegate is what is being 
+     * registered).
+     * 
+     * Note that this is package protected since only the implementation classes should be calling
+     * it.
+     */
+    void registerUse() {
+        useCount++;
+    }
+
+    /**
+     * Deregister that this ServiceDescription is being used by a service delegate instance.  Note
+     * this is only used on the client side (since the service delegate is what is being 
+     * registered).
 
+     * Note that this is package protected since only the implementation classes should be calling
+     * it.
+     */
+    void deregisterUse() {
+        if (useCount > 0) {
+            useCount--;
+        }
+    }
+
+    public void releaseResources(Object delegate) {
+        try {
+        if (log.isDebugEnabled()) {
+            log.debug("Entry ServiceDescription release resources with delegate " + delegate);
+        }
+        if (delegate != null) {
+            deregisterUse();
+        } else {
+            // If no ServiceDelegate specified, then return
+            return;
+        }
+        if (isInUse()) {
+            if (log.isDebugEnabled()) {
+                log.debug("ServiceDescription still in use; not released");
+            }
+            return;
+        }
+        
+        // Remove the Service Description from the cache before we release the associated 
+        // resouces.
+        DescriptionFactoryImpl.removeFromCache(this);
+        
+        // Close all the endpoint descs, both declared and dynamic
+        Collection<EndpointDescription> definedEndpoints = definedEndpointDescriptions.values(); 
+        if (definedEndpoints.size() > 0) {
+            if (log.isDebugEnabled()) {
+                log.debug("Releasing defined endpoints, size: " + definedEndpoints.size());
+            }
+            for (EndpointDescription endpointDesc : definedEndpoints) {
+                ((EndpointDescriptionImpl) endpointDesc).releaseResources(getAxisConfigContext());
+            }
+        }
+        definedEndpointDescriptions.clear();
+        
+        Collection<Map<QName, EndpointDescriptionImpl>> dynamicEndpointsMap = 
+            dynamicEndpointDescriptions.values();
+        if (log.isDebugEnabled()) {
+            log.debug("Releasing dynamic endpoints, size: " + dynamicEndpointsMap.size());
+        }
+        Iterator<Map<QName, EndpointDescriptionImpl>> dynamicEndpointsMapIterator = dynamicEndpointsMap.iterator();
+        while (dynamicEndpointsMapIterator.hasNext()) {
+            Map<QName, EndpointDescriptionImpl> mapEntry = dynamicEndpointsMapIterator.next();
+            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);
+                }
+            }
+        }
+        dynamicEndpointDescriptions.clear();
+        
+        } catch (Throwable t) {
+            if (log.isDebugEnabled()) {
+                log.debug("Release resorces in ServiceDesc caught throwable ", t);
+            }
+            throw ExceptionFactory.makeWebServiceException(t);
+        }
+    }
+    
+    /**
+     * Remove the endpointDescription from the list of dynamic ports held on the
+     * AxisConfiguration object.
+     * 
+     * @param endpointDesc The endpointDescription to be removed from the list.
+     */
+    private void removeFromDynamicEndpointCache(EndpointDescription endpointDesc) {
+        AxisConfiguration configuration = configContext.getAxisConfiguration();
+        Parameter parameter = configuration.getParameter(JAXWS_DYNAMIC_ENDPOINTS);
+        HashMap cachedDescriptions = (HashMap)
+                ((parameter == null) ? null : parameter.getValue());
+        if (cachedDescriptions != null) {
+            synchronized(cachedDescriptions) {
+                Set cachedDescSet = cachedDescriptions.entrySet();
+                Iterator cachedDescIterator = cachedDescSet.iterator();
+                while (cachedDescIterator.hasNext()) {
+                    Map.Entry mapEntry = (Map.Entry) cachedDescIterator.next();
+                    WeakReference weakRef = (WeakReference) mapEntry.getValue();
+                    if (weakRef != null) {
+                        EndpointDescriptionImpl checkDynamicEndpointDesc = (EndpointDescriptionImpl) weakRef.get();
+                        if (endpointDesc == checkDynamicEndpointDesc) {
+                            cachedDescIterator.remove();
+                            if (log.isDebugEnabled()) {
+                                log.debug("Removing endpoint desc from dynamic cache on configuration");
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
 }