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.status;
20  
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.List;
24  import java.util.stream.Collectors;
25  import org.apache.commons.lang3.tuple.Pair;
26  import org.apache.syncope.client.console.commons.DirectoryDataProvider;
27  import org.apache.syncope.client.console.commons.IdMConstants;
28  import org.apache.syncope.client.console.commons.status.AbstractStatusBeanProvider;
29  import org.apache.syncope.client.console.panels.AjaxDataTablePanel;
30  import org.apache.syncope.client.console.panels.DirectoryPanel;
31  import org.apache.syncope.client.console.panels.LinkedAccountsStatusModalPanel;
32  import org.apache.syncope.client.console.panels.MultilevelPanel;
33  import org.apache.syncope.client.console.rest.AbstractAnyRestClient;
34  import org.apache.syncope.client.console.rest.AnyObjectRestClient;
35  import org.apache.syncope.client.console.rest.GroupRestClient;
36  import org.apache.syncope.client.console.rest.ResourceRestClient;
37  import org.apache.syncope.client.console.rest.UserRestClient;
38  import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
39  import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
40  import org.apache.syncope.client.ui.commons.Constants;
41  import org.apache.syncope.client.ui.commons.panels.ModalPanel;
42  import org.apache.syncope.client.ui.commons.status.Status;
43  import org.apache.syncope.client.ui.commons.status.StatusBean;
44  import org.apache.syncope.client.ui.commons.status.StatusUtils;
45  import org.apache.syncope.common.lib.to.AnyObjectTO;
46  import org.apache.syncope.common.lib.to.AnyTO;
47  import org.apache.syncope.common.lib.to.GroupTO;
48  import org.apache.syncope.common.lib.to.PullTaskTO;
49  import org.apache.syncope.common.lib.to.PushTaskTO;
50  import org.apache.syncope.common.lib.to.ReconStatus;
51  import org.apache.syncope.common.lib.to.ResourceTO;
52  import org.apache.syncope.common.lib.to.UserTO;
53  import org.apache.syncope.common.lib.types.AnyTypeKind;
54  import org.apache.syncope.common.lib.types.IdMEntitlement;
55  import org.apache.syncope.common.lib.types.IdRepoEntitlement;
56  import org.apache.wicket.PageReference;
57  import org.apache.wicket.ajax.AjaxRequestTarget;
58  import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
59  import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
60  import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
61  import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
62  import org.apache.wicket.markup.ComponentTag;
63  import org.apache.wicket.markup.html.basic.Label;
64  import org.apache.wicket.markup.repeater.Item;
65  import org.apache.wicket.model.IModel;
66  import org.apache.wicket.model.Model;
67  import org.apache.wicket.model.StringResourceModel;
68  import org.apache.wicket.spring.injection.annot.SpringBean;
69  
70  public class AnyStatusDirectoryPanel
71          extends DirectoryPanel<StatusBean, StatusBean, DirectoryDataProvider<StatusBean>, AbstractAnyRestClient<?>>
72          implements ModalPanel {
73  
74      private static final long serialVersionUID = -9148734710505211261L;
75  
76      @SpringBean
77      protected ResourceRestClient resourceRestClient;
78  
79      @SpringBean
80      protected ReconStatusUtils reconStatusUtils;
81  
82      @SpringBean
83      protected UserRestClient userRestClient;
84  
85      @SpringBean
86      protected GroupRestClient groupRestClient;
87  
88      @SpringBean
89      protected AnyObjectRestClient anyObjectRestClient;
90  
91      protected final MultilevelPanel multiLevelPanelRef;
92  
93      protected final AnyTO anyTO;
94  
95      protected final AnyTypeKind anyTypeKind;
96  
97      protected final boolean statusOnly;
98  
99      private final List<String> resources;
100 
101     public AnyStatusDirectoryPanel(
102             final MultilevelPanel multiLevelPanelRef,
103             final PageReference pageRef,
104             final AnyTO anyTO,
105             final String itemKeyFieldName,
106             final boolean statusOnly) {
107 
108         super(MultilevelPanel.FIRST_LEVEL_ID, null, pageRef);
109         this.multiLevelPanelRef = multiLevelPanelRef;
110         this.statusOnly = statusOnly;
111         this.anyTO = anyTO;
112         this.itemKeyFieldName = itemKeyFieldName;
113 
114         if (anyTO instanceof UserTO) {
115             this.restClient = userRestClient;
116             anyTypeKind = AnyTypeKind.USER;
117         } else if (anyTO instanceof GroupTO) {
118             this.restClient = groupRestClient;
119             anyTypeKind = AnyTypeKind.GROUP;
120         } else {
121             this.restClient = anyObjectRestClient;
122             anyTypeKind = AnyTypeKind.ANY_OBJECT;
123         }
124 
125         resources = resourceRestClient.list().stream().
126                 filter(resource -> resource.getProvision(anyTO.getType()).isPresent()).
127                 map(ResourceTO::getKey).collect(Collectors.toList());
128 
129         initResultTable();
130     }
131 
132     @Override
133     protected void resultTableCustomChanges(final AjaxDataTablePanel.Builder<StatusBean, String> resultTableBuilder) {
134         resultTableBuilder.setMultiLevelPanel(multiLevelPanelRef);
135     }
136 
137     @Override
138     protected List<IColumn<StatusBean, String>> getColumns() {
139         List<IColumn<StatusBean, String>> columns = new ArrayList<>();
140 
141         columns.add(new AbstractColumn<>(
142                 new StringResourceModel("resource", this), "resource") {
143 
144             private static final long serialVersionUID = 2054811145491901166L;
145 
146             @Override
147             public void populateItem(
148                     final Item<ICellPopulator<StatusBean>> cellItem,
149                     final String componentId,
150                     final IModel<StatusBean> model) {
151 
152                 cellItem.add(new Label(componentId, model.getObject().getResource()) {
153 
154                     private static final long serialVersionUID = 8432079838783825801L;
155 
156                     @Override
157                     protected void onComponentTag(final ComponentTag tag) {
158                         if (anyTO.getResources().contains(model.getObject().getResource())
159                                 || Constants.SYNCOPE.equalsIgnoreCase(model.getObject().getResource())) {
160 
161                             super.onComponentTag(tag);
162                         } else {
163                             tag.put("style", "font-style: italic");
164                         }
165                     }
166                 });
167             }
168         });
169 
170         if (statusOnly) {
171             columns.add(new PropertyColumn<>(
172                     new StringResourceModel("connObjectLink", this), "connObjectLink", "connObjectLink"));
173 
174             columns.add(new AbstractColumn<>(new StringResourceModel("status", this)) {
175 
176                 private static final long serialVersionUID = -3503023501954863131L;
177 
178                 @Override
179                 public void populateItem(
180                         final Item<ICellPopulator<StatusBean>> cellItem,
181                         final String componentId,
182                         final IModel<StatusBean> model) {
183 
184                     if (model.getObject().isLinked()) {
185                         cellItem.add(StatusUtils.getStatusImage(componentId, model.getObject().getStatus()));
186                     } else {
187                         cellItem.add(new Label(componentId, ""));
188                     }
189                 }
190             });
191         }
192 
193         return columns;
194     }
195 
196     @Override
197     public ActionsPanel<StatusBean> getActions(final IModel<StatusBean> model) {
198         final ActionsPanel<StatusBean> panel = super.getActions(model);
199 
200         if (!Constants.SYNCOPE.equalsIgnoreCase(model.getObject().getResource())) {
201             panel.add(new ActionLink<>() {
202 
203                 private static final long serialVersionUID = -7978723352517770645L;
204 
205                 @Override
206                 public void onClick(final AjaxRequestTarget target, final StatusBean bean) {
207                     multiLevelPanelRef.next(bean.getResource(),
208                             new ReconStatusPanel(bean.getResource(), anyTO.getType(), anyTO.getKey()),
209                             target);
210                     target.add(multiLevelPanelRef);
211                     AnyStatusDirectoryPanel.this.getTogglePanel().close(target);
212                 }
213             }, ActionLink.ActionType.VIEW, IdMEntitlement.RESOURCE_GET_CONNOBJECT);
214         }
215 
216         if (!statusOnly) {
217             panel.add(new ActionLink<>() {
218 
219                 private static final long serialVersionUID = -7978723352517770645L;
220 
221                 @Override
222                 public void onClick(final AjaxRequestTarget target, final StatusBean bean) {
223                     multiLevelPanelRef.next("PUSH " + bean.getResource(),
224                             new ReconTaskPanel(
225                                     bean.getResource(),
226                                     new PushTaskTO(),
227                                     anyTO.getType(),
228                                     anyTO.getKey(),
229                                     true,
230                                     multiLevelPanelRef,
231                                     pageRef),
232                             target);
233                     target.add(multiLevelPanelRef);
234                     AnyStatusDirectoryPanel.this.getTogglePanel().close(target);
235                 }
236             }, ActionLink.ActionType.RECONCILIATION_PUSH, IdRepoEntitlement.TASK_EXECUTE);
237 
238             panel.add(new ActionLink<>() {
239 
240                 private static final long serialVersionUID = -7978723352517770645L;
241 
242                 @Override
243                 public void onClick(final AjaxRequestTarget target, final StatusBean bean) {
244                     multiLevelPanelRef.next("PULL " + bean.getResource(),
245                             new ReconTaskPanel(
246                                     bean.getResource(),
247                                     new PullTaskTO(),
248                                     anyTO.getType(),
249                                     anyTO.getKey(),
250                                     true,
251                                     multiLevelPanelRef,
252                                     pageRef),
253                             target);
254                     target.add(multiLevelPanelRef);
255                     AnyStatusDirectoryPanel.this.getTogglePanel().close(target);
256                 }
257             }, ActionLink.ActionType.RECONCILIATION_PULL, IdRepoEntitlement.TASK_EXECUTE);
258         }
259 
260         if (anyTO instanceof UserTO && !UserTO.class.cast(anyTO).getLinkedAccounts().isEmpty()) {
261             UserTO userTO = UserTO.class.cast(anyTO);
262 
263             if (!userTO.getLinkedAccounts().isEmpty()
264                     && userTO.getLinkedAccounts().stream().anyMatch(linkedAccountTO -> {
265                         return linkedAccountTO.getResource().equals(model.getObject().getResource());
266                     })) {
267 
268                 panel.add(new ActionLink<>() {
269 
270                     private static final long serialVersionUID = 5168094747477174155L;
271 
272                     @Override
273                     public void onClick(final AjaxRequestTarget target, final StatusBean bean) {
274                         multiLevelPanelRef.next("ACCOUNTS",
275                                 new LinkedAccountsStatusModalPanel(Model.of(UserTO.class.cast(anyTO)), pageRef),
276                                 target);
277                         target.add(multiLevelPanelRef);
278                         AnyStatusDirectoryPanel.this.getTogglePanel().close(target);
279                     }
280                 }, ActionLink.ActionType.MANAGE_ACCOUNTS,
281                         String.format("%s,%s,%s", IdRepoEntitlement.USER_READ, IdRepoEntitlement.USER_UPDATE,
282                                 IdMEntitlement.RESOURCE_GET_CONNOBJECT));
283             }
284         }
285 
286         return panel;
287     }
288 
289     @Override
290     protected Collection<ActionLink.ActionType> getBatches() {
291         List<ActionLink.ActionType> batches = new ArrayList<>();
292         if (statusOnly) {
293             batches.add(ActionLink.ActionType.SUSPEND);
294             batches.add(ActionLink.ActionType.REACTIVATE);
295         } else {
296             batches.add(ActionLink.ActionType.UNLINK);
297             batches.add(ActionLink.ActionType.LINK);
298             batches.add(ActionLink.ActionType.DEPROVISION);
299             batches.add(ActionLink.ActionType.PROVISION);
300             batches.add(ActionLink.ActionType.ASSIGN);
301             batches.add(ActionLink.ActionType.UNASSIGN);
302         }
303         return batches;
304     }
305 
306     @Override
307     protected AnyStatusProvider dataProvider() {
308         return new AnyStatusProvider(rows);
309     }
310 
311     @Override
312     protected String paginatorRowsKey() {
313         return IdMConstants.PREF_RESOURCE_STATUS_PAGINATOR_ROWS;
314     }
315 
316     protected class AnyStatusProvider extends AbstractStatusBeanProvider {
317 
318         private static final long serialVersionUID = 4586969457669796621L;
319 
320         AnyStatusProvider(final int paginatorRows) {
321             super(paginatorRows, "resource");
322         }
323 
324         @Override
325         protected List<StatusBean> getStatusBeans(final long first, final long count) {
326             // this is required to retrieve updated data by reloading table
327             final AnyTO actual = restClient.read(anyTO.getKey());
328 
329             List<StatusBean> statusBeans = actual.getResources().stream().map(resource -> {
330                 List<Pair<String, ReconStatus>> statuses = List.of();
331                 if (statusOnly) {
332                     statuses = reconStatusUtils.getReconStatuses(anyTO.getType(), anyTO.getKey(), List.of(resource));
333                 }
334 
335                 return StatusUtils.getStatusBean(actual,
336                         resource,
337                         statuses.isEmpty() ? null : statuses.get(0).getRight().getOnResource(),
338                         actual instanceof GroupTO);
339             }).collect(Collectors.toList());
340 
341             if (statusOnly) {
342                 StatusBean syncope = new StatusBean(actual, Constants.SYNCOPE);
343                 switch (anyTypeKind) {
344                     case USER:
345                         syncope.setConnObjectLink(((UserTO) actual).getUsername());
346                         break;
347 
348                     case GROUP:
349                         syncope.setConnObjectLink(((GroupTO) actual).getName());
350                         break;
351 
352                     case ANY_OBJECT:
353                         syncope.setConnObjectLink(((AnyObjectTO) actual).getName());
354                         break;
355 
356                     default:
357                 }
358 
359                 Status syncopeStatus = Status.UNDEFINED;
360                 if (actual.getStatus() != null) {
361                     try {
362                         syncopeStatus = Status.valueOf(actual.getStatus().toUpperCase());
363                     } catch (IllegalArgumentException e) {
364                         LOG.warn("Unexpected status found: {}", actual.getStatus(), e);
365                     }
366                 }
367                 syncope.setStatus(syncopeStatus);
368 
369                 statusBeans.sort(comparator);
370                 statusBeans.add(0, syncope);
371             } else {
372                 statusBeans.addAll(resources.stream().
373                         filter(resource -> !actual.getResources().contains(resource)).
374                         map(resource -> {
375                             StatusBean statusBean = StatusUtils.getStatusBean(actual,
376                                     resource,
377                                     null,
378                                     actual instanceof GroupTO);
379                             statusBean.setLinked(false);
380                             return statusBean;
381                         }).collect(Collectors.toList()));
382 
383                 statusBeans.sort(comparator);
384             }
385 
386             return first == -1 && count == -1
387                     ? statusBeans
388                     : statusBeans.subList((int) first, (int) first + (int) count);
389         }
390     }
391 }