/*
* Copyright (c) 2010-2016 Evolveum
*
* Licensed 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 com.evolveum.midpoint.web.page.admin.users.component;
import com.evolveum.midpoint.gui.api.component.BasePanel;
import com.evolveum.midpoint.gui.api.page.PageBase;
import com.evolveum.midpoint.gui.api.util.WebComponentUtil;
import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils;
import com.evolveum.midpoint.model.api.ModelExecuteOptions;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismObjectDefinition;
import com.evolveum.midpoint.prism.delta.ContainerDelta;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.prism.query.OrgFilter;
import com.evolveum.midpoint.prism.query.OrgFilter.Scope;
import com.evolveum.midpoint.prism.query.builder.QueryBuilder;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.web.component.data.column.ColumnMenuAction;
import com.evolveum.midpoint.web.component.dialog.ConfirmationPanel;
import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItem;
import com.evolveum.midpoint.web.component.util.SelectableBean;
import com.evolveum.midpoint.web.page.admin.orgs.OrgTreeAssignablePanel;
import com.evolveum.midpoint.web.page.admin.orgs.OrgTreePanel;
import com.evolveum.midpoint.web.page.admin.users.PageOrgTree;
import com.evolveum.midpoint.web.page.admin.users.PageOrgUnit;
import com.evolveum.midpoint.web.util.OnePageParameterEncoder;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import org.apache.wicket.RestartResponseException;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.List;
/**
* Used as a main component of the Org tree page.
*
* todo create function computeHeight() in midpoint.js, update height properly
* when in "mobile" mode... [lazyman] todo implement midpoint theme for tree
* [lazyman]
*
* @author lazyman
* @author katkav
*/
public class TreeTablePanel extends BasePanel<String> {
private static final long serialVersionUID = 1L;
private PageBase parentPage;
@Override
public PageBase getPageBase() {
return parentPage;
}
protected static final String DOT_CLASS = TreeTablePanel.class.getName() + ".";
protected static final String OPERATION_DELETE_OBJECTS = DOT_CLASS + "deleteObjects";
protected static final String OPERATION_DELETE_OBJECT = DOT_CLASS + "deleteObject";
protected static final String OPERATION_CHECK_PARENTS = DOT_CLASS + "checkParents";
protected static final String OPERATION_MOVE_OBJECTS = DOT_CLASS + "moveObjects";
protected static final String OPERATION_MOVE_OBJECT = DOT_CLASS + "moveObject";
protected static final String OPERATION_UPDATE_OBJECTS = DOT_CLASS + "updateObjects";
protected static final String OPERATION_UPDATE_OBJECT = DOT_CLASS + "updateObject";
protected static final String OPERATION_RECOMPUTE = DOT_CLASS + "recompute";
protected static final String OPERATION_SEARCH_MANAGERS = DOT_CLASS + "searchManagers";
protected static final String OPERATION_COUNT_CHILDREN = DOT_CLASS + "countChildren";
private static final String ID_TREE_PANEL = "treePanel";
private static final String ID_MEMBER_PANEL = "memberPanel";
private static final Trace LOGGER = TraceManager.getTrace(TreeTablePanel.class);
public TreeTablePanel(String id, IModel<String> rootOid, PageBase parentPage) {
super(id, rootOid);
this.parentPage = parentPage;
setParent(parentPage);
initLayout();
}
protected void initLayout() {
OrgTreePanel treePanel = new OrgTreePanel(ID_TREE_PANEL, getModel(), false) {
private static final long serialVersionUID = 1L;
@Override
protected void selectTreeItemPerformed(SelectableBean<OrgType> selected,
AjaxRequestTarget target) {
TreeTablePanel.this.selectTreeItemPerformed(selected, target);
}
protected List<InlineMenuItem> createTreeMenu() {
return TreeTablePanel.this.createTreeMenu();
}
@Override
protected List<InlineMenuItem> createTreeChildrenMenu() {
return TreeTablePanel.this.createTreeChildrenMenu();
}
};
treePanel.setOutputMarkupId(true);
add(treePanel);
add(createMemberPanel(treePanel.getSelected().getValue()));
setOutputMarkupId(true);
}
private OrgMemberPanel createMemberPanel(OrgType org) {
OrgMemberPanel memberPanel = new OrgMemberPanel(ID_MEMBER_PANEL, new Model<OrgType>(org), parentPage);
memberPanel.setOutputMarkupId(true);
return memberPanel;
}
private OrgTreePanel getTreePanel() {
return (OrgTreePanel) get(ID_TREE_PANEL);
}
private List<InlineMenuItem> createTreeMenu() {
List<InlineMenuItem> items = new ArrayList<>();
return items;
}
private List<InlineMenuItem> createTreeChildrenMenu() {
List<InlineMenuItem> items = new ArrayList<>();
InlineMenuItem item = new InlineMenuItem(createStringResource("TreeTablePanel.move"),
new ColumnMenuAction<SelectableBean<OrgType>>() {
private static final long serialVersionUID = 1L;
@Override
public void onClick(AjaxRequestTarget target) {
moveRootPerformed(getRowModel().getObject(), target);
}
});
items.add(item);
item = new InlineMenuItem(createStringResource("TreeTablePanel.makeRoot"),
new ColumnMenuAction<SelectableBean<OrgType>>() {
private static final long serialVersionUID = 1L;
@Override
public void onClick(AjaxRequestTarget target) {
makeRootPerformed(getRowModel().getObject(), target);
}
});
items.add(item);
item = new InlineMenuItem(createStringResource("TreeTablePanel.delete"),
new ColumnMenuAction<SelectableBean<OrgType>>() {
private static final long serialVersionUID = 1L;
@Override
public void onClick(AjaxRequestTarget target) {
deleteNodePerformed(getRowModel().getObject(), target);
}
});
items.add(item);
item = new InlineMenuItem(createStringResource("TreeTablePanel.recompute"),
new ColumnMenuAction<SelectableBean<OrgType>>() {
private static final long serialVersionUID = 1L;
@Override
public void onClick(AjaxRequestTarget target) {
recomputeRootPerformed(getRowModel().getObject(), target);
}
});
items.add(item);
item = new InlineMenuItem(createStringResource("TreeTablePanel.edit"),
new ColumnMenuAction<SelectableBean<OrgType>>() {
private static final long serialVersionUID = 1L;
@Override
public void onClick(AjaxRequestTarget target) {
editRootPerformed(getRowModel().getObject(), target);
}
});
items.add(item);
item = new InlineMenuItem(createStringResource("TreeTablePanel.createChild"),
new ColumnMenuAction<SelectableBean<OrgType>>() {
private static final long serialVersionUID = 1L;
@Override
public void onClick(AjaxRequestTarget target) {
try {
initObjectForAdd(
ObjectTypeUtil.createObjectRef(getRowModel().getObject().getValue()),
OrgType.COMPLEX_TYPE, null, target);
} catch (SchemaException e) {
throw new SystemException(e.getMessage(), e);
}
}
});
items.add(item);
return items;
}
// TODO: merge this with AbstractRoleMemeberPanel.initObjectForAdd, also see MID-3233
private void initObjectForAdd(ObjectReferenceType parentOrgRef, QName type, QName relation,
AjaxRequestTarget target) throws SchemaException {
TreeTablePanel.this.getPageBase().hideMainPopup(target);
PrismContext prismContext = TreeTablePanel.this.getPageBase().getPrismContext();
PrismObjectDefinition def = prismContext.getSchemaRegistry().findObjectDefinitionByType(type);
PrismObject obj = def.instantiate();
ObjectType objType = (ObjectType) obj.asObjectable();
if (FocusType.class.isAssignableFrom(obj.getCompileTimeClass())) {
AssignmentType assignment = new AssignmentType();
assignment.setTargetRef(parentOrgRef);
((FocusType) objType).getAssignment().add(assignment);
}
// Set parentOrgRef in any case. This is not strictly correct.
// The parentOrgRef should be added by the projector. But
// this is needed to successfully pass through security
// TODO: fix MID-3234
if (parentOrgRef == null) {
ObjectType org = getTreePanel().getSelected().getValue();
parentOrgRef = ObjectTypeUtil.createObjectRef(org);
parentOrgRef.setRelation(relation);
objType.getParentOrgRef().add(parentOrgRef);
} else {
objType.getParentOrgRef().add(parentOrgRef.clone());
}
WebComponentUtil.dispatchToObjectDetailsPage(obj, this);
}
private void selectTreeItemPerformed(SelectableBean<OrgType> selected, AjaxRequestTarget target) {
if (selected.getValue() == null) {
return;
}
getTreePanel().setSelected(selected);
target.add(addOrReplace(createMemberPanel(selected.getValue())));
}
private void moveRootPerformed(SelectableBean<OrgType> root, AjaxRequestTarget target) {
if (root == null) {
root = getTreePanel().getRootFromProvider();
}
final SelectableBean<OrgType> orgToMove = root;
OrgTreeAssignablePanel orgAssignablePanel = new OrgTreeAssignablePanel(
parentPage.getMainPopupBodyId(), false, parentPage) {
private static final long serialVersionUID = 1L;
@Override
protected void onItemSelect(SelectableBean<OrgType> selected, AjaxRequestTarget target) {
moveConfirmPerformed(orgToMove, selected, target);
}
};
parentPage.showMainPopup(orgAssignablePanel, target);
}
private void moveConfirmPerformed(SelectableBean<OrgType> orgToMove, SelectableBean<OrgType> selected,
AjaxRequestTarget target) {
getPageBase().hideMainPopup(target);
Task task = getPageBase().createSimpleTask(OPERATION_MOVE_OBJECT);
OperationResult result = new OperationResult(OPERATION_MOVE_OBJECT);
OrgType toMove = orgToMove.getValue();
if (toMove == null || selected.getValue() == null) {
return;
}
ObjectDelta<OrgType> moveOrgDelta = ObjectDelta.createEmptyModifyDelta(OrgType.class, toMove.getOid(),
getPageBase().getPrismContext());
try {
for (OrgType parentOrg : toMove.getParentOrg()) {
AssignmentType oldRoot = new AssignmentType();
oldRoot.setTargetRef(ObjectTypeUtil.createObjectRef(parentOrg));
moveOrgDelta.addModification(ContainerDelta.createModificationDelete(OrgType.F_ASSIGNMENT,
OrgType.class, getPageBase().getPrismContext(), oldRoot.asPrismContainerValue()));
// moveOrgDelta.addModification(ReferenceDelta.createModificationDelete(OrgType.F_PARENT_ORG_REF,
// toMove.asPrismObject().getDefinition(),
// ObjectTypeUtil.createObjectRef(parentOrg).asReferenceValue()));
}
AssignmentType newRoot = new AssignmentType();
newRoot.setTargetRef(ObjectTypeUtil.createObjectRef(selected.getValue()));
moveOrgDelta.addModification(ContainerDelta.createModificationAdd(OrgType.F_ASSIGNMENT,
OrgType.class, getPageBase().getPrismContext(), newRoot.asPrismContainerValue()));
// moveOrgDelta.addModification(ReferenceDelta.createModificationAdd(OrgType.F_PARENT_ORG_REF,
// toMove.asPrismObject().getDefinition(),
// ObjectTypeUtil.createObjectRef(selected.getValue()).asReferenceValue()));
getPageBase().getPrismContext().adopt(moveOrgDelta);
getPageBase().getModelService()
.executeChanges(WebComponentUtil.createDeltaCollection(moveOrgDelta), null, task, result);
result.computeStatus();
} catch (ObjectAlreadyExistsException | ObjectNotFoundException | SchemaException
| ExpressionEvaluationException | CommunicationException | ConfigurationException
| PolicyViolationException | SecurityViolationException e) {
result.recordFatalError("Failed to move organization unit " + toMove, e);
LoggingUtils.logUnexpectedException(LOGGER, "Failed to move organization unit" + toMove, e);
}
parentPage.showResult(result);
target.add(parentPage.getFeedbackPanel());
setResponsePage(PageOrgTree.class);
}
private void makeRootPerformed(SelectableBean<OrgType> newRoot, AjaxRequestTarget target) {
Task task = getPageBase().createSimpleTask(OPERATION_MOVE_OBJECT);
OperationResult result = new OperationResult(OPERATION_MOVE_OBJECT);
OrgType toMove = newRoot.getValue();
if (toMove == null) {
return;
}
ObjectDelta<OrgType> moveOrgDelta = ObjectDelta.createEmptyModifyDelta(OrgType.class, toMove.getOid(),
getPageBase().getPrismContext());
try {
for (ObjectReferenceType parentOrg : toMove.getParentOrgRef()) {
AssignmentType oldRoot = new AssignmentType();
oldRoot.setTargetRef(parentOrg);
moveOrgDelta.addModification(ContainerDelta.createModificationDelete(OrgType.F_ASSIGNMENT,
OrgType.class, getPageBase().getPrismContext(), oldRoot.asPrismContainerValue()));
}
getPageBase().getPrismContext().adopt(moveOrgDelta);
getPageBase().getModelService()
.executeChanges(WebComponentUtil.createDeltaCollection(moveOrgDelta), null, task, result);
result.computeStatus();
} catch (ObjectAlreadyExistsException | ObjectNotFoundException | SchemaException
| ExpressionEvaluationException | CommunicationException | ConfigurationException
| PolicyViolationException | SecurityViolationException e) {
result.recordFatalError("Failed to move organization unit " + toMove, e);
LoggingUtils.logUnexpectedException(LOGGER, "Failed to move organization unit" + toMove, e);
}
parentPage.showResult(result);
target.add(parentPage.getFeedbackPanel());
// target.add(getTreePanel());
setResponsePage(PageOrgTree.class);
}
private void recomputeRootPerformed(SelectableBean<OrgType> root, AjaxRequestTarget target) {
if (root == null) {
root = getTreePanel().getRootFromProvider();
}
recomputePerformed(root, target);
}
private void recomputePerformed(SelectableBean<OrgType> orgToRecompute, AjaxRequestTarget target) {
Task task = getPageBase().createSimpleTask(OPERATION_RECOMPUTE);
OperationResult result = new OperationResult(OPERATION_RECOMPUTE);
if (orgToRecompute.getValue() == null) {
return;
}
try {
ObjectDelta emptyDelta = ObjectDelta.createEmptyModifyDelta(OrgType.class,
orgToRecompute.getValue().getOid(), getPageBase().getPrismContext());
ModelExecuteOptions options = new ModelExecuteOptions();
options.setReconcile(true);
getPageBase().getModelService().executeChanges(WebComponentUtil.createDeltaCollection(emptyDelta),
options, task, result);
result.recordSuccess();
} catch (Exception e) {
result.recordFatalError(getString("TreeTablePanel.message.recomputeError"), e);
LoggingUtils.logUnexpectedException(LOGGER, getString("TreeTablePanel.message.recomputeError"), e);
}
getPageBase().showResult(result);
target.add(getPageBase().getFeedbackPanel());
getTreePanel().refreshTabbedPanel(target);
}
private void deleteNodePerformed(final SelectableBean<OrgType> orgToDelete, AjaxRequestTarget target) {
ConfirmationPanel confirmationPanel = new ConfirmationPanel(getPageBase().getMainPopupBodyId(),
new AbstractReadOnlyModel<String>() {
private static final long serialVersionUID = 1L;
@Override
public String getObject() {
if (hasChildren(orgToDelete)) {
return createStringResource("TreeTablePanel.message.warn.deleteTreeObjectConfirm",
WebComponentUtil.getEffectiveName(orgToDelete.getValue(),
OrgType.F_DISPLAY_NAME)).getObject();
}
return createStringResource("TreeTablePanel.message.deleteTreeObjectConfirm",
WebComponentUtil.getEffectiveName(orgToDelete.getValue(),
OrgType.F_DISPLAY_NAME)).getObject();
}
}) {
private static final long serialVersionUID = 1L;
@Override
public void yesPerformed(AjaxRequestTarget target) {
deleteNodeConfirmedPerformed(orgToDelete, target);
}
};
confirmationPanel.setOutputMarkupId(true);
getPageBase().showMainPopup(confirmationPanel, target);
}
private boolean hasChildren(SelectableBean<OrgType> orgToDelete) {
ObjectQuery query = QueryBuilder.queryFor(ObjectType.class, getPageBase().getPrismContext())
.isChildOf(orgToDelete.getValue().getOid()) // TODO what if orgToDelete.getValue()==null
.build();
Task task = getPageBase().createSimpleTask(OPERATION_COUNT_CHILDREN);
OperationResult result = new OperationResult(OPERATION_COUNT_CHILDREN);
try {
int count = getPageBase().getModelService().countObjects(ObjectType.class,
query, null, task, result);
return (count > 0);
} catch (SchemaException | ObjectNotFoundException | SecurityViolationException
| ConfigurationException | CommunicationException e) {
LoggingUtils.logUnexpectedException(LOGGER, e.getMessage(), e);
result.recordFatalError("Could not count members for org " + orgToDelete.getValue(), e);
return false;
}
}
private void deleteNodeConfirmedPerformed(SelectableBean<OrgType> orgToDelete, AjaxRequestTarget target) {
getPageBase().hideMainPopup(target);
OperationResult result = new OperationResult(OPERATION_DELETE_OBJECT);
PageBase page = getPageBase();
if (orgToDelete == null) {
orgToDelete = getTreePanel().getRootFromProvider();
}
if (orgToDelete.getValue() == null) {
return;
}
String oidToDelete = orgToDelete.getValue().getOid();
WebModelServiceUtils.deleteObject(OrgType.class, oidToDelete, result, page);
result.computeStatusIfUnknown();
page.showResult(result);
// even if we theoretically could refresh page only if non-leaf node is deleted,
// for simplicity we do it each time
//
// Instruction to refresh only the part would be:
// - getTreePanel().refreshTabbedPanel(target);
//
// But how to refresh whole page? target.add(getPage()) is not sufficient - content is unchanged;
// so we use the following.
// TODO is this ok? [pmed]
throw new RestartResponseException(getPage().getClass());
}
private void editRootPerformed(SelectableBean<OrgType> root, AjaxRequestTarget target) {
if (root == null) {
root = getTreePanel().getRootFromProvider();
}
if (root.getValue() == null) {
return;
}
PageParameters parameters = new PageParameters();
parameters.add(OnePageParameterEncoder.PARAMETER, root.getValue().getOid());
getPageBase().navigateToNext(PageOrgUnit.class, parameters);
}
}