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.core.rest.cxf.service;
20  
21  import java.io.InputStream;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Optional;
25  import java.util.Set;
26  import javax.validation.ValidationException;
27  import javax.ws.rs.core.HttpHeaders;
28  import javax.ws.rs.core.Response;
29  import javax.ws.rs.core.StreamingOutput;
30  import org.apache.commons.lang3.StringUtils;
31  import org.apache.commons.lang3.exception.ExceptionUtils;
32  import org.apache.commons.lang3.tuple.Pair;
33  import org.apache.cxf.jaxrs.ext.search.SearchBean;
34  import org.apache.cxf.jaxrs.ext.search.SearchCondition;
35  import org.apache.syncope.common.lib.SyncopeClientException;
36  import org.apache.syncope.common.lib.SyncopeConstants;
37  import org.apache.syncope.common.lib.to.ProvisioningReport;
38  import org.apache.syncope.common.lib.to.PullTaskTO;
39  import org.apache.syncope.common.lib.to.PushTaskTO;
40  import org.apache.syncope.common.lib.to.ReconStatus;
41  import org.apache.syncope.common.lib.types.ClientExceptionType;
42  import org.apache.syncope.common.rest.api.RESTHeaders;
43  import org.apache.syncope.common.rest.api.beans.AnyQuery;
44  import org.apache.syncope.common.rest.api.beans.CSVPullSpec;
45  import org.apache.syncope.common.rest.api.beans.CSVPushSpec;
46  import org.apache.syncope.common.rest.api.beans.ReconQuery;
47  import org.apache.syncope.common.rest.api.service.ReconciliationService;
48  import org.apache.syncope.core.logic.ReconciliationLogic;
49  import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
50  import org.apache.syncope.core.persistence.api.search.FilterVisitor;
51  import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
52  import org.apache.syncope.core.spring.security.AuthContextUtils;
53  import org.identityconnectors.framework.common.objects.filter.Filter;
54  import org.springframework.stereotype.Service;
55  
56  @Service
57  public class ReconciliationServiceImpl extends AbstractSearchService implements ReconciliationService {
58  
59      protected final ReconciliationLogic logic;
60  
61      public ReconciliationServiceImpl(final SearchCondVisitor searchCondVisitor, final ReconciliationLogic logic) {
62          super(searchCondVisitor);
63          this.logic = logic;
64      }
65  
66      private void validate(final ReconQuery reconQuery) {
67          if ((reconQuery.getAnyKey() == null && reconQuery.getFiql() == null)
68                  || (reconQuery.getAnyKey() != null && reconQuery.getFiql() != null)) {
69  
70              throw new ValidationException("Either provide anyKey or fiql, not both");
71          }
72      }
73  
74      private Pair<Filter, Set<String>> buildFromFIQL(final ReconQuery reconQuery) {
75          Filter filter = null;
76          Set<String> moreAttrsToGet = new HashSet<>();
77          if (reconQuery.getMoreAttrsToGet() != null) {
78              moreAttrsToGet.addAll(reconQuery.getMoreAttrsToGet());
79          }
80          if (StringUtils.isNotBlank(reconQuery.getFiql())) {
81              try {
82                  FilterVisitor visitor = new FilterVisitor();
83                  SearchCondition<SearchBean> sc = searchContext.getCondition(reconQuery.getFiql(), SearchBean.class);
84                  sc.accept(visitor);
85  
86                  filter = visitor.getQuery();
87                  moreAttrsToGet.addAll(visitor.getAttrs());
88              } catch (Exception e) {
89                  LOG.error("Invalid FIQL expression: {}", reconQuery.getFiql(), e);
90  
91                  SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidSearchParameters);
92                  sce.getElements().add(reconQuery.getFiql());
93                  sce.getElements().add(ExceptionUtils.getRootCauseMessage(e));
94                  throw sce;
95              }
96          }
97  
98          return Pair.of(filter, moreAttrsToGet);
99      }
100 
101     @Override
102     public ReconStatus status(final ReconQuery query) {
103         validate(query);
104 
105         if (query.getAnyKey() != null) {
106             return logic.status(
107                     query.getAnyTypeKey(),
108                     query.getResourceKey(),
109                     query.getAnyKey(),
110                     Optional.ofNullable(query.getMoreAttrsToGet()).orElse(Set.of()));
111         }
112 
113         Pair<Filter, Set<String>> fromFIQL = buildFromFIQL(query);
114         return logic.status(query.getAnyTypeKey(), query.getResourceKey(), fromFIQL.getLeft(), fromFIQL.getRight());
115     }
116 
117     @Override
118     public List<ProvisioningReport> push(final ReconQuery query, final PushTaskTO pushTask) {
119         validate(query);
120 
121         if (query.getAnyKey() != null) {
122             return logic.push(query.getAnyTypeKey(), query.getResourceKey(), query.getAnyKey(), pushTask);
123         }
124 
125         Pair<Filter, Set<String>> fromFIQL = buildFromFIQL(query);
126         return logic.push(
127                 query.getAnyTypeKey(), query.getResourceKey(), fromFIQL.getLeft(), fromFIQL.getRight(), pushTask);
128     }
129 
130     @Override
131     public List<ProvisioningReport> pull(final ReconQuery query, final PullTaskTO pullTask) {
132         validate(query);
133 
134         if (query.getAnyKey() != null) {
135             return logic.pull(
136                     query.getAnyTypeKey(),
137                     query.getResourceKey(),
138                     query.getAnyKey(),
139                     Optional.ofNullable(query.getMoreAttrsToGet()).orElse(Set.of()),
140                     pullTask);
141         }
142 
143         Pair<Filter, Set<String>> fromFIQL = buildFromFIQL(query);
144         return logic.pull(
145                 query.getAnyTypeKey(), query.getResourceKey(), fromFIQL.getLeft(), fromFIQL.getRight(), pullTask);
146     }
147 
148     @Override
149     public Response push(final AnyQuery query, final CSVPushSpec spec) {
150         String realm = StringUtils.prependIfMissing(query.getRealm(), SyncopeConstants.ROOT_REALM);
151 
152         SearchCond searchCond = StringUtils.isBlank(query.getFiql())
153                 ? null
154                 : getSearchCond(query.getFiql(), realm);
155 
156         StreamingOutput sout = os -> logic.push(
157                 searchCond,
158                 query.getPage(),
159                 query.getSize(),
160                 getOrderByClauses(query.getOrderBy()),
161                 realm,
162                 spec,
163                 os);
164 
165         return Response.ok(sout).
166                 type(RESTHeaders.TEXT_CSV).
167                 header(HttpHeaders.CONTENT_DISPOSITION,
168                         "attachment; filename=" + AuthContextUtils.getDomain() + ".csv").
169                 build();
170     }
171 
172     @Override
173     public List<ProvisioningReport> pull(final CSVPullSpec spec, final InputStream csv) {
174         return logic.pull(spec, csv);
175     }
176 }