package com.evolveum.midpoint.web.page.self;
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.model.api.context.*;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.delta.ContainerDelta;
import com.evolveum.midpoint.prism.delta.DeltaSetTriple;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.query.InOidFilter;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.result.OperationResultStatus;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.task.api.TaskCategory;
import com.evolveum.midpoint.util.exception.SchemaException;
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.AjaxButton;
import com.evolveum.midpoint.web.component.AjaxSubmitButton;
import com.evolveum.midpoint.web.component.assignment.AssignmentEditorDto;
import com.evolveum.midpoint.web.component.assignment.AssignmentTablePanel;
import com.evolveum.midpoint.web.component.form.Form;
import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItem;
import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItemAction;
import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour;
import com.evolveum.midpoint.web.page.admin.users.component.ExecuteChangeOptionsDto;
import com.evolveum.midpoint.web.page.admin.users.dto.UserDtoStatus;
import com.evolveum.midpoint.web.page.self.dto.AssignmentConflictDto;
import com.evolveum.midpoint.web.session.SessionStorage;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import org.apache.commons.lang.StringUtils;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* Created by honchar.
*/
public class PageAssignmentsList<F extends FocusType> extends PageBase{
private static final String ID_ASSIGNMENT_TABLE_PANEL = "assignmentTablePanel";
private static final String ID_FORM = "mainForm";
private static final String ID_BACK = "back";
private static final String ID_REQUEST_BUTTON = "request";
private static final String ID_RESOLVE_CONFLICTS_BUTTON = "resolveConflicts";
private static final String ID_DESCRIPTION = "description";
private static final Trace LOGGER = TraceManager.getTrace(PageRequestRole.class);
private static final String DOT_CLASS = PageAssignmentsList.class.getName() + ".";
private static final String OPERATION_REQUEST_ASSIGNMENTS = DOT_CLASS + "requestAssignments";
private static final String OPERATION_WF_TASK_CREATED = "com.evolveum.midpoint.wf.impl.WfHook.invoke";
private static final String OPERATION_PREVIEW_ASSIGNMENT_CONFLICTS = "reviewAssignmentConflicts";
private IModel<List<AssignmentEditorDto>> assignmentsModel;
private OperationResult backgroundTaskOperationResult = null;
IModel<String> descriptionModel;
public PageAssignmentsList() {
this(false);
}
public PageAssignmentsList(boolean setConflictsToSession){
initModels();
if (setConflictsToSession) {
getSessionStorage().getRoleCatalog().setConflictsList(getAssignmentConflicts());
}
initLayout();
}
private void initModels() {
assignmentsModel = Model.ofList(getSessionStorage().getRoleCatalog().getAssignmentShoppingCart());
descriptionModel = Model.of(getSessionStorage().getRoleCatalog().getRequestDescription());
}
public void initLayout() {
setOutputMarkupId(true);
Form mainForm = new Form(ID_FORM);
mainForm.setOutputMarkupId(true);
add(mainForm);
AssignmentTablePanel panel = new AssignmentTablePanel<UserType>(ID_ASSIGNMENT_TABLE_PANEL,
createStringResource("FocusType.assignment"), assignmentsModel, PageAssignmentsList.this){
@Override
protected List<InlineMenuItem> createAssignmentMenu() {
List<InlineMenuItem> items = new ArrayList<>();
InlineMenuItem item = new InlineMenuItem(createStringResource("AssignmentTablePanel.menu.unassign"),
new InlineMenuItemAction() {
private static final long serialVersionUID = 1L;
@Override
public void onClick(AjaxRequestTarget target) {
deleteAssignmentPerformed(target);
}
});
items.add(item);
return items;
}
};
mainForm.add(panel);
TextArea descriptionInput = new TextArea<String>(ID_DESCRIPTION, descriptionModel);
descriptionInput.add(new AjaxFormComponentUpdatingBehavior("blur") {
@Override
protected void onUpdate(AjaxRequestTarget target) {
getSessionStorage().getRoleCatalog().setRequestDescription(getDescriptionComponent().getValue());
}
});
mainForm.add(descriptionInput);
AjaxButton back = new AjaxButton(ID_BACK, createStringResource("PageAssignmentDetails.backButton")) {
@Override
public void onClick(AjaxRequestTarget target) {
redirectBack();
}
};
mainForm.add(back);
AjaxSubmitButton requestAssignments = new AjaxSubmitButton(ID_REQUEST_BUTTON, createStringResource("PageAssignmentsList.requestButton")) {
@Override
protected void onError(AjaxRequestTarget target, org.apache.wicket.markup.html.form.Form<?> form) {
target.add(getFeedbackPanel());
}
@Override
protected void onSubmit(AjaxRequestTarget target, org.apache.wicket.markup.html.form.Form<?> form) {
if (getSessionStorage().getRoleCatalog().getTargetUserList() == null ||
getSessionStorage().getRoleCatalog().getTargetUserList().size() <= 1) {
onSingleUserRequestPerformed(target);
} else {
onMultiUserRequestPerformed(target);
}
}
};
requestAssignments.add(new VisibleEnableBehaviour(){
@Override
public boolean isEnabled(){
return getSessionStorage().getRoleCatalog().isMultiUserRequest() || areConflictsResolved();
}
});
mainForm.add(requestAssignments);
AjaxSubmitButton resolveAssignments = new AjaxSubmitButton(ID_RESOLVE_CONFLICTS_BUTTON,
createStringResource("PageAssignmentsList.resolveConflicts")) {
@Override
protected void onError(AjaxRequestTarget target, org.apache.wicket.markup.html.form.Form<?> form) {
target.add(getFeedbackPanel());
}
@Override
protected void onSubmit(AjaxRequestTarget target, org.apache.wicket.markup.html.form.Form<?> form) {
PageAssignmentsList.this.navigateToNext(PageAssignmentConflicts.class);
}
};
resolveAssignments.add(new VisibleEnableBehaviour(){
@Override
public boolean isVisible(){
return !getSessionStorage().getRoleCatalog().isMultiUserRequest()
&& getSessionStorage().getRoleCatalog().getConflictsList() != null
&& getSessionStorage().getRoleCatalog().getConflictsList().size() > 0;
}
});
mainForm.add(resolveAssignments);
}
private void onSingleUserRequestPerformed(AjaxRequestTarget target) {
OperationResult result = new OperationResult(OPERATION_REQUEST_ASSIGNMENTS);
ObjectDelta<UserType> delta;
try {
PrismObject<UserType> user = getTargetUser();
delta = user.createModifyDelta();
PrismContainerDefinition def = user.getDefinition().findContainerDefinition(UserType.F_ASSIGNMENT);
handleAssignmentDeltas(delta, addAssignmentsToUser(user.asObjectable()), def);
getModelService().executeChanges(Collections.singletonList(delta), createOptions(), createSimpleTask(OPERATION_REQUEST_ASSIGNMENTS), result);
result.recordSuccess();
SessionStorage storage = getSessionStorage();
storage.getRoleCatalog().getAssignmentShoppingCart().clear();
} catch (Exception e) {
LoggingUtils.logUnexpectedException(LOGGER, "Could not save assignments ", e);
error("Could not save assignments. Reason: " + e);
target.add(getFeedbackPanel());
} finally {
result.recomputeStatus();
}
findBackgroundTaskOperation(result);
if (backgroundTaskOperationResult != null
&& StringUtils.isNotEmpty(backgroundTaskOperationResult.getBackgroundTaskOid())){
result.setMessage(createStringResource("operation.com.evolveum.midpoint.web.page.self.PageRequestRole.taskCreated").getString());
showResult(result);
setResponsePage(PageAssignmentShoppingKart.class);
return;
}
showResult(result);
if (!WebComponentUtil.isSuccessOrHandledError(result)) {
target.add(getFeedbackPanel());
target.add(PageAssignmentsList.this.get(ID_FORM));
} else {
setResponsePage(PageAssignmentShoppingKart.class);
}
}
@NotNull
private ModelExecuteOptions createOptions() {
OperationBusinessContextType businessContextType;
if (descriptionModel.getObject() != null) {
businessContextType = new OperationBusinessContextType();
businessContextType.setComment(descriptionModel.getObject());
} else {
businessContextType = null;
}
ModelExecuteOptions options = ExecuteChangeOptionsDto.createFromSystemConfiguration().createOptions();
options.setRequestBusinessContext(businessContextType);
return options;
}
private void onMultiUserRequestPerformed(AjaxRequestTarget target) {
OperationResult result = new OperationResult(OPERATION_REQUEST_ASSIGNMENTS);
Task operationalTask = createSimpleTask(OPERATION_REQUEST_ASSIGNMENTS);
try {
TaskType task = WebComponentUtil.createSingleRecurrenceTask(
createStringResource(OPERATION_REQUEST_ASSIGNMENTS).getString(),
UserType.COMPLEX_TYPE,
getTaskQuery(), prepareDelta(result), createOptions(), TaskCategory.EXECUTE_CHANGES, PageAssignmentsList.this);
WebModelServiceUtils.runTask(task, operationalTask, result, PageAssignmentsList.this);
} catch (SchemaException e) {
result.recordFatalError(result.getOperation(), e);
LoggingUtils.logUnexpectedException(LOGGER,
"Failed to execute operaton " + result.getOperation(), e);
target.add(getFeedbackPanel());
}
findBackgroundTaskOperation(result);
if (backgroundTaskOperationResult != null
&& StringUtils.isNotEmpty(backgroundTaskOperationResult.getBackgroundTaskOid())) {
result.setMessage(createStringResource("operation.com.evolveum.midpoint.web.page.self.PageRequestRole.taskCreated").getString());
showResult(result);
setResponsePage(PageAssignmentShoppingKart.class);
return;
}
if (WebComponentUtil.isSuccessOrHandledError(result)
|| OperationResultStatus.IN_PROGRESS.equals(result.getStatus())) {
SessionStorage storage = getSessionStorage();
if (storage.getRoleCatalog().getAssignmentShoppingCart() != null) {
storage.getRoleCatalog().getAssignmentShoppingCart().clear();
}
if (storage.getRoleCatalog().getTargetUserList() != null){
storage.getRoleCatalog().getTargetUserList().clear();
}
setResponsePage(PageAssignmentShoppingKart.class);
} else {
showResult(result);
target.add(getFeedbackPanel());
target.add(PageAssignmentsList.this.get(ID_FORM));
}
}
private ContainerDelta handleAssignmentDeltas(ObjectDelta<UserType> focusDelta,
List<AssignmentEditorDto> assignments, PrismContainerDefinition def) throws SchemaException {
ContainerDelta assDelta = new ContainerDelta(new ItemPath(), def.getName(), def, getPrismContext());
for (AssignmentEditorDto assDto : assignments) {
PrismContainerValue newValue = assDto.getNewValue(getPrismContext());
switch (assDto.getStatus()) {
case ADD:
newValue.applyDefinition(def, false);
assDelta.addValueToAdd(newValue.clone());
break;
case DELETE:
PrismContainerValue oldValue = assDto.getOldValue();
oldValue.applyDefinition(def);
assDelta.addValueToDelete(oldValue.clone());
break;
case MODIFY:
if (!assDto.isModified(getPrismContext())) {
LOGGER.trace("Assignment '{}' not modified.", new Object[]{assDto.getName()});
continue;
}
handleModifyAssignmentDelta(assDto, def, newValue, focusDelta);
break;
default:
warn(getString("pageAdminUser.message.illegalAssignmentState", assDto.getStatus()));
}
}
if (!assDelta.isEmpty()) {
assDelta = focusDelta.addModification(assDelta);
}
return assDelta;
}
private void findBackgroundTaskOperation(OperationResult result){
if (backgroundTaskOperationResult != null) {
return;
} else {
List<OperationResult> subresults = result.getSubresults();
if (subresults == null || subresults.size() == 0) {
return;
}
for (OperationResult subresult : subresults) {
if (subresult.getOperation().equals(OPERATION_WF_TASK_CREATED)) {
backgroundTaskOperationResult = subresult;
return;
} else {
findBackgroundTaskOperation(subresult);
}
}
}
return;
}
private void handleModifyAssignmentDelta(AssignmentEditorDto assDto,
PrismContainerDefinition assignmentDef, PrismContainerValue newValue, ObjectDelta<UserType> focusDelta)
throws SchemaException {
LOGGER.debug("Handling modified assignment '{}', computing delta.",
new Object[]{assDto.getName()});
PrismValue oldValue = assDto.getOldValue();
Collection<? extends ItemDelta> deltas = oldValue.diff(newValue);
for (ItemDelta delta : deltas) {
ItemPath deltaPath = delta.getPath().rest();
ItemDefinition deltaDef = assignmentDef.findItemDefinition(deltaPath);
delta.setParentPath(WebComponentUtil.joinPath(oldValue.getPath(), delta.getPath().allExceptLast()));
delta.applyDefinition(deltaDef);
focusDelta.addModification(delta);
}
}
private List<AssignmentEditorDto> addAssignmentsToUser(UserType user){
List<String> assignmentsToDeselect = getAssignmentsToDeselectList(user);
List<AssignmentEditorDto> assignmentsToRemove = getAssignmentsToRemoveList(user);
List<AssignmentEditorDto> assignmentsList = new ArrayList<>();
assignmentsList.addAll(assignmentsToRemove);
if (assignmentsModel != null && assignmentsModel.getObject() != null) {
for (AssignmentEditorDto assignmentsToAdd : assignmentsModel.getObject()) {
if (!assignmentsToDeselect.contains(assignmentsToAdd.getTargetRef().getOid())) {
assignmentsToAdd.setStatus(UserDtoStatus.ADD);
assignmentsList.add(assignmentsToAdd);
}
}
}
return assignmentsList;
}
private List<AssignmentConflictDto> getAssignmentConflicts(){
ModelContext<UserType> modelContext = null;
ObjectDelta<UserType> delta;
OperationResult result = new OperationResult(OPERATION_PREVIEW_ASSIGNMENT_CONFLICTS);
Task task = createSimpleTask(OPERATION_PREVIEW_ASSIGNMENT_CONFLICTS);
List<AssignmentConflictDto> conflictsList = new ArrayList<>();
try {
PrismObject<UserType> user = getTargetUser();
delta = user.createModifyDelta();
PrismContainerDefinition def = user.getDefinition().findContainerDefinition(UserType.F_ASSIGNMENT);
handleAssignmentDeltas(delta, addAssignmentsToUser(user.asObjectable()), def);
modelContext = getModelInteractionService()
.previewChanges(WebComponentUtil.createDeltaCollection(delta), null, task, result);
DeltaSetTriple<? extends EvaluatedAssignment> evaluatedAssignmentTriple = modelContext
.getEvaluatedAssignmentTriple();
Collection<? extends EvaluatedAssignment> addedAssignments = evaluatedAssignmentTriple
.getPlusSet();
Map<String, AssignmentConflictDto> conflictOidsMap = new HashMap<>();
if (addedAssignments != null) {
for (EvaluatedAssignment<UserType> evaluatedAssignment : addedAssignments) {
for (EvaluatedPolicyRule policyRule : evaluatedAssignment.getAllTargetsPolicyRules()) {
for (EvaluatedPolicyRuleTrigger<?> trigger : policyRule.getAllTriggers()) {
if (trigger instanceof EvaluatedExclusionTrigger) {
PrismObject<F> addedAssignmentTargetObj = (PrismObject<F>)evaluatedAssignment.getTarget();
EvaluatedAssignment<F> conflictingAssignment = ((EvaluatedExclusionTrigger) trigger).getConflictingAssignment();
PrismObject<F> exclusionTargetObj = (PrismObject<F>)conflictingAssignment.getTarget();
AssignmentConflictDto dto = new AssignmentConflictDto(exclusionTargetObj, addedAssignmentTargetObj);
boolean isWarning = policyRule.getActions() != null
&& policyRule.getActions().getApproval() != null;
dto.setError(!isWarning);
if (conflictOidsMap.containsKey(exclusionTargetObj.getOid()) && isWarning){
conflictOidsMap.replace(exclusionTargetObj.getOid(), dto);
} else {
conflictOidsMap.put(exclusionTargetObj.getOid(), dto);
}
}
}
}
}
}
conflictsList.addAll(conflictOidsMap.values());
} catch (Exception e) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't get assignments conflicts. Reason: ", e);
error("Couldn't get assignments conflicts. Reason: " + e);
}
return conflictsList;
}
private boolean areConflictsResolved(){
List<AssignmentConflictDto> list = getSessionStorage().getRoleCatalog().getConflictsList();
for (AssignmentConflictDto dto : list){
if (!dto.isError()){
continue;
}
if (!dto.isSolved()){
return false;
}
}
return true;
}
private ObjectDelta prepareDelta(OperationResult result) {
ObjectDelta delta = null;
try{
delta = ObjectDelta.createModificationAddContainer(UserType.class, "fakeOid",
FocusType.F_ASSIGNMENT, getPrismContext(), getAddAssignmentContainerValues(assignmentsModel.getObject()));
if (!getSessionStorage().getRoleCatalog().isMultiUserRequest()) {
List<PrismObject<UserType>> usersList = getSessionStorage().getRoleCatalog().getTargetUserList();
PrismObject<UserType> user = usersList != null && usersList.size() > 0 ?
usersList.get(0) : loadUserSelf(PageAssignmentsList.this);
delta.addModificationDeleteContainer(FocusType.F_ASSIGNMENT,
getDeleteAssignmentContainerValues(user.asObjectable()));
}
} catch (SchemaException e) {
LoggingUtils.logUnexpectedException(LOGGER, "Failed to prepare delta for operation " + OPERATION_REQUEST_ASSIGNMENTS, e);
result.recordFatalError("Failed to prepare delta for operation " + OPERATION_REQUEST_ASSIGNMENTS, e);
}
return delta;
}
private ObjectQuery getTaskQuery(){
List<PrismObject<UserType>> userList = getSessionStorage().getRoleCatalog().getTargetUserList();
if (userList == null || userList.size() == 0){
userList.add(loadUserSelf(PageAssignmentsList.this));
}
Set<String> oids = new HashSet<>();
for (PrismObject<UserType> user : userList){
oids.add(user.getOid());
}
return ObjectQuery.createObjectQuery(InOidFilter.createInOid(oids));
}
private PrismContainerValue[] getAddAssignmentContainerValues(List<AssignmentEditorDto> assignments) throws SchemaException {
List<PrismContainerValue<AssignmentType>> addContainerValues = new ArrayList<>();
for (AssignmentEditorDto assDto : assignments) {
if (UserDtoStatus.ADD.equals(assDto.getStatus())) {
addContainerValues.add(assDto.getNewValue(getPrismContext()).clone()); // clone is to eliminate "Attempt to reset value parent ..." exceptions (in some cases)
}
}
return addContainerValues.toArray(new PrismContainerValue[addContainerValues.size()]);
}
private PrismContainerValue[] getDeleteAssignmentContainerValues(UserType user) throws SchemaException {
List<PrismContainerValue<AssignmentType>> deleteAssignmentValues = new ArrayList<>();
for (AssignmentEditorDto assDto : getAssignmentsToRemoveList(user)) {
deleteAssignmentValues.add(assDto.getNewValue(getPrismContext()));
}
return deleteAssignmentValues.toArray(new PrismContainerValue[deleteAssignmentValues.size()]);
}
private List<AssignmentEditorDto> getAssignmentsToRemoveList(UserType user){
List<AssignmentConflictDto> conflicts = getSessionStorage().getRoleCatalog().getConflictsList();
List<String> assignmentsToRemoveOids = new ArrayList<>();
for (AssignmentConflictDto dto : conflicts){
if (dto.isRemovedOld()){
assignmentsToRemoveOids.add(dto.getExistingAssignmentTargetObj().getOid());
}
}
List<AssignmentEditorDto> assignmentsToDelete = new ArrayList<>();
for (AssignmentType assignment : user.getAssignment()){
if (assignment.getTargetRef() == null){
continue;
}
if (assignmentsToRemoveOids.contains(assignment.getTargetRef().getOid())){
assignmentsToDelete.add(new AssignmentEditorDto(UserDtoStatus.DELETE, assignment, this));
}
}
return assignmentsToDelete;
}
private List<String> getAssignmentsToDeselectList(UserType user){
List<AssignmentConflictDto> conflicts = getSessionStorage().getRoleCatalog().getConflictsList();
List<String> assignmentsToDeselectOids = new ArrayList<>();
for (AssignmentConflictDto dto : conflicts){
if (dto.isUnassignedNew()){
assignmentsToDeselectOids.add(dto.getExistingAssignmentTargetObj().getOid());
}
}
return assignmentsToDeselectOids;
}
private TextArea getDescriptionComponent(){
return (TextArea) get(ID_FORM).get(ID_DESCRIPTION);
}
private PrismObject<UserType> getTargetUser() throws SchemaException {
List<PrismObject<UserType>> usersList = getSessionStorage().getRoleCatalog().getTargetUserList();
PrismObject<UserType> user = usersList != null && usersList.size() > 0 ?
usersList.get(0) : loadUserSelf(PageAssignmentsList.this);
getPrismContext().adopt(user);
return user;
}
@Override
public boolean canRedirectBack(){
return true;
}
}