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.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 }