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.servicemix.soap;
018    
019    import java.net.MalformedURLException;
020    import java.net.URI;
021    import java.net.URL;
022    import java.util.Collections;
023    import java.util.HashMap;
024    import java.util.Iterator;
025    import java.util.List;
026    import java.util.Map;
027    
028    import javax.jbi.component.ComponentContext;
029    import javax.jbi.messaging.MessageExchange;
030    import javax.jbi.messaging.MessageExchange.Role;
031    import javax.jbi.servicedesc.ServiceEndpoint;
032    import javax.wsdl.Definition;
033    import javax.wsdl.Import;
034    import javax.wsdl.Types;
035    import javax.wsdl.WSDLException;
036    import javax.wsdl.extensions.ExtensibilityElement;
037    import javax.wsdl.extensions.ExtensionRegistry;
038    import javax.wsdl.extensions.schema.Schema;
039    import javax.wsdl.extensions.schema.SchemaImport;
040    import javax.wsdl.extensions.schema.SchemaReference;
041    import javax.wsdl.factory.WSDLFactory;
042    import javax.wsdl.xml.WSDLReader;
043    import javax.wsdl.xml.WSDLWriter;
044    import javax.xml.namespace.QName;
045    
046    import org.apache.servicemix.common.JbiConstants;
047    import org.apache.servicemix.common.endpoints.AbstractEndpoint;
048    import org.apache.servicemix.common.security.AuthenticationService;
049    import org.apache.servicemix.common.security.KeystoreManager;
050    import org.apache.servicemix.common.wsdl1.JbiExtension;
051    import org.apache.servicemix.soap.handlers.addressing.AddressingHandler;
052    import org.springframework.core.io.Resource;
053    import org.springframework.core.io.UrlResource;
054    import org.w3c.dom.Document;
055    
056    import com.ibm.wsdl.Constants;
057    
058    public abstract class SoapEndpoint extends AbstractEndpoint {
059    
060        protected ServiceEndpoint activated;
061        protected SoapExchangeProcessor processor;
062        protected Role role;
063        protected URI defaultMep = JbiConstants.IN_OUT;
064        protected boolean soap;
065        protected String soapVersion;
066        protected Resource wsdlResource;
067        protected QName defaultOperation;
068        protected QName targetInterfaceName;
069        protected QName targetService;
070        protected String targetEndpoint;
071        protected List policies;
072        protected Map wsdls = new HashMap();
073        protected boolean dynamic;
074        
075        public SoapEndpoint() {
076            policies = Collections.singletonList(new AddressingHandler());
077        }
078    
079        public SoapEndpoint(boolean dynamic) {
080            this();
081            this.dynamic = dynamic;
082        }
083    
084        public AuthenticationService getAuthenticationService() {
085            return null;
086        }
087        
088        public KeystoreManager getKeystoreManager() {
089            return null;
090        }
091        
092        /**
093         * @return the policies
094         */
095        public List getPolicies() {
096            return policies;
097        }
098        /**
099         * @param policies the policies to set
100         */
101        public void setPolicies(List policies) {
102            this.policies = policies;
103        }
104        /**
105         * @return Returns the defaultMep.
106         */
107        public URI getDefaultMep() {
108            return defaultMep;
109        }
110        /**
111         * @param defaultMep The defaultMep to set.
112         */
113        public void setDefaultMep(URI defaultMep) {
114            this.defaultMep = defaultMep;
115        }
116        /**
117         * @return Returns the defaultOperation.
118         */
119        public QName getDefaultOperation() {
120            return defaultOperation;
121        }
122        /**
123         * @param defaultOperation The defaultOperation to set.
124         */
125        public void setDefaultOperation(QName defaultOperation) {
126            this.defaultOperation = defaultOperation;
127        }
128        /**
129         * @return Returns the role.
130         */
131        public Role getRole() {
132            return role;
133        }
134        /**
135         * @param role The role to set.
136         */
137        public void setRole(Role role) {
138            this.role = role;
139        }
140        /**
141         * @return Returns the soap.
142         */
143        public boolean isSoap() {
144            return soap;
145        }
146        /**
147         * @param soap The soap to set.
148         */
149        public void setSoap(boolean soap) {
150            this.soap = soap;
151        }
152        /**
153         * @return Returns the soapVersion.
154         */
155        public String getSoapVersion() {
156            return soapVersion;
157        }
158        /**
159         * @param soapVersion The soapVersion to set.
160         */
161        public void setSoapVersion(String soapVersion) {
162            this.soapVersion = soapVersion;
163        }
164        /**
165         * @return Returns the targetEndpoint.
166         */
167        public String getTargetEndpoint() {
168            return targetEndpoint;
169        }
170        /**
171         * @param targetEndpoint The targetEndpoint to set.
172         */
173        public void setTargetEndpoint(String targetEndpoint) {
174            this.targetEndpoint = targetEndpoint;
175        }
176        /**
177         * @return Returns the targetInterfaceName.
178         */
179        public QName getTargetInterfaceName() {
180            return targetInterfaceName;
181        }
182        /**
183         * @param targetInterfaceName The targetInterfaceName to set.
184         */
185        public void setTargetInterfaceName(QName targetInterfaceName) {
186            this.targetInterfaceName = targetInterfaceName;
187        }
188        /**
189         * @return Returns the targetServiceName.
190         */
191        public QName getTargetService() {
192            return targetService;
193        }
194        /**
195         * @param targetServiceName The targetServiceName to set.
196         */
197        public void setTargetService(QName targetServiceName) {
198            this.targetService = targetServiceName;
199        }
200        /**
201         * @return Returns the wsdlResource.
202         */
203        public Resource getWsdlResource() {
204            return wsdlResource;
205        }
206        /**
207         * @param wsdlResource The wsdlResource to set.
208         */
209        public void setWsdlResource(Resource wsdlResource) {
210            this.wsdlResource = wsdlResource;
211        }
212        /**
213         * @org.apache.xbean.Property alias="role"
214         * @param role
215         */
216        public void setRoleAsString(String role) {
217            if (role == null) {
218                throw new IllegalArgumentException("Role must be specified");
219            } else if (JbiExtension.ROLE_CONSUMER.equals(role)) {
220                setRole(Role.CONSUMER);
221            } else if (JbiExtension.ROLE_PROVIDER.equals(role)) {
222                setRole(Role.PROVIDER);
223            } else {
224                throw new IllegalArgumentException("Unrecognized role: " + role);
225            }
226        }
227        
228        /**
229         * In addition to setting the description, attempts to set
230         * the wsdlResource to fix a reloading issue.
231         */
232        @Override
233        public void setDescription(Document description) {
234            super.setDescription(description);
235            String uri = description.getBaseURI();
236            if (uri != null) {
237                try {
238                    URL url = new URL(uri);
239                    logger.debug("Setting wsdlResource: " + url.toExternalForm());
240                    this.setWsdlResource(new UrlResource(url));
241                } catch (MalformedURLException e) {
242                    logger.warn("Could not parse URL", e);
243                }
244            }
245        }
246    
247        /**
248         * Load the wsdl for this endpoint.
249         */
250        protected void loadWsdl() {
251            // Load WSDL from the resource
252            if (description == null && wsdlResource != null) {
253                ClassLoader cl = Thread.currentThread().getContextClassLoader();
254                try {
255                    Thread.currentThread().setContextClassLoader(serviceUnit.getConfigurationClassLoader());
256                    WSDLReader reader = WSDLFactory.newInstance().newWSDLReader(); 
257                    reader.setFeature(Constants.FEATURE_VERBOSE, false);
258                    Definition def = reader.readWSDL(wsdlResource.getURL().toString());
259                    overrideDefinition(def);
260                } catch (Exception e) {
261                    logger.warn("Could not load description from resource", e);
262                } finally {
263                    Thread.currentThread().setContextClassLoader(cl);
264                }
265            }
266            // If the endpoint is a consumer, try to find
267            // the proxied endpoint description
268            if (description == null && definition == null && getRole() == Role.CONSUMER) {
269                retrieveProxiedEndpointDefinition();
270            }
271            // If the wsdl definition is provided,
272            // convert it to a DOM document
273            if (description == null && definition != null) {
274                try {
275                    description = WSDLFactory.newInstance().newWSDLWriter().getDocument(definition);
276                } catch (Exception e) {
277                    logger.warn("Could not create document from wsdl description", e);
278                }
279            }
280            // If the dom description is provided
281            // convert it to a WSDL definition
282            if (definition == null && description != null) {
283                try {
284                    definition = WSDLFactory.newInstance().newWSDLReader().readWSDL(null, description);
285                } catch (Exception e) {
286                    logger.warn("Could not create wsdl definition from dom document", e);
287                }
288            }
289            if (definition != null) {
290                try {
291                    mapDefinition(definition);
292                } catch (Exception e) {
293                    logger.warn("Could not map wsdl definition to documents", e);
294                }
295            }
296        }
297    
298        /**
299         * Create a wsdl definition for a consumer endpoint.
300         * Loads the target endpoint definition and add http binding
301         * informations to it.
302         */
303        protected void retrieveProxiedEndpointDefinition() {
304            if (logger.isDebugEnabled()) {
305                logger.debug("Retrieving proxied endpoint definition");
306            }
307            try {
308                ComponentContext ctx = this.serviceUnit.getComponent().getComponentContext();
309                ServiceEndpoint ep = null;
310                if (targetService != null && targetEndpoint != null) {
311                    ep = ctx.getEndpoint(targetService, targetEndpoint);
312                    if (ep == null && logger.isDebugEnabled()) {
313                        logger.debug("Could not retrieve endpoint targetService/targetEndpoint");
314                    }
315                }
316                if (ep == null && targetService != null) {
317                    ServiceEndpoint[] eps = ctx.getEndpointsForService(targetService);
318                    if (eps != null && eps.length > 0) {
319                        ep = eps[0];
320                    }
321                    if (ep == null && logger.isDebugEnabled()) {
322                        logger.debug("Could not retrieve endpoint for targetService");
323                    }
324                }
325                if (ep == null && targetInterfaceName != null) {
326                    ServiceEndpoint[] eps = ctx.getEndpoints(targetInterfaceName);
327                    if (eps != null && eps.length > 0) {
328                        ep = eps[0];
329                    }
330                    if (ep == null && logger.isDebugEnabled()) {
331                        logger.debug("Could not retrieve endpoint for targetInterfaceName");
332                    }
333                }
334                if (ep == null && service != null && endpoint != null) {
335                    ep = ctx.getEndpoint(service, endpoint);
336                    if (ep == null && logger.isDebugEnabled()) {
337                        logger.debug("Could not retrieve endpoint for service/endpoint");
338                    }
339                }
340                if (ep != null) {
341                    Document doc = ctx.getEndpointDescriptor(ep);
342                    if (doc != null) {
343                        WSDLReader reader = WSDLFactory.newInstance().newWSDLReader(); 
344                        reader.setFeature(Constants.FEATURE_VERBOSE, false);
345                        Definition def = reader.readWSDL(null, doc);
346                        if (def != null) {
347                            overrideDefinition(def);
348                        }
349                    }
350                }
351            } catch (Exception e) {
352                logger.debug("Unable to retrieve target endpoint descriptor", e);
353            }
354        }
355        
356        /* (non-Javadoc)
357         * @see org.servicemix.common.Endpoint#activate()
358         */
359        public void activate() throws Exception {
360            if (dynamic) {
361                if (getRole() == Role.PROVIDER) {
362                    processor = createProviderProcessor();
363                } else {
364                    processor = createConsumerProcessor();
365                }
366            } else {
367                ComponentContext ctx = this.serviceUnit.getComponent().getComponentContext();
368                loadWsdl();
369                if (getRole() == Role.PROVIDER) {
370                    activated = ctx.activateEndpoint(service, endpoint);
371                    processor = createProviderProcessor();
372                } else {
373                    activated = createExternalEndpoint();
374                    ctx.registerExternalEndpoint(activated);
375                    processor = createConsumerProcessor();
376                }
377            }
378            try {
379                processor.init();
380            } catch (Exception e) {
381                if (!dynamic && getRole() != Role.PROVIDER) {
382                    //ensure deregister external endpoint if endpoint init failed so that
383                    //it won't block the redeployment with correct endpoint
384                    ComponentContext ctx = this.serviceUnit.getComponent().getComponentContext();
385                    ctx.deregisterExternalEndpoint(activated);
386                }
387                throw e;
388            }
389        }
390        
391        public void start() throws Exception {
392            processor.start();
393        }
394    
395        public void stop() throws Exception {
396            processor.stop();
397        }
398    
399        public void process(MessageExchange exchange) throws Exception {
400            processor.process(exchange);
401        }
402    
403        /* (non-Javadoc)
404         * @see org.servicemix.common.Endpoint#deactivate()
405         */
406        public void deactivate() throws Exception {
407            processor.shutdown();
408            if (activated != null) {
409                ComponentContext ctx = this.serviceUnit.getComponent().getComponentContext();
410                if (getRole() == Role.PROVIDER) {
411                    ServiceEndpoint ep = activated;
412                    activated = null;
413                    ctx.deactivateEndpoint(ep);
414                } else {
415                    ServiceEndpoint ep = activated;
416                    activated = null;
417                    ctx.deregisterExternalEndpoint(ep);
418                }
419            }
420        }
421    
422        protected abstract void overrideDefinition(Definition def) throws Exception;
423        
424        protected abstract SoapExchangeProcessor createProviderProcessor();
425        
426        protected abstract SoapExchangeProcessor createConsumerProcessor();
427        
428        protected abstract ServiceEndpoint createExternalEndpoint();
429    
430        protected WSDLReader createWsdlReader() throws WSDLException {
431            WSDLFactory factory = WSDLFactory.newInstance();
432            ExtensionRegistry registry = factory.newPopulatedExtensionRegistry();
433            registerExtensions(registry);
434            WSDLReader reader = factory.newWSDLReader();
435            reader.setFeature(Constants.FEATURE_VERBOSE, false);
436            reader.setExtensionRegistry(registry);
437            return reader;
438        }
439        
440        protected WSDLWriter createWsdlWriter() throws WSDLException {
441            WSDLFactory factory = WSDLFactory.newInstance();
442            ExtensionRegistry registry = factory.newPopulatedExtensionRegistry();
443            registerExtensions(registry);
444            WSDLWriter writer = factory.newWSDLWriter();
445            //writer.setExtensionRegistry(registry);
446            return writer;
447        }
448        
449        protected void registerExtensions(ExtensionRegistry registry) {
450            JbiExtension.register(registry);
451        }
452    
453        
454        protected void mapDefinition(Definition def) throws WSDLException {
455            wsdls.put("main.wsdl", createWsdlWriter().getDocument(def));
456            mapImports(def, "");
457        }
458    
459        protected void mapImports(Definition def, String contextPath) throws WSDLException {
460            // Add other imports to mapping
461            Map imports = def.getImports();
462            for (Iterator iter = imports.values().iterator(); iter.hasNext();) {
463                List imps = (List) iter.next();
464                for (Iterator iterator = imps.iterator(); iterator.hasNext();) {
465                    Import imp = (Import) iterator.next();
466                    Definition impDef = imp.getDefinition();
467                    String impLoc = imp.getLocationURI();
468                    if (impDef != null && impLoc != null && !URI.create(impLoc).isAbsolute()) {
469                        impLoc = resolveRelativeURI(contextPath, impLoc);
470                        wsdls.put(impLoc, createWsdlWriter().getDocument(impDef));
471                        mapImports(impDef, getURIParent(impLoc));
472                    }
473                }
474            }
475            // Add schemas to mapping
476            Types types = def.getTypes();
477            if (types != null) {
478                for (Iterator it = types.getExtensibilityElements().iterator(); it.hasNext();) {
479                    ExtensibilityElement ee = (ExtensibilityElement) it.next();
480                    if (ee instanceof Schema) {
481                        Schema schema = (Schema) ee;
482                        mapSchema(schema, "");
483                    }
484                }
485            }
486        }
487    
488        private void mapSchema(Schema schema, String contextPath) {
489            Map schemaImports = schema.getImports();
490            for (Iterator iter = schemaImports.values().iterator(); iter.hasNext();) {
491                List imps = (List) iter.next();
492                for (Iterator iterator = imps.iterator(); iterator.hasNext();) {
493                    SchemaImport schemaImport = (SchemaImport) iterator.next();
494                    Schema schemaImp = schemaImport.getReferencedSchema();
495                    String schemaLoc = schemaImport.getSchemaLocationURI();
496                    if (schemaLoc != null && schemaImp != null && schemaImp.getElement() != null && !URI.create(schemaLoc).isAbsolute()) {
497                        schemaLoc = resolveRelativeURI(contextPath, schemaLoc);
498                        wsdls.put(schemaLoc, schemaImp.getElement());
499                        // recursively map imported schemas
500                        mapSchema(schemaImp, getURIParent(schemaLoc));
501                    }
502                }
503            }
504            List schemaIncludes = schema.getIncludes();
505            for (Iterator iter = schemaIncludes.iterator(); iter.hasNext();) {
506                SchemaReference schemaInclude = (SchemaReference) iter.next();
507                Schema schemaImp = schemaInclude.getReferencedSchema();
508                String schemaLoc = schemaInclude.getSchemaLocationURI();
509                if (schemaLoc != null && schemaImp != null && schemaImp.getElement() != null && !URI.create(schemaLoc).isAbsolute()) {
510                    schemaLoc = resolveRelativeURI(contextPath, schemaLoc);
511                    wsdls.put(schemaLoc, schemaImp.getElement());
512                    // recursively map included schemas
513                    mapSchema(schemaImp, getURIParent(schemaLoc));
514                }
515            }
516        }
517        
518        /**
519         * Combines a relative path with a current directory, normalising any
520         * relative pathnames like "." and "..".
521         * <p>
522         * Example:
523         * <table>
524         * <tr><th>context</th><th>path</th><th>resolveRelativeURI(context, path)</th></tr>
525         * <tr><td>addressModification</td><td>../common/DataType.xsd</td><td>common/DataType.xsd</td></tr>
526         * </table>
527         *
528         * @param context The current directory.
529         * @param path The relative path to resolve against the current directory.
530         * @return the normalised path.
531         */
532        private static String resolveRelativeURI(String context, String path) {
533            if (context.length() > 0) {
534                return URI.create(context + "/" + path).normalize().getPath();
535            } else {
536                return path;
537            }
538        }
539    
540        /**
541         * Removes the filename part of a URI path.
542         *
543         * @param path A URI path part.
544         * @return The URI path part with the filename part removed.
545         */
546        private static String getURIParent(String path) {
547            return URI.create(path + "/..").normalize().getPath();
548        }
549    
550        /**
551         * @return Returns the wsdls.
552         */
553        public Map getWsdls() {
554            return wsdls;
555        }
556        
557    }