View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2016 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.caravan.jaxws.consumer;
21  
22  import java.io.IOException;
23  import java.security.GeneralSecurityException;
24  
25  import javax.net.ssl.KeyManagerFactory;
26  import javax.net.ssl.TrustManagerFactory;
27  import javax.xml.ws.BindingProvider;
28  
29  import org.apache.commons.lang3.StringUtils;
30  import org.apache.commons.lang3.builder.EqualsBuilder;
31  import org.apache.commons.lang3.builder.HashCodeBuilder;
32  import org.apache.cxf.configuration.jsse.TLSClientParameters;
33  import org.apache.cxf.configuration.security.ProxyAuthorizationPolicy;
34  import org.apache.cxf.endpoint.Client;
35  import org.apache.cxf.frontend.ClientFactoryBean;
36  import org.apache.cxf.frontend.ClientProxy;
37  import org.apache.cxf.interceptor.Fault;
38  import org.apache.cxf.jaxws.JaxWsClientProxy;
39  import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
40  import org.apache.cxf.message.Message;
41  import org.apache.cxf.phase.AbstractPhaseInterceptor;
42  import org.apache.cxf.phase.Phase;
43  import org.apache.cxf.transport.http.HTTPConduit;
44  import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
45  import org.apache.cxf.transports.http.configuration.ProxyServerType;
46  import org.apache.cxf.ws.addressing.AddressingProperties;
47  import org.apache.cxf.ws.addressing.AttributedURIType;
48  import org.apache.cxf.ws.addressing.JAXWSAConstants;
49  import org.apache.cxf.ws.addressing.WSAddressingFeature;
50  import org.osgi.annotation.versioning.ConsumerType;
51  
52  import io.wcm.caravan.jaxws.consumer.impl.CertificateLoader;
53  import io.wcm.caravan.jaxws.consumer.impl.OsgiAwareJaxWsClientFactoryBean;
54  
55  /**
56   * Default JAX-WS SOAP client initializer
57   */
58  @ConsumerType
59  public class JaxWsClientInitializer {
60  
61    // Use to disable jaxb default validation for CXF
62    private static final String JAXB_VALIDATION = "set-jaxb-validation-event-handler";
63    private static final String SCHEMA_VALIDATION = "schema-validation-enabled";
64  
65    /**
66     * List of properties of this class that contain sensitive information which should not be logged.
67     */
68    public static final String[] SENSITIVE_PROPERTY_NAMES = new String[] {
69        "proxyPassword",
70        "certstorePassword",
71        "trustStorePassword"
72    };
73  
74    private int connectTimeout;
75    private int socketTimeout;
76    private String httpUser;
77    private String httpPassword;
78    private String proxyHost;
79    private int proxyPort;
80    private String proxyUser;
81    private String proxyPassword;
82    private String sslContextType = CertificateLoader.SSL_CONTEXT_TYPE_DEFAULT;
83    private String keyManagerType = CertificateLoader.KEY_MANAGER_TYPE_DEFAULT;
84    private String keyStoreType = CertificateLoader.KEY_STORE_TYPE_DEFAULT;
85    private String keyStoreProvider;
86    private String keyStorePath;
87    private String keyStorePassword;
88    private String trustManagerType = CertificateLoader.TRUST_MANAGER_TYPE_DEFAULT;
89    private String trustStoreType = CertificateLoader.TRUST_STORE_TYPE_DEFAULT;
90    private String trustStoreProvider;
91    private String trustStorePath;
92    private String trustStorePassword;
93    private String wsAddressingToUri;
94    private boolean ignoreUnexpectedElements;
95    private boolean allowChunking = true;
96  
97    private transient TLSClientParameters tlsClientParameters;
98  
99    /**
100    * Initialize JAXWS proxy factory bean
101    * @param factory JAXWS Proxy factory bean
102    */
103   public void initializeFactory(JaxWsProxyFactoryBean factory) {
104 
105     // set outgoing security (username/password)
106     if (StringUtils.isNotEmpty(getHttpUser())) {
107       factory.setUsername(getHttpUser());
108       factory.setPassword(getHttpPassword());
109     }
110 
111     // enable WS-addressing
112     if (StringUtils.isNotEmpty(getWSAddressingToUri())) {
113       factory.getFeatures().add(new WSAddressingFeature());
114     }
115 
116   }
117 
118   /**
119    * Initialize SOAP client pFactory and create SOAP client object instance.
120    * @param factory JAXWS proxy pFactory bean (has to be already initialized)
121    * @return SOAP client object
122    */
123   public Object createClient(JaxWsProxyFactoryBean factory) {
124     try {
125 
126       // create port object
127       Object portObject = createPortObject(factory);
128 
129       // initialize endpiont client
130       Client endpointClient = ClientProxy.getClient(portObject);
131       initializeEndpointClient(endpointClient);
132 
133       // set http settings
134       HTTPConduit httpConduit = (HTTPConduit)endpointClient.getConduit();
135       initializeHttpConduit(httpConduit);
136 
137       // make sure request context is per thread local
138       // see http://cxf.apache.org/faq.html#FAQ-AreJAXWSclientproxiesthreadsafe%3F
139       ((BindingProvider)portObject).getRequestContext().put(JaxWsClientProxy.THREAD_LOCAL_REQUEST_CONTEXT, Boolean.TRUE);
140 
141       // ignore unexpected elements and attributes on data binding
142       if (isIgnoreUnexpectedElements()) {
143         // disable schema validation to be upward-compatible to future schema changes
144         ((BindingProvider)portObject).getRequestContext().put(JAXB_VALIDATION, false);
145         ((BindingProvider)portObject).getRequestContext().put(SCHEMA_VALIDATION, false);
146       }
147 
148       return portObject;
149     }
150     catch (Throwable ex) {
151       throw new JaxWsClientInitializeException("SOAP client initialization failed "
152           + "(" + factory.getServiceClass().getName() + ").", ex);
153     }
154   }
155 
156   /**
157    * Create port object
158    * @param factory JAXWS proxy factory bean
159    * @return Port object
160    */
161   protected Object createPortObject(JaxWsProxyFactoryBean factory) {
162     return factory.create();
163   }
164 
165   /**
166    * Initialize endpoint client
167    * @param endpointClient Endpoint client
168    */
169   protected void initializeEndpointClient(Client endpointClient) {
170 
171     // set destination address for WS-addressing
172     if (StringUtils.isNotEmpty(getWSAddressingToUri())) {
173       endpointClient.getOutInterceptors().add(new AbstractPhaseInterceptor<Message>(Phase.SETUP) {
174 
175         @Override
176         public void handleMessage(Message pMessage) throws Fault {
177           AttributedURIType uri = new AttributedURIType();
178           uri.setValue(getWSAddressingToUri());
179 
180           AddressingProperties maps = new AddressingProperties();
181           maps.setTo(uri);
182 
183           pMessage.put(JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES, maps);
184         }
185 
186       });
187     }
188 
189     // add interceptors
190     addInterceptors(endpointClient);
191 
192   }
193 
194   /**
195    * Add interceptors (e.g. for request/response logging)
196    * @param endpointClient Endpoint client
197    */
198   protected void addInterceptors(Client endpointClient) {
199     // can be overridden by subclasses
200   }
201 
202   /**
203    * Initialize HTTP conduit
204    * @param httpConduit HTTP conduit
205    * @throws GeneralSecurityException security exception
206    * @throws IOException I/O exception
207    */
208   protected void initializeHttpConduit(HTTPConduit httpConduit) throws IOException, GeneralSecurityException {
209 
210     HTTPClientPolicy clientPolicy = httpConduit.getClient();
211     clientPolicy.setAllowChunking(isAllowChunking());
212     if (getConnectTimeout() > 0) {
213       clientPolicy.setConnectionTimeout(getConnectTimeout());
214     }
215     if (getSocketTimeout() > 0) {
216       clientPolicy.setReceiveTimeout(getSocketTimeout());
217     }
218 
219     // optionally enable proxy server
220     if (StringUtils.isNotEmpty(getProxyHost()) && getProxyPort() > 0) {
221       clientPolicy.setProxyServerType(ProxyServerType.HTTP);
222       clientPolicy.setProxyServer(getProxyHost());
223       clientPolicy.setProxyServerPort(getProxyPort());
224 
225       // optionally define proxy authentication
226       if (StringUtils.isNotEmpty(getProxyUser())) {
227         ProxyAuthorizationPolicy proxyAuthentication = new ProxyAuthorizationPolicy();
228         proxyAuthentication.setUserName(getProxyUser());
229         proxyAuthentication.setPassword(getProxyPassword());
230         httpConduit.setProxyAuthorization(proxyAuthentication);
231       }
232     }
233 
234     // setup TLS - enable certificate for WS access
235     if (CertificateLoader.isSslKeyManagerEnabled(this) || CertificateLoader.isSslTrustStoreEnbaled(this)) {
236       httpConduit.setTlsClientParameters(getTLSClientParameters());
237     }
238 
239   }
240 
241   /**
242    * Get JAX-WS client factory bean instance.
243    * @return Client factory bean.
244    */
245   public ClientFactoryBean createClientFactoryBean() {
246     return new OsgiAwareJaxWsClientFactoryBean();
247   }
248 
249   /**
250    * @return Connection timeout in ms.
251    */
252   public final int getConnectTimeout() {
253     return this.connectTimeout;
254   }
255 
256   /**
257    * @param value Connection timeout in ms.
258    */
259   public final void setConnectTimeout(int value) {
260     this.connectTimeout = value;
261   }
262 
263   /**
264    * @return Response timeout in ms.
265    */
266   public final int getSocketTimeout() {
267     return this.socketTimeout;
268   }
269 
270   /**
271    * @param value Response timeout in ms.
272    */
273   public final void setSocketTimeout(int value) {
274     this.socketTimeout = value;
275   }
276 
277   /**
278    * @return Http basic authentication user.
279    */
280   public final String getHttpUser() {
281     return this.httpUser;
282   }
283 
284   /**
285    * @param value Http basic authentication user.
286    */
287   public final void setHttpUser(String value) {
288     this.httpUser = value;
289   }
290 
291   /**
292    * @return Http basic authentication password
293    */
294   public final String getHttpPassword() {
295     return this.httpPassword;
296   }
297 
298   /**
299    * @param value Http basic authentication password
300    */
301   public final void setHttpPassword(String value) {
302     this.httpPassword = value;
303   }
304 
305   /**
306    * @return Proxy host name
307    */
308   public final String getProxyHost() {
309     return this.proxyHost;
310   }
311 
312   /**
313    * @param value Proxy host name
314    */
315   public final void setProxyHost(String value) {
316     this.proxyHost = value;
317   }
318 
319   /**
320    * @return Proxy port
321    */
322   public final int getProxyPort() {
323     return this.proxyPort;
324   }
325 
326   /**
327    * @param value Proxy port
328    */
329   public final void setProxyPort(int value) {
330     this.proxyPort = value;
331   }
332 
333   /**
334    * @return Proxy user name
335    */
336   public final String getProxyUser() {
337     return this.proxyUser;
338   }
339 
340   /**
341    * @param value Proxy user name
342    */
343   public final void setProxyUser(String value) {
344     this.proxyUser = value;
345   }
346 
347   /**
348    * @return Proxy password
349    */
350   public final String getProxyPassword() {
351     return this.proxyPassword;
352   }
353 
354   /**
355    * @param value Proxy password
356    */
357   public final void setProxyPassword(String value) {
358     this.proxyPassword = value;
359   }
360 
361   /**
362    * @return SSL context type (default: TLS)
363    */
364   public final String getSslContextType() {
365     return this.sslContextType;
366   }
367 
368   /**
369    * @param value SSL context type (default: TLS)
370    */
371   public final void setSslContextType(String value) {
372     this.sslContextType = value;
373     this.tlsClientParameters = null;
374   }
375 
376   /**
377    * @return Key manager type (default: SunX509)
378    */
379   public final String getKeyManagerType() {
380     return this.keyManagerType;
381   }
382 
383   /**
384    * @param value Key manager type (default: SunX509)
385    */
386   public final void setKeyManagerType(String value) {
387     this.keyManagerType = value;
388     this.tlsClientParameters = null;
389   }
390 
391   /**
392    * @return Key store type (default: PKCS12)
393    */
394   public final String getKeyStoreType() {
395     return this.keyStoreType;
396   }
397 
398   /**
399    * @param value Key store type (default: PKCS12)
400    */
401   public final void setKeyStoreType(String value) {
402     this.keyStoreType = value;
403     this.tlsClientParameters = null;
404   }
405 
406   /**
407    * @return Key store provider (default: null = use first matching security provider)
408    */
409   public String getKeyStoreProvider() {
410     return this.keyStoreProvider;
411   }
412 
413   /**
414    * @param value Key store provider (default: null = use first matching security provider)
415    */
416   public void setKeyStoreProvider(String value) {
417     this.keyStoreProvider = value;
418     this.tlsClientParameters = null;
419   }
420 
421   /**
422    * @return Key store file path
423    */
424   public final String getKeyStorePath() {
425     return this.keyStorePath;
426   }
427 
428   /**
429    * @param value Key store file path
430    */
431   public final void setKeyStorePath(String value) {
432     this.keyStorePath = value;
433     this.tlsClientParameters = null;
434   }
435 
436   /**
437    * @return Key store password
438    */
439   public final String getKeyStorePassword() {
440     return this.keyStorePassword;
441   }
442 
443   /**
444    * @param value Key store password
445    */
446   public final void setKeyStorePassword(String value) {
447     this.keyStorePassword = value;
448     this.tlsClientParameters = null;
449   }
450 
451   /**
452    * @return Trust manager type (default: SunX509)
453    */
454   public final String getTrustManagerType() {
455     return this.trustManagerType;
456   }
457 
458   /**
459    * @param value Trust manager type (default: SunX509)
460    */
461   public final void setTrustManagerType(String value) {
462     this.trustManagerType = value;
463     this.tlsClientParameters = null;
464   }
465 
466   /**
467    * @return Trust store type (default: JKS)
468    */
469   public final String getTrustStoreType() {
470     return this.trustStoreType;
471   }
472 
473   /**
474    * @param value Trust store type (default: JKS)
475    */
476   public final void setTrustStoreType(String value) {
477     this.trustStoreType = value;
478     this.tlsClientParameters = null;
479   }
480 
481   /**
482    * @return Trust store provider (default: null = use first matching security provider)
483    */
484   public String getTrustStoreProvider() {
485     return this.trustStoreProvider;
486   }
487 
488   /**
489    * @param value Trust store provider (default: null = use first matching security provider)
490    */
491   public void setTrustStoreProvider(String value) {
492     this.trustStoreProvider = value;
493     this.tlsClientParameters = null;
494   }
495 
496   /**
497    * @return Trust store file path
498    */
499   public final String getTrustStorePath() {
500     return this.trustStorePath;
501   }
502 
503   /**
504    * @param value Trust store file path
505    */
506   public final void setTrustStorePath(String value) {
507     this.trustStorePath = value;
508     this.tlsClientParameters = null;
509   }
510 
511   /**
512    * @return Trust store password
513    */
514   public final String getTrustStorePassword() {
515     return this.trustStorePassword;
516   }
517 
518   /**
519    * @param vaule Trust store password
520    */
521   public final void setTrustStorePassword(String vaule) {
522     this.trustStorePassword = vaule;
523     this.tlsClientParameters = null;
524   }
525 
526   /**
527    * Create TLS client parameters based on given certstore path/password parameters.
528    * Caches the parameter in member variable of this factory.
529    * @return TLS client parameters
530    * @throws IOException I/O exception
531    * @throws GeneralSecurityException General security exception
532    */
533   public final TLSClientParameters getTLSClientParameters() throws IOException, GeneralSecurityException {
534     if (tlsClientParameters == null) {
535       TLSClientParameters tlsCP = new TLSClientParameters();
536 
537       // initialize certstore
538       if (CertificateLoader.isSslTrustStoreEnbaled(this)) {
539         try {
540           KeyManagerFactory keyManagerFactory = CertificateLoader.getKeyManagerFactory(this);
541           tlsCP.setKeyManagers(keyManagerFactory.getKeyManagers());
542         }
543         catch (Throwable ex) {
544           throw new RuntimeException("Unable to initialize certificate store for SOAP endpoint.\n"
545               + "Please check configuration parameters 'certstorePath' and 'certstorePassword' in this config:\n"
546               + this.toString(), ex);
547         }
548       }
549 
550       // initialize trustStore
551       if (CertificateLoader.isSslTrustStoreEnbaled(this)) {
552         try {
553           TrustManagerFactory trustManagerFactory = CertificateLoader.getTrustManagerFactory(this);
554           tlsCP.setTrustManagers(trustManagerFactory.getTrustManagers());
555         }
556         catch (Throwable ex) {
557           throw new RuntimeException("Unable to initialize trust store for SOAP endpoint.\n"
558               + "Please check configuration parameters 'trustStorePath' and 'trustStorePassword' in this config:\n"
559               + this.toString(), ex);
560         }
561       }
562 
563       tlsClientParameters = tlsCP;
564     }
565     return tlsClientParameters;
566   }
567 
568   /**
569    * @param value CXF TSL client parameters
570    */
571   public final void setTLSClientParameters(TLSClientParameters value) {
572     this.tlsClientParameters = value;
573   }
574 
575   /**
576    * @return Addressing-To URI to be sent as WS-Adressing header
577    */
578   public final String getWSAddressingToUri() {
579     return this.wsAddressingToUri;
580   }
581 
582   /**
583    * @param value Addressing-To URI to be sent as WS-Adressing header
584    */
585   public final void setWSAddressingToUri(String value) {
586     this.wsAddressingToUri = value;
587   }
588 
589   /**
590    * @return If true compatibility mode for WSDL/schema changes is activated.
591    *         If the SOAP server returns unknown XML element they are ignored during validation.
592    */
593   public final boolean isIgnoreUnexpectedElements() {
594     return this.ignoreUnexpectedElements;
595   }
596 
597   /**
598    * @param value If true compatibility mode for WSDL/schema changes is activated.
599    *          If the SOAP server returns unknown XML element they are ignored during validation.
600    */
601   public final void setIgnoreUnexpectedElements(boolean value) {
602     this.ignoreUnexpectedElements = value;
603   }
604 
605   /**
606    * @return Allow HTTP 1.1 chunking
607    */
608   public final boolean isAllowChunking() {
609     return this.allowChunking;
610   }
611 
612   /**
613    * @param value Allow HTTP 1.1 chunking
614    */
615   public final void setAllowChunking(boolean value) {
616     this.allowChunking = value;
617   }
618 
619   @Override
620   public int hashCode() {
621     return HashCodeBuilder.reflectionHashCode(this, false);
622   }
623 
624   @Override
625   public boolean equals(Object pObj) {
626     return EqualsBuilder.reflectionEquals(this, pObj, false);
627   }
628 
629 }