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.batch;
20  
21  import java.io.Serializable;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  import org.apache.cxf.helpers.CastUtils;
31  import org.apache.syncope.client.console.SyncopeConsoleSession;
32  import org.apache.syncope.client.console.pages.BasePage;
33  import org.apache.syncope.client.console.panels.MultilevelPanel;
34  import org.apache.syncope.client.console.rest.AbstractAnyRestClient;
35  import org.apache.syncope.client.console.rest.AnyObjectRestClient;
36  import org.apache.syncope.client.console.rest.GroupRestClient;
37  import org.apache.syncope.client.console.rest.UserRestClient;
38  import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BatchResponseColumn;
39  import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
40  import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
41  import org.apache.syncope.client.lib.batch.BatchRequest;
42  import org.apache.syncope.client.ui.commons.Constants;
43  import org.apache.syncope.client.ui.commons.rest.RestClient;
44  import org.apache.syncope.client.ui.commons.status.StatusBean;
45  import org.apache.syncope.common.lib.request.BooleanReplacePatchItem;
46  import org.apache.syncope.common.lib.request.StatusR;
47  import org.apache.syncope.common.lib.request.UserUR;
48  import org.apache.syncope.common.lib.to.AnyTO;
49  import org.apache.syncope.common.lib.to.ExecTO;
50  import org.apache.syncope.common.lib.to.GroupTO;
51  import org.apache.syncope.common.lib.to.ReportTO;
52  import org.apache.syncope.common.lib.to.TaskTO;
53  import org.apache.syncope.common.lib.to.UserTO;
54  import org.apache.syncope.common.lib.types.ExecStatus;
55  import org.apache.syncope.common.lib.types.ResourceAssociationAction;
56  import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
57  import org.apache.syncope.common.lib.types.StatusRType;
58  import org.apache.syncope.common.lib.types.TaskType;
59  import org.apache.syncope.common.rest.api.beans.ExecSpecs;
60  import org.apache.syncope.common.rest.api.service.AnyObjectService;
61  import org.apache.syncope.common.rest.api.service.AnyService;
62  import org.apache.syncope.common.rest.api.service.GroupService;
63  import org.apache.syncope.common.rest.api.service.ReportService;
64  import org.apache.syncope.common.rest.api.service.TaskService;
65  import org.apache.syncope.common.rest.api.service.UserService;
66  import org.apache.wicket.PageReference;
67  import org.apache.wicket.ajax.AjaxRequestTarget;
68  import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxFallbackDefaultDataTable;
69  import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
70  import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
71  import org.apache.wicket.markup.html.WebMarkupContainer;
72  import org.apache.wicket.model.CompoundPropertyModel;
73  import org.apache.wicket.model.IModel;
74  import org.slf4j.Logger;
75  import org.slf4j.LoggerFactory;
76  import org.springframework.util.CollectionUtils;
77  
78  public class BatchContent<T extends Serializable, S> extends MultilevelPanel.SecondLevel {
79  
80      private static final long serialVersionUID = 4114026480146090963L;
81  
82      private static final Logger LOG = LoggerFactory.getLogger(BatchContent.class);
83  
84      private WebMarkupContainer container;
85  
86      private ActionsPanel<Serializable> actionPanel;
87  
88      private SortableDataProvider<T, S> dataProvider;
89  
90      public BatchContent(
91              final List<T> items,
92              final List<IColumn<T, S>> columns,
93              final Collection<ActionLink.ActionType> actions,
94              final RestClient batchExecutor,
95              final String keyFieldName) {
96  
97          this(MultilevelPanel.SECOND_LEVEL_ID, items, columns, actions, batchExecutor, keyFieldName);
98      }
99  
100     public BatchContent(
101             final String id,
102             final List<T> items,
103             final List<IColumn<T, S>> columns,
104             final Collection<ActionLink.ActionType> actions,
105             final RestClient batchExecutor,
106             final String keyFieldName) {
107 
108         super(id);
109 
110         setup(items, columns);
111 
112         for (ActionLink.ActionType action : actions) {
113             actionPanel.add(new ActionLink<>() {
114 
115                 private static final long serialVersionUID = -3722207913631435501L;
116 
117                 @Override
118                 protected boolean statusCondition(final Serializable modelObject) {
119                     return !CollectionUtils.isEmpty(items);
120                 }
121 
122                 @Override
123                 @SuppressWarnings("unchecked")
124                 public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
125                     if (CollectionUtils.isEmpty(items)) {
126                         throw new IllegalArgumentException("Invalid items");
127                     }
128 
129                     Map<String, String> results;
130                     try {
131                         T singleItem = items.iterator().next();
132 
133                         if (singleItem instanceof ExecTO) {
134                             results = new HashMap<>();
135                             items.forEach(item -> {
136                                 ExecTO exec = ExecTO.class.cast(item);
137 
138                                 try {
139                                     batchExecutor.getClass().getMethod("deleteExecution", String.class).
140                                             invoke(batchExecutor, exec.getKey());
141                                     results.put(exec.getKey(), ExecStatus.SUCCESS.name());
142                                 } catch (Exception e) {
143                                     LOG.error("Error deleting execution {}", exec.getKey(), e);
144                                     results.put(exec.getKey(), ExecStatus.FAILURE.name());
145                                 }
146                             });
147                         } else if (singleItem instanceof StatusBean) {
148                             AbstractAnyRestClient<?> anyRestClient = AbstractAnyRestClient.class.cast(batchExecutor);
149 
150                             // Group bean information by anyKey
151                             Map<String, List<StatusBean>> beans = new HashMap<>();
152                             items.stream().map(StatusBean.class::cast).
153                                     forEachOrdered(sb -> {
154                                         final List<StatusBean> sblist;
155                                         if (beans.containsKey(sb.getKey())) {
156                                             sblist = beans.get(sb.getKey());
157                                         } else {
158                                             sblist = new ArrayList<>();
159                                             beans.put(sb.getKey(), sblist);
160                                         }
161                                         sblist.add(sb);
162                                     });
163 
164                             results = new HashMap<>();
165                             beans.forEach((key, value) -> {
166                                 String etag = anyRestClient.read(key).getETagValue();
167 
168                                 switch (action) {
169                                     case DEPROVISION:
170                                         results.putAll(anyRestClient.deassociate(
171                                                 ResourceDeassociationAction.DEPROVISION, etag, key, value));
172                                         break;
173 
174                                     case UNASSIGN:
175                                         results.putAll(anyRestClient.deassociate(
176                                                 ResourceDeassociationAction.UNASSIGN, etag, key, value));
177                                         break;
178 
179                                     case UNLINK:
180                                         results.putAll(anyRestClient.deassociate(
181                                                 ResourceDeassociationAction.UNLINK, etag, key, value));
182                                         break;
183 
184                                     case ASSIGN:
185                                         results.putAll(anyRestClient.associate(
186                                                 ResourceAssociationAction.ASSIGN, etag, key, value));
187                                         break;
188 
189                                     case LINK:
190                                         results.putAll(anyRestClient.associate(
191                                                 ResourceAssociationAction.LINK, etag, key, value));
192                                         break;
193 
194                                     case PROVISION:
195                                         results.putAll(anyRestClient.associate(
196                                                 ResourceAssociationAction.PROVISION, etag, key, value));
197                                         break;
198 
199                                     case SUSPEND:
200                                         results.putAll(((UserRestClient) anyRestClient).suspend(etag, key, value));
201                                         break;
202 
203                                     case REACTIVATE:
204                                         results.putAll(((UserRestClient) anyRestClient).reactivate(etag, key, value));
205                                         break;
206 
207                                     default:
208                                 }
209                             });
210                         } else {
211                             BatchRequest batch = SyncopeConsoleSession.get().batch();
212 
213                             UserService batchUserService = batch.getService(UserService.class);
214                             GroupService batchGroupService = batch.getService(GroupService.class);
215                             AnyObjectService batchAnyObjectService = batch.getService(AnyObjectService.class);
216                             AnyService<?> batchAnyService = singleItem instanceof UserTO
217                                     ? batchUserService
218                                     : singleItem instanceof GroupTO
219                                             ? batchGroupService
220                                             : batchAnyObjectService;
221                             TaskService batchTaskService = batch.getService(TaskService.class);
222                             ReportService batchReportService = batch.getService(ReportService.class);
223 
224                             Set<String> deletedAnys = new HashSet<>();
225 
226                             switch (action) {
227                                 case MUSTCHANGEPASSWORD:
228                                     items.forEach(item -> {
229                                         UserTO user = (UserTO) item;
230 
231                                         UserUR req = new UserUR();
232                                         req.setKey(user.getKey());
233                                         req.setMustChangePassword(new BooleanReplacePatchItem.Builder().
234                                                 value(!user.isMustChangePassword()).build());
235 
236                                         batchUserService.update(req);
237                                     });
238                                     break;
239 
240                                 case SUSPEND:
241                                     items.forEach(item -> {
242                                         UserTO user = (UserTO) item;
243 
244                                         StatusR req = new StatusR.Builder(user.getKey(), StatusRType.SUSPEND).
245                                                 onSyncope(true).
246                                                 resources(user.getResources()).
247                                                 build();
248 
249                                         batchUserService.status(req);
250                                     });
251                                     break;
252 
253                                 case REACTIVATE:
254                                     items.forEach(item -> {
255                                         UserTO user = (UserTO) item;
256 
257                                         StatusR req = new StatusR.Builder(user.getKey(), StatusRType.REACTIVATE).
258                                                 onSyncope(true).
259                                                 resources(user.getResources()).
260                                                 build();
261 
262                                         batchUserService.status(req);
263                                     });
264                                     break;
265 
266                                 case DELETE:
267                                     items.forEach(item -> {
268                                         if (singleItem instanceof AnyTO) {
269                                             AnyTO any = (AnyTO) item;
270 
271                                             batchAnyService.delete(any.getKey());
272                                             deletedAnys.add(any.getKey());
273                                         } else if (singleItem instanceof TaskTO) {
274                                             TaskTO task = (TaskTO) item;
275 
276                                             batchTaskService.delete(
277                                                     TaskType.fromTOClass(task.getClass()),
278                                                     task.getKey());
279                                         } else if (singleItem instanceof ReportTO) {
280                                             ReportTO report = (ReportTO) item;
281 
282                                             batchReportService.delete(report.getKey());
283                                         } else {
284                                             LOG.warn("Unsupported for DELETE: {}", singleItem.getClass().getName());
285                                         }
286                                     });
287 
288                                     break;
289 
290                                 case DRYRUN:
291                                     items.forEach(item -> {
292                                         TaskTO task = (TaskTO) item;
293 
294                                         batchTaskService.execute(
295                                                 new ExecSpecs.Builder().dryRun(true).key(task.getKey()).build());
296                                     });
297                                     break;
298 
299                                 case EXECUTE:
300                                     items.forEach(item -> {
301                                         if (singleItem instanceof TaskTO) {
302                                             TaskTO task = (TaskTO) item;
303 
304                                             batchTaskService.execute(
305                                                     new ExecSpecs.Builder().dryRun(false).key(task.getKey()).build());
306                                         } else if (singleItem instanceof ReportTO) {
307                                             ReportTO report = (ReportTO) item;
308 
309                                             batchReportService.execute(
310                                                     new ExecSpecs.Builder().key(report.getKey()).build());
311                                         }
312                                     });
313                                     break;
314 
315                                 default:
316                             }
317 
318                             results = CastUtils.cast(Map.class.cast(
319                                     batchExecutor.getClass().getMethod("batch",
320                                             BatchRequest.class).invoke(batchExecutor, batch)));
321 
322                             if (singleItem instanceof AnyTO) {
323                                 AbstractAnyRestClient<? extends AnyTO> anyRestClient = singleItem instanceof UserTO
324                                         ? UserRestClient.class.cast(batchExecutor)
325                                         : singleItem instanceof GroupTO
326                                                 ? GroupRestClient.class.cast(batchExecutor)
327                                                 : AnyObjectRestClient.class.cast(batchExecutor);
328                                 for (int i = 0; i < items.size(); i++) {
329                                     String key = ((AnyTO) items.get(i)).getKey();
330                                     if (!deletedAnys.contains(key)) {
331                                         items.set(i, (T) anyRestClient.read(key));
332                                     }
333                                 }
334                             }
335                         }
336 
337                         List<IColumn<T, S>> newColumnList = new ArrayList<>(columns);
338                         newColumnList.add(newColumnList.size(), new BatchResponseColumn<>(results, keyFieldName));
339 
340                         container.addOrReplace(new AjaxFallbackDefaultDataTable<>(
341                                 "selectedObjects",
342                                 newColumnList,
343                                 dataProvider,
344                                 Integer.MAX_VALUE).setVisible(!items.isEmpty()));
345 
346                         actionPanel.setEnabled(false);
347                         actionPanel.setVisible(false);
348                         target.add(container);
349                         target.add(actionPanel);
350 
351                         SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
352                     } catch (Exception e) {
353                         LOG.error("Batch failure", e);
354                         SyncopeConsoleSession.get().error("Operation " + action.getActionId() + " failed");
355                     }
356                     ((BasePage) getPage()).getNotificationPanel().refresh(target);
357                 }
358             }, action, null).hideLabel();
359         }
360     }
361 
362     public BatchContent(
363             final String id,
364             final List<T> items,
365             final List<IColumn<T, S>> columns,
366             final Map<String, String> results,
367             final String keyFieldName,
368             final AjaxRequestTarget target,
369             final PageReference pageRef) {
370 
371         super(id);
372 
373         List<IColumn<T, S>> newColumnList = new ArrayList<>(columns);
374         newColumnList.add(newColumnList.size(), new BatchResponseColumn<>(results, keyFieldName));
375         setup(items, newColumnList);
376 
377         actionPanel.setEnabled(false);
378         actionPanel.setVisible(false);
379         target.add(container);
380         target.add(actionPanel);
381 
382         SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
383         ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
384     }
385 
386     private void setup(
387             final List<T> items,
388             final List<IColumn<T, S>> columns) {
389 
390         container = new WebMarkupContainer("container");
391         container.setOutputMarkupId(true);
392         add(container);
393 
394         dataProvider = new SortableDataProvider<>() {
395 
396             private static final long serialVersionUID = 5291903859908641954L;
397 
398             @Override
399             public Iterator<? extends T> iterator(final long first, final long count) {
400                 return items.iterator();
401             }
402 
403             @Override
404             public long size() {
405                 return items.size();
406             }
407 
408             @Override
409             public IModel<T> model(final T object) {
410                 return new CompoundPropertyModel<>(object);
411             }
412         };
413 
414         container.add(new AjaxFallbackDefaultDataTable<>(
415                 "selectedObjects",
416                 columns,
417                 dataProvider,
418                 Integer.MAX_VALUE).setMarkupId("selectedObjects").setVisible(!CollectionUtils.isEmpty(items)));
419 
420         actionPanel = new ActionsPanel<>("actions", null);
421         container.add(actionPanel);
422     }
423 }