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 de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
22  import java.io.Serializable;
23  import java.lang.reflect.Field;
24  import java.time.OffsetDateTime;
25  import java.util.ArrayList;
26  import java.util.Collection;
27  import java.util.Date;
28  import java.util.List;
29  import java.util.Objects;
30  import java.util.Optional;
31  import java.util.stream.Collectors;
32  import org.apache.commons.lang3.tuple.Triple;
33  import org.apache.syncope.client.console.PreferenceManager;
34  import org.apache.syncope.client.console.SyncopeConsoleSession;
35  import org.apache.syncope.client.console.SyncopeWebApplication;
36  import org.apache.syncope.client.console.commons.AnyDataProvider;
37  import org.apache.syncope.client.console.rest.AbstractAnyRestClient;
38  import org.apache.syncope.client.console.rest.AuditRestClient;
39  import org.apache.syncope.client.console.rest.SchemaRestClient;
40  import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.AttrColumn;
41  import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn;
42  import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn;
43  import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.KeyPropertyColumn;
44  import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.TokenColumn;
45  import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
46  import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
47  import org.apache.syncope.client.console.wizards.any.StatusPanel;
48  import org.apache.syncope.client.ui.commons.Constants;
49  import org.apache.syncope.client.ui.commons.status.ConnObjectWrapper;
50  import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
51  import org.apache.syncope.common.lib.SyncopeClientException;
52  import org.apache.syncope.common.lib.SyncopeConstants;
53  import org.apache.syncope.common.lib.to.AnyTO;
54  import org.apache.syncope.common.lib.to.AnyTypeClassTO;
55  import org.apache.syncope.common.lib.to.ConnObject;
56  import org.apache.syncope.common.lib.to.DerSchemaTO;
57  import org.apache.syncope.common.lib.to.PlainSchemaTO;
58  import org.apache.syncope.common.lib.to.ProvisioningResult;
59  import org.apache.syncope.common.lib.types.SchemaType;
60  import org.apache.wicket.PageReference;
61  import org.apache.wicket.ajax.AjaxRequestTarget;
62  import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
63  import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
64  import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
65  import org.apache.wicket.markup.html.panel.Panel;
66  import org.apache.wicket.model.ResourceModel;
67  import org.apache.wicket.model.util.ListModel;
68  import org.apache.wicket.spring.injection.annot.SpringBean;
69  import org.springframework.util.ReflectionUtils;
70  
71  public abstract class AnyDirectoryPanel<A extends AnyTO, E extends AbstractAnyRestClient<A>>
72          extends DirectoryPanel<A, AnyWrapper<A>, AnyDataProvider<A>, E> {
73  
74      private static final long serialVersionUID = -1100228004207271270L;
75  
76      @SpringBean
77      protected SchemaRestClient schemaRestClient;
78  
79      @SpringBean
80      protected AuditRestClient auditRestClient;
81  
82      protected final List<PlainSchemaTO> plainSchemas;
83  
84      protected final List<DerSchemaTO> derSchemas;
85  
86      /**
87       * Filter used in case of filtered search.
88       */
89      protected String fiql;
90  
91      /**
92       * Realm related to current panel.
93       */
94      protected final String realm;
95  
96      /**
97       * Any type related to current panel.
98       */
99      protected final String type;
100 
101     protected final BaseModal<Serializable> utilityModal = new BaseModal<>(Constants.OUTER);
102 
103     protected AnyDirectoryPanel(final String id, final Builder<A, E> builder) {
104         this(id, builder, true);
105     }
106 
107     protected AnyDirectoryPanel(final String id, final Builder<A, E> builder, final boolean wizardInModal) {
108         super(id, builder, wizardInModal);
109         if (SyncopeConsoleSession.get().owns(String.format("%s_CREATE", builder.type), builder.realm)
110                 && builder.realm.startsWith(SyncopeConstants.ROOT_REALM)) {
111             MetaDataRoleAuthorizationStrategy.authorizeAll(addAjaxLink, RENDER);
112         } else {
113             MetaDataRoleAuthorizationStrategy.unauthorizeAll(addAjaxLink, RENDER);
114         }
115         if (builder.dynRealm == null) {
116             setReadOnly(!SyncopeConsoleSession.get().owns(String.format("%s_UPDATE", builder.type), builder.realm));
117         } else {
118             setReadOnly(!SyncopeConsoleSession.get().owns(String.format("%s_UPDATE", builder.type), builder.dynRealm));
119         }
120 
121         realm = builder.realm;
122         type = builder.type;
123         fiql = builder.fiql;
124 
125         utilityModal.size(Modal.Size.Large);
126         addOuterObject(utilityModal);
127         setWindowClosedReloadCallback(utilityModal);
128 
129         modal.size(Modal.Size.Large);
130 
131         altDefaultModal.size(Modal.Size.Large);
132 
133         plainSchemas = AnyDirectoryPanelBuilder.class.cast(builder).getAnyTypeClassTOs().stream().
134                 flatMap(anyTypeClassTO -> anyTypeClassTO.getPlainSchemas().stream()).
135                 map(schema -> {
136                     try {
137                         return schemaRestClient.<PlainSchemaTO>read(SchemaType.PLAIN, schema);
138                     } catch (SyncopeClientException e) {
139                         LOG.warn("Could not read plain schema {}, ignoring", e);
140                         return null;
141                     }
142                 }).
143                 filter(Objects::nonNull).
144                 collect(Collectors.toList());
145 
146         derSchemas = AnyDirectoryPanelBuilder.class.cast(builder).getAnyTypeClassTOs().stream().
147                 flatMap(anyTypeClassTO -> anyTypeClassTO.getDerSchemas().stream()).
148                 map(schema -> {
149                     try {
150                         return schemaRestClient.<DerSchemaTO>read(SchemaType.DERIVED, schema);
151                     } catch (SyncopeClientException e) {
152                         LOG.warn("Could not read derived schema {}, ignoring", e);
153                         return null;
154                     }
155                 }).
156                 filter(Objects::nonNull).
157                 collect(Collectors.toList());
158 
159         initResultTable();
160 
161         SyncopeWebApplication.get().getAnyDirectoryPanelAdditionalActionsProvider().add(
162                 this,
163                 modal,
164                 wizardInModal,
165                 container,
166                 type,
167                 realm,
168                 fiql,
169                 rows,
170                 plainSchemas.stream().map(PlainSchemaTO::getKey).collect(Collectors.toList()),
171                 derSchemas.stream().map(DerSchemaTO::getKey).collect(Collectors.toList()),
172                 pageRef);
173     }
174 
175     @Override
176     protected List<IColumn<A, String>> getColumns() {
177         List<IColumn<A, String>> columns = new ArrayList<>();
178         columns.add(new KeyPropertyColumn<>(
179                 new ResourceModel(Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME), Constants.KEY_FIELD_NAME));
180 
181         List<IColumn<A, String>> prefcolumns = new ArrayList<>();
182         PreferenceManager.getList(DisplayAttributesModalPanel.getPrefDetailView(type)).stream().
183                 filter(name -> !Constants.KEY_FIELD_NAME.equalsIgnoreCase(name)).
184                 forEach(name -> addPropertyColumn(
185                 name,
186                 ReflectionUtils.findField(DisplayAttributesModalPanel.getTOClass(type), name),
187                 prefcolumns));
188 
189         PreferenceManager.getList(DisplayAttributesModalPanel.getPrefPlainAttributeView(type)).stream().
190                 map(a -> plainSchemas.stream().filter(p -> p.getKey().equals(a)).findFirst()).
191                 filter(Optional::isPresent).map(Optional::get).
192                 forEach(s -> prefcolumns.add(new AttrColumn<>(
193                 s.getKey(), s.getLabel(SyncopeConsoleSession.get().getLocale()), SchemaType.PLAIN)));
194 
195         PreferenceManager.getList(DisplayAttributesModalPanel.getPrefDerivedAttributeView(type)).stream().
196                 map(a -> derSchemas.stream().filter(p -> p.getKey().equals(a)).findFirst()).
197                 filter(Optional::isPresent).map(Optional::get).
198                 forEach(s -> prefcolumns.add(new AttrColumn<>(
199                 s.getKey(), s.getLabel(SyncopeConsoleSession.get().getLocale()), SchemaType.DERIVED)));
200 
201         // Add defaults in case of no selection
202         if (prefcolumns.isEmpty()) {
203             for (String name : getDefaultAttributeSelection()) {
204                 addPropertyColumn(
205                         name,
206                         ReflectionUtils.findField(DisplayAttributesModalPanel.getTOClass(type), name),
207                         prefcolumns);
208             }
209 
210             PreferenceManager.setList(
211                     DisplayAttributesModalPanel.getPrefDetailView(type),
212                     List.of(getDefaultAttributeSelection()));
213         }
214 
215         columns.addAll(prefcolumns);
216         return columns;
217     }
218 
219     protected abstract String[] getDefaultAttributeSelection();
220 
221     protected void addPropertyColumn(
222             final String name,
223             final Field field,
224             final List<IColumn<A, String>> columns) {
225 
226         if (Constants.KEY_FIELD_NAME.equalsIgnoreCase(name)) {
227             columns.add(new KeyPropertyColumn<>(new ResourceModel(name, name), name, name));
228         } else if (Constants.DEFAULT_TOKEN_FIELD_NAME.equalsIgnoreCase(name)) {
229             columns.add(new TokenColumn<>(new ResourceModel(name, name), name));
230         } else if (field != null && !field.isSynthetic()
231                 && (field.getType().equals(Boolean.class) || field.getType().equals(boolean.class))) {
232 
233             columns.add(new BooleanPropertyColumn<>(new ResourceModel(name, name), name, name));
234         } else if (field != null && !field.isSynthetic()
235                 && (field.getType().equals(Date.class) || field.getType().equals(OffsetDateTime.class))) {
236 
237             columns.add(new DatePropertyColumn<>(new ResourceModel(name, name), name, name));
238         } else {
239             columns.add(new PropertyColumn<>(new ResourceModel(name, name), name, name));
240         }
241     }
242 
243     @Override
244     protected AnyDataProvider<A> dataProvider() {
245         return new AnyDataProvider<>(restClient, rows, filtered, realm, type, pageRef).setFIQL(this.fiql);
246     }
247 
248     public AnyDataProvider<A> getDataProvider() {
249         return dataProvider;
250     }
251 
252     public void search(final String fiql, final AjaxRequestTarget target) {
253         this.fiql = fiql;
254         dataProvider.setFIQL(fiql);
255         super.search(target);
256     }
257 
258     @Override
259     protected Collection<ActionLink.ActionType> getBatches() {
260         List<ActionLink.ActionType> batches = new ArrayList<>();
261         batches.add(ActionLink.ActionType.DELETE);
262         return batches;
263     }
264 
265     @FunctionalInterface
266     public interface AnyDirectoryPanelBuilder extends Serializable {
267 
268         List<AnyTypeClassTO> getAnyTypeClassTOs();
269     }
270 
271     public abstract static class Builder<A extends AnyTO, E extends AbstractAnyRestClient<A>>
272             extends DirectoryPanel.Builder<A, AnyWrapper<A>, E>
273             implements AnyDirectoryPanelBuilder {
274 
275         private static final long serialVersionUID = -6828423611982275640L;
276 
277         /**
278          * Realm related to current panel.
279          */
280         protected String realm = SyncopeConstants.ROOT_REALM;
281 
282         protected String dynRealm = null;
283 
284         /**
285          * Any type related to current panel.
286          */
287         protected final String type;
288 
289         private final List<AnyTypeClassTO> anyTypeClassTOs;
290 
291         public Builder(
292                 final List<AnyTypeClassTO> anyTypeClassTOs,
293                 final E restClient,
294                 final String type,
295                 final PageReference pageRef) {
296 
297             super(restClient, pageRef);
298             this.anyTypeClassTOs = anyTypeClassTOs;
299             this.type = type;
300         }
301 
302         public Builder<A, E> setRealm(final String realm) {
303             this.realm = realm;
304             return this;
305         }
306 
307         public Builder<A, E> setDynRealm(final String dynRealm) {
308             this.dynRealm = dynRealm;
309             return this;
310         }
311 
312         @Override
313         public List<AnyTypeClassTO> getAnyTypeClassTOs() {
314             return this.anyTypeClassTOs;
315         }
316     }
317 
318     @Override
319     @SuppressWarnings("unchecked")
320     protected Panel customResultBody(final String panelId, final AnyWrapper<A> item, final Serializable result) {
321         if (!(result instanceof ProvisioningResult)) {
322             throw new IllegalStateException("Unsupported result type");
323         }
324 
325         return new StatusPanel(
326                 panelId,
327                 ((ProvisioningResult<A>) result).getEntity(),
328                 new ListModel<>(new ArrayList<>()),
329                 ((ProvisioningResult<A>) result).getPropagationStatuses().stream().map(status -> {
330                     ConnObject before = status.getBeforeObj();
331                     ConnObjectWrapper afterObjWrapper = new ConnObjectWrapper(
332                             ((ProvisioningResult<A>) result).getEntity(),
333                             status.getResource(),
334                             status.getAfterObj());
335                     return Triple.of(before, afterObjWrapper, status.getFailureReason());
336                 }).collect(Collectors.toList()),
337                 pageRef);
338     }
339 }