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.client.console.notifications;
20  
21  import java.io.Serializable;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.Optional;
26  import java.util.stream.Collectors;
27  import org.apache.commons.lang3.tuple.Pair;
28  import org.apache.syncope.client.console.SyncopeConsoleSession;
29  import org.apache.syncope.client.console.events.EventCategoryPanel;
30  import org.apache.syncope.client.console.panels.search.AbstractSearchPanel;
31  import org.apache.syncope.client.console.panels.search.AnyObjectSearchPanel;
32  import org.apache.syncope.client.console.panels.search.GroupSearchPanel;
33  import org.apache.syncope.client.console.panels.search.SearchClause;
34  import org.apache.syncope.client.console.panels.search.UserSearchPanel;
35  import org.apache.syncope.client.console.rest.AnyTypeRestClient;
36  import org.apache.syncope.client.console.rest.ImplementationRestClient;
37  import org.apache.syncope.client.console.rest.NotificationRestClient;
38  import org.apache.syncope.client.console.rest.SchemaRestClient;
39  import org.apache.syncope.client.console.wicket.markup.html.form.MultiFieldPanel;
40  import org.apache.syncope.client.console.wicket.markup.html.form.MultiPanel;
41  import org.apache.syncope.client.console.wizards.BaseAjaxWizardBuilder;
42  import org.apache.syncope.client.ui.commons.Constants;
43  import org.apache.syncope.client.ui.commons.markup.html.form.AjaxCheckBoxPanel;
44  import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
45  import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
46  import org.apache.syncope.common.lib.SyncopeClientException;
47  import org.apache.syncope.common.lib.audit.EventCategory;
48  import org.apache.syncope.common.lib.to.AnyTypeTO;
49  import org.apache.syncope.common.lib.to.DerSchemaTO;
50  import org.apache.syncope.common.lib.to.ImplementationTO;
51  import org.apache.syncope.common.lib.to.MailTemplateTO;
52  import org.apache.syncope.common.lib.to.NotificationTO;
53  import org.apache.syncope.common.lib.to.PlainSchemaTO;
54  import org.apache.syncope.common.lib.to.VirSchemaTO;
55  import org.apache.syncope.common.lib.types.AnyTypeKind;
56  import org.apache.syncope.common.lib.types.IdRepoImplementationType;
57  import org.apache.syncope.common.lib.types.SchemaType;
58  import org.apache.syncope.common.lib.types.TraceLevel;
59  import org.apache.wicket.PageReference;
60  import org.apache.wicket.ajax.AjaxRequestTarget;
61  import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
62  import org.apache.wicket.extensions.wizard.WizardModel;
63  import org.apache.wicket.extensions.wizard.WizardStep;
64  import org.apache.wicket.markup.html.WebMarkupContainer;
65  import org.apache.wicket.markup.html.list.ListItem;
66  import org.apache.wicket.markup.html.panel.Panel;
67  import org.apache.wicket.model.IModel;
68  import org.apache.wicket.model.LoadableDetachableModel;
69  import org.apache.wicket.model.Model;
70  import org.apache.wicket.model.PropertyModel;
71  import org.apache.wicket.model.ResourceModel;
72  import org.apache.wicket.model.util.ListModel;
73  import org.apache.wicket.validation.validator.EmailAddressValidator;
74  
75  public class NotificationWizardBuilder extends BaseAjaxWizardBuilder<NotificationWrapper> {
76  
77      private static final long serialVersionUID = -1975312550059578553L;
78  
79      protected final NotificationRestClient notificationRestClient;
80  
81      protected final AnyTypeRestClient anyTypeRestClient;
82  
83      protected final ImplementationRestClient implementationRestClient;
84  
85      protected final SchemaRestClient schemaRestClient;
86  
87      protected final IModel<List<EventCategory>> eventCategories;
88  
89      public NotificationWizardBuilder(
90              final NotificationTO notificationTO,
91              final NotificationRestClient notificationRestClient,
92              final AnyTypeRestClient anyTypeRestClient,
93              final ImplementationRestClient implementationRestClient,
94              final SchemaRestClient schemaRestClient,
95              final IModel<List<EventCategory>> eventCategories,
96              final PageReference pageRef) {
97  
98          super(new NotificationWrapper(notificationTO), pageRef);
99  
100         this.notificationRestClient = notificationRestClient;
101         this.anyTypeRestClient = anyTypeRestClient;
102         this.implementationRestClient = implementationRestClient;
103         this.schemaRestClient = schemaRestClient;
104         this.eventCategories = eventCategories;
105     }
106 
107     @Override
108     protected Serializable onApplyInternal(final NotificationWrapper modelObject) {
109         modelObject.fillRecipientConditions();
110         modelObject.fillAboutConditions();
111 
112         final boolean createFlag = modelObject.getInnerObject().getKey() == null;
113         if (createFlag) {
114             notificationRestClient.create(modelObject.getInnerObject());
115         } else {
116             notificationRestClient.update(modelObject.getInnerObject());
117         }
118 
119         return null;
120     }
121 
122     @Override
123     protected WizardModel buildModelSteps(final NotificationWrapper modelObject, final WizardModel wizardModel) {
124         wizardModel.add(new Details(modelObject));
125         wizardModel.add(new Recipients(modelObject));
126         wizardModel.add(new Events(modelObject));
127         wizardModel.add(new Abouts(modelObject));
128         return wizardModel;
129     }
130 
131     public class Details extends WizardStep {
132 
133         private static final long serialVersionUID = -7709805590497687958L;
134 
135         public Details(final NotificationWrapper modelObject) {
136             NotificationTO notificationTO = modelObject.getInnerObject();
137             boolean createFlag = notificationTO.getKey() == null;
138 
139             AjaxTextFieldPanel sender = new AjaxTextFieldPanel("sender", getString("sender"),
140                     new PropertyModel<>(notificationTO, "sender"));
141             sender.addRequiredLabel();
142             sender.addValidator(EmailAddressValidator.getInstance());
143             add(sender);
144 
145             AjaxTextFieldPanel subject = new AjaxTextFieldPanel("subject", getString("subject"),
146                     new PropertyModel<>(notificationTO, "subject"));
147             subject.addRequiredLabel();
148             add(subject);
149 
150             AjaxDropDownChoicePanel<String> template = new AjaxDropDownChoicePanel<>(
151                     "template", getString("template"),
152                     new PropertyModel<>(notificationTO, "template"));
153             template.setChoices(notificationRestClient.listTemplates().stream().
154                     map(MailTemplateTO::getKey).collect(Collectors.toList()));
155 
156             template.addRequiredLabel();
157             add(template);
158 
159             AjaxDropDownChoicePanel<TraceLevel> traceLevel = new AjaxDropDownChoicePanel<>(
160                     "traceLevel", getString("traceLevel"),
161                     new PropertyModel<>(notificationTO, "traceLevel"));
162             traceLevel.setChoices(List.of(TraceLevel.values()));
163             traceLevel.addRequiredLabel();
164             add(traceLevel);
165 
166             final AjaxCheckBoxPanel isActive = new AjaxCheckBoxPanel("isActive",
167                     getString("isActive"), new PropertyModel<>(notificationTO, "active"));
168             if (createFlag) {
169                 isActive.getField().setDefaultModelObject(Boolean.TRUE);
170             }
171             add(isActive);
172         }
173 
174     }
175 
176     public class Events extends WizardStep {
177 
178         private static final long serialVersionUID = -7709805590497687958L;
179 
180         public Events(final NotificationWrapper modelObject) {
181             setTitleModel(new ResourceModel("events"));
182 
183             add(new EventCategoryPanel(
184                     "eventSelection",
185                     eventCategories.getObject(),
186                     new PropertyModel<>(modelObject.getInnerObject(), "events")) {
187 
188                 private static final long serialVersionUID = 6429053774964787735L;
189 
190                 @Override
191                 protected List<String> getListAuthRoles() {
192                     return List.of();
193                 }
194 
195                 @Override
196                 protected List<String> getChangeAuthRoles() {
197                     return List.of();
198                 }
199             });
200         }
201     }
202 
203     public class About extends Panel {
204 
205         private static final long serialVersionUID = -9149543787708482882L;
206 
207         public About(final String id, final IModel<Pair<String, List<SearchClause>>> model) {
208             super(id, model);
209             setOutputMarkupId(true);
210 
211             AjaxDropDownChoicePanel<String> type =
212                     new AjaxDropDownChoicePanel<>("about", "anyType", new Model<>() {
213 
214                         private static final long serialVersionUID = -2350296434572623272L;
215 
216                         @Override
217                         public String getObject() {
218                             return model.getObject().getLeft();
219                         }
220 
221                         @Override
222                         public void setObject(final String object) {
223                             model.setObject(Pair.of(object, model.getObject().getRight()));
224                         }
225 
226                     });
227             type.setChoices(anyTypeRestClient.list());
228             type.addRequiredLabel();
229             add(type);
230 
231             ListModel<SearchClause> clauseModel = new ListModel<>() {
232 
233                 private static final long serialVersionUID = 3769540249683319782L;
234 
235                 @Override
236                 public List<SearchClause> getObject() {
237                     return model.getObject().getRight();
238                 }
239 
240                 @Override
241                 public void setObject(final List<SearchClause> object) {
242                     model.getObject().setValue(object);
243                 }
244             };
245 
246             WebMarkupContainer searchContainer = new WebMarkupContainer("search");
247             add(searchContainer.setOutputMarkupId(true));
248 
249             searchContainer.add(getClauseBuilder(model.getObject().getLeft(), clauseModel).build("clauses"));
250 
251             type.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
252 
253                 private static final long serialVersionUID = -1107858522700306810L;
254 
255                 @Override
256                 protected void onUpdate(final AjaxRequestTarget target) {
257                     clauseModel.getObject().clear();
258                     searchContainer.addOrReplace(getClauseBuilder(type.getModelObject(), clauseModel).
259                             build("clauses").setRenderBodyOnly(true));
260                     target.add(searchContainer);
261                 }
262             });
263         }
264 
265         private AbstractSearchPanel.Builder<?> getClauseBuilder(
266                 final String type, final ListModel<SearchClause> clauseModel) {
267 
268             AbstractSearchPanel.Builder<?> clause;
269 
270             switch (type) {
271                 case "USER":
272                     clause = new UserSearchPanel.Builder(clauseModel, pageRef);
273                     break;
274 
275                 case "GROUP":
276                     clause = new GroupSearchPanel.Builder(clauseModel, pageRef);
277                     break;
278 
279                 default:
280                     clause = new AnyObjectSearchPanel.Builder(type, clauseModel, pageRef);
281             }
282 
283             return clause;
284         }
285     }
286 
287     public class Abouts extends WizardStep {
288 
289         private static final long serialVersionUID = -7709805590497687958L;
290 
291         public Abouts(final NotificationWrapper modelObject) {
292             setTitleModel(new ResourceModel("about"));
293 
294             WebMarkupContainer aboutContainer = new WebMarkupContainer("about");
295             aboutContainer.setOutputMarkupId(true);
296             add(aboutContainer);
297 
298             IModel<List<Pair<String, List<SearchClause>>>> model = new PropertyModel<>(modelObject, "aboutClauses");
299 
300             aboutContainer.add(new MultiPanel<>("abouts", "abouts", model) {
301 
302                 private static final long serialVersionUID = -2481579077338205547L;
303 
304                 @Override
305                 protected Pair<String, List<SearchClause>> newModelObject() {
306                     return Pair.<String, List<SearchClause>>of(AnyTypeKind.USER.name(), new ArrayList<>());
307                 }
308 
309                 @Override
310                 protected About getItemPanel(final ListItem<Pair<String, List<SearchClause>>> item) {
311 
312                     return new About("panel", new Model<>() {
313 
314                         private static final long serialVersionUID = 6799404673615637845L;
315 
316                         @Override
317                         public Pair<String, List<SearchClause>> getObject() {
318                             return item.getModelObject();
319                         }
320 
321                         @Override
322                         public void setObject(final Pair<String, List<SearchClause>> object) {
323                             item.setModelObject(object);
324                         }
325 
326                         @Override
327                         public void detach() {
328                             // no detach
329                         }
330                     });
331                 }
332 
333                 @Override
334                 protected void sendError(final String message) {
335                     SyncopeConsoleSession.get().error(getString(Constants.OPERATION_ERROR));
336                 }
337             }.hideLabel());
338         }
339     }
340 
341     public class Recipients extends WizardStep {
342 
343         private static final long serialVersionUID = -7709805590497687958L;
344 
345         private final IModel<List<String>> recipientProviders = new LoadableDetachableModel<>() {
346 
347             private static final long serialVersionUID = 5275935387613157447L;
348 
349             @Override
350             protected List<String> load() {
351                 return implementationRestClient.list(IdRepoImplementationType.RECIPIENTS_PROVIDER).stream().
352                         map(ImplementationTO::getKey).sorted().collect(Collectors.toList());
353             }
354         };
355 
356         public Recipients(final NotificationWrapper modelObject) {
357             setTitleModel(new ResourceModel("recipients"));
358 
359             NotificationTO notificationTO = modelObject.getInnerObject();
360 
361             AjaxTextFieldPanel recipientAttrName = new AjaxTextFieldPanel(
362                     "recipientAttrName", new ResourceModel("recipientAttrName", "recipientAttrName").getObject(),
363                     new PropertyModel<>(notificationTO, "recipientAttrName"));
364             recipientAttrName.setChoices(getSchemas());
365             recipientAttrName.addRequiredLabel();
366             recipientAttrName.setTitle(getString("intAttrNameInfo.help")
367                     + "<code>groups[groupName].attribute</code>, "
368                     + "<code>users[userName].attribute</code>, "
369                     + "<code>anyObjects[anyObjectName].attribute</code>, "
370                     + "<code>relationships[relationshipType][anyType].attribute</code> or "
371                     + "<code>memberships[groupName].attribute</code> or "
372                     + "<code>privileges[applicationKey]</code>", true);
373             add(recipientAttrName);
374 
375             AjaxTextFieldPanel staticRecipientsFieldPanel =
376                     new AjaxTextFieldPanel("panel", "staticRecipients", new Model<>());
377             staticRecipientsFieldPanel.addValidator(EmailAddressValidator.getInstance());
378             add(new MultiFieldPanel.Builder<>(
379                     new PropertyModel<List<String>>(notificationTO, "staticRecipients")).
380                     build("staticRecipients", "staticRecipients", staticRecipientsFieldPanel).hideLabel());
381 
382             add(new UserSearchPanel.Builder(
383                     new PropertyModel<>(modelObject, "recipientClauses"), pageRef).
384                     required(false).build("recipients"));
385 
386             AjaxDropDownChoicePanel<String> recipientsProvider = new AjaxDropDownChoicePanel<>(
387                     "recipientsProvider", "recipientsProvider",
388                     new PropertyModel<>(notificationTO, "recipientsProvider"), false);
389             recipientsProvider.setChoices(recipientProviders.getObject());
390             add(recipientsProvider);
391 
392             AjaxCheckBoxPanel selfAsRecipient = new AjaxCheckBoxPanel("selfAsRecipient",
393                     getString("selfAsRecipient"), new PropertyModel<>(notificationTO, "selfAsRecipient"));
394             if (notificationTO.getKey() == null) {
395                 selfAsRecipient.getField().setDefaultModelObject(Boolean.FALSE);
396             }
397             add(selfAsRecipient);
398         }
399     }
400 
401     protected List<String> getSchemas() {
402         AnyTypeTO type = null;
403         try {
404             type = anyTypeRestClient.read(AnyTypeKind.USER.name());
405         } catch (SyncopeClientException e) {
406             LOG.error("While reading all any types", e);
407         }
408 
409         String[] anyTypeClasses = Optional.ofNullable(type).
410                 map(anyTypeTO -> anyTypeTO.getClasses().toArray(String[]::new)).orElseGet(() -> new String[0]);
411 
412         List<String> result = new ArrayList<>();
413         result.add(Constants.USERNAME_FIELD_NAME);
414 
415         result.addAll(schemaRestClient.<PlainSchemaTO>getSchemas(SchemaType.PLAIN, null, anyTypeClasses).
416                 stream().map(PlainSchemaTO::getKey).collect(Collectors.toList()));
417         result.addAll(schemaRestClient.<DerSchemaTO>getSchemas(SchemaType.DERIVED, null, anyTypeClasses).
418                 stream().map(DerSchemaTO::getKey).collect(Collectors.toList()));
419         result.addAll(schemaRestClient.<VirSchemaTO>getSchemas(SchemaType.VIRTUAL, null, anyTypeClasses).
420                 stream().map(VirSchemaTO::getKey).collect(Collectors.toList()));
421 
422         Collections.sort(result);
423         return result;
424     }
425 }