View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.client.lib;
20  
21  import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
22  import com.fasterxml.jackson.jaxrs.xml.JacksonXMLProvider;
23  import com.fasterxml.jackson.jaxrs.yaml.JacksonYAMLProvider;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Optional;
27  import javax.ws.rs.core.MediaType;
28  import org.apache.commons.lang3.StringUtils;
29  import org.apache.cxf.configuration.jsse.TLSClientParameters;
30  import org.apache.cxf.ext.logging.LoggingFeature;
31  import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
32  import org.apache.cxf.transports.http.configuration.ConnectionType;
33  import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
34  import org.apache.syncope.common.lib.jackson.SyncopeJsonMapper;
35  import org.apache.syncope.common.lib.jackson.SyncopeXmlMapper;
36  import org.apache.syncope.common.lib.jackson.SyncopeYAMLMapper;
37  import org.apache.syncope.common.rest.api.DateParamConverterProvider;
38  import org.apache.syncope.common.rest.api.RESTHeaders;
39  
40  /**
41   * Factory bean for creating instances of {@link SyncopeClient}.
42   * Supports Spring-bean configuration and override via subclassing (see protected methods).
43   */
44  public class SyncopeClientFactoryBean {
45  
46      public enum ContentType {
47  
48          JSON(MediaType.APPLICATION_JSON_TYPE),
49          YAML(RESTHeaders.APPLICATION_YAML_TYPE),
50          XML(MediaType.APPLICATION_XML_TYPE);
51  
52          private final MediaType mediaType;
53  
54          ContentType(final MediaType mediaType) {
55              this.mediaType = mediaType;
56          }
57  
58          public MediaType getMediaType() {
59              return mediaType;
60          }
61  
62          public static ContentType fromString(final String value) {
63              return XML.getMediaType().toString().equalsIgnoreCase(value)
64                      ? XML
65                      : YAML.getMediaType().toString().equalsIgnoreCase(value)
66                      ? YAML
67                      : JSON;
68          }
69      }
70  
71      private JacksonJsonProvider jsonProvider;
72  
73      private JacksonXMLProvider xmlProvider;
74  
75      private JacksonYAMLProvider yamlProvider;
76  
77      private RestClientExceptionMapper exceptionMapper;
78  
79      private String address;
80  
81      private ContentType contentType;
82  
83      private String domain;
84  
85      private boolean useCompression;
86  
87      private HTTPClientPolicy httpClientPolicy;
88  
89      private TLSClientParameters tlsClientParameters;
90  
91      private JAXRSClientFactoryBean restClientFactoryBean;
92  
93      protected static JacksonJsonProvider defaultJsonProvider() {
94          return new JacksonJsonProvider(new SyncopeJsonMapper());
95      }
96  
97      protected static JacksonXMLProvider defaultXmlProvider() {
98          return new JacksonXMLProvider(new SyncopeXmlMapper());
99      }
100 
101     protected static JacksonYAMLProvider defaultYamlProvider() {
102         return new JacksonYAMLProvider(new SyncopeYAMLMapper());
103     }
104 
105     protected static RestClientExceptionMapper defaultExceptionMapper() {
106         return new RestClientExceptionMapper();
107     }
108 
109     protected static HTTPClientPolicy defaultHTTPClientPolicy() {
110         HTTPClientPolicy policy = new HTTPClientPolicy();
111         policy.setConnection(ConnectionType.CLOSE);
112         return policy;
113     }
114 
115     protected JAXRSClientFactoryBean defaultRestClientFactoryBean() {
116         JAXRSClientFactoryBean defaultRestClientFactoryBean = new JAXRSClientFactoryBean();
117         defaultRestClientFactoryBean.setHeaders(new HashMap<>());
118 
119         if (StringUtils.isBlank(address)) {
120             throw new IllegalArgumentException("Property 'address' is missing");
121         }
122         defaultRestClientFactoryBean.setAddress(address);
123 
124         if (StringUtils.isNotBlank(domain)) {
125             defaultRestClientFactoryBean.getHeaders().put(RESTHeaders.DOMAIN, List.of(domain));
126         }
127 
128         defaultRestClientFactoryBean.setThreadSafe(true);
129         defaultRestClientFactoryBean.setInheritHeaders(true);
130 
131         defaultRestClientFactoryBean.setFeatures(List.of(new LoggingFeature()));
132 
133         defaultRestClientFactoryBean.setProviders(List.of(
134                 new DateParamConverterProvider(),
135                 getJsonProvider(),
136                 getXmlProvider(),
137                 getYamlProvider(),
138                 getExceptionMapper()));
139 
140         return defaultRestClientFactoryBean;
141     }
142 
143     public JacksonJsonProvider getJsonProvider() {
144         return Optional.ofNullable(jsonProvider).orElseGet(SyncopeClientFactoryBean::defaultJsonProvider);
145     }
146 
147     public void setJsonProvider(final JacksonJsonProvider jsonProvider) {
148         this.jsonProvider = jsonProvider;
149     }
150 
151     public JacksonXMLProvider getXmlProvider() {
152         return Optional.ofNullable(xmlProvider).orElseGet(SyncopeClientFactoryBean::defaultXmlProvider);
153     }
154 
155     public void setXmlProvider(final JacksonXMLProvider xmlProvider) {
156         this.xmlProvider = xmlProvider;
157     }
158 
159     public JacksonYAMLProvider getYamlProvider() {
160         return Optional.ofNullable(yamlProvider).orElseGet(SyncopeClientFactoryBean::defaultYamlProvider);
161     }
162 
163     public void setYamlProvider(final JacksonYAMLProvider yamlProvider) {
164         this.yamlProvider = yamlProvider;
165     }
166 
167     public RestClientExceptionMapper getExceptionMapper() {
168         return Optional.ofNullable(exceptionMapper).orElseGet(SyncopeClientFactoryBean::defaultExceptionMapper);
169     }
170 
171     public SyncopeClientFactoryBean setExceptionMapper(final RestClientExceptionMapper exceptionMapper) {
172         this.exceptionMapper = exceptionMapper;
173         return this;
174     }
175 
176     public String getAddress() {
177         return address;
178     }
179 
180     public SyncopeClientFactoryBean setAddress(final String address) {
181         this.address = address;
182         return this;
183     }
184 
185     public ContentType getContentType() {
186         return Optional.ofNullable(contentType).orElse(ContentType.JSON);
187     }
188 
189     public SyncopeClientFactoryBean setContentType(final ContentType contentType) {
190         this.contentType = contentType;
191         return this;
192     }
193 
194     public SyncopeClientFactoryBean setContentType(final String contentType) {
195         this.contentType = ContentType.fromString(contentType);
196         return this;
197     }
198 
199     public String getDomain() {
200         return domain;
201     }
202 
203     public SyncopeClientFactoryBean setDomain(final String domain) {
204         this.domain = domain;
205         return this;
206     }
207 
208     /**
209      * Sets the given service instance for transparent gzip {@code Content-Encoding} handling.
210      *
211      * @param useCompression whether transparent gzip {@code Content-Encoding} handling is to be enabled
212      * @return the current instance
213      */
214     public SyncopeClientFactoryBean setUseCompression(final boolean useCompression) {
215         this.useCompression = useCompression;
216         return this;
217     }
218 
219     public boolean isUseCompression() {
220         return useCompression;
221     }
222 
223     public SyncopeClientFactoryBean setHttpClientPolicy(final HTTPClientPolicy httpClientPolicy) {
224         this.httpClientPolicy = httpClientPolicy;
225         return this;
226     }
227 
228     public HTTPClientPolicy getHttpClientPolicy() {
229         return Optional.ofNullable(httpClientPolicy).orElseGet(SyncopeClientFactoryBean::defaultHTTPClientPolicy);
230     }
231 
232     /**
233      * Sets the client TLS configuration.
234      *
235      * @param tlsClientParameters client TLS configuration
236      * @return the current instance
237      */
238     public SyncopeClientFactoryBean setTlsClientParameters(final TLSClientParameters tlsClientParameters) {
239         this.tlsClientParameters = tlsClientParameters;
240         return this;
241     }
242 
243     public TLSClientParameters getTlsClientParameters() {
244         return tlsClientParameters;
245     }
246 
247     public JAXRSClientFactoryBean getRestClientFactoryBean() {
248         return Optional.ofNullable(restClientFactoryBean).orElseGet(this::defaultRestClientFactoryBean);
249     }
250 
251     public SyncopeClientFactoryBean setRestClientFactoryBean(final JAXRSClientFactoryBean restClientFactoryBean) {
252         this.restClientFactoryBean = restClientFactoryBean;
253         return this;
254     }
255 
256     /**
257      * Builds client instance with the given credentials.
258      * Such credentials will be used only to obtain a valid JWT in the
259      * {@link javax.ws.rs.core.HttpHeaders#AUTHORIZATION} header;
260      *
261      * @param username username
262      * @param password password
263      * @return client instance with the given credentials
264      */
265     public SyncopeClient create(final String username, final String password) {
266         return create(new BasicAuthenticationHandler(username, password));
267     }
268 
269     /**
270      * Builds client instance which will be passing the provided value in the
271      * {@link javax.ws.rs.core.HttpHeaders#AUTHORIZATION} request header.
272      *
273      * @param jwt value received after login, in the {@link RESTHeaders#TOKEN} response header
274      * @return client instance which will be passing the provided value in the
275      * {@link javax.ws.rs.core.HttpHeaders#AUTHORIZATION} request header
276      */
277     public SyncopeClient create(final String jwt) {
278         return create(new JWTAuthenticationHandler(jwt));
279     }
280 
281     /**
282      * Builds client instance with the given authentication handler.
283      *
284      * @param handler authentication handler
285      * @return client instance with the given authentication handler
286      */
287     public SyncopeClient create(final AuthenticationHandler handler) {
288         return new SyncopeClient(
289                 getContentType().getMediaType(),
290                 getRestClientFactoryBean(),
291                 getExceptionMapper(),
292                 handler,
293                 useCompression,
294                 getHttpClientPolicy(),
295                 tlsClientParameters);
296     }
297 
298     /**
299      * Builds client instance with the given anonymous credentials.
300      *
301      * @param username username
302      * @param password password
303      * @return client instance with the given credentials
304      */
305     public SyncopeAnonymousClient createAnonymous(final String username, final String password) {
306         return new SyncopeAnonymousClient(
307                 getContentType().getMediaType(),
308                 getRestClientFactoryBean(),
309                 getExceptionMapper(),
310                 new AnonymousAuthenticationHandler(username, password),
311                 useCompression,
312                 getHttpClientPolicy(),
313                 tlsClientParameters);
314     }
315 }