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.wizards.resources;
20  
21  import java.io.Serializable;
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.Objects;
25  import java.util.Optional;
26  import java.util.stream.Collectors;
27  import org.apache.syncope.client.console.SyncopeConsoleSession;
28  import org.apache.syncope.client.console.panels.AbstractModalPanel;
29  import org.apache.syncope.client.console.panels.ListViewPanel;
30  import org.apache.syncope.client.console.panels.ListViewPanel.ListViewReload;
31  import org.apache.syncope.client.console.rest.AnyTypeRestClient;
32  import org.apache.syncope.client.console.rest.ConnectorRestClient;
33  import org.apache.syncope.client.console.rest.ResourceRestClient;
34  import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
35  import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
36  import org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksTogglePanel;
37  import org.apache.syncope.client.console.wizards.WizardMgtPanel;
38  import org.apache.syncope.client.ui.commons.Constants;
39  import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
40  import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
41  import org.apache.syncope.common.lib.SyncopeClientException;
42  import org.apache.syncope.common.lib.SyncopeConstants;
43  import org.apache.syncope.common.lib.to.Item;
44  import org.apache.syncope.common.lib.to.ResourceTO;
45  import org.apache.syncope.common.lib.types.IdMEntitlement;
46  import org.apache.wicket.PageReference;
47  import org.apache.wicket.ajax.AjaxRequestTarget;
48  import org.apache.wicket.ajax.markup.html.AjaxLink;
49  import org.apache.wicket.event.Broadcast;
50  import org.apache.wicket.model.LoadableDetachableModel;
51  import org.apache.wicket.model.Model;
52  import org.apache.wicket.model.StringResourceModel;
53  import org.apache.wicket.spring.injection.annot.SpringBean;
54  
55  public class ResourceProvisionPanel extends AbstractModalPanel<Serializable> {
56  
57      private static final long serialVersionUID = -7982691107029848579L;
58  
59      @SpringBean
60      protected ResourceRestClient resourceRestClient;
61  
62      @SpringBean
63      protected ConnectorRestClient connectorRestClient;
64  
65      @SpringBean
66      protected AnyTypeRestClient anyTypeRestClient;
67  
68      protected final ResourceTO resourceTO;
69  
70      protected final List<ResourceProvision> provisions;
71  
72      protected final ObjectTypeTogglePanel objectTypeTogglePanel;
73  
74      protected final WizardMgtPanel<ResourceProvision> list;
75  
76      protected final ProvisionWizardBuilder wizard;
77  
78      protected final AjaxLink<ResourceProvision> addAjaxLink;
79  
80      protected ActionLinksTogglePanel<ResourceProvision> actionTogglePanel;
81  
82      public ResourceProvisionPanel(
83              final BaseModal<Serializable> modal,
84              final ResourceTO resourceTO,
85              final String adminRealm,
86              final PageReference pageRef) {
87  
88          super(modal, pageRef);
89          this.resourceTO = resourceTO;
90  
91          setOutputMarkupId(true);
92  
93          actionTogglePanel = new ActionLinksTogglePanel<>("toggle", pageRef);
94          add(actionTogglePanel);
95  
96          wizard = new ProvisionWizardBuilder(resourceTO, adminRealm, connectorRestClient, pageRef);
97  
98          ListViewPanel.Builder<ResourceProvision> builder = new ListViewPanel.Builder<>(
99                  ResourceProvision.class, pageRef) {
100 
101             private static final long serialVersionUID = 4907732721283972943L;
102 
103             @Override
104             protected ResourceProvision getActualItem(
105                     final ResourceProvision item, final List<ResourceProvision> list) {
106 
107                 return Optional.ofNullable(item).flatMap(resourceProvision -> list.stream()
108                         .filter(in -> ((resourceProvision.getKey() == null && in.getKey() == null)
109                         || (in.getKey() != null && in.getKey().equals(resourceProvision.getKey())))
110                         && ((resourceProvision.getAnyType() == null && in.getAnyType() == null)
111                         || (in.getAnyType() != null && in.getAnyType().equals(resourceProvision.getAnyType())))).
112                         findAny()).orElse(null);
113             }
114 
115             @Override
116             protected void customActionCallback(final AjaxRequestTarget target) {
117                 // change modal footer visibility
118                 send(ResourceProvisionPanel.this, Broadcast.BUBBLE, new BaseModal.ChangeFooterVisibilityEvent(target));
119             }
120 
121             @Override
122             protected void customActionOnCancelCallback(final AjaxRequestTarget target) {
123                 // change modal footer visibility
124                 send(ResourceProvisionPanel.this, Broadcast.BUBBLE, new BaseModal.ChangeFooterVisibilityEvent(target));
125             }
126 
127             @Override
128             protected void customActionOnFinishCallback(final AjaxRequestTarget target) {
129                 checkAddButton(adminRealm);
130 
131                 // keep list ordered - SYNCOPE-1154
132                 sortProvisions();
133 
134                 // change modal footer visibility
135                 send(ResourceProvisionPanel.this, Broadcast.BUBBLE, new BaseModal.ChangeFooterVisibilityEvent(target));
136             }
137 
138             @Override
139             protected ActionLinksTogglePanel<ResourceProvision> getTogglePanel() {
140                 return actionTogglePanel;
141             }
142         };
143 
144         provisions = new ArrayList<>();
145         if (resourceTO.getOrgUnit() != null) {
146             provisions.add(new ResourceProvision(resourceTO.getOrgUnit()));
147         }
148         resourceTO.getProvisions().forEach(provision -> provisions.add(new ResourceProvision(provision)));
149         // keep list ordered - SYNCOPE-1154
150         sortProvisions();
151 
152         builder.setItems(provisions);
153         builder.includes("anyType", "objectClass", "auxClasses");
154         builder.setReuseItem(false);
155 
156         builder.addAction(new ActionLink<>() {
157 
158             private static final long serialVersionUID = -3722207913631435504L;
159 
160             @Override
161             public void onClick(final AjaxRequestTarget target, final ResourceProvision provision) {
162                 try {
163                     send(ResourceProvisionPanel.this, Broadcast.DEPTH,
164                             new AjaxWizard.NewItemActionEvent<>(provision, 1, target).setTitleModel(
165                                     new StringResourceModel("inner.provision.mapping",
166                                             ResourceProvisionPanel.this,
167                                             Model.of(provision))));
168                 } catch (SyncopeClientException e) {
169                     LOG.error("While contacting resource", e);
170                     SyncopeConsoleSession.get().onException(e);
171                     ((BaseWebPage) pageRef.getPage()).getNotificationPanel().refresh(target);
172                 }
173             }
174         }, ActionLink.ActionType.MAPPING, IdMEntitlement.RESOURCE_READ).
175                 addAction(new ActionLink<>() {
176 
177                     private static final long serialVersionUID = -7780999687733432439L;
178 
179                     @Override
180                     public void onClick(final AjaxRequestTarget target, final ResourceProvision provision) {
181                         try {
182                             resourceRestClient.setLatestSyncToken(resourceTO.getKey(), provision.getAnyType());
183                             SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
184                         } catch (Exception e) {
185                             LOG.error("While setting latest sync token for {}/{}",
186                                     resourceTO.getKey(), provision.getAnyType(), e);
187                             SyncopeConsoleSession.get().onException(e);
188                         }
189                         ((BaseWebPage) pageRef.getPage()).getNotificationPanel().refresh(target);
190                     }
191                 }, ActionLink.ActionType.SET_LATEST_SYNC_TOKEN, IdMEntitlement.RESOURCE_UPDATE).
192                 addAction(new ActionLink<>() {
193 
194                     private static final long serialVersionUID = -7780999687733432439L;
195 
196                     @Override
197                     public void onClick(final AjaxRequestTarget target, final ResourceProvision provision) {
198                         try {
199                             resourceRestClient.removeSyncToken(resourceTO.getKey(), provision.getAnyType());
200                             SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
201                         } catch (Exception e) {
202                             LOG.error("While removing sync token for {}/{}",
203                                     resourceTO.getKey(), provision.getAnyType(), e);
204                             SyncopeConsoleSession.get().onException(e);
205                         }
206                         ((BaseWebPage) pageRef.getPage()).getNotificationPanel().refresh(target);
207                     }
208                 }, ActionLink.ActionType.REMOVE_SYNC_TOKEN, IdMEntitlement.RESOURCE_UPDATE).
209                 addAction(new ActionLink<>() {
210 
211                     private static final long serialVersionUID = -3722207913631435544L;
212 
213                     @Override
214                     public void onClick(final AjaxRequestTarget target, final ResourceProvision provision) {
215                         if (provision.getOrgUnitTO() != null) {
216                             resourceTO.setOrgUnit(null);
217                         } else if (provision.getProvisionTO() != null) {
218                             resourceTO.getProvisions().remove(provision.getProvisionTO());
219                         }
220                         provisions.remove(provision);
221                         checkAddButton(adminRealm);
222                         send(ResourceProvisionPanel.this, Broadcast.DEPTH, new ListViewReload<>(target));
223                     }
224                 }, ActionLink.ActionType.DELETE, IdMEntitlement.RESOURCE_UPDATE, true);
225 
226         builder.addNewItemPanelBuilder(wizard);
227 
228         list = builder.build("provision");
229         list.setReadOnly(!SyncopeConsoleSession.get().owns(IdMEntitlement.RESOURCE_UPDATE, adminRealm));
230 
231         addAjaxLink = new AjaxLink<>("add") {
232 
233             private static final long serialVersionUID = -7978723352517770644L;
234 
235             @Override
236             public void onClick(final AjaxRequestTarget target) {
237                 send(ResourceProvisionPanel.this, Broadcast.BREADTH,
238                         new ActionLinksTogglePanel.ActionLinkToggleCloseEventPayload(target));
239                 objectTypeTogglePanel.setHeaderLabel(target);
240                 objectTypeTogglePanel.toggle(target, true);
241             }
242         };
243         list.addOrReplaceInnerObject(addAjaxLink);
244         add(list);
245 
246         // ----------------------------------------------------------------------
247         // toggle panel, used to choose 'type' before starting wizard - SYNCOPE-1167
248         final ResourceProvision provision = new ResourceProvision();
249         provision.setAnyType("");
250         objectTypeTogglePanel = new ObjectTypeTogglePanel("objectTypeToggle", provision, getAnyTypes(), pageRef) {
251 
252             private static final long serialVersionUID = 7878063325027015067L;
253 
254             @Override
255             protected void onSubmit(final String type, final AjaxRequestTarget target) {
256                 provision.setAnyType(type);
257 
258                 send(list, Broadcast.BREADTH,
259                         new ActionLinksTogglePanel.ActionLinkToggleCloseEventPayload(target));
260                 send(list, Broadcast.DEPTH,
261                         new AjaxWizard.NewItemActionEvent<>(provision, target));
262 
263                 wizard.setObjectClassModelObject(type);
264             }
265 
266         };
267         checkAddButton(adminRealm);
268         add(objectTypeTogglePanel);
269     }
270 
271     private void checkConnObjectKeyCount(final String anyType, final List<Item> items) {
272         long connObjectKeyCount = items.stream().filter(Item::isConnObjectKey).count();
273 
274         if (connObjectKeyCount != 1) {
275             throw new IllegalArgumentException(anyType + ": "
276                     + new StringResourceModel("connObjectKeyValidation", ResourceProvisionPanel.this).getString());
277         }
278     }
279 
280     @Override
281     public void onSubmit(final AjaxRequestTarget target) {
282         try {
283             if (resourceTO.getOrgUnit() != null) {
284                 checkConnObjectKeyCount(SyncopeConstants.REALM_ANYTYPE, resourceTO.getOrgUnit().getItems());
285             }
286 
287             new ArrayList<>(resourceTO.getProvisions()).stream().
288                     filter(Objects::nonNull).
289                     forEachOrdered(provision -> {
290                         if (provision.getMapping() == null || provision.getMapping().getItems().isEmpty()) {
291                             resourceTO.getProvisions().remove(provision);
292                         } else {
293                             checkConnObjectKeyCount(provision.getAnyType(), provision.getMapping().getItems());
294                         }
295                     });
296 
297             resourceRestClient.update(resourceTO);
298             SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
299             modal.close(target);
300         } catch (Exception e) {
301             LOG.error("While creating or updating {}", resourceTO, e);
302             SyncopeConsoleSession.get().onException(e);
303         }
304         ((BaseWebPage) pageRef.getPage()).getNotificationPanel().refresh(target);
305     }
306 
307     private void sortProvisions() {
308         provisions.sort((o1, o2) -> AnyTypeRestClient.KEY_COMPARATOR.compare(o1.getAnyType(), o2.getAnyType()));
309     }
310 
311     private LoadableDetachableModel<List<String>> getAnyTypes() {
312         return new LoadableDetachableModel<>() {
313 
314             private static final long serialVersionUID = 5275935387613157437L;
315 
316             @Override
317             protected List<String> load() {
318                 List<String> anyTypes = anyTypeRestClient.list().stream().
319                         filter(anyType -> resourceTO.getProvision(anyType).isEmpty()).
320                         collect(Collectors.toList());
321                 if (resourceTO.getOrgUnit() == null) {
322                     anyTypes.add(SyncopeConstants.REALM_ANYTYPE);
323                 }
324 
325                 anyTypes.sort(AnyTypeRestClient.KEY_COMPARATOR);
326                 return anyTypes;
327             }
328         };
329     }
330 
331     private void checkAddButton(final String adminRealm) {
332         boolean enabled = SyncopeConsoleSession.get().owns(IdMEntitlement.RESOURCE_UPDATE, adminRealm)
333                 && !getAnyTypes().getObject().isEmpty();
334         addAjaxLink.setVisible(enabled);
335         objectTypeTogglePanel.setEnabled(enabled);
336     }
337 }