1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 }