/*
* 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;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.xml.namespace.QName;
import com.evolveum.midpoint.web.component.assignment.AssignmentEditorDto;
import org.apache.commons.lang.StringUtils;
import org.apache.wicket.Page;
import org.apache.wicket.RestartResponseException;
import org.apache.wicket.ajax.AbstractAjaxTimerBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.util.string.StringValue;
import com.evolveum.midpoint.gui.api.model.LoadableModel;
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.PrismObject;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.RetrieveOption;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.constants.ObjectTypes;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.AuthorizationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SystemException;
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.FocusSummaryPanel;
import com.evolveum.midpoint.web.component.objectdetails.AbstractObjectMainPanel;
import com.evolveum.midpoint.web.component.prism.ContainerStatus;
import com.evolveum.midpoint.web.component.prism.ObjectWrapper;
import com.evolveum.midpoint.web.component.prism.ObjectWrapperFactory;
import com.evolveum.midpoint.web.component.progress.ProgressReporter;
import com.evolveum.midpoint.web.component.progress.ProgressReportingAwarePage;
import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour;
import com.evolveum.midpoint.web.page.admin.users.dto.FocusSubwrapperDto;
import com.evolveum.midpoint.web.util.OnePageParameterEncoder;
import com.evolveum.midpoint.web.util.validation.MidpointFormValidator;
import com.evolveum.midpoint.web.util.validation.SimpleValidationError;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AdminGuiConfigurationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectFormType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectFormsType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
import org.apache.wicket.util.time.Duration;
/**
* @author semancik
*/
public abstract class PageAdminObjectDetails<O extends ObjectType> extends PageAdmin
implements ProgressReportingAwarePage {
private static final long serialVersionUID = 1L;
private static final String DOT_CLASS = PageAdminObjectDetails.class.getName() + ".";
public static final String PARAM_RETURN_PAGE = "returnPage";
private static final String OPERATION_LOAD_OBJECT = DOT_CLASS + "loadObject";
private static final String OPERATION_LOAD_PARENT_ORGS = DOT_CLASS + "loadParentOrgs";
private static final String OPERATION_LOAD_GUI_CONFIGURATION = DOT_CLASS + "loadGuiConfiguration";
protected static final String OPERATION_SAVE = DOT_CLASS + "save";
protected static final String OPERATION_PREVIEW_CHANGES = DOT_CLASS + "previewChanges";
protected static final String OPERATION_SEND_TO_SUBMIT = DOT_CLASS + "sendToSubmit";
protected static final String ID_SUMMARY_PANEL = "summaryPanel";
protected static final String ID_MAIN_PANEL = "mainPanel";
private static final Trace LOGGER = TraceManager.getTrace(PageAdminObjectDetails.class);
private LoadableModel<ObjectWrapper<O>> objectModel;
private LoadableModel<List<FocusSubwrapperDto<OrgType>>> parentOrgModel;
private ProgressReporter progressReporter;
// used to determine whether to leave this page or stay on it (after
// operation finishing)
private ObjectDelta<O> delta;
private AbstractObjectMainPanel<O> mainPanel;
private boolean saveOnConfigure; // ugly hack - whether to invoke 'Save' when returning to this page
@Override
protected void createBreadcrumb() {
createInstanceBreadcrumb();
}
@Override
protected void onConfigure() {
super.onConfigure();
if (saveOnConfigure) {
saveOnConfigure = false;
add(new AbstractAjaxTimerBehavior(Duration.milliseconds(100)) {
@Override
protected void onTimer(AjaxRequestTarget target) {
stop(target);
savePerformed(target);
}
});
}
}
@Override
protected IModel<String> createPageTitleModel() {
return new LoadableModel<String>() {
private static final long serialVersionUID = 1L;
@Override
protected String load() {
if (!isEditingFocus()) {
String key = "PageAdminObjectDetails.title.new" + getCompileTimeClass().getSimpleName();
return createStringResource(key).getObject();
}
String name = null;
if (getObjectWrapper() != null && getObjectWrapper().getObject() != null) {
name = WebComponentUtil.getName(getObjectWrapper().getObject());
}
String key = "PageAdminObjectDetails.title.edit" + getCompileTimeClass().getSimpleName();
return createStringResource(key, name).getObject();
}
};
}
public LoadableModel<ObjectWrapper<O>> getObjectModel() {
return objectModel;
}
public LoadableModel<List<FocusSubwrapperDto<OrgType>>> getParentOrgModel() {
return parentOrgModel;
}
protected AbstractObjectMainPanel<O> getMainPanel() {
return mainPanel;
}
public ObjectWrapper<O> getObjectWrapper() {
return objectModel.getObject();
}
public List<FocusSubwrapperDto<OrgType>> getParentOrgs() {
return parentOrgModel.getObject();
}
public ObjectDelta<O> getDelta() {
return delta;
}
public void setDelta(ObjectDelta<O> delta) {
this.delta = delta;
}
public ProgressReporter getProgressReporter() {
return progressReporter;
}
protected void reviveModels() throws SchemaException {
WebComponentUtil.revive(objectModel, getPrismContext());
WebComponentUtil.revive(parentOrgModel, getPrismContext());
}
public abstract Class<O> getCompileTimeClass();
public void initialize(final PrismObject<O> objectToEdit) {
initializeModel(objectToEdit);
initLayout();
}
protected void initializeModel(final PrismObject<O> objectToEdit) {
objectModel = new LoadableModel<ObjectWrapper<O>>(false) {
private static final long serialVersionUID = 1L;
@Override
protected ObjectWrapper<O> load() {
return loadObjectWrapper(objectToEdit);
}
};
parentOrgModel = new LoadableModel<List<FocusSubwrapperDto<OrgType>>>(false) {
private static final long serialVersionUID = 1L;
@Override
protected List<FocusSubwrapperDto<OrgType>> load() {
return loadOrgWrappers();
}
};
}
protected List<FocusSubwrapperDto<OrgType>> loadOrgWrappers() {
// WRONG!! TODO: fix
return null;
}
protected abstract O createNewObject();
protected void initLayout() {
initLayoutSummaryPanel();
mainPanel = createMainPanel(ID_MAIN_PANEL);
mainPanel.setOutputMarkupId(true);
add(mainPanel);
progressReporter = createProgressReporter("progressPanel");
add(progressReporter.getProgressPanel());
}
protected ProgressReporter createProgressReporter(String id) {
return ProgressReporter.create(id, this);
}
protected abstract FocusSummaryPanel<O> createSummaryPanel();
protected void initLayoutSummaryPanel() {
FocusSummaryPanel<O> summaryPanel = createSummaryPanel();
summaryPanel.setOutputMarkupId(true);
setSummaryPanelVisibility(summaryPanel);
add(summaryPanel);
}
protected void setSummaryPanelVisibility(FocusSummaryPanel<O> summaryPanel){
summaryPanel.add(new VisibleEnableBehaviour() {
private static final long serialVersionUID = 1L;
@Override
public boolean isVisible() {
return isEditingFocus();
}
});
}
protected abstract AbstractObjectMainPanel<O> createMainPanel(String id);
protected String getObjectOidParameter() {
PageParameters parameters = getPageParameters();
LOGGER.trace("Page parameters: {}", parameters);
StringValue oidValue = getPageParameters().get(OnePageParameterEncoder.PARAMETER);
LOGGER.trace("OID parameter: {}", oidValue);
if (oidValue == null) {
return null;
}
String oid = oidValue.toString();
if (StringUtils.isBlank(oid)) {
return null;
}
return oid;
}
public boolean isEditingFocus() {
return getObjectOidParameter() != null;
}
protected ObjectWrapper<O> loadObjectWrapper(PrismObject<O> objectToEdit) {
Task task = createSimpleTask(OPERATION_LOAD_OBJECT);
OperationResult result = task.getResult();
PrismObject<O> object = null;
Collection<SelectorOptions<GetOperationOptions>> loadOptions = null;
try {
if (!isEditingFocus()) {
if (objectToEdit == null) {
LOGGER.trace("Loading object: New object (creating)");
O focusType = createNewObject();
getMidpointApplication().getPrismContext().adopt(focusType);
object = (PrismObject<O>) focusType.asPrismObject();
} else {
LOGGER.trace("Loading object: New object (supplied): {}", objectToEdit);
object = objectToEdit;
}
} else {
loadOptions = SelectorOptions.createCollection(UserType.F_JPEG_PHOTO,
GetOperationOptions.createRetrieve(RetrieveOption.INCLUDE));
String focusOid = getObjectOidParameter();
object = WebModelServiceUtils.loadObject(getCompileTimeClass(), focusOid, loadOptions, this, task,
result);
LOGGER.trace("Loading object: Existing object (loadled): {} -> {}", focusOid, object);
}
result.recordSuccess();
} catch (Exception ex) {
result.recordFatalError("Couldn't get object.", ex);
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't load object", ex);
}
showResult(result, false);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Loaded object:\n{}", object.debugDump());
}
if (object == null) {
if (isEditingFocus()) {
getSession().error(getString("pageAdminFocus.message.cantEditFocus"));
} else {
getSession().error(getString("pageAdminFocus.message.cantNewFocus"));
}
throw new RestartResponseException(getRestartResponsePage());
}
ContainerStatus status = isEditingFocus() ? ContainerStatus.MODIFYING : ContainerStatus.ADDING;
ObjectWrapper<O> wrapper;
ObjectWrapperFactory owf = new ObjectWrapperFactory(this);
try {
wrapper = owf.createObjectWrapper("pageAdminFocus.focusDetails", null, object, status);
} catch (Exception ex) {
result.recordFatalError("Couldn't get user.", ex);
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't load user", ex);
wrapper = owf.createObjectWrapper("pageAdminFocus.focusDetails", null, object, null, null, status, false);
}
wrapper.setLoadOptions(loadOptions);
showResult(wrapper.getResult(), false);
loadParentOrgs(wrapper, task, result);
wrapper.setShowEmpty(!isEditingFocus());
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Loaded focus wrapper:\n{}", wrapper.debugDump());
}
return wrapper;
}
private void loadParentOrgs(ObjectWrapper<O> wrapper, Task task, OperationResult result) {
OperationResult subResult = result.createMinorSubresult(OPERATION_LOAD_PARENT_ORGS);
PrismObject<O> focus = wrapper.getObject();
// Load parent organizations (full objects). There are used in the
// summary panel and also in the main form.
// Do it here explicitly instead of using resolve option to have ability
// to better handle (ignore) errors.
for (ObjectReferenceType parentOrgRef : focus.asObjectable().getParentOrgRef()) {
PrismObject<OrgType> parentOrg = null;
try {
parentOrg = getModelService().getObject(OrgType.class, parentOrgRef.getOid(), null, task,
subResult);
LOGGER.trace("Loaded parent org with result {}",
new Object[] { subResult.getLastSubresult() });
} catch (AuthorizationException e) {
// This can happen if the user has permission to read parentOrgRef but it does not have
// the permission to read target org
// It is OK to just ignore it.
subResult.muteLastSubresultError();
LOGGER.debug("User {} does not have permission to read parent org unit {} (ignoring error)", task.getOwner().getName(), parentOrgRef.getOid());
} catch (Exception ex) {
subResult.recordWarning("Cannot load parent org " + parentOrgRef.getOid(), ex);
LOGGER.warn("Cannot load parent org {}: {}", parentOrgRef.getOid(), ex.getMessage(), ex);
}
if (parentOrg != null) {
wrapper.getParentOrgs().add(parentOrg);
}
}
subResult.computeStatus();
}
protected abstract Class<? extends Page> getRestartResponsePage();
public Object findParam(String param, String oid, OperationResult result) {
Object object = null;
for (OperationResult subResult : result.getSubresults()) {
if (subResult != null && subResult.getParams() != null) {
if (subResult.getParams().get(param) != null
&& subResult.getParams().get(OperationResult.PARAM_OID) != null
&& subResult.getParams().get(OperationResult.PARAM_OID).equals(oid)) {
return subResult.getParams().get(param);
}
object = findParam(param, oid, subResult);
}
}
return object;
}
// TODO put this into correct place
protected boolean previewRequested;
/**
* This will be called from the main form when save button is pressed.
*/
public void savePerformed(AjaxRequestTarget target) {
progressReporter.onSaveSubmit();
OperationResult result = new OperationResult(OPERATION_SAVE);
previewRequested = false;
saveOrPreviewPerformed(target, result, false);
}
public void previewPerformed(AjaxRequestTarget target) {
progressReporter.onSaveSubmit();
OperationResult result = new OperationResult(OPERATION_PREVIEW_CHANGES);
previewRequested = true;
saveOrPreviewPerformed(target, result, true);
}
public void saveOrPreviewPerformed(AjaxRequestTarget target, OperationResult result, boolean previewOnly) {
boolean isAnythingChanged = processDeputyAssignments();
ObjectWrapper<O> objectWrapper = getObjectWrapper();
LOGGER.debug("Saving object {}", objectWrapper);
// todo: improve, delta variable is quickfix for MID-1006
// redirecting to user list page everytime user is created in repository
// during user add in gui,
// and we're not taking care about account/assignment create errors
// (error message is still displayed)
delta = null;
Task task = createSimpleTask(OPERATION_SEND_TO_SUBMIT);
ModelExecuteOptions options = getExecuteChangesOptions();
LOGGER.debug("Using execute options {}.", new Object[] { options });
try {
reviveModels();
delta = objectWrapper.getObjectDelta();
if (objectWrapper.getOldDelta() != null) {
delta = ObjectDelta.summarize(objectWrapper.getOldDelta(), delta);
}
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("User delta computed from form:\n{}", new Object[] { delta.debugDump(3) });
}
} catch (Exception ex) {
result.recordFatalError(getString("pageUser.message.cantCreateUser"), ex);
LoggingUtils.logUnexpectedException(LOGGER, "Create user failed", ex);
showResult(result);
return;
}
switch (objectWrapper.getStatus()) {
case ADDING:
try {
PrismObject<O> objectToAdd = delta.getObjectToAdd();
WebComponentUtil.encryptCredentials(objectToAdd, true, getMidpointApplication());
prepareObjectForAdd(objectToAdd);
getPrismContext().adopt(objectToAdd, getCompileTimeClass());
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Delta before add user:\n{}", new Object[] { delta.debugDump(3) });
}
if (!delta.isEmpty()) {
delta.revive(getPrismContext());
final Collection<ObjectDelta<? extends ObjectType>> deltas = WebComponentUtil.createDeltaCollection(delta);
final Collection<SimpleValidationError> validationErrors = performCustomValidation(objectToAdd, deltas);
if (checkValidationErrors(target, validationErrors)) {
return;
}
progressReporter.executeChanges(deltas, previewOnly, options, task, result, target);
} else {
result.recordSuccess();
}
} catch (Exception ex) {
result.recordFatalError(getString("pageFocus.message.cantCreateFocus"), ex);
LoggingUtils.logUnexpectedException(LOGGER, "Create user failed", ex);
showResult(result);
}
break;
case MODIFYING:
try {
WebComponentUtil.encryptCredentials(delta, true, getMidpointApplication());
prepareObjectDeltaForModify(delta);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Delta before modify user:\n{}", new Object[] { delta.debugDump(3) });
}
Collection<ObjectDelta<? extends ObjectType>> deltas = new ArrayList<>();
if (!delta.isEmpty()) {
delta.revive(getPrismContext());
deltas.add(delta);
}
List<ObjectDelta<? extends ObjectType>> additionalDeltas = getAdditionalModifyDeltas(result);
if (additionalDeltas != null) {
for (ObjectDelta additionalDelta : additionalDeltas) {
if (!additionalDelta.isEmpty()) {
additionalDelta.revive(getPrismContext());
deltas.add(additionalDelta);
}
}
}
if (delta.isEmpty() && ModelExecuteOptions.isReconcile(options)) {
ObjectDelta emptyDelta = ObjectDelta.createEmptyModifyDelta(getCompileTimeClass(),
objectWrapper.getObject().getOid(), getPrismContext());
deltas.add(emptyDelta);
Collection<SimpleValidationError> validationErrors = performCustomValidation(null, deltas);
if (checkValidationErrors(target, validationErrors)) {
return;
}
progressReporter.executeChanges(deltas, previewOnly, options, task, result, target);
} else if (!deltas.isEmpty()) {
Collection<SimpleValidationError> validationErrors = performCustomValidation(null, deltas);
if (checkValidationErrors(target, validationErrors)) {
return;
}
progressReporter.executeChanges(deltas, previewOnly, options, task, result, target);
} else {
progressReporter.clearProgressPanel(); // from previous attempts (useful only if we would call finishProcessing at the end, but that's not the case now)
if (!previewOnly) {
if (!isAnythingChanged) {
result.recordWarning(getString("PageAdminObjectDetails.noChangesSave"));
showResult(result);
}
redirectBack();
} else {
warn(getString("PageAdminObjectDetails.noChangesPreview"));
target.add(getFeedbackPanel());
}
}
} catch (Exception ex) {
if (!executeForceDelete(objectWrapper, task, options, result)) {
result.recordFatalError(getString("pageUser.message.cantUpdateUser"), ex);
LoggingUtils.logUnexpectedException(LOGGER, getString("pageUser.message.cantUpdateUser"), ex);
} else {
result.recomputeStatus();
}
showResult(result);
}
break;
// support for add/delete containers (e.g. delete credentials)
default:
error(getString("pageAdminFocus.message.unsupportedState", objectWrapper.getStatus()));
}
// result.recomputeStatus();
//
// if (!result.isInProgress()) {
// LOGGER.trace("Result NOT in progress, calling finishProcessing");
// finishProcessing(target, result, false);
// }
LOGGER.trace("returning from saveOrPreviewPerformed");
}
protected boolean processDeputyAssignments(){
return false;
}
protected boolean checkValidationErrors(AjaxRequestTarget target, Collection<SimpleValidationError> validationErrors) {
if (validationErrors != null && !validationErrors.isEmpty()) {
for (SimpleValidationError error : validationErrors) {
LOGGER.error("Validation error, attribute: '" + error.printAttribute()
+ "', message: '" + error.getMessage() + "'.");
error("Validation error, attribute: '" + error.printAttribute()
+ "', message: '" + error.getMessage() + "'.");
}
target.add(getFeedbackPanel());
return true;
}
return false;
}
@Override
public void startProcessing(AjaxRequestTarget target, OperationResult result) {
LOGGER.trace("startProcessing called, making main panel invisible");
mainPanel.setVisible(false);
target.add(mainPanel);
}
protected ModelExecuteOptions getExecuteChangesOptions() {
return mainPanel.getExecuteChangeOptionsDto().createOptions();
}
protected void prepareObjectForAdd(PrismObject<O> object) throws SchemaException {
}
protected void prepareObjectDeltaForModify(ObjectDelta<O> objectDelta) throws SchemaException {
}
protected List<ObjectDelta<? extends ObjectType>> getAdditionalModifyDeltas(OperationResult result) {
return null;
}
protected boolean executeForceDelete(ObjectWrapper userWrapper, Task task, ModelExecuteOptions options,
OperationResult parentResult) {
return isForce();
}
protected boolean isForce() {
return getMainPanel().getExecuteChangeOptionsDto().isForce();
}
protected boolean isKeepDisplayingResults() {
return getMainPanel().getExecuteChangeOptionsDto().isKeepDisplayingResults();
}
protected Collection<SimpleValidationError> performCustomValidation(PrismObject<O> object,
Collection<ObjectDelta<? extends ObjectType>> deltas) throws SchemaException {
Collection<SimpleValidationError> errors = null;
if (object == null) {
if (getObjectWrapper() != null && getObjectWrapper().getObject() != null) {
object = getObjectWrapper().getObject().clone(); // otherwise original object could get corrupted e.g. by applying the delta below
for (ObjectDelta delta : deltas) {
// because among deltas there can be also ShadowType deltas
if (UserType.class.isAssignableFrom(delta.getObjectTypeClass())) {
delta.applyTo(object);
}
}
}
} else {
object = object.clone();
}
performAdditionalValidation(object, deltas, errors);
for (MidpointFormValidator validator : getFormValidatorRegistry().getValidators()) {
if (errors == null) {
errors = validator.validateObject(object, deltas);
} else {
errors.addAll(validator.validateObject(object, deltas));
}
}
return errors;
}
protected void performAdditionalValidation(PrismObject<O> object,
Collection<ObjectDelta<? extends ObjectType>> deltas, Collection<SimpleValidationError> errors) throws SchemaException {
}
public List<ObjectFormType> getObjectFormTypes() {
Task task = createSimpleTask(OPERATION_LOAD_GUI_CONFIGURATION);
OperationResult result = task.getResult();
AdminGuiConfigurationType adminGuiConfiguration;
try {
adminGuiConfiguration = getModelInteractionService().getAdminGuiConfiguration(task, result);
} catch (ObjectNotFoundException | SchemaException e) {
throw new SystemException("Cannot load GUI configuration: "+e.getMessage(), e);
}
if (adminGuiConfiguration == null) {
return null;
}
ObjectFormsType objectFormsType = adminGuiConfiguration.getObjectForms();
if (objectFormsType == null) {
return null;
}
List<ObjectFormType> objectForms = objectFormsType.getObjectForm();
if (objectForms == null || objectForms.isEmpty()) {
return objectForms;
}
List<ObjectFormType> validObjectForms = new ArrayList<>();
for (ObjectFormType objectForm: objectForms) {
if (isSupportedObjectType(objectForm.getType())) {
validObjectForms.add(objectForm);
}
}
return validObjectForms;
}
protected boolean isSupportedObjectType(QName type) {
ObjectTypes objectType = ObjectTypes.getObjectType(getCompileTimeClass());
return QNameUtil.match(objectType.getTypeQName(),type);
}
public void setSaveOnConfigure(boolean saveOnConfigure) {
this.saveOnConfigure = saveOnConfigure;
}
public boolean isSaveOnConfigure() {
return saveOnConfigure;
}
}