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.panels;
20  
21  import com.fasterxml.jackson.databind.json.JsonMapper;
22  import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
23  import java.io.Serializable;
24  import java.util.Collection;
25  import java.util.List;
26  import java.util.Optional;
27  import org.apache.syncope.client.console.PreferenceManager;
28  import org.apache.syncope.client.console.commons.DirectoryDataProvider;
29  import org.apache.syncope.client.console.pages.BasePage;
30  import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
31  import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
32  import org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksTogglePanel;
33  import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
34  import org.apache.syncope.client.console.wizards.WizardMgtPanel;
35  import org.apache.syncope.client.ui.commons.Constants;
36  import org.apache.syncope.client.ui.commons.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior;
37  import org.apache.syncope.client.ui.commons.panels.WizardModalPanel;
38  import org.apache.syncope.client.ui.commons.rest.RestClient;
39  import org.apache.syncope.common.lib.types.IdRepoEntitlement;
40  import org.apache.wicket.PageReference;
41  import org.apache.wicket.ajax.AjaxRequestTarget;
42  import org.apache.wicket.event.Broadcast;
43  import org.apache.wicket.event.IEvent;
44  import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
45  import org.apache.wicket.markup.html.WebMarkupContainer;
46  import org.apache.wicket.markup.html.form.DropDownChoice;
47  import org.apache.wicket.markup.html.form.Form;
48  import org.apache.wicket.model.IModel;
49  import org.apache.wicket.model.Model;
50  import org.apache.wicket.model.PropertyModel;
51  import org.slf4j.Logger;
52  import org.slf4j.LoggerFactory;
53  
54  public abstract class DirectoryPanel<
55          T extends Serializable, W extends Serializable, DP extends DirectoryDataProvider<T>, E extends RestClient>
56          extends WizardMgtPanel<W> {
57  
58      private static final long serialVersionUID = -9170191461250434024L;
59  
60      protected static final Logger LOG = LoggerFactory.getLogger(DirectoryPanel.class);
61  
62      protected static final JsonMapper MAPPER = JsonMapper.builder().findAndAddModules().build();
63  
64      protected E restClient;
65  
66      /**
67       * Number of rows per page.
68       */
69      protected Integer rows;
70  
71      /**
72       * Container used to refresh table.
73       */
74      protected final WebMarkupContainer container;
75  
76      /**
77       * Specify if results are about a filtered search or not. Using this attribute it is possible to use this panel to
78       * show results about entity list and search.
79       */
80      protected final boolean filtered;
81  
82      private boolean checkBoxEnabled;
83  
84      private boolean showPaginator;
85  
86      /**
87       * Result table.
88       */
89      protected AjaxDataTablePanel<T, String> resultTable;
90  
91      /**
92       * Data provider used to search for entities.
93       */
94      protected DP dataProvider;
95  
96      /**
97       * Owner page.
98       */
99      protected final BasePage page;
100 
101     protected String itemKeyFieldName = Constants.KEY_FIELD_NAME;
102 
103     protected final BaseModal<W> altDefaultModal = new BaseModal<>(Constants.OUTER);
104 
105     protected final BaseModal<W> displayAttributeModal = new BaseModal<>(Constants.OUTER);
106 
107     protected final ActionLinksTogglePanel<T> actionTogglePanel;
108 
109     /**
110      * Create simple unfiltered search result panel.
111      * Use the available builder for powerful configuration options.
112      *
113      * @param id panel id.
114      * @param restClient REST client
115      * @param pageRef page reference.
116      */
117     public DirectoryPanel(final String id, final E restClient, final PageReference pageRef) {
118         this(id, restClient, pageRef, true);
119     }
120 
121     public DirectoryPanel(
122             final String id,
123             final E restClient,
124             final PageReference pageRef,
125             final boolean wizardInModal) {
126 
127         this(id, restClient, pageRef, true, wizardInModal);
128     }
129 
130     public DirectoryPanel(
131             final String id,
132             final E restClient,
133             final PageReference pageRef,
134             final boolean showPaginator,
135             final boolean wizardInModal) {
136 
137         this(id, new Builder<T, W, E>(restClient, pageRef) {
138 
139             private static final long serialVersionUID = -8424727765826509309L;
140 
141             @Override
142             protected WizardMgtPanel<W> newInstance(final String id, final boolean wizardInModal) {
143                 throw new UnsupportedOperationException("Not supported yet.");
144             }
145         }.setFiltered(false), wizardInModal);
146         setPageRef(pageRef);
147         this.showPaginator = showPaginator;
148     }
149 
150     protected DirectoryPanel(final String id, final Builder<T, W, E> builder) {
151         this(id, builder, true);
152     }
153 
154     protected DirectoryPanel(final String id, final Builder<T, W, E> builder, final boolean wizardInModal) {
155         super(id, wizardInModal);
156         setOutputMarkupId(true);
157 
158         actionTogglePanel = actionTogglePanel();
159         addOuterObject(actionTogglePanel);
160 
161         addOuterObject(altDefaultModal);
162         addOuterObject(displayAttributeModal);
163 
164         setPageRef(builder.getPageRef());
165         this.page = (BasePage) builder.getPageRef().getPage();
166 
167         this.filtered = builder.filtered;
168         this.checkBoxEnabled = builder.checkBoxEnabled;
169         this.showPaginator = builder.showPaginator;
170 
171         this.restClient = builder.restClient;
172 
173         // Container for entity search result
174         container = new WebMarkupContainer("searchContainer");
175         container.setOutputMarkupId(true);
176         addInnerObject(container);
177 
178         rows = PreferenceManager.getPaginatorRows(paginatorRowsKey());
179 
180         modal.setWindowClosedCallback(target -> {
181             if (actionTogglePanel.isVisibleInHierarchy() && modal.getContent() instanceof WizardModalPanel) {
182                 actionTogglePanel.updateHeader(target, WizardModalPanel.class.cast(modal.getContent()).getItem());
183             }
184             modal.show(false);
185         });
186 
187         setWindowClosedReloadCallback(altDefaultModal);
188         altDefaultModal.size(Modal.Size.Default);
189 
190         displayAttributeModal.setWindowClosedCallback(target -> {
191             EventDataWrapper data = new EventDataWrapper();
192             data.setTarget(target);
193             data.setRows(rows);
194 
195             send(DirectoryPanel.this, Broadcast.EXACT, data);
196 
197             displayAttributeModal.size(Modal.Size.Default);
198             modal.show(false);
199         });
200         displayAttributeModal.size(Modal.Size.Default);
201         displayAttributeModal.addSubmitButton();
202     }
203 
204     protected abstract DP dataProvider();
205 
206     protected abstract String paginatorRowsKey();
207 
208     protected abstract List<IColumn<T, String>> getColumns();
209 
210     protected void initResultTable() {
211         // ---------------------------
212         // Result table initialization
213         // ---------------------------
214         updateResultTable(false);
215         // ---------------------------
216 
217         // ---------------------------
218         // Rows-per-page selector
219         // ---------------------------
220         Form<?> paginatorForm = new Form<>("paginator");
221         paginatorForm.setOutputMarkupPlaceholderTag(true);
222         paginatorForm.setVisible(showPaginator);
223         container.add(paginatorForm);
224 
225         DropDownChoice<Integer> rowsChooser = new DropDownChoice<>(
226                 "rowsChooser", new PropertyModel<>(this, "rows"), PreferenceManager.getPaginatorChoices());
227         rowsChooser.add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
228 
229             private static final long serialVersionUID = -1107858522700306810L;
230 
231             @Override
232             protected void onUpdate(final AjaxRequestTarget target) {
233                 PreferenceManager.set(paginatorRowsKey(), String.valueOf(rows));
234 
235                 EventDataWrapper data = new EventDataWrapper();
236                 data.setTarget(target);
237                 data.setRows(rows);
238 
239                 send(getParent(), Broadcast.BREADTH, data);
240             }
241         });
242         paginatorForm.add(rowsChooser);
243         // ---------------------------
244 
245         // ---------------------------
246         // Table handling
247         // ---------------------------
248         container.add(getHeader("tablehandling"));
249         // ---------------------------
250     }
251 
252     protected ActionsPanel<Serializable> getHeader(final String componentId) {
253         final ActionsPanel<Serializable> panel = new ActionsPanel<>(componentId, null);
254 
255         panel.add(new ActionLink<>() {
256 
257             private static final long serialVersionUID = -7978723352517770644L;
258 
259             @Override
260             public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
261                 if (target != null) {
262                     target.add(container);
263                 }
264             }
265         }, ActionLink.ActionType.RELOAD, IdRepoEntitlement.USER_SEARCH).hideLabel();
266         return panel;
267     }
268 
269     public void search(final AjaxRequestTarget target) {
270         target.add(container);
271     }
272 
273     public void updateResultTable(final AjaxRequestTarget target) {
274         updateResultTable(false);
275         if (container.isVisibleInHierarchy()) {
276             target.add(container);
277         }
278     }
279 
280     protected void updateResultTable(final boolean create) {
281         updateResultTable(create, rows);
282     }
283 
284     protected void updateResultTable(final boolean create, final int rows) {
285         if (dataProvider == null) {
286             dataProvider = dataProvider();
287         }
288         dataProvider.setPaginatorRows(rows);
289 
290         int currentPage = Optional.ofNullable(resultTable).
291                 map(table -> (create ? (int) table.getPageCount() - 1 : (int) table.getCurrentPage())).orElse(0);
292 
293         // take care of restClient handle: maybe not useful to keep into
294         AjaxDataTablePanel.Builder<T, String> resultTableBuilder = new AjaxDataTablePanel.Builder<>(
295                 dataProvider, page.getPageReference()) {
296 
297             private static final long serialVersionUID = 2205322679547329123L;
298 
299             @Override
300             protected ActionsPanel<T> getActions(final IModel<T> model) {
301                 return DirectoryPanel.this.getActions(model);
302             }
303 
304             @Override
305             protected ActionLinksTogglePanel<T> getTogglePanel() {
306                 return DirectoryPanel.this.getTogglePanel();
307             }
308 
309         }.setColumns(getColumns()).
310                 setRowsPerPage(rows).
311                 setBatches(getBatches(), restClient, itemKeyFieldName).
312                 setContainer(container);
313 
314         if (!checkBoxEnabled) {
315             resultTableBuilder.disableCheckBoxes();
316         }
317 
318         resultTableCustomChanges(resultTableBuilder);
319         resultTable = resultTableBuilder.build("resultTable");
320 
321         resultTable.setCurrentPage(currentPage);
322         resultTable.setOutputMarkupId(true);
323         container.addOrReplace(resultTable);
324     }
325 
326     /**
327      * Called before build. Override it to customize result table.
328      *
329      * @param resultTableBuilder result table builder.
330      */
331     protected void resultTableCustomChanges(final AjaxDataTablePanel.Builder<T, String> resultTableBuilder) {
332     }
333 
334     public DirectoryPanel<T, W, DP, E> disableCheckBoxes() {
335         checkBoxEnabled = false;
336         return this;
337     }
338 
339     @Override
340     public void onEvent(final IEvent<?> event) {
341         if (event.getPayload() instanceof EventDataWrapper) {
342             EventDataWrapper data = (EventDataWrapper) event.getPayload();
343 
344             if (data.getRows() < 1) {
345                 updateResultTable(data.isCreate());
346             } else {
347                 updateResultTable(data.isCreate(), data.getRows());
348             }
349 
350             if (container.isVisibleInHierarchy()) {
351                 data.getTarget().add(container);
352             }
353         }
354         super.onEvent(event);
355     }
356 
357     @Override
358     protected void customActionOnFinishCallback(final AjaxRequestTarget target) {
359         EventDataWrapper data = new EventDataWrapper();
360         data.setTarget(target);
361         data.setRows(rows);
362         send(getParent(), Broadcast.BREADTH, data);
363     }
364 
365     protected ActionsPanel<T> getActions(final IModel<T> model) {
366         return new ActionsPanel<>("actions", model == null ? new Model<>() : model);
367     }
368 
369     protected ActionLinksTogglePanel<T> actionTogglePanel() {
370         return new ActionLinksTogglePanel<>(Constants.OUTER, pageRef);
371     }
372 
373     protected ActionLinksTogglePanel<T> getTogglePanel() {
374         return actionTogglePanel;
375     }
376 
377     public static class EventDataWrapper {
378 
379         private AjaxRequestTarget target;
380 
381         private boolean create;
382 
383         private int rows;
384 
385         public AjaxRequestTarget getTarget() {
386             return target;
387         }
388 
389         public void setTarget(final AjaxRequestTarget target) {
390             this.target = target;
391         }
392 
393         public boolean isCreate() {
394             return create;
395         }
396 
397         public void setCreate(final boolean create) {
398             this.create = create;
399         }
400 
401         public int getRows() {
402             return rows;
403         }
404 
405         public void setRows(final int rows) {
406             this.rows = rows;
407         }
408     }
409 
410     protected abstract Collection<ActionLink.ActionType> getBatches();
411 
412     public abstract static class Builder<T extends Serializable, W extends Serializable, E extends RestClient>
413             extends WizardMgtPanel.Builder<W> {
414 
415         private static final long serialVersionUID = 5088962796986706805L;
416 
417         /**
418          * Specify if results are about a filtered search or not.
419          * By using this attribute it is possible to force this panel to show results about entity list and search.
420          */
421         protected boolean filtered = false;
422 
423         protected boolean checkBoxEnabled = true;
424 
425         protected boolean showPaginator = true;
426 
427         /**
428          * Filter used in case of filtered search.
429          */
430         protected String fiql;
431 
432         protected final E restClient;
433 
434         protected Builder(final E restClient, final PageReference pageRef) {
435             super(pageRef);
436             this.restClient = restClient;
437         }
438 
439         public Builder<T, W, E> setFiltered(final boolean filtered) {
440             this.filtered = filtered;
441             return this;
442         }
443 
444         public Builder<T, W, E> disableCheckBoxes() {
445             this.checkBoxEnabled = false;
446             return this;
447         }
448 
449         public Builder<T, W, E> hidePaginator() {
450             this.showPaginator = false;
451             return this;
452         }
453 
454         public Builder<T, W, E> setFiql(final String fiql) {
455             this.fiql = fiql;
456             return this;
457         }
458 
459         private PageReference getPageRef() {
460             return pageRef;
461         }
462     }
463 }