Thu, 31 Aug 2017 15:18:52 +0800
merge
aoqi@0 | 1 | /* |
aoqi@0 | 2 | * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. |
aoqi@0 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
aoqi@0 | 4 | * |
aoqi@0 | 5 | * This code is free software; you can redistribute it and/or modify it |
aoqi@0 | 6 | * under the terms of the GNU General Public License version 2 only, as |
aoqi@0 | 7 | * published by the Free Software Foundation. Oracle designates this |
aoqi@0 | 8 | * particular file as subject to the "Classpath" exception as provided |
aoqi@0 | 9 | * by Oracle in the LICENSE file that accompanied this code. |
aoqi@0 | 10 | * |
aoqi@0 | 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
aoqi@0 | 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
aoqi@0 | 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
aoqi@0 | 14 | * version 2 for more details (a copy is included in the LICENSE file that |
aoqi@0 | 15 | * accompanied this code). |
aoqi@0 | 16 | * |
aoqi@0 | 17 | * You should have received a copy of the GNU General Public License version |
aoqi@0 | 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
aoqi@0 | 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
aoqi@0 | 20 | * |
aoqi@0 | 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
aoqi@0 | 22 | * or visit www.oracle.com if you need additional information or have any |
aoqi@0 | 23 | * questions. |
aoqi@0 | 24 | */ |
aoqi@0 | 25 | |
aoqi@0 | 26 | package com.sun.xml.internal.ws.client; |
aoqi@0 | 27 | |
aoqi@0 | 28 | import com.sun.istack.internal.NotNull; |
aoqi@0 | 29 | import com.sun.istack.internal.Nullable; |
aoqi@0 | 30 | import com.sun.xml.internal.stream.buffer.XMLStreamBuffer; |
aoqi@0 | 31 | import com.sun.xml.internal.ws.addressing.WSEPRExtension; |
aoqi@0 | 32 | import com.sun.xml.internal.ws.api.BindingID; |
aoqi@0 | 33 | import com.sun.xml.internal.ws.api.Component; |
aoqi@0 | 34 | import com.sun.xml.internal.ws.api.ComponentFeature; |
aoqi@0 | 35 | import com.sun.xml.internal.ws.api.ComponentFeature.Target; |
aoqi@0 | 36 | import com.sun.xml.internal.ws.api.ComponentRegistry; |
aoqi@0 | 37 | import com.sun.xml.internal.ws.api.ComponentsFeature; |
aoqi@0 | 38 | import com.sun.xml.internal.ws.api.EndpointAddress; |
aoqi@0 | 39 | import com.sun.xml.internal.ws.api.WSBinding; |
aoqi@0 | 40 | import com.sun.xml.internal.ws.api.WSService; |
aoqi@0 | 41 | import com.sun.xml.internal.ws.api.addressing.AddressingVersion; |
aoqi@0 | 42 | import com.sun.xml.internal.ws.api.addressing.WSEndpointReference; |
aoqi@0 | 43 | import com.sun.xml.internal.ws.api.client.WSPortInfo; |
aoqi@0 | 44 | import com.sun.xml.internal.ws.api.message.AddressingUtils; |
aoqi@0 | 45 | import com.sun.xml.internal.ws.api.message.Header; |
aoqi@0 | 46 | import com.sun.xml.internal.ws.api.message.HeaderList; |
aoqi@0 | 47 | import com.sun.xml.internal.ws.api.message.MessageHeaders; |
aoqi@0 | 48 | import com.sun.xml.internal.ws.api.message.Packet; |
aoqi@0 | 49 | import com.sun.xml.internal.ws.api.model.SEIModel; |
aoqi@0 | 50 | import com.sun.xml.internal.ws.api.model.wsdl.WSDLPort; |
aoqi@0 | 51 | import com.sun.xml.internal.ws.api.pipe.ClientTubeAssemblerContext; |
aoqi@0 | 52 | import com.sun.xml.internal.ws.api.pipe.Engine; |
aoqi@0 | 53 | import com.sun.xml.internal.ws.api.pipe.Fiber; |
aoqi@0 | 54 | import com.sun.xml.internal.ws.api.pipe.FiberContextSwitchInterceptorFactory; |
aoqi@0 | 55 | import com.sun.xml.internal.ws.api.pipe.SyncStartForAsyncFeature; |
aoqi@0 | 56 | import com.sun.xml.internal.ws.api.pipe.Tube; |
aoqi@0 | 57 | import com.sun.xml.internal.ws.api.pipe.TubelineAssembler; |
aoqi@0 | 58 | import com.sun.xml.internal.ws.api.pipe.TubelineAssemblerFactory; |
aoqi@0 | 59 | import com.sun.xml.internal.ws.api.server.Container; |
aoqi@0 | 60 | import com.sun.xml.internal.ws.api.server.ContainerResolver; |
aoqi@0 | 61 | import com.sun.xml.internal.ws.binding.BindingImpl; |
aoqi@0 | 62 | import com.sun.xml.internal.ws.developer.JAXWSProperties; |
aoqi@0 | 63 | import com.sun.xml.internal.ws.developer.WSBindingProvider; |
aoqi@0 | 64 | import com.sun.xml.internal.ws.model.wsdl.WSDLDirectProperties; |
aoqi@0 | 65 | import com.sun.xml.internal.ws.model.wsdl.WSDLPortProperties; |
aoqi@0 | 66 | import com.sun.xml.internal.ws.model.wsdl.WSDLProperties; |
aoqi@0 | 67 | import com.sun.xml.internal.ws.resources.ClientMessages; |
aoqi@0 | 68 | import com.sun.xml.internal.ws.util.Pool; |
aoqi@0 | 69 | import com.sun.xml.internal.ws.util.Pool.TubePool; |
aoqi@0 | 70 | import com.sun.xml.internal.ws.util.RuntimeVersion; |
aoqi@0 | 71 | import com.sun.xml.internal.ws.wsdl.OperationDispatcher; |
aoqi@0 | 72 | import com.sun.org.glassfish.gmbal.ManagedObjectManager; |
aoqi@0 | 73 | |
aoqi@0 | 74 | import javax.xml.namespace.QName; |
aoqi@0 | 75 | import javax.xml.stream.XMLStreamException; |
aoqi@0 | 76 | import javax.xml.ws.BindingProvider; |
aoqi@0 | 77 | import javax.xml.ws.EndpointReference; |
aoqi@0 | 78 | import javax.xml.ws.RespectBindingFeature; |
aoqi@0 | 79 | import javax.xml.ws.Response; |
aoqi@0 | 80 | import javax.xml.ws.WebServiceException; |
aoqi@0 | 81 | import javax.xml.ws.http.HTTPBinding; |
aoqi@0 | 82 | import javax.xml.ws.wsaddressing.W3CEndpointReference; |
aoqi@0 | 83 | import java.util.ArrayList; |
aoqi@0 | 84 | import java.util.Collections; |
aoqi@0 | 85 | import java.util.List; |
aoqi@0 | 86 | import java.util.Map; |
aoqi@0 | 87 | import java.util.Set; |
aoqi@0 | 88 | import java.util.concurrent.CopyOnWriteArraySet; |
aoqi@0 | 89 | import java.util.concurrent.Executor; |
aoqi@0 | 90 | import java.util.logging.Level; |
aoqi@0 | 91 | import java.util.logging.Logger; |
aoqi@0 | 92 | import javax.management.ObjectName; |
aoqi@0 | 93 | |
aoqi@0 | 94 | /** |
aoqi@0 | 95 | * Base class for stubs, which accept method invocations from |
aoqi@0 | 96 | * client applications and pass the message to a {@link Tube} |
aoqi@0 | 97 | * for processing. |
aoqi@0 | 98 | * |
aoqi@0 | 99 | * <p> |
aoqi@0 | 100 | * This class implements the management of pipe instances, |
aoqi@0 | 101 | * and most of the {@link BindingProvider} methods. |
aoqi@0 | 102 | * |
aoqi@0 | 103 | * @author Kohsuke Kawaguchi |
aoqi@0 | 104 | */ |
aoqi@0 | 105 | public abstract class Stub implements WSBindingProvider, ResponseContextReceiver, ComponentRegistry { |
aoqi@0 | 106 | /** |
aoqi@0 | 107 | * Internal flag indicating async dispatch should be used even when the |
aoqi@0 | 108 | * SyncStartForAsyncInvokeFeature is present on the binding associated |
aoqi@0 | 109 | * with a stub. There is no type associated with this property on the |
aoqi@0 | 110 | * request context. Its presence is what triggers the 'prevent' behavior. |
aoqi@0 | 111 | */ |
aoqi@0 | 112 | public static final String PREVENT_SYNC_START_FOR_ASYNC_INVOKE = "com.sun.xml.internal.ws.client.StubRequestSyncStartForAsyncInvoke"; |
aoqi@0 | 113 | |
aoqi@0 | 114 | /** |
aoqi@0 | 115 | * Reuse pipelines as it's expensive to create. |
aoqi@0 | 116 | * <p> |
aoqi@0 | 117 | * Set to null when {@link #close() closed}. |
aoqi@0 | 118 | */ |
aoqi@0 | 119 | private Pool<Tube> tubes; |
aoqi@0 | 120 | |
aoqi@0 | 121 | private final Engine engine; |
aoqi@0 | 122 | |
aoqi@0 | 123 | /** |
aoqi@0 | 124 | * The {@link WSServiceDelegate} object that owns us. |
aoqi@0 | 125 | */ |
aoqi@0 | 126 | protected final WSServiceDelegate owner; |
aoqi@0 | 127 | |
aoqi@0 | 128 | /** |
aoqi@0 | 129 | * Non-null if this stub is configured to talk to an EPR. |
aoqi@0 | 130 | * <p> |
aoqi@0 | 131 | * When this field is non-null, its reference parameters are sent as out-bound headers. |
aoqi@0 | 132 | * This field can be null even when addressing is enabled, but if the addressing is |
aoqi@0 | 133 | * not enabled, this field must be null. |
aoqi@0 | 134 | * <p> |
aoqi@0 | 135 | * Unlike endpoint address, we are not letting users to change the EPR, |
aoqi@0 | 136 | * as it contains references to services and so on that we don't want to change. |
aoqi@0 | 137 | */ |
aoqi@0 | 138 | protected |
aoqi@0 | 139 | @Nullable |
aoqi@0 | 140 | WSEndpointReference endpointReference; |
aoqi@0 | 141 | |
aoqi@0 | 142 | protected final BindingImpl binding; |
aoqi@0 | 143 | |
aoqi@0 | 144 | protected final WSPortInfo portInfo; |
aoqi@0 | 145 | |
aoqi@0 | 146 | /** |
aoqi@0 | 147 | * represents AddressingVersion on binding if enabled, otherwise null; |
aoqi@0 | 148 | */ |
aoqi@0 | 149 | protected AddressingVersion addrVersion; |
aoqi@0 | 150 | |
aoqi@0 | 151 | public RequestContext requestContext = new RequestContext(); |
aoqi@0 | 152 | |
aoqi@0 | 153 | private final RequestContext cleanRequestContext; |
aoqi@0 | 154 | |
aoqi@0 | 155 | /** |
aoqi@0 | 156 | * {@link ResponseContext} from the last synchronous operation. |
aoqi@0 | 157 | */ |
aoqi@0 | 158 | private ResponseContext responseContext; |
aoqi@0 | 159 | @Nullable |
aoqi@0 | 160 | protected final WSDLPort wsdlPort; |
aoqi@0 | 161 | |
aoqi@0 | 162 | protected QName portname; |
aoqi@0 | 163 | |
aoqi@0 | 164 | /** |
aoqi@0 | 165 | * {@link Header}s to be added to outbound {@link Packet}. |
aoqi@0 | 166 | * The contents is determined by the user. |
aoqi@0 | 167 | */ |
aoqi@0 | 168 | @Nullable |
aoqi@0 | 169 | private volatile Header[] userOutboundHeaders; |
aoqi@0 | 170 | |
aoqi@0 | 171 | private final |
aoqi@0 | 172 | @NotNull |
aoqi@0 | 173 | WSDLProperties wsdlProperties; |
aoqi@0 | 174 | protected OperationDispatcher operationDispatcher = null; |
aoqi@0 | 175 | private final |
aoqi@0 | 176 | @NotNull |
aoqi@0 | 177 | ManagedObjectManager managedObjectManager; |
aoqi@0 | 178 | private boolean managedObjectManagerClosed = false; |
aoqi@0 | 179 | |
aoqi@0 | 180 | private final Set<Component> components = new CopyOnWriteArraySet<Component>(); |
aoqi@0 | 181 | |
aoqi@0 | 182 | /** |
aoqi@0 | 183 | * @param master The created stub will send messages to this pipe. |
aoqi@0 | 184 | * @param binding As a {@link BindingProvider}, this object will |
aoqi@0 | 185 | * return this binding from {@link BindingProvider#getBinding()}. |
aoqi@0 | 186 | * @param defaultEndPointAddress The destination of the message. The actual destination |
aoqi@0 | 187 | * could be overridden by {@link RequestContext}. |
aoqi@0 | 188 | * @param epr To create a stub that sends out reference parameters |
aoqi@0 | 189 | * of a specific EPR, give that instance. Otherwise null. |
aoqi@0 | 190 | * Its address field will not be used, and that should be given |
aoqi@0 | 191 | * separately as the <tt>defaultEndPointAddress</tt>. |
aoqi@0 | 192 | */ |
aoqi@0 | 193 | @Deprecated |
aoqi@0 | 194 | protected Stub(WSServiceDelegate owner, Tube master, BindingImpl binding, WSDLPort wsdlPort, EndpointAddress defaultEndPointAddress, @Nullable WSEndpointReference epr) { |
aoqi@0 | 195 | this(owner, master, null, null, binding, wsdlPort, defaultEndPointAddress, epr); |
aoqi@0 | 196 | } |
aoqi@0 | 197 | |
aoqi@0 | 198 | /** |
aoqi@0 | 199 | * @param portname The name of this port |
aoqi@0 | 200 | * @param master The created stub will send messages to this pipe. |
aoqi@0 | 201 | * @param binding As a {@link BindingProvider}, this object will |
aoqi@0 | 202 | * return this binding from {@link BindingProvider#getBinding()}. |
aoqi@0 | 203 | * @param defaultEndPointAddress The destination of the message. The actual destination |
aoqi@0 | 204 | * could be overridden by {@link RequestContext}. |
aoqi@0 | 205 | * @param epr To create a stub that sends out reference parameters |
aoqi@0 | 206 | * of a specific EPR, give that instance. Otherwise null. |
aoqi@0 | 207 | * Its address field will not be used, and that should be given |
aoqi@0 | 208 | * separately as the <tt>defaultEndPointAddress</tt>. |
aoqi@0 | 209 | */ |
aoqi@0 | 210 | @Deprecated |
aoqi@0 | 211 | protected Stub(QName portname, WSServiceDelegate owner, Tube master, BindingImpl binding, WSDLPort wsdlPort, EndpointAddress defaultEndPointAddress, @Nullable WSEndpointReference epr) { |
aoqi@0 | 212 | this(owner, master, null, portname, binding, wsdlPort, defaultEndPointAddress, epr); |
aoqi@0 | 213 | } |
aoqi@0 | 214 | |
aoqi@0 | 215 | /** |
aoqi@0 | 216 | * @param portInfo PortInfo for this stub |
aoqi@0 | 217 | * @param binding As a {@link BindingProvider}, this object will |
aoqi@0 | 218 | * return this binding from {@link BindingProvider#getBinding()}. |
aoqi@0 | 219 | * @param master The created stub will send messages to this pipe. |
aoqi@0 | 220 | * @param defaultEndPointAddress The destination of the message. The actual destination |
aoqi@0 | 221 | * could be overridden by {@link RequestContext}. |
aoqi@0 | 222 | * @param epr To create a stub that sends out reference parameters |
aoqi@0 | 223 | * of a specific EPR, give that instance. Otherwise null. |
aoqi@0 | 224 | * Its address field will not be used, and that should be given |
aoqi@0 | 225 | * separately as the <tt>defaultEndPointAddress</tt>. |
aoqi@0 | 226 | */ |
aoqi@0 | 227 | protected Stub(WSPortInfo portInfo, BindingImpl binding, Tube master,EndpointAddress defaultEndPointAddress, @Nullable WSEndpointReference epr) { |
aoqi@0 | 228 | this((WSServiceDelegate) portInfo.getOwner(), master, portInfo, null, binding,portInfo.getPort(), defaultEndPointAddress, epr); |
aoqi@0 | 229 | } |
aoqi@0 | 230 | |
aoqi@0 | 231 | /** |
aoqi@0 | 232 | * @param portInfo PortInfo for this stub |
aoqi@0 | 233 | * @param binding As a {@link BindingProvider}, this object will |
aoqi@0 | 234 | * return this binding from {@link BindingProvider#getBinding()}. |
aoqi@0 | 235 | * @param defaultEndPointAddress The destination of the message. The actual destination |
aoqi@0 | 236 | * could be overridden by {@link RequestContext}. |
aoqi@0 | 237 | * @param epr To create a stub that sends out reference parameters |
aoqi@0 | 238 | * of a specific EPR, give that instance. Otherwise null. |
aoqi@0 | 239 | * Its address field will not be used, and that should be given |
aoqi@0 | 240 | * separately as the <tt>defaultEndPointAddress</tt>. |
aoqi@0 | 241 | */ |
aoqi@0 | 242 | protected Stub(WSPortInfo portInfo, BindingImpl binding, EndpointAddress defaultEndPointAddress, @Nullable WSEndpointReference epr) { |
aoqi@0 | 243 | this(portInfo,binding,null, defaultEndPointAddress,epr); |
aoqi@0 | 244 | |
aoqi@0 | 245 | } |
aoqi@0 | 246 | |
aoqi@0 | 247 | private Stub(WSServiceDelegate owner, @Nullable Tube master, @Nullable WSPortInfo portInfo, QName portname, BindingImpl binding, @Nullable WSDLPort wsdlPort, EndpointAddress defaultEndPointAddress, @Nullable WSEndpointReference epr) { |
aoqi@0 | 248 | Container old = ContainerResolver.getDefault().enterContainer(owner.getContainer()); |
aoqi@0 | 249 | try { |
aoqi@0 | 250 | this.owner = owner; |
aoqi@0 | 251 | this.portInfo = portInfo; |
aoqi@0 | 252 | this.wsdlPort = wsdlPort != null ? wsdlPort : (portInfo != null ? portInfo.getPort() : null); |
aoqi@0 | 253 | this.portname = portname; |
aoqi@0 | 254 | if (portname == null) { |
aoqi@0 | 255 | if (portInfo != null) { |
aoqi@0 | 256 | this.portname = portInfo.getPortName(); |
aoqi@0 | 257 | } else if (wsdlPort != null) { |
aoqi@0 | 258 | this.portname = wsdlPort.getName(); |
aoqi@0 | 259 | } |
aoqi@0 | 260 | } |
aoqi@0 | 261 | this.binding = binding; |
aoqi@0 | 262 | |
aoqi@0 | 263 | ComponentFeature cf = binding.getFeature(ComponentFeature.class); |
aoqi@0 | 264 | if (cf != null && Target.STUB.equals(cf.getTarget())) { |
aoqi@0 | 265 | components.add(cf.getComponent()); |
aoqi@0 | 266 | } |
aoqi@0 | 267 | ComponentsFeature csf = binding.getFeature(ComponentsFeature.class); |
aoqi@0 | 268 | if (csf != null) { |
aoqi@0 | 269 | for (ComponentFeature cfi : csf.getComponentFeatures()) { |
aoqi@0 | 270 | if (Target.STUB.equals(cfi.getTarget())) |
aoqi@0 | 271 | components.add(cfi.getComponent()); |
aoqi@0 | 272 | } |
aoqi@0 | 273 | } |
aoqi@0 | 274 | |
aoqi@0 | 275 | // if there is an EPR, EPR's address should be used for invocation instead of default address |
aoqi@0 | 276 | if (epr != null) { |
aoqi@0 | 277 | this.requestContext.setEndPointAddressString(epr.getAddress()); |
aoqi@0 | 278 | } else { |
aoqi@0 | 279 | this.requestContext.setEndpointAddress(defaultEndPointAddress); |
aoqi@0 | 280 | } |
aoqi@0 | 281 | this.engine = new Engine(getStringId(), owner.getContainer(), owner.getExecutor()); |
aoqi@0 | 282 | this.endpointReference = epr; |
aoqi@0 | 283 | wsdlProperties = (wsdlPort == null) ? new WSDLDirectProperties(owner.getServiceName(), portname) : new WSDLPortProperties(wsdlPort); |
aoqi@0 | 284 | |
aoqi@0 | 285 | this.cleanRequestContext = this.requestContext.copy(); |
aoqi@0 | 286 | |
aoqi@0 | 287 | // ManagedObjectManager MUST be created before the pipeline |
aoqi@0 | 288 | // is constructed. |
aoqi@0 | 289 | |
aoqi@0 | 290 | managedObjectManager = new MonitorRootClient(this).createManagedObjectManager(this); |
aoqi@0 | 291 | |
aoqi@0 | 292 | if (master != null) { |
aoqi@0 | 293 | this.tubes = new TubePool(master); |
aoqi@0 | 294 | } else { |
aoqi@0 | 295 | this.tubes = new TubePool(createPipeline(portInfo, binding)); |
aoqi@0 | 296 | } |
aoqi@0 | 297 | |
aoqi@0 | 298 | addrVersion = binding.getAddressingVersion(); |
aoqi@0 | 299 | |
aoqi@0 | 300 | // This needs to happen after createPipeline. |
aoqi@0 | 301 | // TBD: Check if it needs to happen outside the Stub constructor. |
aoqi@0 | 302 | managedObjectManager.resumeJMXRegistration(); |
aoqi@0 | 303 | } finally { |
aoqi@0 | 304 | ContainerResolver.getDefault().exitContainer(old); |
aoqi@0 | 305 | } |
aoqi@0 | 306 | } |
aoqi@0 | 307 | |
aoqi@0 | 308 | /** |
aoqi@0 | 309 | * Creates a new pipeline for the given port name. |
aoqi@0 | 310 | */ |
aoqi@0 | 311 | private Tube createPipeline(WSPortInfo portInfo, WSBinding binding) { |
aoqi@0 | 312 | //Check all required WSDL extensions are understood |
aoqi@0 | 313 | checkAllWSDLExtensionsUnderstood(portInfo, binding); |
aoqi@0 | 314 | SEIModel seiModel = null; |
aoqi@0 | 315 | Class sei = null; |
aoqi@0 | 316 | if (portInfo instanceof SEIPortInfo) { |
aoqi@0 | 317 | SEIPortInfo sp = (SEIPortInfo) portInfo; |
aoqi@0 | 318 | seiModel = sp.model; |
aoqi@0 | 319 | sei = sp.sei; |
aoqi@0 | 320 | } |
aoqi@0 | 321 | BindingID bindingId = portInfo.getBindingId(); |
aoqi@0 | 322 | |
aoqi@0 | 323 | TubelineAssembler assembler = TubelineAssemblerFactory.create( |
aoqi@0 | 324 | Thread.currentThread().getContextClassLoader(), bindingId, owner.getContainer()); |
aoqi@0 | 325 | if (assembler == null) { |
aoqi@0 | 326 | throw new WebServiceException("Unable to process bindingID=" + bindingId); // TODO: i18n |
aoqi@0 | 327 | } |
aoqi@0 | 328 | return assembler.createClient( |
aoqi@0 | 329 | new ClientTubeAssemblerContext( |
aoqi@0 | 330 | portInfo.getEndpointAddress(), |
aoqi@0 | 331 | portInfo.getPort(), |
aoqi@0 | 332 | this, binding, owner.getContainer(), ((BindingImpl) binding).createCodec(), seiModel, sei)); |
aoqi@0 | 333 | } |
aoqi@0 | 334 | |
aoqi@0 | 335 | public WSDLPort getWSDLPort() { |
aoqi@0 | 336 | return wsdlPort; |
aoqi@0 | 337 | } |
aoqi@0 | 338 | |
aoqi@0 | 339 | public WSService getService() { |
aoqi@0 | 340 | return owner; |
aoqi@0 | 341 | } |
aoqi@0 | 342 | |
aoqi@0 | 343 | public Pool<Tube> getTubes() { |
aoqi@0 | 344 | return tubes; |
aoqi@0 | 345 | } |
aoqi@0 | 346 | |
aoqi@0 | 347 | /** |
aoqi@0 | 348 | * Checks only if RespectBindingFeature is enabled |
aoqi@0 | 349 | * checks if all required wsdl extensions in the |
aoqi@0 | 350 | * corresponding wsdl:Port are understood when RespectBindingFeature is enabled. |
aoqi@0 | 351 | * @throws WebServiceException |
aoqi@0 | 352 | * when any wsdl extension that has wsdl:required=true is not understood |
aoqi@0 | 353 | */ |
aoqi@0 | 354 | private static void checkAllWSDLExtensionsUnderstood(WSPortInfo port, WSBinding binding) { |
aoqi@0 | 355 | if (port.getPort() != null && binding.isFeatureEnabled(RespectBindingFeature.class)) { |
aoqi@0 | 356 | port.getPort().areRequiredExtensionsUnderstood(); |
aoqi@0 | 357 | } |
aoqi@0 | 358 | } |
aoqi@0 | 359 | |
aoqi@0 | 360 | @Override |
aoqi@0 | 361 | public WSPortInfo getPortInfo() { |
aoqi@0 | 362 | return portInfo; |
aoqi@0 | 363 | } |
aoqi@0 | 364 | |
aoqi@0 | 365 | /** |
aoqi@0 | 366 | * Nullable when there is no associated WSDL Model |
aoqi@0 | 367 | * @return |
aoqi@0 | 368 | */ |
aoqi@0 | 369 | public |
aoqi@0 | 370 | @Nullable |
aoqi@0 | 371 | OperationDispatcher getOperationDispatcher() { |
aoqi@0 | 372 | if (operationDispatcher == null && wsdlPort != null) { |
aoqi@0 | 373 | operationDispatcher = new OperationDispatcher(wsdlPort, binding, null); |
aoqi@0 | 374 | } |
aoqi@0 | 375 | return operationDispatcher; |
aoqi@0 | 376 | } |
aoqi@0 | 377 | |
aoqi@0 | 378 | /** |
aoqi@0 | 379 | * Gets the port name that this stub is configured to talk to. |
aoqi@0 | 380 | * <p> |
aoqi@0 | 381 | * When {@link #wsdlPort} is non-null, the port name is always |
aoqi@0 | 382 | * the same as {@link WSDLPort#getName()}, but this method |
aoqi@0 | 383 | * returns a port name even if no WSDL is available for this stub. |
aoqi@0 | 384 | */ |
aoqi@0 | 385 | protected abstract |
aoqi@0 | 386 | @NotNull |
aoqi@0 | 387 | QName getPortName(); |
aoqi@0 | 388 | |
aoqi@0 | 389 | /** |
aoqi@0 | 390 | * Gets the service name that this stub is configured to talk to. |
aoqi@0 | 391 | * <p> |
aoqi@0 | 392 | * When {@link #wsdlPort} is non-null, the service name is always |
aoqi@0 | 393 | * the same as the one that's inferred from {@link WSDLPort#getOwner()}, |
aoqi@0 | 394 | * but this method returns a port name even if no WSDL is available for |
aoqi@0 | 395 | * this stub. |
aoqi@0 | 396 | */ |
aoqi@0 | 397 | protected final |
aoqi@0 | 398 | @NotNull |
aoqi@0 | 399 | QName getServiceName() { |
aoqi@0 | 400 | return owner.getServiceName(); |
aoqi@0 | 401 | } |
aoqi@0 | 402 | |
aoqi@0 | 403 | /** |
aoqi@0 | 404 | * Gets the {@link Executor} to be used for asynchronous method invocations. |
aoqi@0 | 405 | * <p> |
aoqi@0 | 406 | * Note that the value this method returns may different from invocations |
aoqi@0 | 407 | * to invocations. The caller must not cache. |
aoqi@0 | 408 | * |
aoqi@0 | 409 | * @return always non-null. |
aoqi@0 | 410 | */ |
aoqi@0 | 411 | public final Executor getExecutor() { |
aoqi@0 | 412 | return owner.getExecutor(); |
aoqi@0 | 413 | } |
aoqi@0 | 414 | |
aoqi@0 | 415 | /** |
aoqi@0 | 416 | * Passes a message to a pipe for processing. |
aoqi@0 | 417 | * <p> |
aoqi@0 | 418 | * Unlike {@link Tube} instances, |
aoqi@0 | 419 | * this method is thread-safe and can be invoked from |
aoqi@0 | 420 | * multiple threads concurrently. |
aoqi@0 | 421 | * |
aoqi@0 | 422 | * @param packet The message to be sent to the server |
aoqi@0 | 423 | * @param requestContext The {@link RequestContext} when this invocation is originally scheduled. |
aoqi@0 | 424 | * This must be the same object as {@link #requestContext} for synchronous |
aoqi@0 | 425 | * invocations, but for asynchronous invocations, it needs to be a snapshot |
aoqi@0 | 426 | * captured at the point of invocation, to correctly satisfy the spec requirement. |
aoqi@0 | 427 | * @param receiver Receives the {@link ResponseContext}. Since the spec requires |
aoqi@0 | 428 | * that the asynchronous invocations must not update response context, |
aoqi@0 | 429 | * depending on the mode of invocation they have to go to different places. |
aoqi@0 | 430 | * So we take a setter that abstracts that away. |
aoqi@0 | 431 | */ |
aoqi@0 | 432 | protected final Packet process(Packet packet, RequestContext requestContext, ResponseContextReceiver receiver) { |
aoqi@0 | 433 | packet.isSynchronousMEP = true; |
aoqi@0 | 434 | packet.component = this; |
aoqi@0 | 435 | configureRequestPacket(packet, requestContext); |
aoqi@0 | 436 | Pool<Tube> pool = tubes; |
aoqi@0 | 437 | if (pool == null) { |
aoqi@0 | 438 | throw new WebServiceException("close method has already been invoked"); // TODO: i18n |
aoqi@0 | 439 | } |
aoqi@0 | 440 | |
aoqi@0 | 441 | Fiber fiber = engine.createFiber(); |
aoqi@0 | 442 | configureFiber(fiber); |
aoqi@0 | 443 | |
aoqi@0 | 444 | // then send it away! |
aoqi@0 | 445 | Tube tube = pool.take(); |
aoqi@0 | 446 | |
aoqi@0 | 447 | try { |
aoqi@0 | 448 | return fiber.runSync(tube, packet); |
aoqi@0 | 449 | } finally { |
aoqi@0 | 450 | // this allows us to capture the packet even when the call failed with an exception. |
aoqi@0 | 451 | // when the call fails with an exception it's no longer a 'reply' but it may provide some information |
aoqi@0 | 452 | // about what went wrong. |
aoqi@0 | 453 | |
aoqi@0 | 454 | // note that Packet can still be updated after |
aoqi@0 | 455 | // ResponseContext is created. |
aoqi@0 | 456 | Packet reply = (fiber.getPacket() == null) ? packet : fiber.getPacket(); |
aoqi@0 | 457 | receiver.setResponseContext(new ResponseContext(reply)); |
aoqi@0 | 458 | |
aoqi@0 | 459 | pool.recycle(tube); |
aoqi@0 | 460 | } |
aoqi@0 | 461 | } |
aoqi@0 | 462 | |
aoqi@0 | 463 | private void configureRequestPacket(Packet packet, RequestContext requestContext) { |
aoqi@0 | 464 | // fill in Packet |
aoqi@0 | 465 | packet.proxy = this; |
aoqi@0 | 466 | packet.handlerConfig = binding.getHandlerConfig(); |
aoqi@0 | 467 | |
aoqi@0 | 468 | // to make it multi-thread safe we need to first get a stable snapshot |
aoqi@0 | 469 | Header[] hl = userOutboundHeaders; |
aoqi@0 | 470 | if (hl != null) { |
aoqi@0 | 471 | MessageHeaders mh = packet.getMessage().getHeaders(); |
aoqi@0 | 472 | for (Header h : hl) { |
aoqi@0 | 473 | mh.add(h); |
aoqi@0 | 474 | } |
aoqi@0 | 475 | } |
aoqi@0 | 476 | |
aoqi@0 | 477 | requestContext.fill(packet, (binding.getAddressingVersion() != null)); |
aoqi@0 | 478 | packet.addSatellite(wsdlProperties); |
aoqi@0 | 479 | |
aoqi@0 | 480 | if (addrVersion != null) { |
aoqi@0 | 481 | // populate request WS-Addressing headers |
aoqi@0 | 482 | MessageHeaders headerList = packet.getMessage().getHeaders(); |
aoqi@0 | 483 | AddressingUtils.fillRequestAddressingHeaders(headerList, wsdlPort, binding, packet); |
aoqi@0 | 484 | |
aoqi@0 | 485 | |
aoqi@0 | 486 | // Spec is not clear on if ReferenceParameters are to be added when addressing is not enabled, |
aoqi@0 | 487 | // but the EPR has ReferenceParameters. |
aoqi@0 | 488 | // Current approach: Add ReferenceParameters only if addressing enabled. |
aoqi@0 | 489 | if (endpointReference != null) { |
aoqi@0 | 490 | endpointReference.addReferenceParametersToList(packet.getMessage().getHeaders()); |
aoqi@0 | 491 | } |
aoqi@0 | 492 | } |
aoqi@0 | 493 | } |
aoqi@0 | 494 | |
aoqi@0 | 495 | /** |
aoqi@0 | 496 | * Passes a message through a {@link Tube}line for processing. The processing happens |
aoqi@0 | 497 | * asynchronously and when the response is available, Fiber.CompletionCallback is |
aoqi@0 | 498 | * called. The processing could happen on multiple threads. |
aoqi@0 | 499 | * |
aoqi@0 | 500 | * <p> |
aoqi@0 | 501 | * Unlike {@link Tube} instances, |
aoqi@0 | 502 | * this method is thread-safe and can be invoked from |
aoqi@0 | 503 | * multiple threads concurrently. |
aoqi@0 | 504 | * |
aoqi@0 | 505 | * @param receiver The {@link Response} implementation |
aoqi@0 | 506 | * @param request The message to be sent to the server |
aoqi@0 | 507 | * @param requestContext The {@link RequestContext} when this invocation is originally scheduled. |
aoqi@0 | 508 | * This must be the same object as {@link #requestContext} for synchronous |
aoqi@0 | 509 | * invocations, but for asynchronous invocations, it needs to be a snapshot |
aoqi@0 | 510 | * captured at the point of invocation, to correctly satisfy the spec requirement. |
aoqi@0 | 511 | * @param completionCallback Once the processing is done, the callback is invoked. |
aoqi@0 | 512 | */ |
aoqi@0 | 513 | protected final void processAsync(AsyncResponseImpl<?> receiver, Packet request, RequestContext requestContext, final Fiber.CompletionCallback completionCallback) { |
aoqi@0 | 514 | // fill in Packet |
aoqi@0 | 515 | request.component = this; |
aoqi@0 | 516 | configureRequestPacket(request, requestContext); |
aoqi@0 | 517 | |
aoqi@0 | 518 | final Pool<Tube> pool = tubes; |
aoqi@0 | 519 | if (pool == null) { |
aoqi@0 | 520 | throw new WebServiceException("close method has already been invoked"); // TODO: i18n |
aoqi@0 | 521 | } |
aoqi@0 | 522 | |
aoqi@0 | 523 | final Fiber fiber = engine.createFiber(); |
aoqi@0 | 524 | configureFiber(fiber); |
aoqi@0 | 525 | |
aoqi@0 | 526 | receiver.setCancelable(fiber); |
aoqi@0 | 527 | |
aoqi@0 | 528 | // check race condition on cancel |
aoqi@0 | 529 | if (receiver.isCancelled()) { |
aoqi@0 | 530 | return; |
aoqi@0 | 531 | } |
aoqi@0 | 532 | |
aoqi@0 | 533 | FiberContextSwitchInterceptorFactory fcsif = owner.getSPI(FiberContextSwitchInterceptorFactory.class); |
aoqi@0 | 534 | if (fcsif != null) { |
aoqi@0 | 535 | fiber.addInterceptor(fcsif.create()); |
aoqi@0 | 536 | } |
aoqi@0 | 537 | |
aoqi@0 | 538 | // then send it away! |
aoqi@0 | 539 | final Tube tube = pool.take(); |
aoqi@0 | 540 | |
aoqi@0 | 541 | Fiber.CompletionCallback fiberCallback = new Fiber.CompletionCallback() { |
aoqi@0 | 542 | @Override |
aoqi@0 | 543 | public void onCompletion(@NotNull Packet response) { |
aoqi@0 | 544 | pool.recycle(tube); |
aoqi@0 | 545 | completionCallback.onCompletion(response); |
aoqi@0 | 546 | } |
aoqi@0 | 547 | |
aoqi@0 | 548 | @Override |
aoqi@0 | 549 | public void onCompletion(@NotNull Throwable error) { |
aoqi@0 | 550 | // let's not reuse tubes as they might be in a wrong state, so not |
aoqi@0 | 551 | // calling pool.recycle() |
aoqi@0 | 552 | completionCallback.onCompletion(error); |
aoqi@0 | 553 | } |
aoqi@0 | 554 | }; |
aoqi@0 | 555 | |
aoqi@0 | 556 | // Check for SyncStartForAsyncInvokeFeature |
aoqi@0 | 557 | |
aoqi@0 | 558 | fiber.start(tube, request, fiberCallback, |
aoqi@0 | 559 | getBinding().isFeatureEnabled(SyncStartForAsyncFeature.class) && |
aoqi@0 | 560 | !requestContext.containsKey(PREVENT_SYNC_START_FOR_ASYNC_INVOKE)); |
aoqi@0 | 561 | } |
aoqi@0 | 562 | |
aoqi@0 | 563 | protected void configureFiber(Fiber fiber) { |
aoqi@0 | 564 | // no-op in the base class, but can be used by derived classes to configure the Fiber prior |
aoqi@0 | 565 | // to invocation |
aoqi@0 | 566 | } |
aoqi@0 | 567 | |
aoqi@0 | 568 | private static final Logger monitoringLogger = Logger.getLogger(com.sun.xml.internal.ws.util.Constants.LoggingDomain + ".monitoring"); |
aoqi@0 | 569 | |
aoqi@0 | 570 | @Override |
aoqi@0 | 571 | public void close() { |
aoqi@0 | 572 | TubePool tp = (TubePool) tubes; |
aoqi@0 | 573 | if (tp != null) { |
aoqi@0 | 574 | // multi-thread safety of 'close' needs to be considered more carefully. |
aoqi@0 | 575 | // some calls might be pending while this method is invoked. Should we |
aoqi@0 | 576 | // block until they are complete, or should we abort them (but how?) |
aoqi@0 | 577 | Tube p = tp.takeMaster(); |
aoqi@0 | 578 | p.preDestroy(); |
aoqi@0 | 579 | tubes = null; |
aoqi@0 | 580 | } |
aoqi@0 | 581 | if (!managedObjectManagerClosed) { |
aoqi@0 | 582 | try { |
aoqi@0 | 583 | final ObjectName name = managedObjectManager.getObjectName(managedObjectManager.getRoot()); |
aoqi@0 | 584 | // The name is null when the MOM is a NOOP. |
aoqi@0 | 585 | if (name != null) { |
aoqi@0 | 586 | monitoringLogger.log(Level.INFO, "Closing Metro monitoring root: {0}", name); |
aoqi@0 | 587 | } |
aoqi@0 | 588 | managedObjectManager.close(); |
aoqi@0 | 589 | } catch (java.io.IOException e) { |
aoqi@0 | 590 | monitoringLogger.log(Level.WARNING, "Ignoring error when closing Managed Object Manager", e); |
aoqi@0 | 591 | } |
aoqi@0 | 592 | managedObjectManagerClosed = true; |
aoqi@0 | 593 | } |
aoqi@0 | 594 | } |
aoqi@0 | 595 | |
aoqi@0 | 596 | @Override |
aoqi@0 | 597 | public final WSBinding getBinding() { |
aoqi@0 | 598 | return binding; |
aoqi@0 | 599 | } |
aoqi@0 | 600 | |
aoqi@0 | 601 | @Override |
aoqi@0 | 602 | public final Map<String, Object> getRequestContext() { |
aoqi@0 | 603 | return requestContext.asMap(); |
aoqi@0 | 604 | } |
aoqi@0 | 605 | |
aoqi@0 | 606 | public void resetRequestContext() { |
aoqi@0 | 607 | requestContext = cleanRequestContext.copy(); |
aoqi@0 | 608 | } |
aoqi@0 | 609 | |
aoqi@0 | 610 | @Override |
aoqi@0 | 611 | public final ResponseContext getResponseContext() { |
aoqi@0 | 612 | return responseContext; |
aoqi@0 | 613 | } |
aoqi@0 | 614 | |
aoqi@0 | 615 | @Override |
aoqi@0 | 616 | public void setResponseContext(ResponseContext rc) { |
aoqi@0 | 617 | this.responseContext = rc; |
aoqi@0 | 618 | } |
aoqi@0 | 619 | |
aoqi@0 | 620 | private String getStringId() { |
aoqi@0 | 621 | return RuntimeVersion.VERSION + ": Stub for " + getRequestContext().get(BindingProvider.ENDPOINT_ADDRESS_PROPERTY); |
aoqi@0 | 622 | } |
aoqi@0 | 623 | |
aoqi@0 | 624 | @Override |
aoqi@0 | 625 | public String toString() { |
aoqi@0 | 626 | return getStringId(); |
aoqi@0 | 627 | } |
aoqi@0 | 628 | |
aoqi@0 | 629 | @Override |
aoqi@0 | 630 | public final WSEndpointReference getWSEndpointReference() { |
aoqi@0 | 631 | if (binding.getBindingID().equals(HTTPBinding.HTTP_BINDING)) { |
aoqi@0 | 632 | throw new java.lang.UnsupportedOperationException( |
aoqi@0 | 633 | ClientMessages.UNSUPPORTED_OPERATION("BindingProvider.getEndpointReference(Class<T> class)", "XML/HTTP Binding", "SOAP11 or SOAP12 Binding") |
aoqi@0 | 634 | ); |
aoqi@0 | 635 | } |
aoqi@0 | 636 | |
aoqi@0 | 637 | if (endpointReference != null) { |
aoqi@0 | 638 | return endpointReference; |
aoqi@0 | 639 | } |
aoqi@0 | 640 | |
aoqi@0 | 641 | String eprAddress = requestContext.getEndpointAddress().toString(); |
aoqi@0 | 642 | QName portTypeName = null; |
aoqi@0 | 643 | String wsdlAddress = null; |
aoqi@0 | 644 | List<WSEndpointReference.EPRExtension> wsdlEPRExtensions = new ArrayList<WSEndpointReference.EPRExtension>(); |
aoqi@0 | 645 | if (wsdlPort != null) { |
aoqi@0 | 646 | portTypeName = wsdlPort.getBinding().getPortTypeName(); |
aoqi@0 | 647 | wsdlAddress = eprAddress + "?wsdl"; |
aoqi@0 | 648 | |
aoqi@0 | 649 | //gather EPRExtensions specified in WSDL. |
aoqi@0 | 650 | try { |
aoqi@0 | 651 | WSEndpointReference wsdlEpr = wsdlPort.getEPR(); |
aoqi@0 | 652 | if (wsdlEpr != null) { |
aoqi@0 | 653 | for (WSEndpointReference.EPRExtension extnEl : wsdlEpr.getEPRExtensions()) { |
aoqi@0 | 654 | wsdlEPRExtensions.add(new WSEPRExtension( |
aoqi@0 | 655 | XMLStreamBuffer.createNewBufferFromXMLStreamReader(extnEl.readAsXMLStreamReader()), extnEl.getQName())); |
aoqi@0 | 656 | } |
aoqi@0 | 657 | } |
aoqi@0 | 658 | |
aoqi@0 | 659 | } catch (XMLStreamException ex) { |
aoqi@0 | 660 | throw new WebServiceException(ex); |
aoqi@0 | 661 | } |
aoqi@0 | 662 | } |
aoqi@0 | 663 | AddressingVersion av = AddressingVersion.W3C; |
aoqi@0 | 664 | this.endpointReference = new WSEndpointReference( |
aoqi@0 | 665 | av, eprAddress, getServiceName(), getPortName(), portTypeName, null, wsdlAddress, null, wsdlEPRExtensions, null); |
aoqi@0 | 666 | |
aoqi@0 | 667 | return this.endpointReference; |
aoqi@0 | 668 | } |
aoqi@0 | 669 | |
aoqi@0 | 670 | |
aoqi@0 | 671 | @Override |
aoqi@0 | 672 | public final W3CEndpointReference getEndpointReference() { |
aoqi@0 | 673 | if (binding.getBindingID().equals(HTTPBinding.HTTP_BINDING)) { |
aoqi@0 | 674 | throw new java.lang.UnsupportedOperationException( |
aoqi@0 | 675 | ClientMessages.UNSUPPORTED_OPERATION("BindingProvider.getEndpointReference()", "XML/HTTP Binding", "SOAP11 or SOAP12 Binding")); |
aoqi@0 | 676 | } |
aoqi@0 | 677 | return getEndpointReference(W3CEndpointReference.class); |
aoqi@0 | 678 | } |
aoqi@0 | 679 | |
aoqi@0 | 680 | @Override |
aoqi@0 | 681 | public final <T extends EndpointReference> T getEndpointReference(Class<T> clazz) { |
aoqi@0 | 682 | return getWSEndpointReference().toSpec(clazz); |
aoqi@0 | 683 | } |
aoqi@0 | 684 | |
aoqi@0 | 685 | public |
aoqi@0 | 686 | @NotNull |
aoqi@0 | 687 | @Override |
aoqi@0 | 688 | ManagedObjectManager getManagedObjectManager() { |
aoqi@0 | 689 | return managedObjectManager; |
aoqi@0 | 690 | } |
aoqi@0 | 691 | |
aoqi@0 | 692 | // |
aoqi@0 | 693 | // |
aoqi@0 | 694 | // WSBindingProvider methods |
aoqi@0 | 695 | // |
aoqi@0 | 696 | // |
aoqi@0 | 697 | @Override |
aoqi@0 | 698 | public final void setOutboundHeaders(List<Header> headers) { |
aoqi@0 | 699 | if (headers == null) { |
aoqi@0 | 700 | this.userOutboundHeaders = null; |
aoqi@0 | 701 | } else { |
aoqi@0 | 702 | for (Header h : headers) { |
aoqi@0 | 703 | if (h == null) { |
aoqi@0 | 704 | throw new IllegalArgumentException(); |
aoqi@0 | 705 | } |
aoqi@0 | 706 | } |
aoqi@0 | 707 | userOutboundHeaders = headers.toArray(new Header[headers.size()]); |
aoqi@0 | 708 | } |
aoqi@0 | 709 | } |
aoqi@0 | 710 | |
aoqi@0 | 711 | @Override |
aoqi@0 | 712 | public final void setOutboundHeaders(Header... headers) { |
aoqi@0 | 713 | if (headers == null) { |
aoqi@0 | 714 | this.userOutboundHeaders = null; |
aoqi@0 | 715 | } else { |
aoqi@0 | 716 | for (Header h : headers) { |
aoqi@0 | 717 | if (h == null) { |
aoqi@0 | 718 | throw new IllegalArgumentException(); |
aoqi@0 | 719 | } |
aoqi@0 | 720 | } |
aoqi@0 | 721 | Header[] hl = new Header[headers.length]; |
aoqi@0 | 722 | System.arraycopy(headers, 0, hl, 0, headers.length); |
aoqi@0 | 723 | userOutboundHeaders = hl; |
aoqi@0 | 724 | } |
aoqi@0 | 725 | } |
aoqi@0 | 726 | |
aoqi@0 | 727 | @Override |
aoqi@0 | 728 | public final List<Header> getInboundHeaders() { |
aoqi@0 | 729 | return Collections.unmodifiableList(((MessageHeaders) |
aoqi@0 | 730 | responseContext.get(JAXWSProperties.INBOUND_HEADER_LIST_PROPERTY)).asList()); |
aoqi@0 | 731 | } |
aoqi@0 | 732 | |
aoqi@0 | 733 | @Override |
aoqi@0 | 734 | public final void setAddress(String address) { |
aoqi@0 | 735 | requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, address); |
aoqi@0 | 736 | } |
aoqi@0 | 737 | |
aoqi@0 | 738 | @Override |
aoqi@0 | 739 | public <S> S getSPI(Class<S> spiType) { |
aoqi@0 | 740 | for (Component c : components) { |
aoqi@0 | 741 | S s = c.getSPI(spiType); |
aoqi@0 | 742 | if (s != null) { |
aoqi@0 | 743 | return s; |
aoqi@0 | 744 | } |
aoqi@0 | 745 | } |
aoqi@0 | 746 | return owner.getSPI(spiType); |
aoqi@0 | 747 | } |
aoqi@0 | 748 | |
aoqi@0 | 749 | @Override |
aoqi@0 | 750 | public Set<Component> getComponents() { |
aoqi@0 | 751 | return components; |
aoqi@0 | 752 | } |
aoqi@0 | 753 | } |