Modified: axis/axis2/java/core/branches/1_6/modules/jaxws/test/org/apache/axis2/jaxws/client/dispatch/DynamicPortCachingTests.java URL: http://svn.apache.org/viewvc/axis/axis2/java/core/branches/1_6/modules/jaxws/test/org/apache/axis2/jaxws/client/dispatch/DynamicPortCachingTests.java?rev=1156372&r1=1156371&r2=1156372&view=diff ============================================================================== --- axis/axis2/java/core/branches/1_6/modules/jaxws/test/org/apache/axis2/jaxws/client/dispatch/DynamicPortCachingTests.java (original) +++ axis/axis2/java/core/branches/1_6/modules/jaxws/test/org/apache/axis2/jaxws/client/dispatch/DynamicPortCachingTests.java Wed Aug 10 21:17:15 2011 @@ -19,26 +19,41 @@ package org.apache.axis2.jaxws.client.dispatch; import junit.framework.TestCase; + +import org.apache.axis2.AxisFault; +import org.apache.axis2.description.Parameter; +import org.apache.axis2.engine.AxisConfiguration; +import org.apache.axis2.jaxws.Constants; 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.description.builder.MDQConstants; +import org.apache.axis2.jaxws.description.impl.ServiceDescriptionImpl; import org.apache.axis2.jaxws.spi.ClientMetadataTest; import org.apache.axis2.jaxws.spi.ServiceDelegate; import javax.xml.namespace.QName; import javax.xml.ws.Service; import java.util.ArrayList; +import java.util.Collection; import java.util.Iterator; import java.util.List; /** * Tests the caching and isolation of dynamic ports,i.e. those created with * Service.addPort(...). Dynamic ports should - * 1) Only be visible to services on which an addPort was done + * 1) Only be visible to the service instances on which an addPort was done * 2) Share instances of the description objects (e.g. AxisService) for ports - * added to different instances of the same service that use the same client - * configuration - * 3) Identical ports on services using different client configuration should - * not be shared + * added to the same instance of a service + * 3) Different service instances of the same-named service should not share the + * the list of added dynamic ports. Even if the same named port is added to each + * service, they should not share metadata objects (e.g. EndpointDescription, AxisService) + * + * Also validate the property that enables the previous behavior that allowed + * sharing of dyamic ports across services on the same AxisConfiguration. With + * that property enabled, Dynamic ports should + * 1) Be shared across all services on the AxisConfiguration based on the key + * (PortQName, BindingId, EndpointAddress). */ public class DynamicPortCachingTests extends TestCase { static final String namespaceURI = "http://dispatch.client.jaxws.axis2.apache.org"; @@ -49,11 +64,149 @@ public class DynamicPortCachingTests ext static final String epr1 = null; /** - * Two different instances of the same service should share the same - * description information (e.g. AxisService) if the same port is added - * to both + * Validate setting the property enables the old behavior, which is that dynamic ports are + * shared across all services on an AxisConfiguration based on the key + * (PortQName, BindingId, EndpointAddress). This test validates that two ports that share the + * same Service QName will be share the same dynamic port objects. + * + * NOTE!!! This test exists for validating backwards compatability. This behavior was NOT + * intended in the runtime, but since it existed that way, customers could be depending on it. + */ + public void testSamePortsSameServiceName_AxisConfig_PropertyTrue() { + try { + ClientMetadataTest.installCachingFactory(); + QName svcQN = new QName(namespaceURI, svcLocalPart); + + Service svc1 = Service.create(svcQN); + assertNotNull(svc1); + ServiceDelegate svcDlg1 = DescriptionTestUtils2.getServiceDelegate(svc1); + assertNotNull(svcDlg1); + ServiceDescription svcDesc1 = svcDlg1.getServiceDescription(); + assertNotNull(svcDesc1); + + // Set the property to revert the behavior. Note that although we are passing ni + // a particular service, the property is set on the AxisConfig shared by all + // services. + setAxisConfigParameter(svc1, MDQConstants.SHARE_DYNAMIC_PORTS_ACROSS_SERVICES, "true"); + + Service svc2 = Service.create(svcQN); + assertNotNull(svc2); + ServiceDelegate svcDlg2 = DescriptionTestUtils2.getServiceDelegate(svc2); + assertNotNull(svcDlg2); + ServiceDescription svcDesc2 = svcDlg2.getServiceDescription(); + assertNotNull(svcDesc2); + + assertNotSame("Service instances should not be the same", svc1, svc2); + assertNotSame("Service delegates should not be the same", svcDlg1, svcDlg2); + assertSame("Instance of ServiceDescription should be the same", svcDesc1, svcDesc2); + + // Add a port to 1st service, should not be visible under the 2nd service + svc1.addPort(new QName(namespaceURI, dynamicPort1), + bindingID1, + epr1); + assertEquals(1, getList(svc1.getPorts()).size()); + assertEquals(0, getList(svc2.getPorts()).size()); + + // Add the same port to 2nd service, should now have same ports and description + // objects + svc2.addPort(new QName(namespaceURI, dynamicPort1), + bindingID1, + epr1); + assertEquals(1, getList(svc1.getPorts()).size()); + assertEquals(1, getList(svc2.getPorts()).size()); + + // Make sure the EndpointDescription objects are shared. + Collection<?> epDesc1Collection = + ((ServiceDescriptionImpl) svcDesc1).getDynamicEndpointDescriptions_AsCollection(svcDlg1); + Collection<?> epDesc2Collection = + ((ServiceDescriptionImpl) svcDesc2).getDynamicEndpointDescriptions_AsCollection(svcDlg2); + assertEquals("Wrong number of dynamic endpoints", 1, epDesc1Collection.size()); + assertEquals("Wrong number of dynamic endpoints", 1, epDesc2Collection.size()); + + EndpointDescription epDesc1 = (EndpointDescription) epDesc1Collection.toArray()[0]; + EndpointDescription epDesc2 = (EndpointDescription) epDesc2Collection.toArray()[0]; + assertSame("EndpointDescriptions not shared", epDesc1, epDesc2); + + } finally { + ClientMetadataTest.restoreOriginalFactory(); + } + + } + /** + * Validate setting the property enables the old behavior, which is that dynamic ports are + * shared across all services on an AxisConfiguration based on the key + * (PortQName, BindingId, EndpointAddress). This test validates that two ports that have + * different Service QNames will still share the same dynamic port objects. + * + * NOTE!!! This test exists for validating backwards compatability. This behavior was NOT + * intended in the runtime, but since it existed that way, customers could be depending on it. + */ + public void testSamePortsDifferentServiceName_AxisConfig_PropertyTrue() { + try { + ClientMetadataTest.installCachingFactory(); + QName svcQN = new QName(namespaceURI, svcLocalPart); + + Service svc1 = Service.create(svcQN); + assertNotNull(svc1); + ServiceDelegate svcDlg1 = DescriptionTestUtils2.getServiceDelegate(svc1); + assertNotNull(svcDlg1); + ServiceDescription svcDesc1 = svcDlg1.getServiceDescription(); + assertNotNull(svcDesc1); + + // Set the property to revert the behavior. Note that although we are passing ni + // a particular service, the property is set on the AxisConfig shared by all + // services. + setAxisConfigParameter(svc1, MDQConstants.SHARE_DYNAMIC_PORTS_ACROSS_SERVICES, "true"); + + QName svcQN2 = new QName(namespaceURI, svcLocalPart + "2"); + Service svc2 = Service.create(svcQN2); + assertNotNull(svc2); + ServiceDelegate svcDlg2 = DescriptionTestUtils2.getServiceDelegate(svc2); + assertNotNull(svcDlg2); + ServiceDescription svcDesc2 = svcDlg2.getServiceDescription(); + assertNotNull(svcDesc2); + + assertNotSame("Service instances should not be the same", svc1, svc2); + assertNotSame("Service delegates should not be the same", svcDlg1, svcDlg2); + assertNotSame("Instance of ServiceDescription should be the same", svcDesc1, svcDesc2); + + // Add a port to 1st service, should not be visible under the 2nd service + svc1.addPort(new QName(namespaceURI, dynamicPort1), + bindingID1, + epr1); + assertEquals(1, getList(svc1.getPorts()).size()); + assertEquals(0, getList(svc2.getPorts()).size()); + + // Add the same port to 2nd service, should now have same ports and description + // objects + svc2.addPort(new QName(namespaceURI, dynamicPort1), + bindingID1, + epr1); + assertEquals(1, getList(svc1.getPorts()).size()); + assertEquals(1, getList(svc2.getPorts()).size()); + + // Make sure the EndpointDescription objects are shared. + Collection<?> epDesc1Collection = + ((ServiceDescriptionImpl) svcDesc1).getDynamicEndpointDescriptions_AsCollection(svcDlg1); + Collection<?> epDesc2Collection = + ((ServiceDescriptionImpl) svcDesc2).getDynamicEndpointDescriptions_AsCollection(svcDlg2); + assertEquals("Wrong number of dynamic endpoints", 1, epDesc1Collection.size()); + assertEquals("Wrong number of dynamic endpoints", 1, epDesc2Collection.size()); + + EndpointDescription epDesc1 = (EndpointDescription) epDesc1Collection.toArray()[0]; + EndpointDescription epDesc2 = (EndpointDescription) epDesc2Collection.toArray()[0]; + assertSame("EndpointDescriptions not shared", epDesc1, epDesc2); + + } finally { + ClientMetadataTest.restoreOriginalFactory(); + } + + } + /** + * Validate that without the property set to revert the behavior, the default is that the ports are + * NOT shared across different instances of services with the same name */ - public void _testSamePortsSameService() { + public void testSamePortsSameServiceNameDifferentInstances() { try { ClientMetadataTest.installCachingFactory(); QName svcQN = new QName(namespaceURI, svcLocalPart); @@ -91,8 +244,122 @@ public class DynamicPortCachingTests ext assertEquals(1, getList(svc1.getPorts()).size()); assertEquals(1, getList(svc2.getPorts()).size()); + // Make sure the EndpointDescription objects are not shared. + Collection<?> epDesc1Collection = + ((ServiceDescriptionImpl) svcDesc1).getDynamicEndpointDescriptions_AsCollection(svcDlg1); + Collection<?> epDesc2Collection = + ((ServiceDescriptionImpl) svcDesc2).getDynamicEndpointDescriptions_AsCollection(svcDlg2); + assertEquals("Wrong number of dynamic endpoints", 1, epDesc1Collection.size()); + assertEquals("Wrong number of dynamic endpoints", 1, epDesc2Collection.size()); + + EndpointDescription epDesc1 = (EndpointDescription) epDesc1Collection.toArray()[0]; + EndpointDescription epDesc2 = (EndpointDescription) epDesc2Collection.toArray()[0]; + assertNotSame("EndpointDescriptions not shared", epDesc1, epDesc2); + } finally { + ClientMetadataTest.restoreOriginalFactory(); + } + + } + + /** + * Validate that adding the same dynamic port to the same service instance re-uses the same + * description objects (e.g. EndpointDescription) + */ + public void testSamePortsSameServiceInstance() { + try { + ClientMetadataTest.installCachingFactory(); + QName svcQN = new QName(namespaceURI, svcLocalPart); + + Service svc1 = Service.create(svcQN); + assertNotNull(svc1); + ServiceDelegate svcDlg1 = DescriptionTestUtils2.getServiceDelegate(svc1); + assertNotNull(svcDlg1); + ServiceDescription svcDesc1 = svcDlg1.getServiceDescription(); + assertNotNull(svcDesc1); + + // Add a port to service, save off the metadata to validate later + svc1.addPort(new QName(namespaceURI, dynamicPort1), + bindingID1, + epr1); + assertEquals(1, getList(svc1.getPorts()).size()); + Collection<?> epDesc1Collection = + ((ServiceDescriptionImpl) svcDesc1).getDynamicEndpointDescriptions_AsCollection(svcDlg1); + assertEquals("Wrong number of dynamic endpoints", 1, epDesc1Collection.size()); + EndpointDescription epDescFirstAddPort = (EndpointDescription) epDesc1Collection.toArray()[0]; + + // Add the same port to the same service instance, should use the same description objects + svc1.addPort(new QName(namespaceURI, dynamicPort1), + bindingID1, + epr1); + assertEquals(1, getList(svc1.getPorts()).size()); + // Make sure the EndpointDescription object is reused for second port. + Collection<?> epDesc2Collection = + ((ServiceDescriptionImpl) svcDesc1).getDynamicEndpointDescriptions_AsCollection(svcDlg1); + assertEquals("Wrong number of dynamic endpoints", 1, epDesc2Collection.size()); + + EndpointDescription epDescSecondAddPort = (EndpointDescription) epDesc1Collection.toArray()[0]; + assertSame("EndpointDescriptions not reused", epDescFirstAddPort, epDescSecondAddPort); + + } finally { + ClientMetadataTest.restoreOriginalFactory(); + } + + } + /** + * Validate that ports added to services with different service names (and thus different service instances) + * are not shared. + */ + public void testSamePortsDifferentServiceNames() { + try { + ClientMetadataTest.installCachingFactory(); + QName svcQN = new QName(namespaceURI, svcLocalPart); + Service svc1 = Service.create(svcQN); + assertNotNull(svc1); + ServiceDelegate svcDlg1 = DescriptionTestUtils2.getServiceDelegate(svc1); + assertNotNull(svcDlg1); + ServiceDescription svcDesc1 = svcDlg1.getServiceDescription(); + assertNotNull(svcDesc1); + + QName svcQN2 = new QName(namespaceURI, svcLocalPart + "2"); + Service svc2 = Service.create(svcQN2); + assertNotNull(svc2); + ServiceDelegate svcDlg2 = DescriptionTestUtils2.getServiceDelegate(svc2); + assertNotNull(svcDlg2); + ServiceDescription svcDesc2 = svcDlg2.getServiceDescription(); + assertNotNull(svcDesc2); + + assertNotSame("Service instances should not be the same", svc1, svc2); + assertNotSame("Service delegates should not be the same", svcDlg1, svcDlg2); + assertNotSame("Instance of ServiceDescription should not be the same", svcDesc1, svcDesc2); + + // Add a port to 1st service, should not be visible under the 2nd service + svc1.addPort(new QName(namespaceURI, dynamicPort1), + bindingID1, + epr1); + assertEquals(1, getList(svc1.getPorts()).size()); + assertEquals(0, getList(svc2.getPorts()).size()); + + // Add the same port to 2nd service, should now have same ports and description + // objects + svc2.addPort(new QName(namespaceURI, dynamicPort1), + bindingID1, + epr1); + assertEquals(1, getList(svc1.getPorts()).size()); + assertEquals(1, getList(svc2.getPorts()).size()); + + // Make sure the EndpointDescription objects are NOT shared. + Collection<?> epDesc1Collection = + ((ServiceDescriptionImpl) svcDesc1).getDynamicEndpointDescriptions_AsCollection(svcDlg1); + Collection<?> epDesc2Collection = + ((ServiceDescriptionImpl) svcDesc2).getDynamicEndpointDescriptions_AsCollection(svcDlg2); + assertEquals("Wrong number of dynamic endpoints", 1, epDesc1Collection.size()); + assertEquals("Wrong number of dynamic endpoints", 1, epDesc2Collection.size()); + + EndpointDescription epDesc1 = (EndpointDescription) epDesc1Collection.toArray().clone()[0]; + EndpointDescription epDesc2 = (EndpointDescription) epDesc2Collection.toArray().clone()[0]; + assertNotSame("EndpointDescriptions should not be shared across different services", epDesc1, epDesc2); } finally { ClientMetadataTest.restoreOriginalFactory(); @@ -124,4 +391,17 @@ public class DynamicPortCachingTests ext } return returnList; } + + private void setAxisConfigParameter(Service service, String key, String value) { + ServiceDelegate delegate = DescriptionTestUtils2.getServiceDelegate(service); + ServiceDescription svcDesc = delegate.getServiceDescription(); + AxisConfiguration axisConfig = svcDesc.getAxisConfigContext().getAxisConfiguration(); + Parameter parameter = new Parameter(key, value); + try { + axisConfig.addParameter(parameter); + } catch (AxisFault e) { + fail("Unable to set Parameter on AxisConfig due to exception " + e); + } + } + } \ No newline at end of file
Modified: axis/axis2/java/core/branches/1_6/modules/metadata/src/org/apache/axis2/jaxws/ExceptionFactory.java URL: http://svn.apache.org/viewvc/axis/axis2/java/core/branches/1_6/modules/metadata/src/org/apache/axis2/jaxws/ExceptionFactory.java?rev=1156372&r1=1156371&r2=1156372&view=diff ============================================================================== --- axis/axis2/java/core/branches/1_6/modules/metadata/src/org/apache/axis2/jaxws/ExceptionFactory.java (original) +++ axis/axis2/java/core/branches/1_6/modules/metadata/src/org/apache/axis2/jaxws/ExceptionFactory.java Wed Aug 10 21:17:15 2011 @@ -323,6 +323,27 @@ public class ExceptionFactory { pw.close(); return sw.getBuffer().toString(); } - + + /** + * Give a target Throwable, set the initialCause Throwable as the initial cause on the target. + * @param target The throwable on which to set the initial cause + * @param initialCause The initial cause to set on the target Throwable. + */ + public static void setInitialCause(Throwable target, Throwable initialCause) { + if (target != null && initialCause != null) { + // Create a WebServiceException from the initialCause throwable. This skips over things like + // AxisFault as a cause. Set the result on the target throwable. + try { + WebServiceException localException = ExceptionFactory.makeWebServiceException(initialCause); + target.initCause(localException.getCause()); + } catch (Throwable t) { + // If the cause had already been set, then it can't be set again; it throws an exception. + if (log.isDebugEnabled()) { + log.debug("Caught exception trying to set initial cause on: " + target +". Initial cause: " + + initialCause + ". Caught: " + t); + } + } + } + } } Modified: axis/axis2/java/core/branches/1_6/modules/metadata/src/org/apache/axis2/jaxws/description/builder/MDQConstants.java URL: http://svn.apache.org/viewvc/axis/axis2/java/core/branches/1_6/modules/metadata/src/org/apache/axis2/jaxws/description/builder/MDQConstants.java?rev=1156372&r1=1156371&r2=1156372&view=diff ============================================================================== --- axis/axis2/java/core/branches/1_6/modules/metadata/src/org/apache/axis2/jaxws/description/builder/MDQConstants.java (original) +++ axis/axis2/java/core/branches/1_6/modules/metadata/src/org/apache/axis2/jaxws/description/builder/MDQConstants.java Wed Aug 10 21:17:15 2011 @@ -98,4 +98,24 @@ public class MDQConstants { public static final String SUN_WEB_METHOD_BEHAVIOR_CHANGE_VERSION = "2.1.6"; public static final String USE_POST_WEB_METHOD_RULES = "jaxws.runtime.usePostWebMethodRules"; + /** + * Context Property: + * Name: jaxws.share.dynamic.ports.enable + * Value: String "false" or "true" + * Default: null, which is interpreted as "false" + * Can be set on: + * - Axis Configuration, which indicates that dynamic ports should be shared across services based on + * a key of (PortQName, BindingId, EndpointAddress) + * + * Dynamic ports, which are those created by Service.addPort(...), should only be visible to the instance + * of the service that did the addPort. However, for backwards compatibility, this flag can be used + * to enable the sharing of dynamic ports across all services on an AxisConfiguration based on the key + * (PortQName, BindingId, EndpointAddress). + * + * The default setting of this property is null, which is interpreted as "false", which will scope the + * visibility of dynamic ports to the instance of the service that did the addPort(). + */ + public static final String SHARE_DYNAMIC_PORTS_ACROSS_SERVICES = "jaxws.share.dynamic.ports.enable"; + + } Modified: axis/axis2/java/core/branches/1_6/modules/metadata/src/org/apache/axis2/jaxws/description/impl/ServiceDescriptionImpl.java URL: http://svn.apache.org/viewvc/axis/axis2/java/core/branches/1_6/modules/metadata/src/org/apache/axis2/jaxws/description/impl/ServiceDescriptionImpl.java?rev=1156372&r1=1156371&r2=1156372&view=diff ============================================================================== --- axis/axis2/java/core/branches/1_6/modules/metadata/src/org/apache/axis2/jaxws/description/impl/ServiceDescriptionImpl.java (original) +++ axis/axis2/java/core/branches/1_6/modules/metadata/src/org/apache/axis2/jaxws/description/impl/ServiceDescriptionImpl.java Wed Aug 10 21:17:15 2011 @@ -146,6 +146,14 @@ public class ServiceDescriptionImpl // RUNTIME INFORMATION Map<String, ServiceRuntimeDescription> runtimeDescMap = new ConcurrentHashMap<String, ServiceRuntimeDescription>(); + + /** + * Property representing a collection of endpointDescription instances for dynamic ports + * which are shared across all services on an AxisConfig. Note that this behavior is incorrect; dynamic + * ports should only be visible to the instance of the service that created them. However, the sharing + * across services is maintained for backwards compatibility. + * @deprecated + */ private static final String JAXWS_DYNAMIC_ENDPOINTS = "jaxws.dynamic.endpoints"; /** @@ -491,7 +499,7 @@ public class ServiceDescriptionImpl Messages.getMessage("serviceDescriptionImplAddPortErr")); } - endpointDescription = createEndpointDescriptionImpl(sei, portQName, bindingId, endpointAddress); + endpointDescription = getDynamicEndpointDescriptionImpl(sei, portQName, bindingId, endpointAddress); addDynamicEndpointDescriptionImpl(endpointDescription, serviceDelegateKey); } else { @@ -596,19 +604,104 @@ public class ServiceDescriptionImpl return endpointDescription; } - private EndpointDescriptionImpl createEndpointDescriptionImpl(Class sei, QName portQName, String bindingId, String endpointAddress) { + /** + * Find or create an EndpointDescription instance for a dynamic port. Dynamic ports should be scoped to + * the instance of the service that created them, so the method getDynamicEndpointDescriptionImpl should + * have find one if it already exists. However, logic was introduced (in Apache Axis2 revision 664411) that + * also scoped dynamic ports to a configuration context based on port name, binding ID, and endpointAddress. + * Although that logic is incorrect, it is maintained for backwards compatability based on the setting of + * a property. + * + * @param sei + * @param portQName + * @param bindingId + * @param endpointAddress + * @return + */ + private EndpointDescriptionImpl getDynamicEndpointDescriptionImpl(Class sei, QName portQName, String bindingId, String endpointAddress) { if (log.isDebugEnabled()) { log.debug("Calling createEndpointDescriptionImpl : (" + portQName + "," + bindingId + "," + endpointAddress + ")"); } EndpointDescriptionImpl endpointDescription = null; + SharedDynamicEndpointEntry sharedDynamicEndpointEntry = null; + boolean areDynamicPortsShared = isShareDynamicPortsAcrossServicesEnabled(); + if (areDynamicPortsShared) { + // If ports are being shared, see if there's already one in the cache. Note that this will + // always return an Entry value, but the endpoint in it may be null if one wasn't found. + sharedDynamicEndpointEntry = findSharedDynamicEndpoint(portQName, bindingId, endpointAddress); + endpointDescription = sharedDynamicEndpointEntry.endpointDescription; + } + + boolean endpointCreated = false; + if(endpointDescription == null) { + endpointDescription = new EndpointDescriptionImpl(sei, portQName, true, this); + endpointCreated = true; + } + if (areDynamicPortsShared && endpointCreated) { + // If ports are being shared and a new endpoint was created, then it needs to be + // added to the cache. Note that the other values in the entry (the cache hashmap and + // the key) were set when we looked for the entry above. + sharedDynamicEndpointEntry.endpointDescription = endpointDescription; + cacheSharedDynamicPointEndpoint(sharedDynamicEndpointEntry); + } + return endpointDescription; + } + + /** + * Add the EndpointDescriptionImpl representing the dynamic port to the cache of ports shared across + * services. + * @param sharedDynamicEndpointEntry Contains the EndpointDescriptionImpl instance and the key + * associated with to be added to the cache, which is also contained in the entry. + */ + private void cacheSharedDynamicPointEndpoint(SharedDynamicEndpointEntry sharedDynamicEndpointEntry) { + + HashMap cachedDescriptions = sharedDynamicEndpointEntry.cachedDescriptions; + String key = sharedDynamicEndpointEntry.key; + EndpointDescriptionImpl endpointDescription = sharedDynamicEndpointEntry.endpointDescription; + + synchronized(cachedDescriptions) { + if (log.isDebugEnabled()) { + log.debug("Calling cachedDescriptions.put : (" + + key.toString() + ") : size - " + cachedDescriptions.size()); + } + cachedDescriptions.put(key.toString(), new WeakReference(endpointDescription)); + } + } + + /** + * Look for an existing shared endpointDescriptionImpl instance corresponding to a dynamic port based on + * a key of(portQname, bindingId, endpointAddress). Note that sharing dynamic ports across services is + * disable by default, and must be enabled via a property. Dynamic ports, by default, should only be + * visible to the instance of the service that added them. + * + * Note that a SharedDynamicEntry will be returned whether or not an existing EndpointDescriptionImpl + * is found. If one is not found, then one needs to be created and then added to the cache. The information + * in the returned SharedDynamicEntry can be used to add it. + * + * @see #isShareDynamicPortsAcrossServicesEnabled() + * @see #cacheSharedDynamicPointEndpoint(SharedDynamicEndpointEntry) + * @see SharedDynamicEndpointEntry + * @see #getDynamicEndpointDescriptionImpl(QName, Object) + * + * @param portQName + * @param bindingId + * @param endpointAddress + * @return A non-null SharedDynamicEndpointEntry is always returned. The key and cachedDescriptions + * variables will always be set. If an existing endpoint is found, then endpointDescription will be non-null. + */ + private SharedDynamicEndpointEntry findSharedDynamicEndpoint(QName portQName, + String bindingId, String endpointAddress) { + + SharedDynamicEndpointEntry returnDynamicEntry = new SharedDynamicEndpointEntry(); + EndpointDescriptionImpl sharedDynamicEndpoint = null; + AxisConfiguration configuration = configContext.getAxisConfiguration(); if (log.isDebugEnabled()) { log.debug("looking for " + JAXWS_DYNAMIC_ENDPOINTS + " in AxisConfiguration : " + configuration); } Parameter parameter = configuration.getParameter(JAXWS_DYNAMIC_ENDPOINTS); - HashMap cachedDescriptions = (HashMap) - ((parameter == null) ? null : parameter.getValue()); + HashMap cachedDescriptions = (HashMap)((parameter == null) ? null : parameter.getValue()); if(cachedDescriptions == null) { cachedDescriptions = new HashMap(); try { @@ -622,10 +715,10 @@ public class ServiceDescriptionImpl } else { if (log.isDebugEnabled()) { log.debug("found old jaxws.dynamic.endpoints cache in AxisConfiguration (" + cachedDescriptions + ") with size : (" - + cachedDescriptions.size() + ")"); + + cachedDescriptions.size() + ")"); } } - + StringBuffer key = new StringBuffer(); key.append(portQName == null ? "NULL" : portQName.toString()); key.append(':'); @@ -635,27 +728,50 @@ public class ServiceDescriptionImpl synchronized(cachedDescriptions) { WeakReference ref = (WeakReference) cachedDescriptions.get(key.toString()); if (ref != null) { - endpointDescription = (EndpointDescriptionImpl) ref.get(); - } - } - if(endpointDescription == null) { - endpointDescription = new EndpointDescriptionImpl(sei, portQName, true, this); - synchronized(cachedDescriptions) { + sharedDynamicEndpoint = (EndpointDescriptionImpl) ref.get(); if (log.isDebugEnabled()) { - log.debug("Calling cachedDescriptions.put : (" - + key.toString() + ") : size - " + cachedDescriptions.size()); + log.debug("found old entry for endpointDescription in jaxws.dynamic.endpoints cache : (" + + cachedDescriptions.size() + ")"); } - cachedDescriptions.put(key.toString(), new WeakReference(endpointDescription)); } - } else { - if (log.isDebugEnabled()) { - log.debug("found old entry for endpointDescription in jaxws.dynamic.endpoints cache : (" - + cachedDescriptions.size() + ")"); + } + + returnDynamicEntry.cachedDescriptions = cachedDescriptions; + returnDynamicEntry.key = key.toString(); + returnDynamicEntry.endpointDescription = sharedDynamicEndpoint; + + return returnDynamicEntry; + } + + /** + * Answer if dynamic ports are to be shared across services. The default value is FALSE, but it + * can be configured by an AxisConfig property. Note that this can only be set via a config property + * and not via a request context property since the setting affects multiple services. + * + * @return true if dynamic ports are to be shared across services, false otherwise. + */ + private boolean isShareDynamicPortsAcrossServicesEnabled() { + boolean resolutionEnabled = false; + + // See if an AxisConfig property enables the sharing of dynamic ports + String flagValue = null; + AxisConfiguration axisConfig = getAxisConfigContext().getAxisConfiguration(); + Parameter parameter = axisConfig.getParameter(MDQConstants.SHARE_DYNAMIC_PORTS_ACROSS_SERVICES); + if (parameter != null) { + flagValue = (String) parameter.getValue(); + } + + // If the property was set, check the value. + if (flagValue != null) { + if ("false".equalsIgnoreCase(flagValue)) { + resolutionEnabled = false; + } else if ("true".equalsIgnoreCase(flagValue)) { + resolutionEnabled = true; } } - return endpointDescription; + return resolutionEnabled; } - + /** * This method will get all properties that have been set on the DescriptionBuilderComposite * instance. If the DBC represents an implementation class that references an SEI, the @@ -3039,4 +3155,13 @@ public class ServiceDescriptionImpl return responses; } + /** + * Entry returned from looking for an endpointDescriptionImpl for a shared dynamic port, or to be used + * to add one that was created to the cache. + */ + class SharedDynamicEndpointEntry { + String key; + EndpointDescriptionImpl endpointDescription; + HashMap cachedDescriptions; + } } Modified: axis/axis2/java/core/branches/1_6/modules/saaj/src/org/apache/axis2/saaj/SOAPMessageImpl.java URL: http://svn.apache.org/viewvc/axis/axis2/java/core/branches/1_6/modules/saaj/src/org/apache/axis2/saaj/SOAPMessageImpl.java?rev=1156372&r1=1156371&r2=1156372&view=diff ============================================================================== --- axis/axis2/java/core/branches/1_6/modules/saaj/src/org/apache/axis2/saaj/SOAPMessageImpl.java (original) +++ axis/axis2/java/core/branches/1_6/modules/saaj/src/org/apache/axis2/saaj/SOAPMessageImpl.java Wed Aug 10 21:17:15 2011 @@ -26,9 +26,12 @@ import org.apache.axiom.om.impl.OMMultip import org.apache.axiom.soap.SOAPEnvelope; import org.apache.axiom.soap.impl.dom.soap11.SOAP11Factory; import org.apache.axiom.soap.impl.dom.soap12.SOAP12Factory; +import org.apache.axiom.util.UIDGenerator; import org.apache.axis2.saaj.util.SAAJUtil; import org.apache.axis2.transport.http.HTTPConstants; +import javax.mail.internet.ContentType; +import javax.mail.internet.ParseException; import javax.xml.soap.AttachmentPart; import javax.xml.soap.MimeHeader; import javax.xml.soap.MimeHeaders; @@ -73,13 +76,13 @@ public class SOAPMessageImpl extends SOA String contentType = null; String tmpContentType = ""; if (mimeHeaders != null) { - String contentTypes[] = mimeHeaders.getHeader(HTTPConstants.CONTENT_TYPE); + String contentTypes[] = mimeHeaders.getHeader(HTTPConstants.HEADER_CONTENT_TYPE); if (contentTypes != null && contentTypes.length > 0) { tmpContentType = contentTypes[0]; contentType = SAAJUtil.normalizeContentType(tmpContentType); } } - if ("multipart/related".equals(contentType)) { + if (HTTPConstants.MEDIA_TYPE_MULTIPART_RELATED.equals(contentType)) { try { Attachments attachments = new Attachments(inputstream, tmpContentType, false, "", ""); @@ -88,7 +91,7 @@ public class SOAPMessageImpl extends SOA // parts of the SOAP message package. We need to reconstruct them from // the available information. MimeHeaders soapPartHeaders = new MimeHeaders(); - soapPartHeaders.addHeader(HTTPConstants.CONTENT_TYPE, + soapPartHeaders.addHeader(HTTPConstants.HEADER_CONTENT_TYPE, attachments.getSOAPPartContentType()); String soapPartContentId = attachments.getSOAPPartContentID(); soapPartHeaders.addHeader("Content-ID", "<" + soapPartContentId + ">"); @@ -164,6 +167,7 @@ public class SOAPMessageImpl extends SOA */ public void removeAllAttachments() { attachmentParts.clear(); + saveRequired = true; } /** @@ -223,7 +227,8 @@ public class SOAPMessageImpl extends SOA public void addAttachmentPart(AttachmentPart attachmentPart) { if (attachmentPart != null) { attachmentParts.add(attachmentPart); - mimeHeaders.setHeader(HTTPConstants.CONTENT_TYPE, "multipart/related"); + mimeHeaders.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, "multipart/related"); + saveRequired = true; } } @@ -267,8 +272,70 @@ public class SOAPMessageImpl extends SOA * @throws SOAPException if there was a problem saving changes to this message. */ public void saveChanges() throws SOAPException { + try { + String contentTypeValue = getSingleHeaderValue(HTTPConstants.HEADER_CONTENT_TYPE); + ContentType contentType = null; + if (isEmptyString(contentTypeValue)) { + if (attachmentParts.size() > 0) { + contentTypeValue = HTTPConstants.MEDIA_TYPE_MULTIPART_RELATED; + } else { + contentTypeValue = getBaseType(); + } + } + contentType = new ContentType(contentTypeValue); + + //Use configures the baseType with multipart/related while no attachment exists or all the attachments are removed + if(contentType.getBaseType().equals(HTTPConstants.MEDIA_TYPE_MULTIPART_RELATED) && attachmentParts.size() == 0) { + contentType = new ContentType(getBaseType()); + } + + //If it is of multipart/related, initialize those required values in the content-type, including boundary etc. + if (contentType.getBaseType().equals(HTTPConstants.MEDIA_TYPE_MULTIPART_RELATED)) { + + //Configure boundary + String boundaryParam = contentType.getParameter("boundary"); + if (isEmptyString(boundaryParam)) { + contentType.setParameter("boundary", UIDGenerator.generateMimeBoundary()); + } + + //Configure start content id, always get it from soapPart in case it is changed + String soapPartContentId = soapPart.getContentId(); + if (isEmptyString(soapPartContentId)) { + soapPartContentId = "<" + UIDGenerator.generateContentId() + ">"; + soapPart.setContentId(soapPartContentId); + } + contentType.setParameter("start", soapPartContentId); + + //Configure contentId for each attachments + for(AttachmentPart attachmentPart : attachmentParts) { + if(isEmptyString(attachmentPart.getContentId())) { + attachmentPart.setContentId("<" + UIDGenerator.generateContentId() + ">"); + } + } + + //Configure type + contentType.setParameter("type", getBaseType()); + + //Configure charset + String soapPartContentTypeValue = getSingleHeaderValue(soapPart.getMimeHeader(HTTPConstants.HEADER_CONTENT_TYPE)); + ContentType soapPartContentType = null; + if (isEmptyString(soapPartContentTypeValue)) { + soapPartContentType = new ContentType(soapPartContentTypeValue); + } else { + soapPartContentType = new ContentType(getBaseType()); + } + setCharsetParameter(soapPartContentType); + } else { + //Configure charset + setCharsetParameter(contentType); + } + + mimeHeaders.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, contentType.toString()); + } catch (ParseException e) { + throw new SOAPException("Invalid Content Type Field in the Mime Message", e); + } + saveRequired = false; - // TODO not sure of the implementation } public void setSaveRequired() { @@ -299,23 +366,45 @@ public class SOAPMessageImpl extends SOA * @throws IOException if an I/O error occurs */ public void writeTo(OutputStream out) throws SOAPException, IOException { - try { + try { + saveChanges(); OMOutputFormat format = new OMOutputFormat(); String enc = (String)getProperty(CHARACTER_SET_ENCODING); format.setCharSetEncoding(enc != null ? enc : OMOutputFormat.DEFAULT_CHAR_SET_ENCODING); String writeXmlDecl = (String)getProperty(WRITE_XML_DECLARATION); if (writeXmlDecl == null || writeXmlDecl.equals("false")) { - //SAAJ default case doesn't send XML decl format.setIgnoreXMLDeclaration(true); } - - SOAPEnvelope envelope = ((SOAPEnvelopeImpl)soapPart.getEnvelope()).getOMEnvelope(); + + SOAPEnvelope envelope = ((SOAPEnvelopeImpl) soapPart.getEnvelope()).getOMEnvelope(); if (attachmentParts.isEmpty()) { envelope.serialize(out, format); } else { - format.setSOAP11(((SOAPEnvelopeImpl)soapPart.getEnvelope()).getOMFactory() - instanceof SOAP11Factory); + ContentType contentType = new ContentType(getSingleHeaderValue(HTTPConstants.HEADER_CONTENT_TYPE)); + String boundary = contentType.getParameter("boundary"); + if(isEmptyString(boundary)) { + boundary = UIDGenerator.generateMimeBoundary(); + contentType.setParameter("boundary", boundary); + } + format.setMimeBoundary(boundary); + + String rootContentId = soapPart.getContentId(); + if(isEmptyString(rootContentId)) { + rootContentId = "<" + UIDGenerator.generateContentId() + ">"; + soapPart.setContentId(rootContentId); + } + contentType.setParameter("start", rootContentId); + if ((rootContentId.indexOf("<") > -1) & (rootContentId.indexOf(">") > -1)) { + rootContentId = rootContentId.substring(1, (rootContentId.length() - 1)); + } + format.setRootContentId(rootContentId); + + format.setSOAP11(((SOAPEnvelopeImpl) soapPart.getEnvelope()).getOMFactory() instanceof SOAP11Factory); + + //Double save the content-type in case anything is updated + mimeHeaders.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, contentType.toString()); + OMMultipartWriter mpw = new OMMultipartWriter(out, format); OutputStream rootPartOutputStream = mpw.writeRootPart(); envelope.serialize(rootPartOutputStream); @@ -325,7 +414,8 @@ public class SOAPMessageImpl extends SOA } mpw.complete(); } - saveChanges(); + + saveRequired = true; } catch (Exception e) { throw new SOAPException(e); } @@ -453,6 +543,7 @@ public class SOAPMessageImpl extends SOA } attachmentParts.clear(); this.attachmentParts = newAttachmentParts; + saveRequired = true; } /** @@ -502,4 +593,45 @@ public class SOAPMessageImpl extends SOA } } } + + private boolean isEmptyString(String value) { + return value == null || value.length() == 0; + } + + private String getSingleHeaderValue(String[] values) { + return values != null && values.length > 0 ? values[0] : null; + } + + private String getSingleHeaderValue(String name) { + String[] values = mimeHeaders.getHeader(name); + if (values == null || values.length == 0) { + return null; + } else { + return values[0]; + } + } + + private String getBaseType() throws SOAPException { + boolean isSOAP12 = ((SOAPEnvelopeImpl) soapPart.getEnvelope()).getOMFactory() instanceof SOAP12Factory; + return isSOAP12 ? HTTPConstants.MEDIA_TYPE_APPLICATION_SOAP_XML : HTTPConstants.MEDIA_TYPE_TEXT_XML; + } + + /** + * If the charset is configured by CHARACTER_SET_ENCODING, set it in the contentPart always. + * If it has already been configured in the contentType, leave it there. + * UTF-8 is used as the default value. + * @param contentType + * @throws SOAPException + */ + private void setCharsetParameter(ContentType contentType) throws SOAPException{ + String charset = (String)getProperty(CHARACTER_SET_ENCODING); + if (!isEmptyString(charset)) { + contentType.setParameter("charset", charset); + } else { + charset = contentType.getParameter("charset"); + if(isEmptyString(charset)) { + contentType.setParameter("charset", "UTF-8"); + } + } + } } Modified: axis/axis2/java/core/branches/1_6/modules/saaj/src/org/apache/axis2/saaj/SOAPPartImpl.java URL: http://svn.apache.org/viewvc/axis/axis2/java/core/branches/1_6/modules/saaj/src/org/apache/axis2/saaj/SOAPPartImpl.java?rev=1156372&r1=1156371&r2=1156372&view=diff ============================================================================== --- axis/axis2/java/core/branches/1_6/modules/saaj/src/org/apache/axis2/saaj/SOAPPartImpl.java (original) +++ axis/axis2/java/core/branches/1_6/modules/saaj/src/org/apache/axis2/saaj/SOAPPartImpl.java Wed Aug 10 21:17:15 2011 @@ -89,7 +89,7 @@ public class SOAPPartImpl extends SOAPPa SOAPEnvelopeImpl soapEnvelope) { //setMimeHeader(HTTPConstants.HEADER_CONTENT_ID, IDGenerator.generateID()); //setMimeHeader(HTTPConstants.HEADER_CONTENT_TYPE, "text/xml"); - this.mimeHeaders = parentSoapMsg.getMimeHeaders(); + this.mimeHeaders = SAAJUtil.copyMimeHeaders(parentSoapMsg.getMimeHeaders()); soapMessage = parentSoapMsg; envelope = soapEnvelope; document = soapEnvelope.getOwnerDocument(); @@ -122,7 +122,7 @@ public class SOAPPartImpl extends SOAPPa this.mimeHeaders.addHeader("Content-ID", IDGenerator.generateID()); this.mimeHeaders.addHeader("content-type", HTTPConstants.MEDIA_TYPE_APPLICATION_SOAP_XML); } else { - String contentTypes[] = mimeHeaders.getHeader(HTTPConstants.CONTENT_TYPE); + String contentTypes[] = mimeHeaders.getHeader(HTTPConstants.HEADER_CONTENT_TYPE); if (contentTypes != null && contentTypes.length > 0) { try { contentType = new ContentType(contentTypes[0]); Modified: axis/axis2/java/core/branches/1_6/modules/saaj/test/org/apache/axis2/saaj/SOAPMessageTest.java URL: http://svn.apache.org/viewvc/axis/axis2/java/core/branches/1_6/modules/saaj/test/org/apache/axis2/saaj/SOAPMessageTest.java?rev=1156372&r1=1156371&r2=1156372&view=diff ============================================================================== --- axis/axis2/java/core/branches/1_6/modules/saaj/test/org/apache/axis2/saaj/SOAPMessageTest.java (original) +++ axis/axis2/java/core/branches/1_6/modules/saaj/test/org/apache/axis2/saaj/SOAPMessageTest.java Wed Aug 10 21:17:15 2011 @@ -21,11 +21,13 @@ package org.apache.axis2.saaj; import junit.framework.Assert; import org.apache.axis2.saaj.util.SAAJDataSource; +import org.apache.axis2.transport.http.HTTPConstants; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import javax.activation.DataHandler; +import javax.mail.internet.ContentType; import javax.xml.namespace.QName; import javax.xml.soap.AttachmentPart; import javax.xml.soap.MessageFactory; @@ -45,6 +47,8 @@ import javax.xml.soap.SOAPMessage; import javax.xml.soap.SOAPPart; import javax.xml.transform.stream.StreamSource; import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; @@ -187,8 +191,7 @@ public class SOAPMessageTest extends Ass } } - // TODO: check why this fails with Sun's SAAJ implementation - @Test + @Validated @Test public void testGetContent() { try { MessageFactory fac = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); @@ -201,7 +204,7 @@ public class SOAPMessageTest extends Ass AttachmentPart ap; InputStream inputStream = TestUtils.getTestFile("attach.xml"); - ap = msg.createAttachmentPart(inputStream, "text/xml"); + ap = msg.createAttachmentPart(new StreamSource(inputStream), "text/xml"); DataHandler dh = new DataHandler(new SAAJDataSource(inputStream, 1000, "text/xml", true)); @@ -229,6 +232,67 @@ public class SOAPMessageTest extends Ass } } + @Validated @Test + public void testContentTypeGeneration() throws Exception{ + MessageFactory fac = MessageFactory.newInstance(); + SOAPMessage msg = fac.createMessage(); + InputStream inputStream = TestUtils.getTestFile("attach.xml"); + AttachmentPart ap = msg.createAttachmentPart(new StreamSource(inputStream), "text/xml"); + msg.addAttachmentPart(ap); + msg.saveChanges(); + assertNotNull(msg.getMimeHeaders().getHeader(HTTPConstants.HEADER_CONTENT_TYPE)); + String contentTypeValue = msg.getMimeHeaders().getHeader(HTTPConstants.HEADER_CONTENT_TYPE)[0]; + ContentType contentType = new ContentType(contentTypeValue); + assertNotNull("boundary parameter should exist in the content-type header", contentType.getParameter("boundary")); + //start parameter is not checked, due to it is optional parameter, and seems RI will not add this value + //assertNotNull("start parameter should exist in the content-type header", contentType.getParameter("start")); + assertNotNull("type parameter should exist in the content-type header", contentType.getParameter("type")); + assertEquals(HTTPConstants.MEDIA_TYPE_MULTIPART_RELATED, contentType.getBaseType()); + } + + @Validated @Test + public void testCreateMessageWithMimeHeaders() throws Exception{ + MessageFactory fac = MessageFactory.newInstance(); + SOAPMessage msg = fac.createMessage(); + InputStream inputStream = TestUtils.getTestFile("attach.xml"); + AttachmentPart ap = msg.createAttachmentPart(new StreamSource(inputStream), "text/xml"); + msg.addAttachmentPart(ap); + msg.saveChanges(); + ContentType contentType = new ContentType(msg.getMimeHeaders().getHeader(HTTPConstants.HEADER_CONTENT_TYPE)[0]); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + msg.writeTo(out); + SOAPMessage msg2 = fac.createMessage(msg.getMimeHeaders(), new ByteArrayInputStream(out.toByteArray())); + msg2.saveChanges(); + ContentType contentType2 = new ContentType(msg2.getMimeHeaders().getHeader(HTTPConstants.HEADER_CONTENT_TYPE)[0]); + + assertEquals(contentType.getBaseType(), contentType2.getBaseType()); + assertEquals(contentType.getParameter("boundary"), contentType2.getParameter("boundary")); + assertEquals(contentType.getParameter("type"), contentType2.getParameter("type")); + //start parameter is not checked, due to it is an optional parameter, and seems RI will not add this value + //assertEquals(contentType.getParameter("start"), contentType2.getParameter("start")); + } + + @Validated @Test + public void testContentTypeUpdateWithAttachmentChanges() throws Exception{ + MessageFactory fac = MessageFactory.newInstance(); + SOAPMessage msg = fac.createMessage(); + InputStream inputStream = TestUtils.getTestFile("attach.xml"); + AttachmentPart ap = msg.createAttachmentPart(new StreamSource(inputStream), "text/xml"); + msg.addAttachmentPart(ap); + msg.saveChanges(); + + assertNotNull(msg.getMimeHeaders().getHeader(HTTPConstants.HEADER_CONTENT_TYPE)); + ContentType contentType = new ContentType(msg.getMimeHeaders().getHeader(HTTPConstants.HEADER_CONTENT_TYPE)[0]); + assertEquals(HTTPConstants.MEDIA_TYPE_MULTIPART_RELATED, contentType.getBaseType()); + + msg.removeAllAttachments(); + msg.saveChanges(); + + assertNotNull(msg.getMimeHeaders().getHeader(HTTPConstants.HEADER_CONTENT_TYPE)); + contentType = new ContentType(msg.getMimeHeaders().getHeader(HTTPConstants.HEADER_CONTENT_TYPE)[0]); + assertEquals("text/xml", contentType.getBaseType()); + } private StringBuffer copyToBuffer(InputStream inputStream) { if (inputStream == null) {