| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.syncope.client.console.wizards.any; |
| |
| import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper; |
| import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.stream.Collectors; |
| import org.apache.commons.collections4.ListUtils; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.syncope.client.ui.commons.Constants; |
| import org.apache.syncope.client.console.panels.AnyDirectoryPanel; |
| import org.apache.syncope.client.console.panels.ListViewPanel; |
| import org.apache.syncope.client.console.panels.ListViewPanel.ListViewReload; |
| import org.apache.syncope.client.console.panels.search.AnyObjectSearchPanel; |
| import org.apache.syncope.client.console.panels.search.AnyObjectSelectionDirectoryPanel; |
| import org.apache.syncope.client.console.panels.search.AnySelectionDirectoryPanel; |
| import org.apache.syncope.client.console.panels.search.SearchClausePanel; |
| import org.apache.syncope.client.console.panels.search.SearchUtils; |
| import org.apache.syncope.client.console.rest.AnyTypeClassRestClient; |
| import org.apache.syncope.client.console.rest.AnyTypeRestClient; |
| import org.apache.syncope.client.console.rest.RelationshipTypeRestClient; |
| import org.apache.syncope.client.ui.commons.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior; |
| import org.apache.syncope.client.ui.commons.ajax.markup.html.LabelInfo; |
| import org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion; |
| import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; |
| import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType; |
| import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; |
| import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel; |
| import org.apache.syncope.client.console.wizards.WizardMgtPanel; |
| import org.apache.syncope.client.lib.SyncopeClient; |
| import org.apache.syncope.common.lib.to.AnyObjectTO; |
| import org.apache.syncope.common.lib.to.AnyTO; |
| import org.apache.syncope.common.lib.to.AnyTypeTO; |
| import org.apache.syncope.common.lib.to.EntityTO; |
| import org.apache.syncope.common.lib.to.GroupableRelatableTO; |
| import org.apache.syncope.common.lib.to.RelationshipTO; |
| import org.apache.syncope.common.lib.types.AnyEntitlement; |
| import org.apache.syncope.common.lib.types.AnyTypeKind; |
| import org.apache.wicket.Component; |
| import org.apache.wicket.PageReference; |
| import org.apache.wicket.ajax.AjaxRequestTarget; |
| import org.apache.wicket.event.Broadcast; |
| import org.apache.wicket.event.IEvent; |
| import org.apache.wicket.extensions.markup.html.tabs.AbstractTab; |
| import org.apache.wicket.extensions.wizard.IWizard; |
| import org.apache.wicket.extensions.wizard.WizardModel.ICondition; |
| import org.apache.wicket.extensions.wizard.WizardStep; |
| import org.apache.wicket.markup.head.IHeaderResponse; |
| import org.apache.wicket.markup.head.OnDomReadyHeaderItem; |
| import org.apache.wicket.markup.html.WebMarkupContainer; |
| import org.apache.wicket.markup.html.basic.Label; |
| import org.apache.wicket.markup.html.form.IChoiceRenderer; |
| import org.apache.wicket.markup.html.panel.Fragment; |
| import org.apache.wicket.markup.html.panel.Panel; |
| import org.apache.wicket.model.IModel; |
| import org.apache.wicket.model.PropertyModel; |
| import org.apache.wicket.model.ResourceModel; |
| import org.apache.wicket.model.util.ListModel; |
| |
| public class Relationships extends WizardStep implements ICondition { |
| |
| private static final long serialVersionUID = 855618618337931784L; |
| |
| private final PageReference pageRef; |
| |
| private final AnyTO anyTO; |
| |
| public Relationships(final AnyWrapper<?> modelObject, final PageReference pageRef) { |
| super(); |
| add(new Label("title", new ResourceModel("any.relationships"))); |
| |
| if (modelObject instanceof UserWrapper |
| && UserWrapper.class.cast(modelObject).getPreviousUserTO() != null |
| && !ListUtils.isEqualList( |
| UserWrapper.class.cast(modelObject).getInnerObject().getRelationships(), |
| UserWrapper.class.cast(modelObject).getPreviousUserTO().getRelationships())) { |
| add(new LabelInfo("changed", StringUtils.EMPTY)); |
| } else { |
| add(new Label("changed", StringUtils.EMPTY)); |
| } |
| |
| this.anyTO = modelObject.getInnerObject(); |
| this.pageRef = pageRef; |
| |
| // ------------------------ |
| // Existing relationships |
| // ------------------------ |
| add(getViewFragment().setRenderBodyOnly(true)); |
| // ------------------------ |
| } |
| |
| @Override |
| public Component getHeader(final String id, final Component parent, final IWizard wizard) { |
| return super.getHeader(id, parent, wizard).setVisible(false); |
| } |
| |
| private Fragment getViewFragment() { |
| final Map<String, List<RelationshipTO>> relationships = new HashMap<>(); |
| addRelationship(relationships, getCurrentRelationships().toArray(new RelationshipTO[] {})); |
| |
| final Fragment viewFragment = new Fragment("relationships", "viewFragment", this); |
| viewFragment.setOutputMarkupId(true); |
| |
| viewFragment.add(new Accordion("relationships", relationships.keySet().stream(). |
| map(relationship -> new AbstractTab(new ResourceModel("relationship", relationship)) { |
| |
| private static final long serialVersionUID = 1037272333056449378L; |
| |
| @Override |
| public Panel getPanel(final String panelId) { |
| return new ListViewPanel.Builder<>(RelationshipTO.class, pageRef). |
| setItems(relationships.get(relationship)). |
| includes("otherEndType", "otherEndKey", "otherEndName"). |
| addAction(new ActionLink<>() { |
| |
| private static final long serialVersionUID = -6847033126124401556L; |
| |
| @Override |
| public void onClick(final AjaxRequestTarget target, final RelationshipTO modelObject) { |
| removeRelationships(relationships, modelObject); |
| send(Relationships.this, Broadcast.DEPTH, new ListViewReload<>(target)); |
| } |
| }, ActionType.DELETE, AnyEntitlement.UPDATE.getFor(anyTO.getType()), true). |
| build(panelId); |
| } |
| }).collect(Collectors.toList())) { |
| |
| private static final long serialVersionUID = 1037272333056449379L; |
| |
| @Override |
| public void renderHead(final IHeaderResponse response) { |
| super.renderHead(response); |
| if (relationships.isEmpty()) { |
| response.render(OnDomReadyHeaderItem.forScript(String.format( |
| "$('#emptyPlaceholder').append(\"%s\")", getString("relationships.empty.list")))); |
| } |
| } |
| }); |
| |
| final ActionsPanel<RelationshipTO> panel = new ActionsPanel<>("actions", null); |
| viewFragment.add(panel); |
| |
| panel.add(new ActionLink<>() { |
| |
| private static final long serialVersionUID = 3257738274365467945L; |
| |
| @Override |
| public void onClick(final AjaxRequestTarget target, final RelationshipTO ignore) { |
| Fragment addFragment = new Fragment("relationships", "addFragment", Relationships.this); |
| addOrReplace(addFragment); |
| addFragment.add(new Specification().setRenderBodyOnly(true)); |
| target.add(Relationships.this); |
| } |
| }, ActionType.CREATE, AnyEntitlement.UPDATE.getFor(anyTO.getType())).hideLabel(); |
| |
| return viewFragment; |
| } |
| |
| private List<RelationshipTO> getCurrentRelationships() { |
| return anyTO instanceof GroupableRelatableTO |
| ? GroupableRelatableTO.class.cast(anyTO).getRelationships() |
| : List.of(); |
| } |
| |
| private static void addRelationship( |
| final Map<String, List<RelationshipTO>> relationships, |
| final RelationshipTO... rels) { |
| |
| for (RelationshipTO relationship : rels) { |
| final List<RelationshipTO> listrels; |
| if (relationships.containsKey(relationship.getType())) { |
| listrels = relationships.get(relationship.getType()); |
| } else { |
| listrels = new ArrayList<>(); |
| relationships.put(relationship.getType(), listrels); |
| } |
| listrels.add(relationship); |
| } |
| } |
| |
| private void addNewRelationships(final RelationshipTO... rels) { |
| getCurrentRelationships().addAll(List.of(rels)); |
| } |
| |
| private void removeRelationships( |
| final Map<String, List<RelationshipTO>> relationships, final RelationshipTO... rels) { |
| final List<RelationshipTO> currentRels = getCurrentRelationships(); |
| for (RelationshipTO relationship : rels) { |
| currentRels.remove(relationship); |
| if (relationships.containsKey(relationship.getType())) { |
| final List<RelationshipTO> rellist = relationships.get(relationship.getType()); |
| rellist.remove(relationship); |
| if (rellist.isEmpty()) { |
| relationships.remove(relationship.getType()); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public boolean evaluate() { |
| // [SYNCOPE-1171] - skip current step when the are no relationships types in Syncope |
| return !RelationshipTypeRestClient.list().isEmpty(); |
| } |
| |
| public class Specification extends Panel { |
| |
| private static final long serialVersionUID = 6199050589175839467L; |
| |
| private final RelationshipTO rel; |
| |
| private AnyObjectSearchPanel anyObjectSearchPanel; |
| |
| private WizardMgtPanel<AnyWrapper<AnyObjectTO>> anyObjectDirectoryPanel; |
| |
| public Specification() { |
| super("specification"); |
| rel = new RelationshipTO(); |
| |
| final List<String> availableRels = RelationshipTypeRestClient.list().stream(). |
| map(EntityTO::getKey).collect(Collectors.toList()); |
| |
| final AjaxDropDownChoicePanel<String> type = new AjaxDropDownChoicePanel<>( |
| "type", "type", new PropertyModel<>(rel, "type")); |
| type.setChoices(availableRels); |
| add(type.setRenderBodyOnly(true)); |
| |
| final List<AnyTypeTO> availableTypes = AnyTypeRestClient.listAnyTypes().stream(). |
| filter(anyType -> anyType.getKind() != AnyTypeKind.GROUP |
| && anyType.getKind() != AnyTypeKind.USER).collect(Collectors.toList()); |
| |
| final AjaxDropDownChoicePanel<AnyTypeTO> otherType = new AjaxDropDownChoicePanel<>( |
| "otherType", "otherType", new PropertyModel<>(rel, "otherType") { |
| |
| private static final long serialVersionUID = -5861057041758169508L; |
| |
| @Override |
| public AnyTypeTO getObject() { |
| for (AnyTypeTO obj : availableTypes) { |
| if (obj.getKey().equals(rel.getOtherEndType())) { |
| return obj; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public void setObject(final AnyTypeTO object) { |
| rel.setOtherEndType(Optional.ofNullable(object).map(AnyTypeTO::getKey).orElse(null)); |
| } |
| }, false); |
| otherType.setChoices(availableTypes); |
| otherType.setChoiceRenderer(new IChoiceRenderer<>() { |
| |
| private static final long serialVersionUID = -734743540442190178L; |
| |
| @Override |
| public Object getDisplayValue(final AnyTypeTO object) { |
| return object.getKey(); |
| } |
| |
| @Override |
| public String getIdValue(final AnyTypeTO object, final int index) { |
| return object.getKey(); |
| } |
| |
| @Override |
| public AnyTypeTO getObject(final String id, final IModel<? extends List<? extends AnyTypeTO>> choices) { |
| return choices.getObject().stream(). |
| filter(anyTypeTO -> id.equals(anyTypeTO.getKey())).findAny().orElse(null); |
| } |
| }); |
| // enable "otherType" dropdown only if "type" option is selected - SYNCOPE-1140 |
| otherType.setEnabled(false); |
| add(otherType); |
| |
| final WebMarkupContainer container = new WebMarkupContainer("searchPanelContainer"); |
| container.setOutputMarkupId(true); |
| add(container); |
| |
| Fragment emptyFragment = new Fragment("searchPanel", "emptyFragment", this); |
| container.add(emptyFragment.setRenderBodyOnly(true)); |
| |
| type.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { |
| |
| private static final long serialVersionUID = -1107858522700306810L; |
| |
| @Override |
| protected void onUpdate(final AjaxRequestTarget target) { |
| Fragment emptyFragment = new Fragment("searchPanel", "emptyFragment", Specification.this); |
| container.addOrReplace(emptyFragment.setRenderBodyOnly(true)); |
| otherType.setModelObject(null); |
| // enable "otherType" dropdown only if "type" option is selected - SYNCOPE-1140 |
| otherType.setEnabled(type.getModelObject() != null && !type.getModelObject().isEmpty()); |
| target.add(otherType); |
| target.add(container); |
| } |
| }); |
| |
| otherType.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { |
| |
| private static final long serialVersionUID = -1107858522700306810L; |
| |
| @Override |
| protected void onUpdate(final AjaxRequestTarget target) { |
| final AnyTypeTO anyType = otherType.getModelObject(); |
| if (anyType == null) { |
| Fragment emptyFragment = new Fragment("searchPanel", "emptyFragment", Specification.this); |
| container.addOrReplace(emptyFragment.setRenderBodyOnly(true)); |
| } else { |
| final Fragment fragment = new Fragment("searchPanel", "searchFragment", Specification.this); |
| container.addOrReplace(fragment.setRenderBodyOnly(true)); |
| |
| anyObjectSearchPanel = new AnyObjectSearchPanel.Builder( |
| anyType.getKey(), |
| new ListModel<>(new ArrayList<>())). |
| enableSearch(Specification.this). |
| build("searchPanel"); |
| fragment.add(anyObjectSearchPanel.setRenderBodyOnly(true)); |
| |
| anyObjectDirectoryPanel = new AnyObjectSelectionDirectoryPanel.Builder( |
| AnyTypeClassRestClient.list(anyType.getClasses()), |
| anyType.getKey(), |
| pageRef). |
| setFiql(SyncopeClient.getAnyObjectSearchConditionBuilder(anyType.getKey()). |
| is(Constants.KEY_FIELD_NAME).notNullValue().query()). |
| setWizardInModal(true).build("searchResultPanel"); |
| fragment.add(anyObjectDirectoryPanel.setRenderBodyOnly(true)); |
| } |
| target.add(container); |
| } |
| }); |
| } |
| |
| @Override |
| public void onEvent(final IEvent<?> event) { |
| if (event.getPayload() instanceof SearchClausePanel.SearchEvent) { |
| final AjaxRequestTarget target = SearchClausePanel.SearchEvent.class.cast(event.getPayload()). |
| getTarget(); |
| final String fiql = SearchUtils.buildFIQL(anyObjectSearchPanel.getModel().getObject(), |
| SyncopeClient.getAnyObjectSearchConditionBuilder(anyObjectSearchPanel.getBackObjectType())); |
| AnyDirectoryPanel.class.cast(Specification.this.anyObjectDirectoryPanel).search(fiql, target); |
| } else if (event.getPayload() instanceof AnySelectionDirectoryPanel.ItemSelection) { |
| final AjaxRequestTarget target = AnySelectionDirectoryPanel.ItemSelection.class.cast(event. |
| getPayload()).getTarget(); |
| |
| AnyTO right = AnySelectionDirectoryPanel.ItemSelection.class.cast(event.getPayload()).getSelection(); |
| rel.setOtherEndKey(right.getKey()); |
| |
| Relationships.this.addNewRelationships(rel); |
| |
| Relationships.this.addOrReplace(getViewFragment().setRenderBodyOnly(true)); |
| target.add(Relationships.this); |
| } else { |
| super.onEvent(event); |
| } |
| } |
| } |
| } |