/* * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.xml.internal.ws.client; import com.sun.istack.internal.NotNull; import com.sun.istack.internal.Nullable; import com.sun.xml.internal.stream.buffer.XMLStreamBuffer; import com.sun.xml.internal.ws.addressing.WSEPRExtension; import com.sun.xml.internal.ws.api.BindingID; import com.sun.xml.internal.ws.api.Component; import com.sun.xml.internal.ws.api.ComponentFeature; import com.sun.xml.internal.ws.api.ComponentFeature.Target; import com.sun.xml.internal.ws.api.ComponentRegistry; import com.sun.xml.internal.ws.api.ComponentsFeature; import com.sun.xml.internal.ws.api.EndpointAddress; import com.sun.xml.internal.ws.api.WSBinding; import com.sun.xml.internal.ws.api.WSService; import com.sun.xml.internal.ws.api.addressing.AddressingVersion; import com.sun.xml.internal.ws.api.addressing.WSEndpointReference; import com.sun.xml.internal.ws.api.client.WSPortInfo; import com.sun.xml.internal.ws.api.message.AddressingUtils; import com.sun.xml.internal.ws.api.message.Header; import com.sun.xml.internal.ws.api.message.HeaderList; import com.sun.xml.internal.ws.api.message.MessageHeaders; import com.sun.xml.internal.ws.api.message.Packet; import com.sun.xml.internal.ws.api.model.SEIModel; import com.sun.xml.internal.ws.api.model.wsdl.WSDLPort; import com.sun.xml.internal.ws.api.pipe.ClientTubeAssemblerContext; import com.sun.xml.internal.ws.api.pipe.Engine; import com.sun.xml.internal.ws.api.pipe.Fiber; import com.sun.xml.internal.ws.api.pipe.FiberContextSwitchInterceptorFactory; import com.sun.xml.internal.ws.api.pipe.SyncStartForAsyncFeature; import com.sun.xml.internal.ws.api.pipe.Tube; import com.sun.xml.internal.ws.api.pipe.TubelineAssembler; import com.sun.xml.internal.ws.api.pipe.TubelineAssemblerFactory; import com.sun.xml.internal.ws.api.server.Container; import com.sun.xml.internal.ws.api.server.ContainerResolver; import com.sun.xml.internal.ws.binding.BindingImpl; import com.sun.xml.internal.ws.developer.JAXWSProperties; import com.sun.xml.internal.ws.developer.WSBindingProvider; import com.sun.xml.internal.ws.model.wsdl.WSDLDirectProperties; import com.sun.xml.internal.ws.model.wsdl.WSDLPortProperties; import com.sun.xml.internal.ws.model.wsdl.WSDLProperties; import com.sun.xml.internal.ws.resources.ClientMessages; import com.sun.xml.internal.ws.util.Pool; import com.sun.xml.internal.ws.util.Pool.TubePool; import com.sun.xml.internal.ws.util.RuntimeVersion; import com.sun.xml.internal.ws.wsdl.OperationDispatcher; import com.sun.org.glassfish.gmbal.ManagedObjectManager; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; import javax.xml.ws.BindingProvider; import javax.xml.ws.EndpointReference; import javax.xml.ws.RespectBindingFeature; import javax.xml.ws.Response; import javax.xml.ws.WebServiceException; import javax.xml.ws.http.HTTPBinding; import javax.xml.ws.wsaddressing.W3CEndpointReference; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.Executor; import java.util.logging.Level; import java.util.logging.Logger; import javax.management.ObjectName; /** * Base class for stubs, which accept method invocations from * client applications and pass the message to a {@link Tube} * for processing. * *

* This class implements the management of pipe instances, * and most of the {@link BindingProvider} methods. * * @author Kohsuke Kawaguchi */ public abstract class Stub implements WSBindingProvider, ResponseContextReceiver, ComponentRegistry { /** * Internal flag indicating async dispatch should be used even when the * SyncStartForAsyncInvokeFeature is present on the binding associated * with a stub. There is no type associated with this property on the * request context. Its presence is what triggers the 'prevent' behavior. */ public static final String PREVENT_SYNC_START_FOR_ASYNC_INVOKE = "com.sun.xml.internal.ws.client.StubRequestSyncStartForAsyncInvoke"; /** * Reuse pipelines as it's expensive to create. *

* Set to null when {@link #close() closed}. */ private Pool tubes; private final Engine engine; /** * The {@link WSServiceDelegate} object that owns us. */ protected final WSServiceDelegate owner; /** * Non-null if this stub is configured to talk to an EPR. *

* When this field is non-null, its reference parameters are sent as out-bound headers. * This field can be null even when addressing is enabled, but if the addressing is * not enabled, this field must be null. *

* Unlike endpoint address, we are not letting users to change the EPR, * as it contains references to services and so on that we don't want to change. */ protected @Nullable WSEndpointReference endpointReference; protected final BindingImpl binding; protected final WSPortInfo portInfo; /** * represents AddressingVersion on binding if enabled, otherwise null; */ protected AddressingVersion addrVersion; public RequestContext requestContext = new RequestContext(); private final RequestContext cleanRequestContext; /** * {@link ResponseContext} from the last synchronous operation. */ private ResponseContext responseContext; @Nullable protected final WSDLPort wsdlPort; protected QName portname; /** * {@link Header}s to be added to outbound {@link Packet}. * The contents is determined by the user. */ @Nullable private volatile Header[] userOutboundHeaders; private final @NotNull WSDLProperties wsdlProperties; protected OperationDispatcher operationDispatcher = null; private final @NotNull ManagedObjectManager managedObjectManager; private boolean managedObjectManagerClosed = false; private final Set components = new CopyOnWriteArraySet(); /** * @param master The created stub will send messages to this pipe. * @param binding As a {@link BindingProvider}, this object will * return this binding from {@link BindingProvider#getBinding()}. * @param defaultEndPointAddress The destination of the message. The actual destination * could be overridden by {@link RequestContext}. * @param epr To create a stub that sends out reference parameters * of a specific EPR, give that instance. Otherwise null. * Its address field will not be used, and that should be given * separately as the {@code defaultEndPointAddress}. */ @Deprecated protected Stub(WSServiceDelegate owner, Tube master, BindingImpl binding, WSDLPort wsdlPort, EndpointAddress defaultEndPointAddress, @Nullable WSEndpointReference epr) { this(owner, master, null, null, binding, wsdlPort, defaultEndPointAddress, epr); } /** * @param portname The name of this port * @param master The created stub will send messages to this pipe. * @param binding As a {@link BindingProvider}, this object will * return this binding from {@link BindingProvider#getBinding()}. * @param defaultEndPointAddress The destination of the message. The actual destination * could be overridden by {@link RequestContext}. * @param epr To create a stub that sends out reference parameters * of a specific EPR, give that instance. Otherwise null. * Its address field will not be used, and that should be given * separately as the {@code defaultEndPointAddress}. */ @Deprecated protected Stub(QName portname, WSServiceDelegate owner, Tube master, BindingImpl binding, WSDLPort wsdlPort, EndpointAddress defaultEndPointAddress, @Nullable WSEndpointReference epr) { this(owner, master, null, portname, binding, wsdlPort, defaultEndPointAddress, epr); } /** * @param portInfo PortInfo for this stub * @param binding As a {@link BindingProvider}, this object will * return this binding from {@link BindingProvider#getBinding()}. * @param master The created stub will send messages to this pipe. * @param defaultEndPointAddress The destination of the message. The actual destination * could be overridden by {@link RequestContext}. * @param epr To create a stub that sends out reference parameters * of a specific EPR, give that instance. Otherwise null. * Its address field will not be used, and that should be given * separately as the {@code defaultEndPointAddress}. */ protected Stub(WSPortInfo portInfo, BindingImpl binding, Tube master,EndpointAddress defaultEndPointAddress, @Nullable WSEndpointReference epr) { this((WSServiceDelegate) portInfo.getOwner(), master, portInfo, null, binding,portInfo.getPort(), defaultEndPointAddress, epr); } /** * @param portInfo PortInfo for this stub * @param binding As a {@link BindingProvider}, this object will * return this binding from {@link BindingProvider#getBinding()}. * @param defaultEndPointAddress The destination of the message. The actual destination * could be overridden by {@link RequestContext}. * @param epr To create a stub that sends out reference parameters * of a specific EPR, give that instance. Otherwise null. * Its address field will not be used, and that should be given * separately as the {@code defaultEndPointAddress}. */ protected Stub(WSPortInfo portInfo, BindingImpl binding, EndpointAddress defaultEndPointAddress, @Nullable WSEndpointReference epr) { this(portInfo,binding,null, defaultEndPointAddress,epr); } private Stub(WSServiceDelegate owner, @Nullable Tube master, @Nullable WSPortInfo portInfo, QName portname, BindingImpl binding, @Nullable WSDLPort wsdlPort, EndpointAddress defaultEndPointAddress, @Nullable WSEndpointReference epr) { Container old = ContainerResolver.getDefault().enterContainer(owner.getContainer()); try { this.owner = owner; this.portInfo = portInfo; this.wsdlPort = wsdlPort != null ? wsdlPort : (portInfo != null ? portInfo.getPort() : null); this.portname = portname; if (portname == null) { if (portInfo != null) { this.portname = portInfo.getPortName(); } else if (wsdlPort != null) { this.portname = wsdlPort.getName(); } } this.binding = binding; ComponentFeature cf = binding.getFeature(ComponentFeature.class); if (cf != null && Target.STUB.equals(cf.getTarget())) { components.add(cf.getComponent()); } ComponentsFeature csf = binding.getFeature(ComponentsFeature.class); if (csf != null) { for (ComponentFeature cfi : csf.getComponentFeatures()) { if (Target.STUB.equals(cfi.getTarget())) components.add(cfi.getComponent()); } } // if there is an EPR, EPR's address should be used for invocation instead of default address if (epr != null) { this.requestContext.setEndPointAddressString(epr.getAddress()); } else { this.requestContext.setEndpointAddress(defaultEndPointAddress); } this.engine = new Engine(getStringId(), owner.getContainer(), owner.getExecutor()); this.endpointReference = epr; wsdlProperties = (wsdlPort == null) ? new WSDLDirectProperties(owner.getServiceName(), portname) : new WSDLPortProperties(wsdlPort); this.cleanRequestContext = this.requestContext.copy(); // ManagedObjectManager MUST be created before the pipeline // is constructed. managedObjectManager = new MonitorRootClient(this).createManagedObjectManager(this); if (master != null) { this.tubes = new TubePool(master); } else { this.tubes = new TubePool(createPipeline(portInfo, binding)); } addrVersion = binding.getAddressingVersion(); // This needs to happen after createPipeline. // TBD: Check if it needs to happen outside the Stub constructor. managedObjectManager.resumeJMXRegistration(); } finally { ContainerResolver.getDefault().exitContainer(old); } } /** * Creates a new pipeline for the given port name. */ private Tube createPipeline(WSPortInfo portInfo, WSBinding binding) { //Check all required WSDL extensions are understood checkAllWSDLExtensionsUnderstood(portInfo, binding); SEIModel seiModel = null; Class sei = null; if (portInfo instanceof SEIPortInfo) { SEIPortInfo sp = (SEIPortInfo) portInfo; seiModel = sp.model; sei = sp.sei; } BindingID bindingId = portInfo.getBindingId(); TubelineAssembler assembler = TubelineAssemblerFactory.create( Thread.currentThread().getContextClassLoader(), bindingId, owner.getContainer()); if (assembler == null) { throw new WebServiceException("Unable to process bindingID=" + bindingId); // TODO: i18n } return assembler.createClient( new ClientTubeAssemblerContext( portInfo.getEndpointAddress(), portInfo.getPort(), this, binding, owner.getContainer(), ((BindingImpl) binding).createCodec(), seiModel, sei)); } public WSDLPort getWSDLPort() { return wsdlPort; } public WSService getService() { return owner; } public Pool getTubes() { return tubes; } /** * Checks only if RespectBindingFeature is enabled * checks if all required wsdl extensions in the * corresponding wsdl:Port are understood when RespectBindingFeature is enabled. * @throws WebServiceException * when any wsdl extension that has wsdl:required=true is not understood */ private static void checkAllWSDLExtensionsUnderstood(WSPortInfo port, WSBinding binding) { if (port.getPort() != null && binding.isFeatureEnabled(RespectBindingFeature.class)) { port.getPort().areRequiredExtensionsUnderstood(); } } @Override public WSPortInfo getPortInfo() { return portInfo; } /** * Nullable when there is no associated WSDL Model * @return */ public @Nullable OperationDispatcher getOperationDispatcher() { if (operationDispatcher == null && wsdlPort != null) { operationDispatcher = new OperationDispatcher(wsdlPort, binding, null); } return operationDispatcher; } /** * Gets the port name that this stub is configured to talk to. *

* When {@link #wsdlPort} is non-null, the port name is always * the same as {@link WSDLPort#getName()}, but this method * returns a port name even if no WSDL is available for this stub. */ protected abstract @NotNull QName getPortName(); /** * Gets the service name that this stub is configured to talk to. *

* When {@link #wsdlPort} is non-null, the service name is always * the same as the one that's inferred from {@link WSDLPort#getOwner()}, * but this method returns a port name even if no WSDL is available for * this stub. */ protected final @NotNull QName getServiceName() { return owner.getServiceName(); } /** * Gets the {@link Executor} to be used for asynchronous method invocations. *

* Note that the value this method returns may different from invocations * to invocations. The caller must not cache. * * @return always non-null. */ public final Executor getExecutor() { return owner.getExecutor(); } /** * Passes a message to a pipe for processing. *

* Unlike {@link Tube} instances, * this method is thread-safe and can be invoked from * multiple threads concurrently. * * @param packet The message to be sent to the server * @param requestContext The {@link RequestContext} when this invocation is originally scheduled. * This must be the same object as {@link #requestContext} for synchronous * invocations, but for asynchronous invocations, it needs to be a snapshot * captured at the point of invocation, to correctly satisfy the spec requirement. * @param receiver Receives the {@link ResponseContext}. Since the spec requires * that the asynchronous invocations must not update response context, * depending on the mode of invocation they have to go to different places. * So we take a setter that abstracts that away. */ protected final Packet process(Packet packet, RequestContext requestContext, ResponseContextReceiver receiver) { packet.isSynchronousMEP = true; packet.component = this; configureRequestPacket(packet, requestContext); Pool pool = tubes; if (pool == null) { throw new WebServiceException("close method has already been invoked"); // TODO: i18n } Fiber fiber = engine.createFiber(); configureFiber(fiber); // then send it away! Tube tube = pool.take(); try { return fiber.runSync(tube, packet); } finally { // this allows us to capture the packet even when the call failed with an exception. // when the call fails with an exception it's no longer a 'reply' but it may provide some information // about what went wrong. // note that Packet can still be updated after // ResponseContext is created. Packet reply = (fiber.getPacket() == null) ? packet : fiber.getPacket(); receiver.setResponseContext(new ResponseContext(reply)); pool.recycle(tube); } } private void configureRequestPacket(Packet packet, RequestContext requestContext) { // fill in Packet packet.proxy = this; packet.handlerConfig = binding.getHandlerConfig(); // to make it multi-thread safe we need to first get a stable snapshot Header[] hl = userOutboundHeaders; if (hl != null) { MessageHeaders mh = packet.getMessage().getHeaders(); for (Header h : hl) { mh.add(h); } } requestContext.fill(packet, (binding.getAddressingVersion() != null)); packet.addSatellite(wsdlProperties); if (addrVersion != null) { // populate request WS-Addressing headers MessageHeaders headerList = packet.getMessage().getHeaders(); AddressingUtils.fillRequestAddressingHeaders(headerList, wsdlPort, binding, packet); // Spec is not clear on if ReferenceParameters are to be added when addressing is not enabled, // but the EPR has ReferenceParameters. // Current approach: Add ReferenceParameters only if addressing enabled. if (endpointReference != null) { endpointReference.addReferenceParametersToList(packet.getMessage().getHeaders()); } } } /** * Passes a message through a {@link Tube}line for processing. The processing happens * asynchronously and when the response is available, Fiber.CompletionCallback is * called. The processing could happen on multiple threads. * *

* Unlike {@link Tube} instances, * this method is thread-safe and can be invoked from * multiple threads concurrently. * * @param receiver The {@link Response} implementation * @param request The message to be sent to the server * @param requestContext The {@link RequestContext} when this invocation is originally scheduled. * This must be the same object as {@link #requestContext} for synchronous * invocations, but for asynchronous invocations, it needs to be a snapshot * captured at the point of invocation, to correctly satisfy the spec requirement. * @param completionCallback Once the processing is done, the callback is invoked. */ protected final void processAsync(AsyncResponseImpl receiver, Packet request, RequestContext requestContext, final Fiber.CompletionCallback completionCallback) { // fill in Packet request.component = this; configureRequestPacket(request, requestContext); final Pool pool = tubes; if (pool == null) { throw new WebServiceException("close method has already been invoked"); // TODO: i18n } final Fiber fiber = engine.createFiber(); configureFiber(fiber); receiver.setCancelable(fiber); // check race condition on cancel if (receiver.isCancelled()) { return; } FiberContextSwitchInterceptorFactory fcsif = owner.getSPI(FiberContextSwitchInterceptorFactory.class); if (fcsif != null) { fiber.addInterceptor(fcsif.create()); } // then send it away! final Tube tube = pool.take(); Fiber.CompletionCallback fiberCallback = new Fiber.CompletionCallback() { @Override public void onCompletion(@NotNull Packet response) { pool.recycle(tube); completionCallback.onCompletion(response); } @Override public void onCompletion(@NotNull Throwable error) { // let's not reuse tubes as they might be in a wrong state, so not // calling pool.recycle() completionCallback.onCompletion(error); } }; // Check for SyncStartForAsyncInvokeFeature fiber.start(tube, request, fiberCallback, getBinding().isFeatureEnabled(SyncStartForAsyncFeature.class) && !requestContext.containsKey(PREVENT_SYNC_START_FOR_ASYNC_INVOKE)); } protected void configureFiber(Fiber fiber) { // no-op in the base class, but can be used by derived classes to configure the Fiber prior // to invocation } private static final Logger monitoringLogger = Logger.getLogger(com.sun.xml.internal.ws.util.Constants.LoggingDomain + ".monitoring"); @Override public void close() { TubePool tp = (TubePool) tubes; if (tp != null) { // multi-thread safety of 'close' needs to be considered more carefully. // some calls might be pending while this method is invoked. Should we // block until they are complete, or should we abort them (but how?) Tube p = tp.takeMaster(); p.preDestroy(); tubes = null; } if (!managedObjectManagerClosed) { try { final ObjectName name = managedObjectManager.getObjectName(managedObjectManager.getRoot()); // The name is null when the MOM is a NOOP. if (name != null) { monitoringLogger.log(Level.INFO, "Closing Metro monitoring root: {0}", name); } managedObjectManager.close(); } catch (java.io.IOException e) { monitoringLogger.log(Level.WARNING, "Ignoring error when closing Managed Object Manager", e); } managedObjectManagerClosed = true; } } @Override public final WSBinding getBinding() { return binding; } @Override public final Map getRequestContext() { return requestContext.asMap(); } public void resetRequestContext() { requestContext = cleanRequestContext.copy(); } @Override public final ResponseContext getResponseContext() { return responseContext; } @Override public void setResponseContext(ResponseContext rc) { this.responseContext = rc; } private String getStringId() { return RuntimeVersion.VERSION + ": Stub for " + getRequestContext().get(BindingProvider.ENDPOINT_ADDRESS_PROPERTY); } @Override public String toString() { return getStringId(); } @Override public final WSEndpointReference getWSEndpointReference() { if (binding.getBindingID().equals(HTTPBinding.HTTP_BINDING)) { throw new java.lang.UnsupportedOperationException( ClientMessages.UNSUPPORTED_OPERATION("BindingProvider.getEndpointReference(Class class)", "XML/HTTP Binding", "SOAP11 or SOAP12 Binding") ); } if (endpointReference != null) { return endpointReference; } String eprAddress = requestContext.getEndpointAddress().toString(); QName portTypeName = null; String wsdlAddress = null; List wsdlEPRExtensions = new ArrayList(); if (wsdlPort != null) { portTypeName = wsdlPort.getBinding().getPortTypeName(); wsdlAddress = eprAddress + "?wsdl"; //gather EPRExtensions specified in WSDL. try { WSEndpointReference wsdlEpr = wsdlPort.getEPR(); if (wsdlEpr != null) { for (WSEndpointReference.EPRExtension extnEl : wsdlEpr.getEPRExtensions()) { wsdlEPRExtensions.add(new WSEPRExtension( XMLStreamBuffer.createNewBufferFromXMLStreamReader(extnEl.readAsXMLStreamReader()), extnEl.getQName())); } } } catch (XMLStreamException ex) { throw new WebServiceException(ex); } } AddressingVersion av = AddressingVersion.W3C; this.endpointReference = new WSEndpointReference( av, eprAddress, getServiceName(), getPortName(), portTypeName, null, wsdlAddress, null, wsdlEPRExtensions, null); return this.endpointReference; } @Override public final W3CEndpointReference getEndpointReference() { if (binding.getBindingID().equals(HTTPBinding.HTTP_BINDING)) { throw new java.lang.UnsupportedOperationException( ClientMessages.UNSUPPORTED_OPERATION("BindingProvider.getEndpointReference()", "XML/HTTP Binding", "SOAP11 or SOAP12 Binding")); } return getEndpointReference(W3CEndpointReference.class); } @Override public final T getEndpointReference(Class clazz) { return getWSEndpointReference().toSpec(clazz); } public @NotNull @Override ManagedObjectManager getManagedObjectManager() { return managedObjectManager; } // // // WSBindingProvider methods // // @Override public final void setOutboundHeaders(List

headers) { if (headers == null) { this.userOutboundHeaders = null; } else { for (Header h : headers) { if (h == null) { throw new IllegalArgumentException(); } } userOutboundHeaders = headers.toArray(new Header[headers.size()]); } } @Override public final void setOutboundHeaders(Header... headers) { if (headers == null) { this.userOutboundHeaders = null; } else { for (Header h : headers) { if (h == null) { throw new IllegalArgumentException(); } } Header[] hl = new Header[headers.length]; System.arraycopy(headers, 0, hl, 0, headers.length); userOutboundHeaders = hl; } } @Override public final List
getInboundHeaders() { return Collections.unmodifiableList(((MessageHeaders) responseContext.get(JAXWSProperties.INBOUND_HEADER_LIST_PROPERTY)).asList()); } @Override public final void setAddress(String address) { requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, address); } @Override public S getSPI(Class spiType) { for (Component c : components) { S s = c.getSPI(spiType); if (s != null) { return s; } } return owner.getSPI(spiType); } @Override public Set getComponents() { return components; } }