Fri, 04 Oct 2013 16:21:34 +0100
8025054: Update JAX-WS RI integration to 2.2.9-b130926.1035
Reviewed-by: chegar
1 /*
2 * Copyright (c) 1997, 2013, 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 */
26 package com.sun.xml.internal.ws.transport.http;
28 import com.oracle.webservices.internal.api.databinding.DatabindingModeFeature;
29 import com.oracle.webservices.internal.api.databinding.ExternalMetadataFeature;
30 import com.sun.istack.internal.NotNull;
31 import com.sun.xml.internal.ws.api.BindingID;
32 import com.sun.xml.internal.ws.api.WSBinding;
33 import com.sun.xml.internal.ws.api.databinding.MetadataReader;
34 import com.sun.xml.internal.ws.api.server.Container;
35 import com.sun.xml.internal.ws.api.server.SDDocumentSource;
36 import com.sun.xml.internal.ws.api.server.WSEndpoint;
37 import com.sun.xml.internal.ws.api.streaming.XMLStreamReaderFactory;
38 import com.sun.xml.internal.ws.binding.WebServiceFeatureList;
40 import com.sun.xml.internal.ws.handler.HandlerChainsModel;
41 import com.sun.xml.internal.ws.resources.ServerMessages;
42 import com.sun.xml.internal.ws.resources.WsservletMessages;
43 import com.sun.xml.internal.ws.server.EndpointFactory;
44 import com.sun.xml.internal.ws.server.ServerRtException;
45 import com.sun.xml.internal.ws.streaming.Attributes;
46 import com.sun.xml.internal.ws.streaming.TidyXMLStreamReader;
47 import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil;
48 import com.sun.xml.internal.ws.util.HandlerAnnotationInfo;
49 import com.sun.xml.internal.ws.util.exception.LocatableWebServiceException;
50 import com.sun.xml.internal.ws.util.xml.XmlUtil;
51 import org.xml.sax.EntityResolver;
53 import javax.xml.namespace.QName;
54 import javax.xml.stream.XMLStreamConstants;
55 import javax.xml.stream.XMLStreamException;
56 import javax.xml.stream.XMLStreamReader;
57 import javax.xml.ws.WebServiceException;
58 import javax.xml.ws.WebServiceFeature;
59 import javax.xml.ws.http.HTTPBinding;
60 import javax.xml.ws.soap.MTOMFeature;
61 import javax.xml.ws.soap.SOAPBinding;
62 import java.io.File;
63 import java.io.FileInputStream;
64 import java.io.IOException;
65 import java.io.InputStream;
66 import java.net.MalformedURLException;
67 import java.net.URL;
68 import java.util.ArrayList;
69 import java.util.Arrays;
70 import java.util.HashMap;
71 import java.util.HashSet;
72 import java.util.List;
73 import java.util.Map;
74 import java.util.Set;
75 import java.util.logging.Level;
76 import java.util.logging.Logger;
78 /**
79 * Parses {@code sun-jaxws.xml} into {@link WSEndpoint}.
80 * <p/>
81 * <p/>
82 * Since {@code sun-jaxws.xml} captures more information than what {@link WSEndpoint}
83 * represents (in particular URL pattern and name), this class
84 * takes a parameterization 'A' so that the user of this parser can choose to
85 * create another type that wraps {@link WSEndpoint}.
86 * <p/>
87 * {@link HttpAdapter} and its derived type is used for this often,
88 * but it can be anything.
89 *
90 * @author WS Development Team
91 * @author Kohsuke Kawaguchi
92 */
93 public class DeploymentDescriptorParser<A> {
95 public static final String NS_RUNTIME = "http://java.sun.com/xml/ns/jax-ws/ri/runtime";
96 public static final String JAXWS_WSDL_DD_DIR = "WEB-INF/wsdl";
98 public static final QName QNAME_ENDPOINTS = new QName(NS_RUNTIME, "endpoints");
99 public static final QName QNAME_ENDPOINT = new QName(NS_RUNTIME, "endpoint");
100 public static final QName QNAME_EXT_METADA = new QName(NS_RUNTIME, "external-metadata");
102 public static final String ATTR_FILE = "file";
103 public static final String ATTR_RESOURCE = "resource";
105 public static final String ATTR_VERSION = "version";
106 public static final String ATTR_NAME = "name";
107 public static final String ATTR_IMPLEMENTATION = "implementation";
108 public static final String ATTR_WSDL = "wsdl";
109 public static final String ATTR_SERVICE = "service";
110 public static final String ATTR_PORT = "port";
111 public static final String ATTR_URL_PATTERN = "url-pattern";
112 public static final String ATTR_ENABLE_MTOM = "enable-mtom";
113 public static final String ATTR_MTOM_THRESHOLD_VALUE = "mtom-threshold-value";
114 public static final String ATTR_BINDING = "binding";
115 public static final String ATTR_DATABINDING = "databinding";
117 public static final List<String> ATTRVALUE_SUPPORTED_VERSIONS = Arrays.asList("2.0", "2.1");
119 private static final Logger logger = Logger.getLogger(com.sun.xml.internal.ws.util.Constants.LoggingDomain + ".server.http");
121 private final Container container;
122 private final ClassLoader classLoader;
123 private final ResourceLoader loader;
124 private final AdapterFactory<A> adapterFactory;
126 /**
127 * Endpoint names that are declared.
128 * Used to catch double definitions.
129 */
130 private final Set<String> names = new HashSet<String>();
132 /**
133 * WSDL/schema documents collected from /WEB-INF/wsdl. Keyed by the system ID.
134 */
135 private final Map<String, SDDocumentSource> docs = new HashMap<String, SDDocumentSource>();
137 /**
138 * @param cl Used to load service implementations.
139 * @param loader Used to locate resources, in particular WSDL.
140 * @param container Optional {@link Container} that {@link WSEndpoint}s receive.
141 * @param adapterFactory Creates {@link HttpAdapter} (or its derived class.)
142 */
143 public DeploymentDescriptorParser(ClassLoader cl, ResourceLoader loader, Container container,
144 AdapterFactory<A> adapterFactory) throws MalformedURLException {
145 classLoader = cl;
146 this.loader = loader;
147 this.container = container;
148 this.adapterFactory = adapterFactory;
150 collectDocs("/WEB-INF/wsdl/");
151 logger.log(Level.FINE, "war metadata={0}", docs);
152 }
154 /**
155 * Parses the {@code sun-jaxws.xml} file and configures
156 * a set of {@link HttpAdapter}s.
157 */
158 public @NotNull List<A> parse(String systemId, InputStream is) {
159 XMLStreamReader reader = null;
160 try {
161 reader = new TidyXMLStreamReader(
162 XMLStreamReaderFactory.create(systemId, is, true), is);
163 XMLStreamReaderUtil.nextElementContent(reader);
164 return parseAdapters(reader);
165 } finally {
166 if (reader != null) {
167 try {
168 reader.close();
169 } catch (XMLStreamException e) {
170 throw new ServerRtException("runtime.parser.xmlReader", e);
171 }
172 }
173 try {
174 is.close();
175 } catch (IOException e) {
176 // ignore
177 }
178 }
179 }
181 /**
182 * Parses the {@code sun-jaxws.xml} file and configures
183 * a set of {@link HttpAdapter}s.
184 */
185 public @NotNull List<A> parse(File f) throws IOException {
186 FileInputStream in = new FileInputStream(f);
187 try {
188 return parse(f.getPath(), in);
189 } finally {
190 in.close();
191 }
192 }
194 /**
195 * Get all the WSDL & schema documents recursively.
196 */
197 private void collectDocs(String dirPath) throws MalformedURLException {
198 Set<String> paths = loader.getResourcePaths(dirPath);
199 if (paths != null) {
200 for (String path : paths) {
201 if (path.endsWith("/")) {
202 if (path.endsWith("/CVS/") || path.endsWith("/.svn/")) {
203 continue;
204 }
205 collectDocs(path);
206 } else {
207 URL res = loader.getResource(path);
208 docs.put(res.toString(), SDDocumentSource.create(res));
209 }
210 }
211 }
212 }
215 private List<A> parseAdapters(XMLStreamReader reader) {
216 if (!reader.getName().equals(QNAME_ENDPOINTS)) {
217 failWithFullName("runtime.parser.invalidElement", reader);
218 }
220 List<A> adapters = new ArrayList<A>();
222 Attributes attrs = XMLStreamReaderUtil.getAttributes(reader);
223 String version = getMandatoryNonEmptyAttribute(reader, attrs, ATTR_VERSION);
224 if (!ATTRVALUE_SUPPORTED_VERSIONS.contains(version)) {
225 failWithLocalName("runtime.parser.invalidVersionNumber", reader, version);
226 }
228 while (XMLStreamReaderUtil.nextElementContent(reader) != XMLStreamConstants.END_ELEMENT) {
230 if (reader.getName().equals(QNAME_ENDPOINT)) {
231 attrs = XMLStreamReaderUtil.getAttributes(reader);
233 String name = getMandatoryNonEmptyAttribute(reader, attrs, ATTR_NAME);
234 if (!names.add(name)) {
235 logger.warning(
236 WsservletMessages.SERVLET_WARNING_DUPLICATE_ENDPOINT_NAME(/*name*/));
237 }
239 String implementationName =
240 getMandatoryNonEmptyAttribute(reader, attrs, ATTR_IMPLEMENTATION);
241 Class<?> implementorClass = getImplementorClass(implementationName, reader);
243 MetadataReader metadataReader = null;
244 ExternalMetadataFeature externalMetadataFeature = null;
246 // parse subelements to instantiate externalMetadataReader, if necessary ...
247 XMLStreamReaderUtil.nextElementContent(reader);
248 if (reader.getEventType() != XMLStreamConstants.END_ELEMENT) {
249 externalMetadataFeature = configureExternalMetadataReader(reader);
250 if (externalMetadataFeature != null) {
251 metadataReader = externalMetadataFeature.getMetadataReader(implementorClass.getClassLoader(), false);
252 }
253 }
255 QName serviceName = getQNameAttribute(attrs, ATTR_SERVICE);
256 if (serviceName == null) {
257 serviceName = EndpointFactory.getDefaultServiceName(implementorClass, metadataReader);
258 }
260 QName portName = getQNameAttribute(attrs, ATTR_PORT);
261 if (portName == null) {
262 portName = EndpointFactory.getDefaultPortName(serviceName, implementorClass, metadataReader);
263 }
265 //get enable-mtom attribute value
266 String enable_mtom = getAttribute(attrs, ATTR_ENABLE_MTOM);
267 String mtomThreshold = getAttribute(attrs, ATTR_MTOM_THRESHOLD_VALUE);
268 String dbMode = getAttribute(attrs, ATTR_DATABINDING);
269 String bindingId = getAttribute(attrs, ATTR_BINDING);
270 if (bindingId != null) {
271 // Convert short-form tokens to API's binding ids
272 bindingId = getBindingIdForToken(bindingId);
273 }
274 WSBinding binding = createBinding(bindingId, implementorClass, enable_mtom, mtomThreshold, dbMode);
275 if (externalMetadataFeature != null) {
276 binding.getFeatures().mergeFeatures(new WebServiceFeature[]{externalMetadataFeature},
277 true);
278 }
280 String urlPattern = getMandatoryNonEmptyAttribute(reader, attrs, ATTR_URL_PATTERN);
282 // TODO use 'docs' as the metadata. If wsdl is non-null it's the primary.
283 boolean handlersSetInDD = setHandlersAndRoles(binding, reader, serviceName, portName);
285 EndpointFactory.verifyImplementorClass(implementorClass, metadataReader);
286 SDDocumentSource primaryWSDL = getPrimaryWSDL(reader, attrs, implementorClass, metadataReader);
288 WSEndpoint<?> endpoint = WSEndpoint.create(
289 implementorClass, !handlersSetInDD,
290 null,
291 serviceName, portName, container, binding,
292 primaryWSDL, docs.values(), createEntityResolver(), false
293 );
294 adapters.add(adapterFactory.createAdapter(name, urlPattern, endpoint));
295 } else {
296 failWithLocalName("runtime.parser.invalidElement", reader);
297 }
298 }
299 return adapters;
300 }
302 /**
303 * @param ddBindingId binding id explicitlyspecified in the DeploymentDescriptor or parameter
304 * @param implClass Endpoint Implementation class
305 * @param mtomEnabled represents mtom-enabled attribute in DD
306 * @param mtomThreshold threshold value specified in DD
307 * @return is returned with only MTOMFeature set resolving the various precendece rules
308 */
309 private static WSBinding createBinding(String ddBindingId, Class implClass,
310 String mtomEnabled, String mtomThreshold, String dataBindingMode) {
311 // Features specified through DD
312 WebServiceFeatureList features;
314 MTOMFeature mtomfeature = null;
315 if (mtomEnabled != null) {
316 if (mtomThreshold != null) {
317 mtomfeature = new MTOMFeature(Boolean.valueOf(mtomEnabled),
318 Integer.valueOf(mtomThreshold));
319 } else {
320 mtomfeature = new MTOMFeature(Boolean.valueOf(mtomEnabled));
321 }
322 }
324 BindingID bindingID;
325 if (ddBindingId != null) {
326 bindingID = BindingID.parse(ddBindingId);
327 features = bindingID.createBuiltinFeatureList();
329 if (checkMtomConflict(features.get(MTOMFeature.class), mtomfeature)) {
330 throw new ServerRtException(ServerMessages.DD_MTOM_CONFLICT(ddBindingId, mtomEnabled));
331 }
332 } else {
333 bindingID = BindingID.parse(implClass);
334 // Since bindingID is coming from implclass,
335 // mtom through Feature annotation or DD takes precendece
337 features = new WebServiceFeatureList();
338 if (mtomfeature != null) { // this wins over MTOM setting in bindingID
339 features.add(mtomfeature);
340 }
341 features.addAll(bindingID.createBuiltinFeatureList());
342 }
344 if (dataBindingMode != null) {
345 features.add(new DatabindingModeFeature(dataBindingMode));
346 }
348 return bindingID.createBinding(features.toArray());
349 }
351 private static boolean checkMtomConflict(MTOMFeature lhs, MTOMFeature rhs) {
352 if (lhs == null || rhs == null) {
353 return false;
354 }
355 return lhs.isEnabled() ^ rhs.isEnabled();
356 }
358 /**
359 * JSR-109 defines short-form tokens for standard binding Ids. These are
360 * used only in DD. So stand alone deployment descirptor should also honor
361 * these tokens. This method converts the tokens to API's standard
362 * binding ids
363 *
364 * @param lexical binding attribute value from DD. Always not null
365 * @return returns corresponding API's binding ID or the same lexical
366 */
367 public static @NotNull String getBindingIdForToken(@NotNull String lexical) {
368 if (lexical.equals("##SOAP11_HTTP")) {
369 return SOAPBinding.SOAP11HTTP_BINDING;
370 } else if (lexical.equals("##SOAP11_HTTP_MTOM")) {
371 return SOAPBinding.SOAP11HTTP_MTOM_BINDING;
372 } else if (lexical.equals("##SOAP12_HTTP")) {
373 return SOAPBinding.SOAP12HTTP_BINDING;
374 } else if (lexical.equals("##SOAP12_HTTP_MTOM")) {
375 return SOAPBinding.SOAP12HTTP_MTOM_BINDING;
376 } else if (lexical.equals("##XML_HTTP")) {
377 return HTTPBinding.HTTP_BINDING;
378 }
379 return lexical;
380 }
382 /**
383 * Creates a new "Adapter".
384 * <p/>
385 * Normally 'A' would be {@link HttpAdapter} or some derived class.
386 * But the parser doesn't require that to be of any particular type.
387 */
388 public static interface AdapterFactory<A> {
389 A createAdapter(String name, String urlPattern, WSEndpoint<?> endpoint);
390 }
392 /**
393 * Checks the deployment descriptor or {@link @WebServiceProvider} annotation
394 * to see if it points to any WSDL. If so, returns the {@link SDDocumentSource}.
395 *
396 * @return The pointed WSDL, if any. Otherwise null.
397 */
398 private SDDocumentSource getPrimaryWSDL(XMLStreamReader xsr, Attributes attrs, Class<?> implementorClass, MetadataReader metadataReader) {
400 String wsdlFile = getAttribute(attrs, ATTR_WSDL);
401 if (wsdlFile == null) {
402 wsdlFile = EndpointFactory.getWsdlLocation(implementorClass, metadataReader);
403 }
405 if (wsdlFile != null) {
406 if (!wsdlFile.startsWith(JAXWS_WSDL_DD_DIR)) {
407 logger.log(Level.WARNING, "Ignoring wrong wsdl={0}. It should start with {1}. Going to generate and publish a new WSDL.", new Object[]{wsdlFile, JAXWS_WSDL_DD_DIR});
408 return null;
409 }
411 URL wsdl;
412 try {
413 wsdl = loader.getResource('/' + wsdlFile);
414 } catch (MalformedURLException e) {
415 throw new LocatableWebServiceException(
416 ServerMessages.RUNTIME_PARSER_WSDL_NOT_FOUND(wsdlFile), e, xsr);
417 }
418 if (wsdl == null) {
419 throw new LocatableWebServiceException(
420 ServerMessages.RUNTIME_PARSER_WSDL_NOT_FOUND(wsdlFile), xsr);
421 }
422 SDDocumentSource docInfo = docs.get(wsdl.toExternalForm());
423 assert docInfo != null;
424 return docInfo;
425 }
427 return null;
428 }
430 /**
431 * Creates an {@link EntityResolver} that consults {@code /WEB-INF/jax-ws-catalog.xml}.
432 */
433 private EntityResolver createEntityResolver() {
434 try {
435 return XmlUtil.createEntityResolver(loader.getCatalogFile());
436 } catch (MalformedURLException e) {
437 throw new WebServiceException(e);
438 }
439 }
441 protected String getAttribute(Attributes attrs, String name) {
442 String value = attrs.getValue(name);
443 if (value != null) {
444 value = value.trim();
445 }
446 return value;
447 }
449 protected QName getQNameAttribute(Attributes attrs, String name) {
450 String value = getAttribute(attrs, name);
451 if (value == null || value.equals("")) {
452 return null;
453 } else {
454 return QName.valueOf(value);
455 }
456 }
458 protected String getNonEmptyAttribute(XMLStreamReader reader, Attributes attrs, String name) {
459 String value = getAttribute(attrs, name);
460 if (value != null && value.equals("")) {
461 failWithLocalName(
462 "runtime.parser.invalidAttributeValue",
463 reader,
464 name);
465 }
466 return value;
467 }
469 protected String getMandatoryAttribute(XMLStreamReader reader, Attributes attrs, String name) {
470 String value = getAttribute(attrs, name);
471 if (value == null) {
472 failWithLocalName("runtime.parser.missing.attribute", reader, name);
473 }
474 return value;
475 }
477 protected String getMandatoryNonEmptyAttribute(XMLStreamReader reader, Attributes attributes,
478 String name) {
479 String value = getAttribute(attributes, name);
480 if (value == null) {
481 failWithLocalName("runtime.parser.missing.attribute", reader, name);
482 } else if (value.equals("")) {
483 failWithLocalName(
484 "runtime.parser.invalidAttributeValue",
485 reader,
486 name);
487 }
488 return value;
489 }
491 /**
492 * Parses the handler and role information and sets it
493 * on the {@link WSBinding}.
494 *
495 * @return true if <handler-chains> element present in DD
496 * false otherwise.
497 */
498 protected boolean setHandlersAndRoles(WSBinding binding, XMLStreamReader reader, QName serviceName, QName portName) {
500 if (reader.getEventType() == XMLStreamConstants.END_ELEMENT ||
501 !reader.getName().equals(HandlerChainsModel.QNAME_HANDLER_CHAINS)) {
502 return false;
503 }
505 HandlerAnnotationInfo handlerInfo = HandlerChainsModel.parseHandlerFile(
506 reader, classLoader, serviceName, portName, binding);
508 binding.setHandlerChain(handlerInfo.getHandlers());
509 if (binding instanceof SOAPBinding) {
510 ((SOAPBinding) binding).setRoles(handlerInfo.getRoles());
511 }
513 // move past </handler-chains>
514 XMLStreamReaderUtil.nextContent(reader);
515 return true;
516 }
518 protected ExternalMetadataFeature configureExternalMetadataReader(XMLStreamReader reader) {
520 ExternalMetadataFeature.Builder featureBuilder = null;
521 while (QNAME_EXT_METADA.equals(reader.getName())) {
523 if (reader.getEventType() == XMLStreamConstants.START_ELEMENT) {
524 Attributes attrs = XMLStreamReaderUtil.getAttributes(reader);
525 String file = getAttribute(attrs, ATTR_FILE);
526 if (file != null) {
527 if (featureBuilder == null) {
528 featureBuilder = ExternalMetadataFeature.builder();
529 }
530 featureBuilder.addFiles(new File(file));
531 }
533 String res = getAttribute(attrs, ATTR_RESOURCE);
534 if (res != null) {
535 if (featureBuilder == null) {
536 featureBuilder = ExternalMetadataFeature.builder();
537 }
538 featureBuilder.addResources(res);
539 }
540 }
542 XMLStreamReaderUtil.nextElementContent(reader);
543 }
545 return buildFeature(featureBuilder);
546 }
548 private ExternalMetadataFeature buildFeature(ExternalMetadataFeature.Builder builder) {
549 return builder != null ? builder.build() : null;
550 }
552 protected static void fail(String key, XMLStreamReader reader) {
553 logger.log(Level.SEVERE, "{0}{1}", new Object[]{key, reader.getLocation().getLineNumber()});
554 throw new ServerRtException(
555 key,
556 Integer.toString(reader.getLocation().getLineNumber()));
557 }
559 protected static void failWithFullName(String key, XMLStreamReader reader) {
560 throw new ServerRtException(
561 key,
562 reader.getLocation().getLineNumber(),
563 reader.getName());
564 }
566 protected static void failWithLocalName(String key, XMLStreamReader reader) {
567 throw new ServerRtException(
568 key,
569 reader.getLocation().getLineNumber(),
570 reader.getLocalName());
571 }
573 protected static void failWithLocalName(String key, XMLStreamReader reader, String arg) {
574 throw new ServerRtException(
575 key,
576 reader.getLocation().getLineNumber(),
577 reader.getLocalName(),
578 arg);
579 }
581 protected Class loadClass(String name) {
582 try {
583 return Class.forName(name, true, classLoader);
584 } catch (ClassNotFoundException e) {
585 logger.log(Level.SEVERE, e.getMessage(), e);
586 throw new ServerRtException(
587 "runtime.parser.classNotFound",
588 name);
589 }
590 }
593 /**
594 * Loads the class of the given name.
595 *
596 * @param xsr Used to report the source location information if there's any error.
597 */
598 private Class getImplementorClass(String name, XMLStreamReader xsr) {
599 try {
600 return Class.forName(name, true, classLoader);
601 } catch (ClassNotFoundException e) {
602 logger.log(Level.SEVERE, e.getMessage(), e);
603 throw new LocatableWebServiceException(
604 ServerMessages.RUNTIME_PARSER_CLASS_NOT_FOUND(name), e, xsr);
605 }
606 }
608 }