1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.persistence.jpa.dao;
20
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.stream.Collectors;
24 import javax.persistence.NoResultException;
25 import javax.persistence.Query;
26 import javax.persistence.TypedQuery;
27 import org.apache.commons.lang3.StringUtils;
28 import org.apache.syncope.common.lib.SyncopeConstants;
29 import org.apache.syncope.core.persistence.api.dao.MalformedPathException;
30 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
31 import org.apache.syncope.core.persistence.api.dao.RoleDAO;
32 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
33 import org.apache.syncope.core.persistence.api.entity.Implementation;
34 import org.apache.syncope.core.persistence.api.entity.Realm;
35 import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
36 import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
37 import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
38 import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
39 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
40 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
41 import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
42 import org.apache.syncope.core.persistence.api.entity.policy.ProvisioningPolicy;
43 import org.apache.syncope.core.persistence.api.entity.policy.TicketExpirationPolicy;
44 import org.apache.syncope.core.persistence.jpa.entity.JPARealm;
45 import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
46 import org.apache.syncope.core.spring.security.AuthContextUtils;
47 import org.identityconnectors.framework.common.objects.SyncDeltaType;
48 import org.springframework.context.ApplicationEventPublisher;
49 import org.springframework.transaction.annotation.Transactional;
50
51 public class JPARealmDAO extends AbstractDAO<Realm> implements RealmDAO {
52
53 protected final RoleDAO roleDAO;
54
55 protected final ApplicationEventPublisher publisher;
56
57 public JPARealmDAO(final RoleDAO roleDAO, final ApplicationEventPublisher publisher) {
58 this.roleDAO = roleDAO;
59 this.publisher = publisher;
60 }
61
62 @Override
63 public Realm getRoot() {
64 TypedQuery<Realm> query = entityManager().createQuery("SELECT e FROM " + JPARealm.class.getSimpleName() + " e "
65 + "WHERE e.parent IS NULL", Realm.class);
66
67 Realm result = null;
68 try {
69 result = query.getSingleResult();
70 } catch (NoResultException e) {
71 LOG.debug("Root realm not found", e);
72 }
73
74 return result;
75 }
76
77 @Transactional(readOnly = true)
78 @Override
79 public Realm find(final String key) {
80 return entityManager().find(JPARealm.class, key);
81 }
82
83 @Transactional(readOnly = true)
84 @Override
85 public Realm findByFullPath(final String fullPath) {
86 if (SyncopeConstants.ROOT_REALM.equals(fullPath)) {
87 return getRoot();
88 }
89
90 if (StringUtils.isBlank(fullPath) || !PATH_PATTERN.matcher(fullPath).matches()) {
91 throw new MalformedPathException(fullPath);
92 }
93
94 TypedQuery<Realm> query = entityManager().createQuery("SELECT e FROM " + JPARealm.class.getSimpleName() + " e "
95 + "WHERE e.fullPath=:fullPath", Realm.class);
96 query.setParameter("fullPath", fullPath);
97
98 Realm result = null;
99 try {
100 result = query.getSingleResult();
101 } catch (NoResultException e) {
102 LOG.debug("Root realm not found", e);
103 }
104
105 return result;
106 }
107
108 @Override
109 public List<Realm> findByName(final String name) {
110 TypedQuery<Realm> query = entityManager().createQuery("SELECT e FROM " + JPARealm.class.getSimpleName() + " e "
111 + "WHERE e.name=:name", Realm.class);
112 query.setParameter("name", name);
113
114 return query.getResultList();
115 }
116
117 @Override
118 public List<Realm> findByResource(final ExternalResource resource) {
119 TypedQuery<Realm> query = entityManager().createQuery("SELECT e FROM " + JPARealm.class.getSimpleName() + " e "
120 + "WHERE :resource MEMBER OF e.resources", Realm.class);
121 query.setParameter("resource", resource);
122
123 return query.getResultList();
124 }
125
126 protected int setParameter(final List<Object> parameters, final Object parameter) {
127 parameters.add(parameter);
128 return parameters.size();
129 }
130
131 protected StringBuilder buildDescendantQuery(
132 final String base,
133 final String keyword,
134 final List<Object> parameters) {
135
136 StringBuilder queryString = new StringBuilder("SELECT e FROM ").
137 append(JPARealm.class.getSimpleName()).append(" e ").
138 append("WHERE (e.fullPath=?").
139 append(setParameter(parameters, base)).
140 append(" OR e.fullPath LIKE ?").
141 append(setParameter(parameters, SyncopeConstants.ROOT_REALM.equals(base) ? "/%" : base + "/%")).
142 append(')');
143
144 if (keyword != null) {
145 queryString.append(" AND LOWER(e.name) LIKE ?").
146 append(setParameter(parameters, "%" + keyword.replaceAll("_", "\\\\_").toLowerCase() + "%"));
147 }
148
149 return queryString;
150 }
151
152 @Override
153 public int countDescendants(final String base, final String keyword) {
154 List<Object> parameters = new ArrayList<>();
155
156 StringBuilder queryString = buildDescendantQuery(base, keyword, parameters);
157 Query query = entityManager().createQuery(StringUtils.replaceOnce(
158 queryString.toString(),
159 "SELECT e ",
160 "SELECT COUNT(e) "));
161
162 for (int i = 1; i <= parameters.size(); i++) {
163 query.setParameter(i, parameters.get(i - 1));
164 }
165
166 return ((Number) query.getSingleResult()).intValue();
167 }
168
169 @Override
170 public List<Realm> findDescendants(
171 final String base,
172 final String keyword,
173 final int page,
174 final int itemsPerPage) {
175
176 List<Object> parameters = new ArrayList<>();
177
178 StringBuilder queryString = buildDescendantQuery(base, keyword, parameters);
179 TypedQuery<Realm> query = entityManager().createQuery(
180 queryString.append(" ORDER BY e.fullPath").toString(), Realm.class);
181
182 for (int i = 1; i <= parameters.size(); i++) {
183 query.setParameter(i, parameters.get(i - 1));
184 }
185
186 query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));
187
188 if (itemsPerPage > 0) {
189 query.setMaxResults(itemsPerPage);
190 }
191
192 return query.getResultList();
193 }
194
195 @Override
196 public List<String> findDescendants(final String base, final String prefix) {
197 List<Object> parameters = new ArrayList<>();
198
199 StringBuilder queryString = buildDescendantQuery(base, null, parameters);
200 TypedQuery<Realm> query = entityManager().createQuery(queryString.
201 append(" AND (e.fullPath=?").
202 append(setParameter(parameters, prefix)).
203 append(" OR e.fullPath LIKE ?").
204 append(setParameter(parameters, SyncopeConstants.ROOT_REALM.equals(prefix) ? "/%" : prefix + "/%")).
205 append(')').
206 append(" ORDER BY e.fullPath").toString(),
207 Realm.class);
208
209 for (int i = 1; i <= parameters.size(); i++) {
210 query.setParameter(i, parameters.get(i - 1));
211 }
212
213 return query.getResultList().stream().map(Realm::getKey).collect(Collectors.toList());
214 }
215
216 protected <T extends Policy> List<Realm> findSamePolicyChildren(final Realm realm, final T policy) {
217 List<Realm> result = new ArrayList<>();
218
219 findChildren(realm).stream().
220 filter(child -> (policy instanceof AccountPolicy
221 && child.getAccountPolicy() == null || policy.equals(child.getAccountPolicy()))
222 || (policy instanceof PasswordPolicy
223 && child.getPasswordPolicy() == null || policy.equals(child.getPasswordPolicy()))).
224 forEach(child -> {
225 result.add(child);
226 result.addAll(findSamePolicyChildren(child, policy));
227 });
228
229 return result;
230 }
231
232 @Override
233 public <T extends Policy> List<Realm> findByPolicy(final T policy) {
234 if (policy instanceof PropagationPolicy || policy instanceof ProvisioningPolicy) {
235 return List.of();
236 }
237
238 String policyColumn = null;
239 if (policy instanceof AccountPolicy) {
240 policyColumn = "accountPolicy";
241 } else if (policy instanceof PasswordPolicy) {
242 policyColumn = "passwordPolicy";
243 } else if (policy instanceof AuthPolicy) {
244 policyColumn = "authPolicy";
245 } else if (policy instanceof AccessPolicy) {
246 policyColumn = "accessPolicy";
247 } else if (policy instanceof AttrReleasePolicy) {
248 policyColumn = "attrReleasePolicy";
249 } else if (policy instanceof TicketExpirationPolicy) {
250 policyColumn = "ticketExpirationPolicy";
251 }
252
253 TypedQuery<Realm> query = entityManager().createQuery(
254 "SELECT e FROM " + JPARealm.class.getSimpleName() + " e WHERE e."
255 + policyColumn + "=:policy", Realm.class);
256 query.setParameter("policy", policy);
257
258 List<Realm> result = new ArrayList<>();
259 query.getResultList().forEach(realm -> {
260 result.add(realm);
261 result.addAll(findSamePolicyChildren(realm, policy));
262 });
263
264 return result;
265 }
266
267 @Override
268 public List<Realm> findByLogicActions(final Implementation logicActions) {
269 TypedQuery<Realm> query = entityManager().createQuery(
270 "SELECT e FROM " + JPARealm.class.getSimpleName() + " e "
271 + "WHERE :logicActions MEMBER OF e.actions", Realm.class);
272 query.setParameter("logicActions", logicActions);
273
274 return query.getResultList();
275 }
276
277 protected void findAncestors(final List<Realm> result, final Realm realm) {
278 if (realm.getParent() != null && !result.contains(realm.getParent())) {
279 result.add(realm.getParent());
280 findAncestors(result, realm.getParent());
281 }
282 }
283
284 @Override
285 public List<Realm> findAncestors(final Realm realm) {
286 List<Realm> result = new ArrayList<>();
287 result.add(realm);
288 findAncestors(result, realm);
289 return result;
290 }
291
292 @Override
293 public List<Realm> findChildren(final Realm realm) {
294 TypedQuery<Realm> query = entityManager().createQuery(
295 "SELECT e FROM " + JPARealm.class.getSimpleName() + " e WHERE e.parent=:realm", Realm.class);
296 query.setParameter("realm", realm);
297
298 return query.getResultList();
299 }
300
301 protected StringBuilder buildDescendantQuery(final String base, final List<Object> parameters) {
302 return new StringBuilder("SELECT e FROM ").
303 append(JPARealm.class.getSimpleName()).append(" e ").
304 append("WHERE e.fullPath=?").
305 append(setParameter(parameters, base)).
306 append(" OR e.fullPath LIKE ?").
307 append(setParameter(parameters, SyncopeConstants.ROOT_REALM.equals(base) ? "/%" : base + "/%")).
308 append(" ORDER BY e.fullPath");
309 }
310
311 protected String buildFullPath(final Realm realm) {
312 return realm.getParent() == null
313 ? SyncopeConstants.ROOT_REALM
314 : StringUtils.appendIfMissing(realm.getParent().getFullPath(), "/") + realm.getName();
315 }
316
317 @Override
318 public int count() {
319 Query query = entityManager().createNativeQuery(
320 "SELECT COUNT(id) FROM " + JPARealm.TABLE);
321 return ((Number) query.getSingleResult()).intValue();
322 }
323
324 @Override
325 public List<String> findAllKeys(final int page, final int itemsPerPage) {
326 Query query = entityManager().createNativeQuery("SELECT id FROM " + JPARealm.TABLE + " ORDER BY fullPath");
327
328 query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));
329
330 if (itemsPerPage > 0) {
331 query.setMaxResults(itemsPerPage);
332 }
333
334 @SuppressWarnings("unchecked")
335 List<Object> raw = query.getResultList();
336 return raw.stream().map(key -> key instanceof Object[]
337 ? (String) ((Object[]) key)[0]
338 : ((String) key)).collect(Collectors.toList());
339 }
340
341 @Override
342 public Realm save(final Realm realm) {
343 String fullPathBefore = realm.getFullPath();
344 String fullPathAfter = buildFullPath(realm);
345 if (!fullPathAfter.equals(fullPathBefore)) {
346 ((JPARealm) realm).setFullPath(fullPathAfter);
347 }
348
349 Realm merged = entityManager().merge(realm);
350
351 if (!fullPathAfter.equals(fullPathBefore)) {
352 findChildren(realm).forEach(this::save);
353 }
354
355 publisher.publishEvent(
356 new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE, merged, AuthContextUtils.getDomain()));
357
358 return merged;
359 }
360
361 @Override
362 public void delete(final Realm realm) {
363 if (realm == null || realm.getParent() == null) {
364 return;
365 }
366
367 findDescendants(realm.getFullPath(), null, -1, -1).forEach(toBeDeleted -> {
368 roleDAO.findByRealm(toBeDeleted).forEach(role -> role.getRealms().remove(toBeDeleted));
369
370 toBeDeleted.setParent(null);
371
372 entityManager().remove(toBeDeleted);
373
374 publisher.publishEvent(
375 new EntityLifecycleEvent<>(this, SyncDeltaType.DELETE, toBeDeleted, AuthContextUtils.getDomain()));
376 });
377 }
378 }