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.components.PopoverBehavior;
22  import de.agilecoders.wicket.core.markup.html.bootstrap.components.PopoverConfig;
23  import de.agilecoders.wicket.core.markup.html.bootstrap.components.TooltipConfig;
24  import java.io.Serializable;
25  import java.util.ArrayList;
26  import java.util.List;
27  import java.util.Optional;
28  import org.apache.commons.lang3.StringUtils;
29  import org.apache.commons.lang3.tuple.Pair;
30  import org.apache.syncope.client.console.SyncopeConsoleSession;
31  import org.apache.syncope.client.console.pages.BasePage;
32  import org.apache.syncope.client.console.panels.ListViewPanel.ListViewReload;
33  import org.apache.syncope.client.console.panels.search.AbstractSearchPanel;
34  import org.apache.syncope.client.console.panels.search.ConnObjectSearchPanel;
35  import org.apache.syncope.client.console.panels.search.SearchClause;
36  import org.apache.syncope.client.console.panels.search.SearchClausePanel;
37  import org.apache.syncope.client.console.panels.search.SearchUtils;
38  import org.apache.syncope.client.console.rest.AnyTypeRestClient;
39  import org.apache.syncope.client.console.rest.ReconciliationRestClient;
40  import org.apache.syncope.client.console.rest.ResourceRestClient;
41  import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.CollectionPanel;
42  import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
43  import org.apache.syncope.client.lib.SyncopeClient;
44  import org.apache.syncope.client.ui.commons.ConnIdSpecialName;
45  import org.apache.syncope.client.ui.commons.Constants;
46  import org.apache.syncope.client.ui.commons.status.StatusUtils;
47  import org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion;
48  import org.apache.syncope.common.lib.Attr;
49  import org.apache.syncope.common.lib.SyncopeConstants;
50  import org.apache.syncope.common.lib.to.ConnObject;
51  import org.apache.syncope.common.lib.to.ReconStatus;
52  import org.apache.syncope.common.lib.to.ResourceTO;
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.syncope.common.lib.types.MatchType;
57  import org.apache.syncope.common.rest.api.beans.ConnObjectTOQuery;
58  import org.apache.syncope.common.rest.api.beans.ReconQuery;
59  import org.apache.wicket.Component;
60  import org.apache.wicket.PageReference;
61  import org.apache.wicket.ajax.AjaxRequestTarget;
62  import org.apache.wicket.ajax.markup.html.AjaxLink;
63  import org.apache.wicket.event.Broadcast;
64  import org.apache.wicket.event.IEvent;
65  import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
66  import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
67  import org.apache.wicket.extensions.markup.html.tabs.ITab;
68  import org.apache.wicket.markup.ComponentTag;
69  import org.apache.wicket.markup.html.WebMarkupContainer;
70  import org.apache.wicket.markup.html.basic.Label;
71  import org.apache.wicket.markup.html.panel.Panel;
72  import org.apache.wicket.model.Model;
73  import org.apache.wicket.model.StringResourceModel;
74  import org.apache.wicket.model.util.ListModel;
75  import org.apache.wicket.spring.injection.annot.SpringBean;
76  import org.slf4j.Logger;
77  import org.slf4j.LoggerFactory;
78  
79  public abstract class ConnObjectListViewPanel extends Panel {
80  
81      private static final long serialVersionUID = 4986172040062752781L;
82  
83      protected static final Logger LOG = LoggerFactory.getLogger(ConnObjectListViewPanel.class);
84  
85      protected static final int SIZE = 10;
86  
87      protected static final String STATUS = "Status";
88  
89      @SpringBean
90      protected ReconciliationRestClient reconciliationRestClient;
91  
92      @SpringBean
93      protected ResourceRestClient resourceRestClient;
94  
95      @SpringBean
96      protected AnyTypeRestClient anyTypeRestClient;
97  
98      protected String nextPageCookie;
99  
100     protected AbstractSearchPanel searchPanel;
101 
102     protected WebMarkupContainer arrows;
103 
104     protected String anyType;
105 
106     protected ResourceTO resource;
107 
108     protected final PageReference pageRef;
109 
110     protected ConnObjectListViewPanel(
111             final String id,
112             final ResourceTO resource,
113             final String anyType,
114             final PageReference pageRef) {
115 
116         super(id);
117 
118         this.anyType = anyType;
119         this.resource = resource;
120         this.pageRef = pageRef;
121 
122         final Model<Integer> model = Model.of(-1);
123         final StringResourceModel res = new StringResourceModel("search.result", this, new Model<>(anyType));
124 
125         final Accordion accordion = new Accordion("accordionPanel", List.of(new AbstractTab(res) {
126 
127             private static final long serialVersionUID = 1037272333056449377L;
128 
129             @Override
130             public WebMarkupContainer getPanel(final String panelId) {
131                 searchPanel = getSearchPanel(panelId, anyType);
132                 return searchPanel;
133             }
134 
135         }), model) {
136 
137             private static final long serialVersionUID = 6581261306163L;
138 
139             @Override
140             protected Component newTitle(final String markupId, final ITab tab, final Accordion.State state) {
141                 return new AjaxLink<Integer>(markupId) {
142 
143                     private static final long serialVersionUID = 6584438659172L;
144 
145                     @Override
146                     protected void onComponentTag(final ComponentTag tag) {
147                         super.onComponentTag(tag);
148                         tag.put("style", "color: #337ab7");
149                     }
150 
151                     @Override
152                     public void onClick(final AjaxRequestTarget target) {
153                         model.setObject(model.getObject() == 0 ? -1 : 0);
154                     }
155                 }.setBody(res);
156             }
157         };
158         accordion.setOutputMarkupId(true);
159         add(accordion.setEnabled(true).setVisible(true));
160 
161         List<ConnObject> listOfItems = reloadItems(resource.getKey(), anyType, null, null);
162 
163         ListViewPanel.Builder<ConnObject> builder = new ListViewPanel.Builder<>(
164                 ConnObject.class, pageRef) {
165 
166             private static final long serialVersionUID = -8251750413385566738L;
167 
168             @Override
169             protected Component getValueComponent(final String key, final ConnObject bean) {
170                 if (StringUtils.equals(key, STATUS)) {
171                     ReconStatus status;
172                     try {
173                         status = reconciliationRestClient.status(
174                                 new ReconQuery.Builder(anyType, resource.getKey()).fiql(bean.getFiql()).build());
175                     } catch (Exception e) {
176                         LOG.error("While requesting for reconciliation status of {} {} with FIQL '{}'",
177                                 anyType, resource.getKey(), bean.getFiql(), e);
178 
179                         status = new ReconStatus();
180                     }
181 
182                     return status.getOnSyncope() == null
183                             ? StatusUtils.getLabel("field", "notfound icon", "Not found", Constants.NOT_FOUND_ICON)
184                             : new Label("field", Model.of()).add(new PopoverBehavior(
185                                     Model.of(),
186                                     Model.of(status.getAnyKey()),
187                                     new PopoverConfig().
188                                             withTitle(status.getMatchType() == MatchType.LINKED_ACCOUNT
189                                                     ? MatchType.LINKED_ACCOUNT.name() + ", " + AnyTypeKind.USER
190                                                     : status.getAnyTypeKind().name()).
191                                             withPlacement(TooltipConfig.Placement.left)) {
192 
193                                 private static final long serialVersionUID = -7867802555691605021L;
194 
195                                 @Override
196                                 protected String createRelAttribute() {
197                                     return "field";
198                                 }
199 
200                                 @Override
201                                 public void onComponentTag(final Component component, final ComponentTag tag) {
202                                     super.onComponentTag(component, tag);
203                                     tag.put("class", Constants.ACTIVE_ICON);
204                                 }
205                             });
206                 } else {
207                     Optional<Attr> attr = bean.getAttrs().stream().
208                             filter(object -> object.getSchema().equals(key)).findAny();
209                     return attr.filter(a -> !a.getValues().isEmpty()).
210                             map(a -> (Component) new CollectionPanel("field", a.getValues())).
211                             orElseGet(() -> new Label("field", StringUtils.EMPTY));
212                 }
213             }
214 
215         };
216 
217         builder.setReuseItem(false);
218         builder.addAction(new ActionLink<>() {
219 
220             private static final long serialVersionUID = 7511002881490248598L;
221 
222             @Override
223             public void onClick(final AjaxRequestTarget target, final ConnObject modelObject) {
224                 viewConnObject(modelObject, target);
225             }
226         }, ActionLink.ActionType.VIEW, IdMEntitlement.RESOURCE_GET_CONNOBJECT).
227                 setItems(listOfItems).
228                 includes(ConnIdSpecialName.UID,
229                         ConnIdSpecialName.NAME,
230                         ConnIdSpecialName.ENABLE).
231                 withChecks(ListViewPanel.CheckAvailability.NONE).
232                 setReuseItem(false);
233 
234         if (!StringUtils.equals(anyType, SyncopeConstants.REALM_ANYTYPE)) {
235             builder.addAction(new ActionLink<>() {
236 
237                 private static final long serialVersionUID = 6377238742125L;
238 
239                 @Override
240                 public void onClick(final AjaxRequestTarget target, final ConnObject modelObject) {
241                     try {
242                         ReconStatus status = reconciliationRestClient.status(
243                                 new ReconQuery.Builder(anyType, resource.getKey()).fiql(modelObject.getFiql()).build());
244 
245                         pullConnObject(
246                                 modelObject.getFiql(),
247                                 target,
248                                 resource.getKey(),
249                                 anyType,
250                                 status.getRealm(),
251                                 StringUtils.isNotBlank(status.getAnyKey()),
252                                 pageRef);
253                     } catch (Exception e) {
254                         LOG.error("While puling single object {} {} with FIQL '{}'",
255                                 anyType, resource.getKey(), modelObject.getFiql(), e);
256 
257                         SyncopeConsoleSession.get().onException(e);
258                         ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
259                     }
260                 }
261             }, ActionLink.ActionType.RECONCILIATION_PULL, IdRepoEntitlement.TASK_EXECUTE);
262 
263             builder.includes(STATUS);
264         }
265 
266         add(builder.build("objs"));
267 
268         arrows = new WebMarkupContainer("arrows");
269         add(arrows.setOutputMarkupId(true));
270 
271         arrows.add(new AjaxLink<Serializable>("next") {
272 
273             private static final long serialVersionUID = -7978723352517770644L;
274 
275             @Override
276             public void onClick(final AjaxRequestTarget target) {
277                 List<ConnObject> listOfItems = reloadItems(resource.getKey(), anyType, nextPageCookie, getFiql());
278                 target.add(arrows);
279                 send(ConnObjectListViewPanel.this, Broadcast.DEPTH, new ListViewReload<>(listOfItems, target));
280             }
281 
282             @Override
283             public boolean isVisible() {
284                 return nextPageCookie != null;
285             }
286         });
287     }
288 
289     @Override
290     public void onEvent(final IEvent<?> event) {
291         if (event.getPayload() instanceof SearchClausePanel.SearchEvent) {
292             this.nextPageCookie = null;
293             AjaxRequestTarget target = SearchClausePanel.SearchEvent.class.cast(event.getPayload()).getTarget();
294             List<ConnObject> listOfItems = reloadItems(resource.getKey(), anyType, null, getFiql());
295             target.add(arrows);
296             send(ConnObjectListViewPanel.this, Broadcast.DEPTH, new ListViewReload<>(listOfItems, target));
297         } else {
298             super.onEvent(event);
299         }
300     }
301 
302     protected abstract void viewConnObject(ConnObject connObjectTO, AjaxRequestTarget target);
303 
304     protected abstract void pullConnObject(
305             String fiql,
306             AjaxRequestTarget target,
307             String resource,
308             String anyType,
309             String realm,
310             boolean isOnSyncope,
311             PageReference pageRef);
312 
313     protected List<ConnObject> reloadItems(
314             final String resource,
315             final String anyType,
316             final String cookie,
317             final String fiql) {
318 
319         Pair<String, List<ConnObject>> items = resourceRestClient.searchConnObjects(
320                 resource,
321                 anyType,
322                 new ConnObjectTOQuery.Builder().
323                         size(SIZE).
324                         pagedResultsCookie(cookie).
325                         fiql(fiql),
326                 new SortParam<>(ConnIdSpecialName.UID, true));
327 
328         nextPageCookie = items.getLeft();
329         return items.getRight();
330     }
331 
332     protected AbstractSearchPanel getSearchPanel(final String id, final String anyType) {
333         final List<SearchClause> clauses = new ArrayList<>();
334         final SearchClause clause = new SearchClause();
335         clauses.add(clause);
336 
337         clause.setComparator(SearchClause.Comparator.EQUALS);
338         clause.setType(SearchClause.Type.ATTRIBUTE);
339         clause.setProperty("");
340 
341         AnyTypeKind anyTypeKind =
342                 StringUtils.equals(anyType, SyncopeConstants.REALM_ANYTYPE) || StringUtils.isEmpty(anyType)
343                 ? AnyTypeKind.ANY_OBJECT
344                 : anyTypeRestClient.read(anyType).getKind();
345 
346         return new ConnObjectSearchPanel.Builder(resource, anyTypeKind, anyType,
347                 new ListModel<>(clauses), pageRef).required(true).enableSearch().build(id);
348     }
349 
350     protected String getFiql() {
351         return SearchUtils.buildFIQL(
352                 searchPanel.getModel().getObject(),
353                 SyncopeClient.getConnObjectTOFiqlSearchConditionBuilder(),
354                 searchPanel.getAvailableSchemaTypes(),
355                 SearchUtils.NO_CUSTOM_CONDITION);
356     }
357 }