|
1 /* |
|
2 * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package com.sun.xml.internal.ws.transport.http.server; |
|
27 |
|
28 import com.sun.istack.internal.Nullable; |
|
29 import com.sun.xml.internal.stream.buffer.XMLStreamBufferResult; |
|
30 import com.sun.xml.internal.ws.api.Component; |
|
31 import com.sun.xml.internal.ws.api.WSBinding; |
|
32 import com.sun.xml.internal.ws.api.BindingID; |
|
33 import com.sun.xml.internal.ws.api.databinding.MetadataReader; |
|
34 import com.sun.xml.internal.ws.api.message.Packet; |
|
35 import com.sun.xml.internal.ws.binding.BindingImpl; |
|
36 import com.sun.xml.internal.ws.api.server.*; |
|
37 import com.sun.xml.internal.ws.server.EndpointFactory; |
|
38 import com.sun.xml.internal.ws.server.ServerRtException; |
|
39 import com.sun.xml.internal.ws.util.xml.XmlUtil; |
|
40 import com.sun.xml.internal.ws.transport.http.HttpAdapterList; |
|
41 import com.sun.xml.internal.ws.transport.http.HttpAdapter; |
|
42 import com.sun.istack.internal.NotNull; |
|
43 |
|
44 import java.net.MalformedURLException; |
|
45 |
|
46 import javax.xml.namespace.QName; |
|
47 import javax.xml.transform.Source; |
|
48 import javax.xml.transform.TransformerException; |
|
49 import javax.xml.ws.*; |
|
50 import javax.xml.ws.spi.http.HttpContext; |
|
51 import javax.xml.ws.wsaddressing.W3CEndpointReference; |
|
52 import javax.xml.parsers.ParserConfigurationException; |
|
53 |
|
54 import java.io.IOException; |
|
55 import java.net.URL; |
|
56 import java.util.ArrayList; |
|
57 import java.util.Collections; |
|
58 import java.util.HashMap; |
|
59 import java.util.List; |
|
60 import java.util.Map; |
|
61 import java.util.concurrent.Executor; |
|
62 import java.lang.reflect.InvocationTargetException; |
|
63 import java.lang.reflect.Method; |
|
64 |
|
65 import org.xml.sax.EntityResolver; |
|
66 import org.xml.sax.SAXException; |
|
67 import org.w3c.dom.Element; |
|
68 |
|
69 |
|
70 /** |
|
71 * Implements {@link Endpoint}. |
|
72 * <p/> |
|
73 * <p/> |
|
74 * This class accumulates the information necessary to create |
|
75 * {@link WSEndpoint}, and then when {@link #publish} method |
|
76 * is called it will be created. |
|
77 * <p/> |
|
78 * <p/> |
|
79 * This object also allows accumulated information to be retrieved. |
|
80 * |
|
81 * @author Jitendra Kotamraju |
|
82 */ |
|
83 public class EndpointImpl extends Endpoint { |
|
84 |
|
85 private static final WebServicePermission ENDPOINT_PUBLISH_PERMISSION = |
|
86 new WebServicePermission("publishEndpoint"); |
|
87 |
|
88 /** |
|
89 * Once the service is published, this field will |
|
90 * be set to the {@link HttpEndpoint} instance. |
|
91 * <p/> |
|
92 * But don't declare the type as {@link HttpEndpoint} |
|
93 * to avoid static type dependency that cause the class loading to |
|
94 * fail if the LW HTTP server doesn't exist. |
|
95 */ |
|
96 private Object actualEndpoint; |
|
97 |
|
98 // information accumulated for creating WSEndpoint |
|
99 private final WSBinding binding; |
|
100 private @Nullable final Object implementor; |
|
101 private List<Source> metadata; |
|
102 private Executor executor; |
|
103 private Map<String, Object> properties = Collections.emptyMap(); // always non-null |
|
104 private boolean stopped; |
|
105 private @Nullable EndpointContext endpointContext; |
|
106 private @NotNull final Class<?> implClass; |
|
107 private final Invoker invoker; |
|
108 private Container container; |
|
109 |
|
110 |
|
111 public EndpointImpl(@NotNull BindingID bindingId, @NotNull Object impl, |
|
112 WebServiceFeature ... features) { |
|
113 this(bindingId, impl, impl.getClass(), |
|
114 InstanceResolver.createSingleton(impl).createInvoker(), features); |
|
115 } |
|
116 |
|
117 public EndpointImpl(@NotNull BindingID bindingId, @NotNull Class implClass, |
|
118 javax.xml.ws.spi.Invoker invoker, |
|
119 WebServiceFeature ... features) { |
|
120 this(bindingId, null, implClass, new InvokerImpl(invoker), features); |
|
121 } |
|
122 |
|
123 private EndpointImpl(@NotNull BindingID bindingId, Object impl, @NotNull Class implClass, |
|
124 Invoker invoker, WebServiceFeature ... features) { |
|
125 binding = BindingImpl.create(bindingId, features); |
|
126 this.implClass = implClass; |
|
127 this.invoker = invoker; |
|
128 this.implementor = impl; |
|
129 } |
|
130 |
|
131 |
|
132 /** |
|
133 * Wraps an already created {@link WSEndpoint} into an {@link EndpointImpl}, |
|
134 * and immediately publishes it with the given context. |
|
135 * |
|
136 * @param wse created endpoint |
|
137 * @param serverContext supported http context |
|
138 * @deprecated This is a backdoor method. Don't use it unless you know what you are doing. |
|
139 */ |
|
140 public EndpointImpl(WSEndpoint wse, Object serverContext) { |
|
141 this(wse, serverContext, null); |
|
142 } |
|
143 |
|
144 /** |
|
145 * Wraps an already created {@link WSEndpoint} into an {@link EndpointImpl}, |
|
146 * and immediately publishes it with the given context. |
|
147 * |
|
148 * @param wse created endpoint |
|
149 * @param serverContext supported http context |
|
150 * @param ctxt endpoint context |
|
151 * @deprecated This is a backdoor method. Don't use it unless you know what you are doing. |
|
152 */ |
|
153 public EndpointImpl(WSEndpoint wse, Object serverContext, EndpointContext ctxt) { |
|
154 endpointContext = ctxt; |
|
155 actualEndpoint = new HttpEndpoint(null, getAdapter(wse, "")); |
|
156 ((HttpEndpoint) actualEndpoint).publish(serverContext); |
|
157 binding = wse.getBinding(); |
|
158 implementor = null; // this violates the semantics, but hey, this is a backdoor. |
|
159 implClass = null; |
|
160 invoker = null; |
|
161 } |
|
162 |
|
163 /** |
|
164 * Wraps an already created {@link WSEndpoint} into an {@link EndpointImpl}, |
|
165 * and immediately publishes it with the given context. |
|
166 * |
|
167 * @param wse created endpoint |
|
168 * @param address endpoint address |
|
169 * @deprecated This is a backdoor method. Don't use it unless you know what you are doing. |
|
170 */ |
|
171 public EndpointImpl(WSEndpoint wse, String address) { |
|
172 this(wse, address, null); |
|
173 } |
|
174 |
|
175 |
|
176 /** |
|
177 * Wraps an already created {@link WSEndpoint} into an {@link EndpointImpl}, |
|
178 * and immediately publishes it with the given context. |
|
179 * |
|
180 * @param wse created endpoint |
|
181 * @param address endpoint address |
|
182 * @param ctxt endpoint context |
|
183 * @deprecated This is a backdoor method. Don't use it unless you know what you are doing. |
|
184 */ |
|
185 public EndpointImpl(WSEndpoint wse, String address, EndpointContext ctxt) { |
|
186 URL url; |
|
187 try { |
|
188 url = new URL(address); |
|
189 } catch (MalformedURLException ex) { |
|
190 throw new IllegalArgumentException("Cannot create URL for this address " + address); |
|
191 } |
|
192 if (!url.getProtocol().equals("http")) { |
|
193 throw new IllegalArgumentException(url.getProtocol() + " protocol based address is not supported"); |
|
194 } |
|
195 if (!url.getPath().startsWith("/")) { |
|
196 throw new IllegalArgumentException("Incorrect WebService address=" + address + |
|
197 ". The address's path should start with /"); |
|
198 } |
|
199 endpointContext = ctxt; |
|
200 actualEndpoint = new HttpEndpoint(null, getAdapter(wse, url.getPath())); |
|
201 ((HttpEndpoint) actualEndpoint).publish(address); |
|
202 binding = wse.getBinding(); |
|
203 implementor = null; // this violates the semantics, but hey, this is a backdoor. |
|
204 implClass = null; |
|
205 invoker = null; |
|
206 } |
|
207 |
|
208 public Binding getBinding() { |
|
209 return binding; |
|
210 } |
|
211 |
|
212 public Object getImplementor() { |
|
213 return implementor; |
|
214 } |
|
215 |
|
216 public void publish(String address) { |
|
217 canPublish(); |
|
218 URL url; |
|
219 try { |
|
220 url = new URL(address); |
|
221 } catch (MalformedURLException ex) { |
|
222 throw new IllegalArgumentException("Cannot create URL for this address " + address); |
|
223 } |
|
224 if (!url.getProtocol().equals("http")) { |
|
225 throw new IllegalArgumentException(url.getProtocol() + " protocol based address is not supported"); |
|
226 } |
|
227 if (!url.getPath().startsWith("/")) { |
|
228 throw new IllegalArgumentException("Incorrect WebService address=" + address + |
|
229 ". The address's path should start with /"); |
|
230 } |
|
231 createEndpoint(url.getPath()); |
|
232 ((HttpEndpoint) actualEndpoint).publish(address); |
|
233 } |
|
234 |
|
235 public void publish(Object serverContext) { |
|
236 canPublish(); |
|
237 if (!com.sun.net.httpserver.HttpContext.class.isAssignableFrom(serverContext.getClass())) { |
|
238 throw new IllegalArgumentException(serverContext.getClass() + " is not a supported context."); |
|
239 } |
|
240 createEndpoint(((com.sun.net.httpserver.HttpContext)serverContext).getPath()); |
|
241 ((HttpEndpoint) actualEndpoint).publish(serverContext); |
|
242 } |
|
243 |
|
244 public void publish(HttpContext serverContext) { |
|
245 canPublish(); |
|
246 createEndpoint(serverContext.getPath()); |
|
247 ((HttpEndpoint) actualEndpoint).publish(serverContext); |
|
248 } |
|
249 |
|
250 public void stop() { |
|
251 if (isPublished()) { |
|
252 ((HttpEndpoint) actualEndpoint).stop(); |
|
253 actualEndpoint = null; |
|
254 stopped = true; |
|
255 } |
|
256 } |
|
257 |
|
258 public boolean isPublished() { |
|
259 return actualEndpoint != null; |
|
260 } |
|
261 |
|
262 public List<Source> getMetadata() { |
|
263 return metadata; |
|
264 } |
|
265 |
|
266 public void setMetadata(java.util.List<Source> metadata) { |
|
267 if (isPublished()) { |
|
268 throw new IllegalStateException("Cannot set Metadata. Endpoint is already published"); |
|
269 } |
|
270 this.metadata = metadata; |
|
271 } |
|
272 |
|
273 public Executor getExecutor() { |
|
274 return executor; |
|
275 } |
|
276 |
|
277 public void setExecutor(Executor executor) { |
|
278 this.executor = executor; |
|
279 } |
|
280 |
|
281 public Map<String, Object> getProperties() { |
|
282 return new HashMap<String, Object>(properties); |
|
283 } |
|
284 |
|
285 public void setProperties(Map<String, Object> map) { |
|
286 this.properties = new HashMap<String, Object>(map); |
|
287 } |
|
288 |
|
289 /* |
|
290 * Checks the permission of "publishEndpoint" before accessing HTTP classes. |
|
291 * Also it checks if there is an available HTTP server implementation. |
|
292 */ |
|
293 private void createEndpoint(String urlPattern) { |
|
294 // Checks permission for "publishEndpoint" |
|
295 SecurityManager sm = System.getSecurityManager(); |
|
296 if (sm != null) { |
|
297 sm.checkPermission(ENDPOINT_PUBLISH_PERMISSION); |
|
298 } |
|
299 |
|
300 // See if HttpServer implementation is available |
|
301 try { |
|
302 Class.forName("com.sun.net.httpserver.HttpServer"); |
|
303 } catch (Exception e) { |
|
304 throw new UnsupportedOperationException("Couldn't load light weight http server", e); |
|
305 } |
|
306 container = getContainer(); |
|
307 MetadataReader metadataReader = EndpointFactory.getExternalMetadatReader(implClass, binding); |
|
308 WSEndpoint wse = WSEndpoint.create( |
|
309 implClass, true, |
|
310 invoker, |
|
311 getProperty(QName.class, Endpoint.WSDL_SERVICE), |
|
312 getProperty(QName.class, Endpoint.WSDL_PORT), |
|
313 container, |
|
314 binding, |
|
315 getPrimaryWsdl(metadataReader), |
|
316 buildDocList(), |
|
317 (EntityResolver) null, |
|
318 false |
|
319 ); |
|
320 // Don't load HttpEndpoint class before as it may load HttpServer classes |
|
321 actualEndpoint = new HttpEndpoint(executor, getAdapter(wse, urlPattern)); |
|
322 } |
|
323 |
|
324 private <T> T getProperty(Class<T> type, String key) { |
|
325 Object o = properties.get(key); |
|
326 if (o == null) return null; |
|
327 if (type.isInstance(o)) |
|
328 return type.cast(o); |
|
329 else |
|
330 throw new IllegalArgumentException("Property " + key + " has to be of type " + type); // i18n |
|
331 } |
|
332 |
|
333 /** |
|
334 * Convert metadata sources using identity transform. So that we can |
|
335 * reuse the Source object multiple times. |
|
336 */ |
|
337 private List<SDDocumentSource> buildDocList() { |
|
338 List<SDDocumentSource> r = new ArrayList<SDDocumentSource>(); |
|
339 |
|
340 if (metadata != null) { |
|
341 for (Source source : metadata) { |
|
342 try { |
|
343 XMLStreamBufferResult xsbr = XmlUtil.identityTransform(source, new XMLStreamBufferResult()); |
|
344 String systemId = source.getSystemId(); |
|
345 |
|
346 r.add(SDDocumentSource.create(new URL(systemId), xsbr.getXMLStreamBuffer())); |
|
347 } catch (TransformerException te) { |
|
348 throw new ServerRtException("server.rt.err", te); |
|
349 } catch (IOException te) { |
|
350 throw new ServerRtException("server.rt.err", te); |
|
351 } catch (SAXException e) { |
|
352 throw new ServerRtException("server.rt.err", e); |
|
353 } catch (ParserConfigurationException e) { |
|
354 throw new ServerRtException("server.rt.err", e); |
|
355 } |
|
356 } |
|
357 } |
|
358 |
|
359 return r; |
|
360 } |
|
361 |
|
362 /** |
|
363 * Gets wsdl from @WebService or @WebServiceProvider |
|
364 */ |
|
365 private @Nullable SDDocumentSource getPrimaryWsdl(MetadataReader metadataReader) { |
|
366 // Takes care of @WebService, @WebServiceProvider's wsdlLocation |
|
367 EndpointFactory.verifyImplementorClass(implClass, metadataReader); |
|
368 String wsdlLocation = EndpointFactory.getWsdlLocation(implClass, metadataReader); |
|
369 if (wsdlLocation != null) { |
|
370 ClassLoader cl = implClass.getClassLoader(); |
|
371 URL url = cl.getResource(wsdlLocation); |
|
372 if (url != null) { |
|
373 return SDDocumentSource.create(url); |
|
374 } |
|
375 throw new ServerRtException("cannot.load.wsdl", wsdlLocation); |
|
376 } |
|
377 return null; |
|
378 } |
|
379 |
|
380 private void canPublish() { |
|
381 if (isPublished()) { |
|
382 throw new IllegalStateException( |
|
383 "Cannot publish this endpoint. Endpoint has been already published."); |
|
384 } |
|
385 if (stopped) { |
|
386 throw new IllegalStateException( |
|
387 "Cannot publish this endpoint. Endpoint has been already stopped."); |
|
388 } |
|
389 } |
|
390 |
|
391 public EndpointReference getEndpointReference(Element...referenceParameters) { |
|
392 return getEndpointReference(W3CEndpointReference.class, referenceParameters); |
|
393 } |
|
394 |
|
395 public <T extends EndpointReference> T getEndpointReference(Class<T> clazz, Element...referenceParameters) { |
|
396 if (!isPublished()) { |
|
397 throw new WebServiceException("Endpoint is not published yet"); |
|
398 } |
|
399 return ((HttpEndpoint)actualEndpoint).getEndpointReference(clazz,referenceParameters); |
|
400 } |
|
401 |
|
402 @Override |
|
403 public void setEndpointContext(EndpointContext ctxt) { |
|
404 this.endpointContext = ctxt; |
|
405 } |
|
406 |
|
407 private HttpAdapter getAdapter(WSEndpoint endpoint, String urlPattern) { |
|
408 HttpAdapterList adapterList = null; |
|
409 if (endpointContext != null) { |
|
410 if (endpointContext instanceof Component) { |
|
411 adapterList = ((Component) endpointContext).getSPI(HttpAdapterList.class); |
|
412 } |
|
413 |
|
414 if (adapterList == null) { |
|
415 for(Endpoint e : endpointContext.getEndpoints()) { |
|
416 if (e.isPublished() && e != this) { |
|
417 adapterList = ((HttpEndpoint)(((EndpointImpl)e).actualEndpoint)).getAdapterOwner(); |
|
418 assert adapterList != null; |
|
419 break; |
|
420 } |
|
421 } |
|
422 } |
|
423 } |
|
424 if (adapterList == null) { |
|
425 adapterList = new ServerAdapterList(); |
|
426 } |
|
427 return adapterList.createAdapter("", urlPattern, endpoint); |
|
428 } |
|
429 |
|
430 /** |
|
431 * Endpoints within a EndpointContext get the same container. |
|
432 */ |
|
433 private Container getContainer() { |
|
434 if (endpointContext != null) { |
|
435 if (endpointContext instanceof Component) { |
|
436 Container c = ((Component) endpointContext).getSPI(Container.class); |
|
437 if (c != null) |
|
438 return c; |
|
439 } |
|
440 |
|
441 for(Endpoint e : endpointContext.getEndpoints()) { |
|
442 if (e.isPublished() && e != this) { |
|
443 return ((EndpointImpl)e).container; |
|
444 } |
|
445 } |
|
446 } |
|
447 return new ServerContainer(); |
|
448 } |
|
449 |
|
450 private static class InvokerImpl extends Invoker { |
|
451 private javax.xml.ws.spi.Invoker spiInvoker; |
|
452 |
|
453 InvokerImpl(javax.xml.ws.spi.Invoker spiInvoker) { |
|
454 this.spiInvoker = spiInvoker; |
|
455 } |
|
456 |
|
457 @Override |
|
458 public void start(@NotNull WSWebServiceContext wsc, @NotNull WSEndpoint endpoint) { |
|
459 try { |
|
460 spiInvoker.inject(wsc); |
|
461 } catch (IllegalAccessException e) { |
|
462 throw new WebServiceException(e); |
|
463 } catch (InvocationTargetException e) { |
|
464 throw new WebServiceException(e); |
|
465 } |
|
466 } |
|
467 |
|
468 public Object invoke(@NotNull Packet p, @NotNull Method m, @NotNull Object... args) throws InvocationTargetException, IllegalAccessException { |
|
469 return spiInvoker.invoke(m, args); |
|
470 } |
|
471 } |
|
472 } |