001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.geronimo.jaxws.builder;
018    
019    import java.io.IOException;
020    import java.io.InputStream;
021    import java.net.MalformedURLException;
022    import java.net.URI;
023    import java.net.URISyntaxException;
024    import java.net.URL;
025    import java.util.ArrayList;
026    import java.util.HashMap;
027    import java.util.Iterator;
028    import java.util.List;
029    import java.util.Map;
030    import java.util.jar.JarFile;
031    import java.util.zip.ZipEntry;
032    
033    import javax.wsdl.Binding;
034    import javax.wsdl.Definition;
035    import javax.wsdl.Port;
036    import javax.wsdl.PortType;
037    import javax.wsdl.Service;
038    import javax.wsdl.WSDLException;
039    import javax.wsdl.extensions.ExtensibilityElement;
040    import javax.wsdl.extensions.soap.SOAPAddress;
041    import javax.wsdl.factory.WSDLFactory;
042    import javax.wsdl.xml.WSDLLocator;
043    import javax.wsdl.xml.WSDLReader;
044    import javax.xml.namespace.QName;
045    import javax.xml.ws.WebServiceClient;
046    
047    import org.apache.commons.logging.Log;
048    import org.apache.commons.logging.LogFactory;
049    import org.apache.geronimo.common.DeploymentException;
050    import org.apache.geronimo.jaxws.JAXWSUtils;
051    import org.apache.geronimo.jaxws.client.EndpointInfo;
052    import org.apache.geronimo.xbeans.geronimo.naming.GerPortType;
053    import org.apache.geronimo.xbeans.geronimo.naming.GerServiceRefType;
054    import org.apache.geronimo.xbeans.javaee.PortComponentRefType;
055    import org.xml.sax.InputSource;
056    
057    public class EndpointInfoBuilder {
058    
059        private static final Log LOG = LogFactory.getLog(EndpointInfoBuilder.class);
060    
061        private JarFile moduleFile;
062    
063        private URI wsdlURI;
064    
065        private QName serviceQName;
066    
067        private Class serviceClass;
068    
069        private GerServiceRefType serviceRefType;
070    
071        private Map<Object, EndpointInfo> portInfoMap = new HashMap<Object, EndpointInfo>();
072    
073        private Map<Class, PortComponentRefType> portComponentRefMap;
074    
075        public EndpointInfoBuilder(Class serviceClass,
076                                   GerServiceRefType serviceRefType,
077                                   Map<Class, PortComponentRefType> portComponentRefMap,
078                                   JarFile moduleFile,
079                                   URI wsdlURI,
080                                   QName serviceQName) {
081            this.serviceClass = serviceClass;
082            this.serviceRefType = serviceRefType;
083            this.portComponentRefMap = portComponentRefMap;
084            this.moduleFile = moduleFile;
085            this.wsdlURI = wsdlURI;
086            this.serviceQName = serviceQName;
087        }
088    
089        public URI getWsdlURI() {
090            return this.wsdlURI;
091        }
092        
093        public QName getServiceQName() {
094            return this.serviceQName;
095        }
096    
097        public Map<Object, EndpointInfo> getEndpointInfo() {
098            return this.portInfoMap;
099        }
100        
101        public void build() throws DeploymentException {
102            if (this.wsdlURI == null) {
103                // wsdl explitely not specified, try to get it from 
104                // the service class annotation
105                WebServiceClient webServiceClient = 
106                    (WebServiceClient) this.serviceClass.getAnnotation(WebServiceClient.class);
107                if (webServiceClient != null) {
108                    this.wsdlURI = getWSDLLocation(webServiceClient);               
109                    this.serviceQName = getServiceQName(webServiceClient);
110                }
111               
112                if (this.wsdlURI == null) {                
113                    return;
114                }
115            }
116            
117            JarWSDLLocator wsdlLocator = null;
118            URL wsdlURL = null;
119            try {
120                wsdlURL = new URL(this.wsdlURI.toString());
121            } catch (MalformedURLException e1) {
122                // not a URL, assume it's a local reference
123                wsdlLocator = new JarWSDLLocator(this.wsdlURI);
124            }
125    
126            Definition definition;
127            WSDLFactory wsdlFactory;
128            try {
129                wsdlFactory = WSDLFactory.newInstance();
130            } catch (WSDLException e) {
131                throw new DeploymentException("Could not create WSDLFactory", e);
132            }
133            WSDLReader wsdlReader = wsdlFactory.newWSDLReader();
134            wsdlReader.setFeature("javax.wsdl.importDocuments", true);
135            wsdlReader.setFeature("javax.wsdl.verbose", false);
136            try {
137                if (wsdlURL != null) {
138                    definition = wsdlReader.readWSDL(wsdlURL.toString());
139                } else if (wsdlLocator != null) {
140                    definition = wsdlReader.readWSDL(wsdlLocator);
141                } else {
142                    throw new DeploymentException("unknown");
143                }
144            } catch (WSDLException e) {
145                throw new DeploymentException("Failed to read wsdl document", e);
146            } catch (RuntimeException e) {
147                throw new DeploymentException(e.getMessage(), e);
148            }
149    
150            verifyPortComponentList(definition);
151            
152            Map services = definition.getServices();
153            if (services.size() == 0) {
154                // partial wsdl, return as is
155    
156                if (this.serviceRefType != null && this.serviceRefType.isSetServiceCompletion()) {
157                    LOG.warn("Service completion is not supported with partial wsdl");
158                }
159            } else {
160                // full wsdl
161    
162                if (this.serviceRefType != null && this.serviceRefType.isSetServiceCompletion()) {
163                    throw new DeploymentException("Full wsdl, but service completion supplied");
164                }
165                
166                Service service = null;
167                if (this.serviceQName != null) {
168                    service = definition.getService(this.serviceQName);
169                    if (service == null) {
170                        throw new DeploymentException(
171                                "No service wsdl for supplied service qname "
172                                        + this.serviceQName);
173                    }
174                } else if (services.size() == 1) {
175                    service = (Service) services.values().iterator().next();
176                    this.serviceQName = service.getQName();
177                } else {
178                    throw new DeploymentException(
179                            "No service qname supplied, and there are "
180                                    + services.size() + " services");
181                }
182    
183                // organize the extra port info
184                Map<String, GerPortType> portMap = new HashMap<String, GerPortType>();
185                if (serviceRefType != null) {
186                    GerPortType[] ports = serviceRefType.getPortArray();
187                    for (int i = 0; i < ports.length; i++) {
188                        GerPortType port = ports[i];
189                        String portName = port.getPortName().trim();
190                        portMap.put(portName, port);
191                    }
192                }
193    
194                Map wsdlPortMap = service.getPorts();
195                for (Iterator iterator = wsdlPortMap.entrySet().iterator(); iterator.hasNext();) {
196                    Map.Entry entry = (Map.Entry) iterator.next();
197                    String portName = (String) entry.getKey();
198                    Port port = (Port) entry.getValue();
199    
200                    GerPortType gerPort = portMap.get(portName);
201    
202                    URL location = (gerPort == null) ? getAddressLocation(port) : getLocation(gerPort);
203                    // skip non-soap ports
204                    if (location == null) {
205                        continue;
206                    }
207                    String credentialsName = (gerPort == null) ? null : getCredentialsName(gerPort);
208                    
209                    Binding binding = port.getBinding();
210                    if (binding == null) {
211                        throw new DeploymentException("No binding for port: " + portName);
212                    }
213                    
214                    PortType portType = binding.getPortType();
215                    if (portType == null) {
216                        throw new DeploymentException("No portType for binding: " + binding.getQName());
217                    }
218    
219                    boolean mtomEnabled = isMTOMEnabled(portType.getQName());
220                    
221                    EndpointInfo info = new EndpointInfo(location, credentialsName, mtomEnabled);
222                    this.portInfoMap.put(portName, info);
223                    // prefer first binding listed in wsdl
224                    if (!this.portInfoMap.containsKey(portType.getQName())) {
225                        this.portInfoMap.put(portType.getQName(), info);
226                    }
227                }
228            }
229        }
230    
231        private QName getServiceQName(WebServiceClient webServiceClient) {
232            if (webServiceClient.targetNamespace() != null && webServiceClient.name() != null) {
233                return new QName(webServiceClient.targetNamespace(), webServiceClient.name());
234            } else {
235                return null;
236            }
237        }
238        
239        private URI getWSDLLocation(WebServiceClient webServiceClient) throws DeploymentException {
240            String wsdlLocation = webServiceClient.wsdlLocation();
241            if (wsdlLocation != null && wsdlLocation.trim().length() > 0) {            
242                try {
243                    return new URI(wsdlLocation.trim());
244                } catch (URISyntaxException e) {
245                    throw new DeploymentException(
246                            "Invalid wsdl location in annotation: " + wsdlLocation, e);
247                }
248            }
249    
250            return null;
251        }
252    
253        private String getCredentialsName(GerPortType port) {
254            String credentialsName = port.getCredentialsName();
255            return (credentialsName == null) ? null : credentialsName.trim();        
256        }
257        
258        private URL getLocation(GerPortType port) throws DeploymentException {
259            String protocol = port.getProtocol().trim();
260            String host = port.getHost().trim();
261            int portNum = port.getPort();
262            String uri = port.getUri().trim();
263            String locationURIString = protocol + "://" + host + ":" + portNum + uri;
264            URL location = getURL(locationURIString);
265            return location;
266        }
267    
268        private URL getAddressLocation(Port port) throws DeploymentException {
269            SOAPAddress soapAddress = 
270                (SOAPAddress) getExtensibilityElement(SOAPAddress.class, port.getExtensibilityElements());
271            URL location = null;
272            if (soapAddress != null) {
273                String locationURIString = soapAddress.getLocationURI();
274                location = getURL(locationURIString);
275            }
276            return location;
277        }
278    
279        private URL getURL(String locationURIString) throws DeploymentException {
280            try {
281                return new URL(locationURIString);
282            } catch (MalformedURLException e) {
283                throw new DeploymentException(
284                        "Could not construct web service location URL from "
285                                + locationURIString, e);
286            }
287        }
288        
289        public static ExtensibilityElement getExtensibilityElement(Class clazz,
290                                                                   List extensibilityElements) {
291            for (Iterator iterator = extensibilityElements.iterator(); iterator
292                    .hasNext();) {
293                ExtensibilityElement extensibilityElement = (ExtensibilityElement) iterator
294                        .next();
295                if (clazz.isAssignableFrom(extensibilityElement.getClass())) {
296                    return extensibilityElement;
297                }
298            }
299            return null;
300        }
301        
302        private void verifyPortComponentList(Definition wsdl) throws DeploymentException {
303            if (this.portComponentRefMap == null) {
304                return;
305            }
306            for (Class sei : this.portComponentRefMap.keySet()) {
307                QName portType = JAXWSUtils.getPortType(sei);
308                if (portType == null) {
309                    continue;
310                }
311                if (wsdl.getPortType(portType) == null) {
312                    throw new DeploymentException("No portType found in WSDL for SEI: " + sei.getName());
313                }            
314            }        
315        }
316        
317        private boolean isMTOMEnabled(QName portType) {
318            boolean mtomEnabled = false;
319            PortComponentRefType portRef = getPortComponentRef(portType);
320            if (portRef != null && portRef.isSetEnableMtom()) {
321                mtomEnabled = portRef.getEnableMtom().getBooleanValue();
322            }
323            return mtomEnabled;
324        }
325        
326        private PortComponentRefType getPortComponentRef(QName portType) {
327            if (this.portComponentRefMap == null) {
328                return null;
329            }
330            for (Class sei : this.portComponentRefMap.keySet()) {
331                QName seiPortType = JAXWSUtils.getPortType(sei);
332                if (seiPortType == null) {
333                    continue;
334                }
335                if (portType.equals(seiPortType)) {
336                    return this.portComponentRefMap.get(sei);
337                }        
338            }
339            return null;
340        }
341        
342        private class JarWSDLLocator implements WSDLLocator {
343    
344            private final List<InputStream> streams = new ArrayList<InputStream>();
345    
346            private final URI wsdlURI;
347    
348            private URI latestImportURI;
349    
350            public JarWSDLLocator(URI wsdlURI) {
351                this.wsdlURI = wsdlURI;
352            }
353    
354            public InputSource getBaseInputSource() {
355                InputStream wsdlInputStream;
356                ZipEntry entry = moduleFile.getEntry(wsdlURI.toString());
357                if (entry == null) {
358                    throw new RuntimeException(
359                            "WSDL file does not exist in the module " + wsdlURI.toString());
360                }
361                try {
362                    wsdlInputStream = moduleFile.getInputStream(entry);
363                    streams.add(wsdlInputStream);
364                } catch (Exception e) {
365                    throw new RuntimeException(
366                            "Could not open stream to wsdl file", e);
367                }
368                return new InputSource(wsdlInputStream);
369            }
370    
371            public String getBaseURI() {
372                return wsdlURI.toString();
373            }
374    
375            public InputSource getImportInputSource(String parentLocation,
376                                                    String relativeLocation) {
377                URI parentURI = URI.create(parentLocation);
378                latestImportURI = parentURI.resolve(relativeLocation);
379                InputStream importInputStream;
380                ZipEntry entry = moduleFile.getEntry(latestImportURI.toString());
381                if (entry == null) {
382                    throw new RuntimeException(
383                            "File does not exist in the module " + latestImportURI.toString());
384                }
385                try {                
386                    importInputStream = moduleFile.getInputStream(entry);
387                    streams.add(importInputStream);
388                } catch (Exception e) {
389                    throw new RuntimeException(
390                            "Could not open stream to import file", e);
391                }
392                InputSource inputSource = new InputSource(importInputStream);
393                inputSource.setSystemId(getLatestImportURI());
394                return inputSource;
395            }
396    
397            public String getLatestImportURI() {
398                return latestImportURI.toString();
399            }
400    
401            public void close() {
402                for (InputStream inputStream : this.streams) {
403                    try {
404                        inputStream.close();
405                    } catch (IOException e) {
406                        // ignore
407                    }
408                }
409                streams.clear();
410            }
411        }
412    }