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.sql.Clob;
22  import java.sql.SQLException;
23  import java.time.OffsetDateTime;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.Objects;
27  import java.util.stream.Collectors;
28  import javax.persistence.Query;
29  import javax.persistence.TypedQuery;
30  import org.apache.commons.lang3.StringUtils;
31  import org.apache.syncope.common.lib.audit.AuditEntry;
32  import org.apache.syncope.common.lib.types.AuditElements;
33  import org.apache.syncope.core.persistence.api.dao.AuditConfDAO;
34  import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
35  import org.apache.syncope.core.persistence.api.entity.AuditConf;
36  import org.apache.syncope.core.persistence.jpa.entity.JPAAuditConf;
37  import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
38  import org.springframework.transaction.annotation.Transactional;
39  
40  public class JPAAuditConfDAO extends AbstractDAO<AuditConf> implements AuditConfDAO {
41  
42      protected static class MessageCriteriaBuilder {
43  
44          protected final StringBuilder query = new StringBuilder();
45  
46          protected String andIfNeeded() {
47              return query.length() == 0 ? " " : " AND ";
48          }
49  
50          protected int setParameter(final List<Object> parameters, final Object parameter) {
51              parameters.add(parameter);
52              return parameters.size();
53          }
54  
55          protected MessageCriteriaBuilder entityKey(final String entityKey) {
56              if (entityKey != null) {
57                  query.append(andIfNeeded()).append(AUDIT_ENTRY_MESSAGE_COLUMN).
58                          append(" LIKE '%key%").append(entityKey).append("%'");
59              }
60              return this;
61          }
62  
63          public MessageCriteriaBuilder type(final AuditElements.EventCategoryType type) {
64              if (type != null) {
65                  query.append(andIfNeeded()).append(AUDIT_ENTRY_MESSAGE_COLUMN).
66                          append(" LIKE '%\"type\":\"").append(type.name()).append("\"%'");
67              }
68              return this;
69          }
70  
71          public MessageCriteriaBuilder category(final String category) {
72              if (StringUtils.isNotBlank(category)) {
73                  query.append(andIfNeeded()).append(AUDIT_ENTRY_MESSAGE_COLUMN).
74                          append(" LIKE '%\"category\":\"").append(category).append("\"%'");
75              }
76              return this;
77          }
78  
79          public MessageCriteriaBuilder subcategory(final String subcategory) {
80              if (StringUtils.isNotBlank(subcategory)) {
81                  query.append(andIfNeeded()).append(AUDIT_ENTRY_MESSAGE_COLUMN).
82                          append(" LIKE '%\"subcategory\":\"").append(subcategory).append("\"%'");
83              }
84              return this;
85          }
86  
87          public MessageCriteriaBuilder events(final List<String> events) {
88              if (!events.isEmpty()) {
89                  query.append(andIfNeeded()).append("( ").
90                          append(events.stream().
91                                  map(event -> AUDIT_ENTRY_MESSAGE_COLUMN + " LIKE '%\"event\":\"" + event + "\"%'").
92                                  collect(Collectors.joining(" OR "))).
93                          append(" )");
94              }
95              return this;
96          }
97  
98          public MessageCriteriaBuilder result(final AuditElements.Result result) {
99              if (result != null) {
100                 query.append(andIfNeeded()).append(AUDIT_ENTRY_MESSAGE_COLUMN).
101                         append(" LIKE '%\"result\":\"").append(result.name()).append("\"%' ");
102             }
103             return this;
104         }
105 
106         public MessageCriteriaBuilder before(final OffsetDateTime before, final List<Object> parameters) {
107             if (before != null) {
108                 query.append(andIfNeeded()).append(AUDIT_ENTRY_EVENT_DATE_COLUMN).
109                         append(" <= ?").append(setParameter(parameters, before));
110             }
111             return this;
112         }
113 
114         public MessageCriteriaBuilder after(final OffsetDateTime after, final List<Object> parameters) {
115             if (after != null) {
116                 query.append(andIfNeeded()).append(AUDIT_ENTRY_EVENT_DATE_COLUMN).
117                         append(" >= ?").append(setParameter(parameters, after));
118             }
119             return this;
120         }
121 
122         public String build() {
123             return query.toString();
124         }
125     }
126 
127     @Override
128     public AuditConf find(final String key) {
129         return entityManager().find(JPAAuditConf.class, key);
130     }
131 
132     @Override
133     public List<AuditConf> findAll() {
134         TypedQuery<AuditConf> query = entityManager().createQuery(
135                 "SELECT e FROM " + JPAAuditConf.class.getSimpleName() + " e ", AuditConf.class);
136         return query.getResultList();
137     }
138 
139     @Override
140     public AuditConf save(final AuditConf auditConf) {
141         return entityManager().merge(auditConf);
142     }
143 
144     @Override
145     public void delete(final AuditConf auditConf) {
146         entityManager().remove(auditConf);
147     }
148 
149     protected MessageCriteriaBuilder messageCriteriaBuilder(final String entityKey) {
150         return new MessageCriteriaBuilder().entityKey(entityKey);
151     }
152 
153     protected void fillWithParameters(final Query query, final List<Object> parameters) {
154         for (int i = 0; i < parameters.size(); i++) {
155             if (parameters.get(i) instanceof Boolean) {
156                 query.setParameter(i + 1, ((Boolean) parameters.get(i)) ? 1 : 0);
157             } else {
158                 query.setParameter(i + 1, parameters.get(i));
159             }
160         }
161     }
162 
163     @Override
164     public int countEntries(
165             final String entityKey,
166             final AuditElements.EventCategoryType type,
167             final String category,
168             final String subcategory,
169             final List<String> events,
170             final AuditElements.Result result,
171             final OffsetDateTime before,
172             final OffsetDateTime after) {
173 
174         List<Object> parameters = new ArrayList<>();
175         String queryString = "SELECT COUNT(0)"
176                 + " FROM " + AUDIT_ENTRY_TABLE
177                 + " WHERE " + messageCriteriaBuilder(entityKey).
178                         type(type).
179                         category(category).
180                         subcategory(subcategory).
181                         result(result).
182                         events(events).
183                         before(before, parameters).
184                         after(after, parameters).
185                         build();
186         Query query = entityManager().createNativeQuery(queryString);
187         fillWithParameters(query, parameters);
188 
189         return ((Number) query.getSingleResult()).intValue();
190     }
191 
192     protected String select() {
193         return AUDIT_ENTRY_MESSAGE_COLUMN;
194     }
195 
196     @Transactional(readOnly = true)
197     @Override
198     public List<AuditEntry> searchEntries(
199             final String entityKey,
200             final int page,
201             final int itemsPerPage,
202             final AuditElements.EventCategoryType type,
203             final String category,
204             final String subcategory,
205             final List<String> events,
206             final AuditElements.Result result,
207             final OffsetDateTime before,
208             final OffsetDateTime after,
209             final List<OrderByClause> orderBy) {
210 
211         List<Object> parameters = new ArrayList<>();
212         String queryString = "SELECT " + select()
213                 + " FROM " + AUDIT_ENTRY_TABLE
214                 + " WHERE " + messageCriteriaBuilder(entityKey).
215                         type(type).
216                         category(category).
217                         subcategory(subcategory).
218                         result(result).
219                         events(events).
220                         before(before, parameters).
221                         after(after, parameters).
222                         build();
223         if (!orderBy.isEmpty()) {
224             queryString += " ORDER BY " + orderBy.stream().
225                     map(clause -> clause.getField() + ' ' + clause.getDirection().name()).
226                     collect(Collectors.joining(","));
227         }
228 
229         Query query = entityManager().createNativeQuery(queryString);
230         fillWithParameters(query, parameters);
231         query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));
232         if (itemsPerPage >= 0) {
233             query.setMaxResults(itemsPerPage);
234         }
235 
236         @SuppressWarnings("unchecked")
237         List<Object> entries = query.getResultList();
238         return entries.stream().map(row -> {
239             String value;
240             if (row instanceof Clob) {
241                 Clob clob = (Clob) row;
242                 try {
243                     value = clob.getSubString(1, (int) clob.length());
244                 } catch (SQLException e) {
245                     LOG.error("Unexpected error reading Audit Entry for entity key {}", entityKey, e);
246                     return null;
247                 }
248             } else {
249                 value = row.toString();
250             }
251             return POJOHelper.deserialize(value, AuditEntry.class);
252         }).filter(Objects::nonNull).collect(Collectors.toList());
253     }
254 }