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 was not explicitly specified            
104                if (javax.xml.ws.Service.class.equals(this.serviceClass)) {
105                    // Generic Service class specified. 
106                    // Service API requires a service qname so create a dummy one
107                    this.serviceQName = new QName("http://noservice", "noservice");
108                    return;
109                } else {
110                    // Generated Service class specified.
111                    // Get the wsdl and service qname from the WebServiceClient annotation 
112                    // of the generated Service class
113                    WebServiceClient webServiceClient = 
114                        (WebServiceClient) this.serviceClass.getAnnotation(WebServiceClient.class);
115                    if (webServiceClient != null) {
116                        this.wsdlURI = getWSDLLocation(webServiceClient);
117                        this.serviceQName = getServiceQName(webServiceClient);
118                    }
119    
120                    // wsdl really shouldn't be null at this point
121                    if (this.wsdlURI == null) {
122                        return;
123                    }
124                }
125            }
126            
127            JarWSDLLocator wsdlLocator = null;
128            URL wsdlURL = null;
129            try {
130                wsdlURL = new URL(this.wsdlURI.toString());
131            } catch (MalformedURLException e1) {
132                // not a URL, assume it's a local reference
133                wsdlLocator = new JarWSDLLocator(this.wsdlURI);
134            }
135    
136            Definition definition;
137            WSDLFactory wsdlFactory;
138            try {
139                wsdlFactory = WSDLFactory.newInstance();
140            } catch (WSDLException e) {
141                throw new DeploymentException("Could not create WSDLFactory", e);
142            }
143            WSDLReader wsdlReader = wsdlFactory.newWSDLReader();
144            wsdlReader.setFeature("javax.wsdl.importDocuments", true);
145            wsdlReader.setFeature("javax.wsdl.verbose", false);
146            try {
147                if (wsdlURL != null) {
148                    definition = wsdlReader.readWSDL(wsdlURL.toString());
149                } else if (wsdlLocator != null) {
150                    definition = wsdlReader.readWSDL(wsdlLocator);
151                } else {
152                    throw new DeploymentException("unknown");
153                }
154            } catch (WSDLException e) {
155                throw new DeploymentException("Failed to read wsdl document", e);
156            } catch (RuntimeException e) {
157                throw new DeploymentException(e.getMessage(), e);
158            }
159    
160            verifyPortComponentList(definition);
161            
162            Map services = definition.getServices();
163            if (services.size() == 0) {
164                // partial wsdl, return as is
165    
166                if (this.serviceRefType != null && this.serviceRefType.isSetServiceCompletion()) {
167                    LOG.warn("Service completion is not supported with partial wsdl");
168                }
169            } else {
170                // full wsdl
171    
172                if (this.serviceRefType != null && this.serviceRefType.isSetServiceCompletion()) {
173                    throw new DeploymentException("Full wsdl, but service completion supplied");
174                }
175                
176                Service service = null;
177                if (this.serviceQName != null) {
178                    service = definition.getService(this.serviceQName);
179                    if (service == null) {
180                        throw new DeploymentException(
181                                "No service wsdl for supplied service qname "
182                                        + this.serviceQName);
183                    }
184                } else if (services.size() == 1) {
185                    service = (Service) services.values().iterator().next();
186                    this.serviceQName = service.getQName();
187                } else {
188                    throw new DeploymentException(
189                            "No service qname supplied, and there are "
190                                    + services.size() + " services");
191                }
192    
193                // organize the extra port info
194                Map<String, GerPortType> portMap = new HashMap<String, GerPortType>();
195                if (serviceRefType != null) {
196                    GerPortType[] ports = serviceRefType.getPortArray();
197                    for (int i = 0; i < ports.length; i++) {
198                        GerPortType port = ports[i];
199                        String portName = port.getPortName().trim();
200                        portMap.put(portName, port);
201                    }
202                }
203    
204                Map wsdlPortMap = service.getPorts();
205                for (Iterator iterator = wsdlPortMap.entrySet().iterator(); iterator.hasNext();) {
206                    Map.Entry entry = (Map.Entry) iterator.next();
207                    String portName = (String) entry.getKey();
208                    Port port = (Port) entry.getValue();
209    
210                    GerPortType gerPort = portMap.get(portName);
211    
212                    URL location = (gerPort == null) ? getAddressLocation(port) : getLocation(gerPort);
213                    // skip non-soap ports
214                    if (location == null) {
215                        continue;
216                    }
217                    String credentialsName = (gerPort == null) ? null : getCredentialsName(gerPort);
218                    
219                    Binding binding = port.getBinding();
220                    if (binding == null) {
221                        throw new DeploymentException("No binding for port: " + portName);
222                    }
223                    
224                    PortType portType = binding.getPortType();
225                    if (portType == null) {
226                        throw new DeploymentException("No portType for binding: " + binding.getQName());
227                    }
228    
229                    boolean mtomEnabled = isMTOMEnabled(portType.getQName());
230                    
231                    EndpointInfo info = new EndpointInfo(location, credentialsName, mtomEnabled);
232                    this.portInfoMap.put(portName, info);
233                    // prefer first binding listed in wsdl
234                    if (!this.portInfoMap.containsKey(portType.getQName())) {
235                        this.portInfoMap.put(portType.getQName(), info);
236                    }
237                }
238            }
239        }
240    
241        private QName getServiceQName(WebServiceClient webServiceClient) {
242            if (webServiceClient.targetNamespace() != null && webServiceClient.name() != null) {
243                return new QName(webServiceClient.targetNamespace(), webServiceClient.name());
244            } else {
245                return null;
246            }
247        }
248        
249        private URI getWSDLLocation(WebServiceClient webServiceClient) throws DeploymentException {
250            String wsdlLocation = webServiceClient.wsdlLocation();
251            if (wsdlLocation != null && wsdlLocation.trim().length() > 0) {            
252                try {
253                    return new URI(wsdlLocation.trim());
254                } catch (URISyntaxException e) {
255                    throw new DeploymentException(
256                            "Invalid wsdl location in annotation: " + wsdlLocation, e);
257                }
258            }
259    
260            return null;
261        }
262    
263        private String getCredentialsName(GerPortType port) {
264            String credentialsName = port.getCredentialsName();
265            return (credentialsName == null) ? null : credentialsName.trim();        
266        }
267        
268        private URL getLocation(GerPortType port) throws DeploymentException {
269            String protocol = port.getProtocol().trim();
270            String host = port.getHost().trim();
271            int portNum = port.getPort();
272            String uri = port.getUri().trim();
273            String locationURIString = protocol + "://" + host + ":" + portNum + uri;
274            URL location = getURL(locationURIString);
275            return location;
276        }
277    
278        private URL getAddressLocation(Port port) throws DeploymentException {
279            SOAPAddress soapAddress = 
280                (SOAPAddress) getExtensibilityElement(SOAPAddress.class, port.getExtensibilityElements());
281            URL location = null;
282            if (soapAddress != null) {
283                String locationURIString = soapAddress.getLocationURI();
284                location = getURL(locationURIString);
285            }
286            return location;
287        }
288    
289        private URL getURL(String locationURIString) throws DeploymentException {
290            try {
291                return new URL(locationURIString);
292            } catch (MalformedURLException e) {
293                throw new DeploymentException(
294                        "Could not construct web service location URL from "
295                                + locationURIString, e);
296            }
297        }
298        
299        public static ExtensibilityElement getExtensibilityElement(Class clazz,
300                                                                   List extensibilityElements) {
301            for (Iterator iterator = extensibilityElements.iterator(); iterator
302                    .hasNext();) {
303                ExtensibilityElement extensibilityElement = (ExtensibilityElement) iterator
304                        .next();
305                if (clazz.isAssignableFrom(extensibilityElement.getClass())) {
306                    return extensibilityElement;
307                }
308            }
309            return null;
310        }
311        
312        private void verifyPortComponentList(Definition wsdl) throws DeploymentException {
313            if (this.portComponentRefMap == null) {
314                return;
315            }
316            for (Class sei : this.portComponentRefMap.keySet()) {
317                QName portType = JAXWSUtils.getPortType(sei);
318                if (portType == null) {
319                    continue;
320                }
321                if (wsdl.getPortType(portType) == null) {
322                    throw new DeploymentException("No portType found in WSDL for SEI: " + sei.getName());
323                }            
324            }        
325        }
326        
327        private boolean isMTOMEnabled(QName portType) {
328            boolean mtomEnabled = false;
329            PortComponentRefType portRef = getPortComponentRef(portType);
330            if (portRef != null && portRef.isSetEnableMtom()) {
331                mtomEnabled = portRef.getEnableMtom().getBooleanValue();
332            }
333            return mtomEnabled;
334        }
335        
336        private PortComponentRefType getPortComponentRef(QName portType) {
337            if (this.portComponentRefMap == null) {
338                return null;
339            }
340            for (Class sei : this.portComponentRefMap.keySet()) {
341                QName seiPortType = JAXWSUtils.getPortType(sei);
342                if (seiPortType == null) {
343                    continue;
344                }
345                if (portType.equals(seiPortType)) {
346                    return this.portComponentRefMap.get(sei);
347                }        
348            }
349            return null;
350        }
351        
352        private class JarWSDLLocator implements WSDLLocator {
353    
354            private final List<InputStream> streams = new ArrayList<InputStream>();
355    
356            private final URI wsdlURI;
357    
358            private URI latestImportURI;
359    
360            public JarWSDLLocator(URI wsdlURI) {
361                this.wsdlURI = wsdlURI;
362            }
363    
364            public InputSource getBaseInputSource() {
365                InputStream wsdlInputStream;
366                ZipEntry entry = moduleFile.getEntry(wsdlURI.toString());
367                if (entry == null) {
368                    throw new RuntimeException(
369                            "WSDL file does not exist in the module " + wsdlURI.toString());
370                }
371                try {
372                    wsdlInputStream = moduleFile.getInputStream(entry);
373                    streams.add(wsdlInputStream);
374                } catch (Exception e) {
375                    throw new RuntimeException(
376                            "Could not open stream to wsdl file", e);
377                }
378                return new InputSource(wsdlInputStream);
379            }
380    
381            public String getBaseURI() {
382                return wsdlURI.toString();
383            }
384    
385            public InputSource getImportInputSource(String parentLocation,
386                                                    String relativeLocation) {
387                URI parentURI = URI.create(parentLocation);
388                latestImportURI = parentURI.resolve(relativeLocation);
389                InputStream importInputStream;
390                ZipEntry entry = moduleFile.getEntry(latestImportURI.toString());
391                if (entry == null) {
392                    throw new RuntimeException(
393                            "File does not exist in the module " + latestImportURI.toString());
394                }
395                try {                
396                    importInputStream = moduleFile.getInputStream(entry);
397                    streams.add(importInputStream);
398                } catch (Exception e) {
399                    throw new RuntimeException(
400                            "Could not open stream to import file", e);
401                }
402                InputSource inputSource = new InputSource(importInputStream);
403                inputSource.setSystemId(getLatestImportURI());
404                return inputSource;
405            }
406    
407            public String getLatestImportURI() {
408                return latestImportURI.toString();
409            }
410    
411            public void close() {
412                for (InputStream inputStream : this.streams) {
413                    try {
414                        inputStream.close();
415                    } catch (IOException e) {
416                        // ignore
417                    }
418                }
419                streams.clear();
420            }
421        }
422    }