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.console.rest;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.nio.charset.StandardCharsets;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.List;
27  import java.util.Optional;
28  import java.util.stream.Collectors;
29  import javax.ws.rs.core.MediaType;
30  import javax.ws.rs.core.Response;
31  import org.apache.commons.io.IOUtils;
32  import org.apache.commons.lang3.tuple.Pair;
33  import org.apache.cxf.jaxrs.client.WebClient;
34  import org.apache.syncope.client.console.SyncopeConsoleSession;
35  import org.apache.syncope.client.lib.WebClientBuilder;
36  import org.apache.syncope.common.lib.SyncopeClientException;
37  import org.apache.syncope.common.lib.to.ConnIdBundle;
38  import org.apache.syncope.common.lib.to.ConnIdObjectClass;
39  import org.apache.syncope.common.lib.to.ConnInstanceTO;
40  import org.apache.syncope.common.lib.to.PlainSchemaTO;
41  import org.apache.syncope.common.lib.types.ConnConfProperty;
42  import org.apache.syncope.common.rest.api.RESTHeaders;
43  import org.apache.syncope.common.rest.api.service.ConnectorService;
44  import org.springframework.beans.BeanUtils;
45  
46  /**
47   * Console client for invoking Rest Connectors services.
48   */
49  public class ConnectorRestClient extends BaseRestClient {
50  
51      private static final long serialVersionUID = -6870366819966266617L;
52  
53      public List<ConnInstanceTO> getAllConnectors() {
54          List<ConnInstanceTO> connectors = List.of();
55          try {
56              connectors = getService(ConnectorService.class).list(SyncopeConsoleSession.get().getLocale().toString());
57          } catch (Exception e) {
58              LOG.error("While reading connectors", e);
59          }
60          return connectors;
61      }
62  
63      public ConnInstanceTO create(final ConnInstanceTO connectorTO) {
64          List<ConnConfProperty> filteredConf = filterProperties(connectorTO.getConf());
65          connectorTO.getConf().clear();
66          connectorTO.getConf().addAll(filteredConf);
67  
68          ConnectorService service = getService(ConnectorService.class);
69          Response response = service.create(connectorTO);
70  
71          return getObject(service, response.getLocation(), ConnInstanceTO.class);
72      }
73  
74      public List<String> getObjectClasses(final String connectorKey) {
75          List<String> result = new ArrayList<>();
76          try {
77              ConnectorService service = getService(ConnectorService.class);
78              ConnInstanceTO connInstance = service.read(connectorKey, SyncopeConsoleSession.get().getLocale().
79                      getLanguage());
80              if (connInstance != null) {
81                  result.addAll(service.buildObjectClassInfo(connInstance, true).stream().
82                          map(ConnIdObjectClass::getType).collect(Collectors.toList()));
83              }
84          } catch (Exception e) {
85              LOG.error("While reading object classes for connector {}", connectorKey, e);
86          }
87          return result;
88      }
89  
90      public List<String> getExtAttrNames(
91              final String adminRealm,
92              final String objectClass,
93              final String connectorKey,
94              final Collection<ConnConfProperty> conf) {
95  
96          ConnInstanceTO connInstanceTO = new ConnInstanceTO();
97          connInstanceTO.setAdminRealm(adminRealm);
98          connInstanceTO.setKey(connectorKey);
99          connInstanceTO.getConf().addAll(conf);
100 
101         // SYNCOPE-156: use provided info to give schema names (and type!) by ObjectClass
102         Optional<ConnIdObjectClass> connIdObjectClass = buildObjectClassInfo(connInstanceTO, false).stream().
103                 filter(object -> object.getType().equalsIgnoreCase(objectClass)).
104                 findAny();
105 
106         return connIdObjectClass.map(connIdObjectClassTO -> connIdObjectClassTO.getAttributes().stream().
107                 map(PlainSchemaTO::getKey).collect(Collectors.toList())).orElseGet(List::of);
108     }
109 
110     /**
111      * Load an already existent connector by its name.
112      *
113      * @param key the id
114      * @return ConnInstanceTO
115      */
116     public ConnInstanceTO read(final String key) {
117         ConnInstanceTO connectorTO = null;
118 
119         try {
120             connectorTO = getService(ConnectorService.class).
121                     read(key, SyncopeConsoleSession.get().getLocale().toString());
122         } catch (SyncopeClientException e) {
123             LOG.error("While reading a connector", e);
124         }
125 
126         return connectorTO;
127     }
128 
129     public void update(final ConnInstanceTO connectorTO) {
130         List<ConnConfProperty> filteredConf = filterProperties(connectorTO.getConf());
131         connectorTO.getConf().clear();
132         connectorTO.getConf().addAll(filteredConf);
133         getService(ConnectorService.class).update(connectorTO);
134     }
135 
136     public ConnInstanceTO delete(final String key) {
137         ConnInstanceTO connectorTO = getService(ConnectorService.class).
138                 read(key, SyncopeConsoleSession.get().getLocale().toString());
139         getService(ConnectorService.class).delete(key);
140         return connectorTO;
141     }
142 
143     public List<ConnIdBundle> getAllBundles() {
144         List<ConnIdBundle> bundles = List.of();
145 
146         try {
147             bundles = getService(ConnectorService.class).getBundles(SyncopeConsoleSession.get().getLocale().toString());
148         } catch (SyncopeClientException e) {
149             LOG.error("While getting connector bundles", e);
150         }
151 
152         return bundles;
153     }
154 
155     protected List<ConnConfProperty> filterProperties(final Collection<ConnConfProperty> properties) {
156         List<ConnConfProperty> newProperties = new ArrayList<>();
157 
158         properties.stream().map(property -> {
159             ConnConfProperty prop = new ConnConfProperty();
160             prop.setSchema(property.getSchema());
161             prop.setOverridable(property.isOverridable());
162             final List<Object> parsed = new ArrayList<>();
163             if (property.getValues() != null) {
164                 property.getValues().stream().
165                         filter(obj -> (obj != null && !obj.toString().isEmpty())).
166                         forEachOrdered(parsed::add);
167             }
168             prop.getValues().addAll(parsed);
169             return prop;
170         }).forEachOrdered(newProperties::add);
171         return newProperties;
172     }
173 
174     public boolean check(final String coreAddress, final String domain, final String jwt, final String key)
175             throws IOException {
176 
177         WebClient client = WebClientBuilder.build(coreAddress).
178                 path("connectors").
179                 accept(MediaType.APPLICATION_JSON_TYPE).
180                 type(MediaType.APPLICATION_JSON_TYPE).
181                 header(RESTHeaders.DOMAIN, domain).
182                 authorization("Bearer " + jwt);
183         Response response = client.path(key).get();
184         if (response.getStatus() == Response.Status.OK.getStatusCode()) {
185             response = client.back(false).path("check").
186                     post(IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8));
187             return response.getStatus() == Response.Status.NO_CONTENT.getStatusCode();
188         }
189         return false;
190     }
191 
192     public Pair<Boolean, String> check(final ConnInstanceTO connectorTO) {
193         ConnInstanceTO toBeChecked = new ConnInstanceTO();
194         BeanUtils.copyProperties(connectorTO, toBeChecked, new String[] { "configuration", "configurationMap" });
195         toBeChecked.getConf().addAll(filterProperties(connectorTO.getConf()));
196 
197         boolean check = false;
198         String errorMessage = null;
199         try {
200             getService(ConnectorService.class).check(toBeChecked);
201             check = true;
202         } catch (Exception e) {
203             LOG.error("While checking {}", toBeChecked, e);
204             errorMessage = e.getMessage();
205         }
206 
207         return Pair.of(check, errorMessage);
208     }
209 
210     public List<ConnIdObjectClass> buildObjectClassInfo(
211             final ConnInstanceTO connInstanceTO, final boolean includeSpecial) {
212 
213         List<ConnIdObjectClass> result = List.of();
214         try {
215             result = getService(ConnectorService.class).buildObjectClassInfo(connInstanceTO, includeSpecial);
216         } catch (Exception e) {
217             LOG.error("While getting supported object classes", e);
218         }
219 
220         return result;
221     }
222 
223     public void reload() {
224         getService(ConnectorService.class).reload();
225     }
226 }