001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036:
037: package com.sun.xml.ws.transport.http;
038:
039: import com.sun.istack.NotNull;
040: import com.sun.xml.ws.api.BindingID;
041: import com.sun.xml.ws.api.WSBinding;
042: import com.sun.xml.ws.api.message.Packet;
043: import com.sun.xml.ws.api.server.Container;
044: import com.sun.xml.ws.api.server.SDDocumentSource;
045: import com.sun.xml.ws.api.server.WSEndpoint;
046: import com.sun.xml.ws.api.streaming.XMLStreamReaderFactory;
047: import com.sun.xml.ws.binding.WebServiceFeatureList;
048: import com.sun.xml.ws.handler.HandlerChainsModel;
049: import com.sun.xml.ws.resources.ServerMessages;
050: import com.sun.xml.ws.resources.WsservletMessages;
051: import com.sun.xml.ws.server.EndpointFactory;
052: import com.sun.xml.ws.server.ServerRtException;
053: import com.sun.xml.ws.streaming.Attributes;
054: import com.sun.xml.ws.streaming.TidyXMLStreamReader;
055: import com.sun.xml.ws.streaming.XMLStreamReaderUtil;
056: import com.sun.xml.ws.util.HandlerAnnotationInfo;
057: import com.sun.xml.ws.util.exception.LocatableWebServiceException;
058: import com.sun.xml.ws.util.xml.XmlUtil;
059: import org.xml.sax.EntityResolver;
060:
061: import javax.xml.namespace.QName;
062: import javax.xml.stream.XMLStreamConstants;
063: import javax.xml.stream.XMLStreamException;
064: import javax.xml.stream.XMLStreamReader;
065: import javax.xml.ws.WebServiceException;
066: import javax.xml.ws.http.HTTPBinding;
067: import javax.xml.ws.soap.MTOMFeature;
068: import javax.xml.ws.soap.SOAPBinding;
069: import java.io.File;
070: import java.io.FileInputStream;
071: import java.io.IOException;
072: import java.io.InputStream;
073: import java.net.MalformedURLException;
074: import java.net.URL;
075: import java.util.ArrayList;
076: import java.util.HashMap;
077: import java.util.HashSet;
078: import java.util.List;
079: import java.util.Map;
080: import java.util.Set;
081: import java.util.logging.Level;
082: import java.util.logging.Logger;
083:
084: /**
085: * Parses {@code sun-jaxws.xml} into {@link WSEndpoint}.
086: *
087: * <p>
088: * Since {@code sun-jaxws.xml} captures more information than what {@link WSEndpoint}
089: * represents (in particular URL pattern and name), this class
090: * takes a parameterization 'A' so that the user of this parser can choose to
091: * create another type that wraps {@link WSEndpoint}.
092: *
093: * {@link HttpAdapter} and its derived type is used for this often,
094: * but it can be anything.
095: *
096: * @author WS Development Team
097: * @author Kohsuke Kawaguchi
098: */
099: public class DeploymentDescriptorParser<A> {
100: private final Container container;
101: private final ClassLoader classLoader;
102: private final ResourceLoader loader;
103: private final AdapterFactory<A> adapterFactory;
104:
105: /**
106: * Endpoint names that are declared.
107: * Used to catch double definitions.
108: */
109: private final Set<String> names = new HashSet<String>();
110:
111: /**
112: * WSDL/schema documents collected from /WEB-INF/wsdl. Keyed by the system ID.
113: */
114: private final Map<String, SDDocumentSource> docs = new HashMap<String, SDDocumentSource>();
115:
116: /**
117: *
118: * @param cl
119: * Used to load service implementations.
120: * @param loader
121: * Used to locate resources, in particular WSDL.
122: * @param container
123: * Optional {@link Container} that {@link WSEndpoint}s receive.
124: * @param adapterFactory
125: * Creates {@link HttpAdapter} (or its derived class.)
126: */
127: public DeploymentDescriptorParser(ClassLoader cl,
128: ResourceLoader loader, Container container,
129: AdapterFactory<A> adapterFactory)
130: throws MalformedURLException {
131: classLoader = cl;
132: this .loader = loader;
133: this .container = container;
134: this .adapterFactory = adapterFactory;
135:
136: collectDocs("/WEB-INF/wsdl/");
137: logger.fine("war metadata=" + docs);
138: }
139:
140: /**
141: * Parses the {@code sun-jaxws.xml} file and configures
142: * a set of {@link HttpAdapter}s.
143: */
144: public @NotNull
145: List<A> parse(String systemId, InputStream is) {
146: XMLStreamReader reader = null;
147: try {
148: reader = new TidyXMLStreamReader(XMLStreamReaderFactory
149: .create(systemId, is, true), is);
150: XMLStreamReaderUtil.nextElementContent(reader);
151: return parseAdapters(reader);
152: } finally {
153: if (reader != null) {
154: try {
155: reader.close();
156: } catch (XMLStreamException e) {
157: throw new ServerRtException(
158: "runtime.parser.xmlReader", e);
159: }
160: }
161: try {
162: is.close();
163: } catch (IOException e) {
164: // ignore
165: }
166: }
167: }
168:
169: /**
170: * Parses the {@code sun-jaxws.xml} file and configures
171: * a set of {@link HttpAdapter}s.
172: */
173: public @NotNull
174: List<A> parse(File f) throws IOException {
175: FileInputStream in = new FileInputStream(f);
176: try {
177: return parse(f.getPath(), in);
178: } finally {
179: in.close();
180: }
181: }
182:
183: /**
184: * Get all the WSDL & schema documents recursively.
185: */
186: private void collectDocs(String dirPath)
187: throws MalformedURLException {
188: Set<String> paths = loader.getResourcePaths(dirPath);
189: if (paths != null) {
190: for (String path : paths) {
191: if (path.endsWith("/")) {
192: if (path.endsWith("/CVS/")
193: || path.endsWith("/.svn/"))
194: continue;
195: collectDocs(path);
196: } else {
197: URL res = loader.getResource(path);
198: docs.put(res.toString(), SDDocumentSource
199: .create(res));
200: }
201: }
202: }
203: }
204:
205: private List<A> parseAdapters(XMLStreamReader reader) {
206: if (!reader.getName().equals(QNAME_ENDPOINTS)) {
207: failWithFullName("runtime.parser.invalidElement", reader);
208: }
209:
210: List<A> adapters = new ArrayList<A>();
211:
212: Attributes attrs = XMLStreamReaderUtil.getAttributes(reader);
213: String version = getMandatoryNonEmptyAttribute(reader, attrs,
214: ATTR_VERSION);
215: if (!version.equals(ATTRVALUE_VERSION_1_0)) {
216: failWithLocalName("runtime.parser.invalidVersionNumber",
217: reader, version);
218: }
219:
220: while (XMLStreamReaderUtil.nextElementContent(reader) != XMLStreamConstants.END_ELEMENT)
221: if (reader.getName().equals(QNAME_ENDPOINT)) {
222:
223: attrs = XMLStreamReaderUtil.getAttributes(reader);
224: String name = getMandatoryNonEmptyAttribute(reader,
225: attrs, ATTR_NAME);
226: if (!names.add(name)) {
227: logger
228: .warning(WsservletMessages
229: .SERVLET_WARNING_DUPLICATE_ENDPOINT_NAME(/*name*/));
230: }
231:
232: String implementationName = getMandatoryNonEmptyAttribute(
233: reader, attrs, ATTR_IMPLEMENTATION);
234: Class<?> implementorClass = getImplementorClass(
235: implementationName, reader);
236: EndpointFactory
237: .verifyImplementorClass(implementorClass);
238:
239: SDDocumentSource primaryWSDL = getPrimaryWSDL(reader,
240: attrs, implementorClass);
241:
242: QName serviceName = getQNameAttribute(attrs,
243: ATTR_SERVICE);
244: if (serviceName == null)
245: serviceName = EndpointFactory
246: .getDefaultServiceName(implementorClass);
247:
248: QName portName = getQNameAttribute(attrs, ATTR_PORT);
249: if (portName == null)
250: portName = EndpointFactory.getDefaultPortName(
251: serviceName, implementorClass);
252:
253: //get enable-mtom attribute value
254: String enable_mtom = getAttribute(attrs,
255: ATTR_ENABLE_MTOM);
256: String mtomThreshold = getAttribute(attrs,
257: ATTR_MTOM_THRESHOLD_VALUE);
258: String bindingId = getAttribute(attrs, ATTR_BINDING);
259: if (bindingId != null)
260: // Convert short-form tokens to API's binding ids
261: bindingId = getBindingIdForToken(bindingId);
262: WSBinding binding = createBinding(bindingId,
263: implementorClass, enable_mtom, mtomThreshold);
264: String urlPattern = getMandatoryNonEmptyAttribute(
265: reader, attrs, ATTR_URL_PATTERN);
266:
267: // TODO use 'docs' as the metadata. If wsdl is non-null it's the primary.
268:
269: boolean handlersSetInDD = setHandlersAndRoles(binding,
270: reader, serviceName, portName);
271:
272: ensureNoContent(reader);
273: WSEndpoint<?> endpoint = WSEndpoint.create(
274: implementorClass, !handlersSetInDD, null,
275: serviceName, portName, container, binding,
276: primaryWSDL, docs.values(),
277: createEntityResolver(), false);
278: adapters.add(adapterFactory.createAdapter(name,
279: urlPattern, endpoint));
280: } else {
281: failWithLocalName("runtime.parser.invalidElement",
282: reader);
283: }
284: return adapters;
285: }
286:
287: /**
288: * @param ddBindingId
289: * binding id explicitlyspecified in the DeploymentDescriptor or parameter
290: * @param implClass
291: * Endpoint Implementation class
292: * @param mtomEnabled
293: * represents mtom-enabled attribute in DD
294: * @param mtomThreshold
295: * threshold value specified in DD
296: * @return
297: * is returned with only MTOMFeature set resolving the various precendece rules
298: */
299: private static WSBinding createBinding(String ddBindingId,
300: Class implClass, String mtomEnabled, String mtomThreshold) {
301: // Features specified through DD
302: WebServiceFeatureList features;
303:
304: MTOMFeature mtomfeature = null;
305: if (mtomEnabled != null) {
306: if (mtomThreshold != null)
307: mtomfeature = new MTOMFeature(Boolean
308: .valueOf(mtomEnabled), Integer
309: .valueOf(mtomThreshold));
310: else
311: mtomfeature = new MTOMFeature(Boolean
312: .valueOf(mtomEnabled));
313: }
314:
315: BindingID bindingID;
316: if (ddBindingId != null) {
317: bindingID = BindingID.parse(ddBindingId);
318: features = bindingID.createBuiltinFeatureList();
319:
320: if (checkMtomConflict(features.get(MTOMFeature.class),
321: mtomfeature)) {
322: throw new ServerRtException(ServerMessages
323: .DD_MTOM_CONFLICT(ddBindingId, mtomEnabled));
324: }
325: } else {
326: bindingID = BindingID.parse(implClass);
327: // Since bindingID is coming from implclass,
328: // mtom through Feature annotation or DD takes precendece
329:
330: features = new WebServiceFeatureList();
331: if (mtomfeature != null)
332: features.add(mtomfeature); // this wins over MTOM setting in bindingID
333: features.addAll(bindingID.createBuiltinFeatureList());
334: }
335:
336: return bindingID.createBinding(features.toArray());
337: }
338:
339: private static boolean checkMtomConflict(MTOMFeature lhs,
340: MTOMFeature rhs) {
341: if (lhs == null || rhs == null)
342: return false;
343: return lhs.isEnabled() ^ rhs.isEnabled();
344: }
345:
346: /**
347: * JSR-109 defines short-form tokens for standard binding Ids. These are
348: * used only in DD. So stand alone deployment descirptor should also honor
349: * these tokens. This method converts the tokens to API's standard
350: * binding ids
351: *
352: * @param lexical binding attribute value from DD. Always not null
353: *
354: * @return returns corresponding API's binding ID or the same lexical
355: */
356: public static @NotNull
357: String getBindingIdForToken(@NotNull
358: String lexical) {
359: if (lexical.equals("##SOAP11_HTTP")) {
360: return SOAPBinding.SOAP11HTTP_BINDING;
361: } else if (lexical.equals("##SOAP11_HTTP_MTOM")) {
362: return SOAPBinding.SOAP11HTTP_MTOM_BINDING;
363: } else if (lexical.equals("##SOAP12_HTTP")) {
364: return SOAPBinding.SOAP12HTTP_BINDING;
365: } else if (lexical.equals("##SOAP12_HTTP_MTOM")) {
366: return SOAPBinding.SOAP12HTTP_MTOM_BINDING;
367: } else if (lexical.equals("##XML_HTTP")) {
368: return HTTPBinding.HTTP_BINDING;
369: }
370: return lexical;
371: }
372:
373: /**
374: * Creates a new "Adapter".
375: *
376: * <P>
377: * Normally 'A' would be {@link HttpAdapter} or some derived class.
378: * But the parser doesn't require that to be of any particular type.
379: */
380: public static interface AdapterFactory<A> {
381: A createAdapter(String name, String urlPattern,
382: WSEndpoint<?> endpoint);
383: }
384:
385: /**
386: * Checks the deployment descriptor or {@link @WebServiceProvider} annotation
387: * to see if it points to any WSDL. If so, returns the {@link SDDocumentSource}.
388: *
389: * @return
390: * The pointed WSDL, if any. Otherwise null.
391: */
392: private SDDocumentSource getPrimaryWSDL(XMLStreamReader xsr,
393: Attributes attrs, Class<?> implementorClass) {
394:
395: String wsdlFile = getAttribute(attrs, ATTR_WSDL);
396: if (wsdlFile == null) {
397: wsdlFile = EndpointFactory
398: .getWsdlLocation(implementorClass);
399: }
400:
401: if (wsdlFile != null) {
402: if (!wsdlFile.startsWith(JAXWS_WSDL_DD_DIR)) {
403: logger
404: .warning("Ignoring wrong wsdl="
405: + wsdlFile
406: + ". It should start with "
407: + JAXWS_WSDL_DD_DIR
408: + ". Going to generate and publish a new WSDL.");
409: return null;
410: }
411:
412: URL wsdl;
413: try {
414: wsdl = loader.getResource('/' + wsdlFile);
415: } catch (MalformedURLException e) {
416: throw new LocatableWebServiceException(ServerMessages
417: .RUNTIME_PARSER_WSDL_NOT_FOUND(wsdlFile), e,
418: xsr);
419: }
420: if (wsdl == null) {
421: throw new LocatableWebServiceException(ServerMessages
422: .RUNTIME_PARSER_WSDL_NOT_FOUND(wsdlFile), xsr);
423: }
424: SDDocumentSource docInfo = docs.get(wsdl.toExternalForm());
425: assert docInfo != null;
426: return docInfo;
427: }
428:
429: return null;
430: }
431:
432: /**
433: * Creates an {@link EntityResolver} that consults {@code /WEB-INF/jax-ws-catalog.xml}.
434: */
435: private EntityResolver createEntityResolver() {
436: try {
437: return XmlUtil
438: .createEntityResolver(loader.getCatalogFile());
439: } catch (MalformedURLException e) {
440: throw new WebServiceException(e);
441: }
442: }
443:
444: protected String getAttribute(Attributes attrs, String name) {
445: String value = attrs.getValue(name);
446: if (value != null) {
447: value = value.trim();
448: }
449: return value;
450: }
451:
452: protected QName getQNameAttribute(Attributes attrs, String name) {
453: String value = getAttribute(attrs, name);
454: if (value == null || value.equals("")) {
455: return null;
456: } else {
457: return QName.valueOf(value);
458: }
459: }
460:
461: protected String getNonEmptyAttribute(XMLStreamReader reader,
462: Attributes attrs, String name) {
463: String value = getAttribute(attrs, name);
464: if (value != null && value.equals("")) {
465: failWithLocalName("runtime.parser.invalidAttributeValue",
466: reader, name);
467: }
468: return value;
469: }
470:
471: protected String getMandatoryAttribute(XMLStreamReader reader,
472: Attributes attrs, String name) {
473: String value = getAttribute(attrs, name);
474: if (value == null) {
475: failWithLocalName("runtime.parser.missing.attribute",
476: reader, name);
477: }
478: return value;
479: }
480:
481: protected String getMandatoryNonEmptyAttribute(
482: XMLStreamReader reader, Attributes attributes, String name) {
483: String value = getAttribute(attributes, name);
484: if (value == null) {
485: failWithLocalName("runtime.parser.missing.attribute",
486: reader, name);
487: } else if (value.equals("")) {
488: failWithLocalName("runtime.parser.invalidAttributeValue",
489: reader, name);
490: }
491: return value;
492: }
493:
494: /**
495: * Parses the handler and role information and sets it
496: * on the {@link WSBinding}.
497: * @return true if <handler-chains> element present in DD
498: * false otherwise.
499: */
500: protected boolean setHandlersAndRoles(WSBinding binding,
501: XMLStreamReader reader, QName serviceName, QName portName) {
502:
503: if (XMLStreamReaderUtil.nextElementContent(reader) == XMLStreamConstants.END_ELEMENT
504: || !reader.getName().equals(
505: HandlerChainsModel.QNAME_HANDLER_CHAINS)) {
506:
507: return false;
508: }
509:
510: HandlerAnnotationInfo handlerInfo = HandlerChainsModel
511: .parseHandlerFile(reader, classLoader, serviceName,
512: portName, binding);
513:
514: binding.setHandlerChain(handlerInfo.getHandlers());
515: if (binding instanceof SOAPBinding) {
516: ((SOAPBinding) binding).setRoles(handlerInfo.getRoles());
517: }
518:
519: // move past </handler-chains>
520: XMLStreamReaderUtil.nextContent(reader);
521: return true;
522: }
523:
524: protected static void ensureNoContent(XMLStreamReader reader) {
525: if (reader.getEventType() != XMLStreamConstants.END_ELEMENT) {
526: fail("runtime.parser.unexpectedContent", reader);
527: }
528: }
529:
530: protected static void fail(String key, XMLStreamReader reader) {
531: logger.log(Level.SEVERE, key
532: + reader.getLocation().getLineNumber());
533: throw new ServerRtException(key, Integer.toString(reader
534: .getLocation().getLineNumber()));
535: }
536:
537: protected static void failWithFullName(String key,
538: XMLStreamReader reader) {
539: throw new ServerRtException(key, reader.getLocation()
540: .getLineNumber(), reader.getName());
541: }
542:
543: protected static void failWithLocalName(String key,
544: XMLStreamReader reader) {
545: throw new ServerRtException(key, reader.getLocation()
546: .getLineNumber(), reader.getLocalName());
547: }
548:
549: protected static void failWithLocalName(String key,
550: XMLStreamReader reader, String arg) {
551: throw new ServerRtException(key, reader.getLocation()
552: .getLineNumber(), reader.getLocalName(), arg);
553: }
554:
555: protected Class loadClass(String name) {
556: try {
557: return Class.forName(name, true, classLoader);
558: } catch (ClassNotFoundException e) {
559: logger.log(Level.SEVERE, e.getMessage(), e);
560: throw new ServerRtException("runtime.parser.classNotFound",
561: name);
562: }
563: }
564:
565: /**
566: * Loads the class of the given name.
567: *
568: * @param xsr
569: * Used to report the source location information if there's any error.
570: */
571: private Class getImplementorClass(String name, XMLStreamReader xsr) {
572: try {
573: return Class.forName(name, true, classLoader);
574: } catch (ClassNotFoundException e) {
575: logger.log(Level.SEVERE, e.getMessage(), e);
576: throw new LocatableWebServiceException(ServerMessages
577: .RUNTIME_PARSER_CLASS_NOT_FOUND(name), e, xsr);
578: }
579: }
580:
581: public static final String NS_RUNTIME = "http://java.sun.com/xml/ns/jax-ws/ri/runtime";
582:
583: public static final String JAXWS_WSDL_DD_DIR = "WEB-INF/wsdl";
584:
585: public static final QName QNAME_ENDPOINTS = new QName(NS_RUNTIME,
586: "endpoints");
587: public static final QName QNAME_ENDPOINT = new QName(NS_RUNTIME,
588: "endpoint");
589:
590: public static final String ATTR_VERSION = "version";
591: public static final String ATTR_NAME = "name";
592: public static final String ATTR_IMPLEMENTATION = "implementation";
593: public static final String ATTR_WSDL = "wsdl";
594: public static final String ATTR_SERVICE = "service";
595: public static final String ATTR_PORT = "port";
596: public static final String ATTR_URL_PATTERN = "url-pattern";
597: public static final String ATTR_ENABLE_MTOM = "enable-mtom";
598: public static final String ATTR_MTOM_THRESHOLD_VALUE = "mtom-threshold-value";
599: public static final String ATTR_BINDING = "binding";
600:
601: public static final String ATTRVALUE_VERSION_1_0 = "2.0";
602: private static final Logger logger = Logger
603: .getLogger(com.sun.xml.ws.util.Constants.LoggingDomain
604: + ".server.http");
605: }
|