package com.evolveum.midpoint.web.page.admin.reports;
import java.util.*;
import com.evolveum.midpoint.gui.api.util.WebComponentUtil;
import com.evolveum.midpoint.web.component.AjaxButton;
import com.evolveum.midpoint.web.component.DateLabelComponent;
import com.evolveum.midpoint.web.component.data.BoxedTablePanel;
import com.evolveum.midpoint.web.component.util.VisibleBehaviour;
import com.evolveum.midpoint.web.page.admin.reports.dto.AuditEventRecordItemValueDto;
import com.evolveum.midpoint.web.page.admin.reports.dto.AuditEventRecordProvider;
import com.evolveum.midpoint.web.session.AuditLogStorage;
import com.evolveum.midpoint.web.session.UserProfileStorage;
import com.evolveum.midpoint.wf.api.WorkflowConstants;
import com.evolveum.midpoint.xml.ns._public.common.audit_3.*;
import org.apache.wicket.ajax.AjaxEventBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import com.evolveum.midpoint.gui.api.component.delta.ObjectDeltaOperationPanel;
import com.evolveum.midpoint.gui.api.model.LoadableModel;
import com.evolveum.midpoint.gui.api.page.PageBase;
import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.security.api.AuthorizationConstants;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.web.application.AuthorizationAction;
import com.evolveum.midpoint.web.application.PageDescriptor;
import com.evolveum.midpoint.web.page.admin.configuration.PageAdminConfiguration;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectDeltaOperationType;
import javax.xml.datatype.XMLGregorianCalendar;
@PageDescriptor(url = "/admin/auditLogDetails", action = {
@AuthorizationAction(actionUri = PageAdminReports.AUTH_REPORTS_ALL,
label = PageAdminConfiguration.AUTH_CONFIGURATION_ALL_LABEL,
description = PageAdminConfiguration.AUTH_CONFIGURATION_ALL_DESCRIPTION),
@AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_AUDIT_LOG_VIEWER_URL,
label = "PageAuditLogViewer.auth.auditLogViewer.label",
description = "PageAuditLogViewer.auth.auditLogViewer.description")})
public class PageAuditLogDetails extends PageBase {
private static final Trace LOGGER = TraceManager.getTrace(PageAuditLogDetails.class);
private static final long serialVersionUID = 1L;
private static final String ID_EVENT_PANEL = "eventPanel";
private static final String ID_DELTA_LIST_PANEL = "deltaListPanel";
private static final String ID_OBJECT_DELTA_OP_PANEL ="objectDeltaOpPanel";
private static final String ID_EVENT_DETAILS_PANEL = "eventDetailsPanel";
private static final String ID_PARAMETERS_TIMESTAMP = "timestamp";
private static final String ID_PARAMETERS_EVENT_IDENTIFIER = "eventIdentifier";
private static final String ID_PARAMETERS_SESSION_IDENTIFIER = "sessionIdentifier";
private static final String ID_PARAMETERS_TASK_IDENTIFIER = "taskIdentifier";
private static final String ID_PARAMETERS_TASK_OID = "taskOID";
private static final String ID_PARAMETERS_HOST_IDENTIFIER = "hostIdentifier";
private static final String ID_PARAMETERS_EVENT_INITIATOR = "initiatorRef";
private static final String ID_PARAMETERS_EVENT_TARGET = "targetRef";
private static final String ID_PARAMETERS_EVENT_TARGET_OWNER = "targetOwnerRef";
private static final String ID_PARAMETERS_EVENT_TYPE = "eventType";
private static final String ID_PARAMETERS_EVENT_STAGE = "eventStage";
private static final String ID_PARAMETERS_CHANNEL = "channel";
private static final String ID_PARAMETERS_EVENT_OUTCOME = "outcome";
private static final String ID_PARAMETERS_EVENT_RESULT = "result";
private static final String ID_PARAMETERS_PARAMETER = "parameter";
private static final String ID_PARAMETERS_MESSAGE = "message";
private static final String ID_ADDITIONAL_ITEMS = "additionalItems";
private static final String ID_ADDITIONAL_ITEM_LINE = "additionalItemLine";
private static final String ID_ITEM_NAME = "itemName";
private static final String ID_ITEM_VALUE = "itemValue";
private static final String ID_HISTORY_PANEL = "historyPanel";
private static final String ID_BUTTON_BACK = "back";
private static final String TASK_IDENTIFIER_PARAMETER = "taskIdentifier";
private static final int TASK_EVENTS_TABLE_SIZE = 10;
private static final String OPERATION_RESOLVE_REFERENCE_NAME = PageAuditLogDetails.class.getSimpleName()
+ ".resolveReferenceName()";
private IModel<AuditEventRecordType> recordModel;
// items that are not listed here are sorted according to their display name
private static final List<String> EXTENSION_ITEMS_ORDER =
Arrays.asList(
WorkflowConstants.AUDIT_OBJECT,
WorkflowConstants.AUDIT_TARGET,
WorkflowConstants.AUDIT_ORIGINAL_ASSIGNEE,
WorkflowConstants.AUDIT_CURRENT_ASSIGNEE,
WorkflowConstants.AUDIT_STAGE_NUMBER,
WorkflowConstants.AUDIT_STAGE_COUNT,
WorkflowConstants.AUDIT_STAGE_NAME,
WorkflowConstants.AUDIT_STAGE_DISPLAY_NAME,
WorkflowConstants.AUDIT_ESCALATION_LEVEL_NUMBER,
WorkflowConstants.AUDIT_ESCALATION_LEVEL_NAME,
WorkflowConstants.AUDIT_ESCALATION_LEVEL_DISPLAY_NAME,
WorkflowConstants.AUDIT_REQUESTER_COMMENT,
WorkflowConstants.AUDIT_COMMENT,
WorkflowConstants.AUDIT_WORK_ITEM_ID,
WorkflowConstants.AUDIT_PROCESS_INSTANCE_ID);
public PageAuditLogDetails() {
AuditLogStorage storage = getSessionStorage().getAuditLog();
initModel(storage.getAuditRecord());
initLayout();
}
public PageAuditLogDetails(AuditEventRecordType recordType) {
initModel(recordType);
initLayout();
}
private void initModel(AuditEventRecordType recordType){
AuditLogStorage storage = getSessionStorage().getAuditLog();
storage.setAuditRecord(recordType);
recordModel = new LoadableModel<AuditEventRecordType>(false) {
@Override
protected AuditEventRecordType load() {
return recordType;
}
};
}
private void initLayout(){
WebMarkupContainer eventPanel = new WebMarkupContainer(ID_EVENT_PANEL);
eventPanel.setOutputMarkupId(true);
add(eventPanel);
initAuditLogHistoryPanel(eventPanel);
initEventPanel(eventPanel);
initDeltasPanel(eventPanel);
initLayoutBackButton();
}
private void initAuditLogHistoryPanel(WebMarkupContainer eventPanel){
AuditEventRecordProvider provider = new AuditEventRecordProvider(PageAuditLogDetails.this){
private static final long serialVersionUID = 1L;
public Map<String, Object> getParameters() {
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put(TASK_IDENTIFIER_PARAMETER, recordModel.getObject().getTaskIdentifier());
return parameters;
}
};
BoxedTablePanel<AuditEventRecordType> table = new BoxedTablePanel<AuditEventRecordType>(
ID_HISTORY_PANEL, provider, initColumns(), UserProfileStorage.TableId.TASK_EVENTS_TABLE, TASK_EVENTS_TABLE_SIZE) {
@Override
protected Item<AuditEventRecordType> customizeNewRowItem(final Item<AuditEventRecordType> item,
final IModel<AuditEventRecordType> rowModel) {
if (rowModel.getObject().getTimestamp().equals(recordModel.getObject().getTimestamp())){
item.add(new AttributeAppender("style", "background-color: #eee; border-color: #d6d6d6; color: #000"));
}
item.add(new AjaxEventBehavior("click") {
private static final long serialVersionUID = 1L;
@Override
protected void onEvent(AjaxRequestTarget target) {
PageAuditLogDetails.this.rowItemClickPerformed(target, item, rowModel);
}
});
return item;
}
};
table.getFooterMenu().setVisible(false);
table.getFooterCountLabel().setVisible(false);
//TODO hidden temporarily
table.setVisible(false);
table.setOutputMarkupId(true);
table.setAdditionalBoxCssClasses("without-box-header-top-border");
eventPanel.addOrReplace(table);
}
protected void rowItemClickPerformed(AjaxRequestTarget target,
Item<AuditEventRecordType> item, final IModel<AuditEventRecordType> rowModel){
recordModel.setObject(rowModel.getObject());
AuditLogStorage storage = getSessionStorage().getAuditLog();
storage.setAuditRecord(rowModel.getObject());
WebMarkupContainer eventPanel = (WebMarkupContainer)PageAuditLogDetails.this.get(ID_EVENT_PANEL);
initAuditLogHistoryPanel(eventPanel);
initEventPanel(eventPanel);
initDeltasPanel(eventPanel);
target.add(eventPanel);
}
private List<IColumn<AuditEventRecordType, String>> initColumns() {
List<IColumn<AuditEventRecordType, String>> columns = new ArrayList<>();
PropertyColumn<AuditEventRecordType, String> timeColumn = new PropertyColumn<AuditEventRecordType, String>
(createStringResource("AuditEventRecordType.timestamp"),
AuditEventRecordType.F_TIMESTAMP.getLocalPart()) {
private static final long serialVersionUID = 1L;
@Override
public void populateItem(Item<ICellPopulator<AuditEventRecordType>> item, String componentId,
IModel<AuditEventRecordType> rowModel) {
XMLGregorianCalendar time = rowModel.getObject().getTimestamp();
item.add(new Label(componentId, WebComponentUtil.getLocalizedDate(time, DateLabelComponent.SHORT_SHORT_STYLE)));
}
};
columns.add(timeColumn);
PropertyColumn<AuditEventRecordType, String> stageColumn = new PropertyColumn<AuditEventRecordType, String>
(createStringResource("PageAuditLogViewer.eventStageShortLabel"),
AuditEventRecordType.F_EVENT_STAGE.getLocalPart()) {
private static final long serialVersionUID = 1L;
@Override
public void populateItem(Item<ICellPopulator<AuditEventRecordType>> item, String componentId,
IModel<AuditEventRecordType> rowModel) {
AuditEventStageType stage = rowModel.getObject().getEventStage();
String shortStage = "";
if (AuditEventStageType.EXECUTION.equals(stage)){
shortStage = AuditEventStageType.EXECUTION.value().substring(0, 4);
} else if (AuditEventStageType.REQUEST.equals(stage)){
shortStage = AuditEventStageType.REQUEST.value().substring(0, 3);
}
item.add(new Label(componentId, shortStage));
}
};
columns.add(stageColumn);
PropertyColumn<AuditEventRecordType, String> typeColumn = new PropertyColumn<AuditEventRecordType, String>
(createStringResource("PageAuditLogViewer.eventTypeShortLabel"),
AuditEventRecordType.F_EVENT_TYPE.getLocalPart()) {
private static final long serialVersionUID = 1L;
@Override
public void populateItem(Item<ICellPopulator<AuditEventRecordType>> item, String componentId,
IModel<AuditEventRecordType> rowModel) {
//TODO create some proper short values
AuditEventTypeType type = rowModel.getObject().getEventType();
String typeVal = type.value().substring(0, 4);
item.add(new Label(componentId, typeVal));
}
};
columns.add(typeColumn);
return columns;
}
private void initEventPanel(WebMarkupContainer eventPanel){
WebMarkupContainer eventDetailsPanel = new WebMarkupContainer(ID_EVENT_DETAILS_PANEL);
eventDetailsPanel.setOutputMarkupId(true);
eventPanel.addOrReplace(eventDetailsPanel);
final Label identifier = new Label(ID_PARAMETERS_EVENT_IDENTIFIER, new PropertyModel(recordModel,ID_PARAMETERS_EVENT_IDENTIFIER));
identifier.setOutputMarkupId(true);
eventDetailsPanel.add(identifier);
final Label timestamp = new Label(ID_PARAMETERS_TIMESTAMP, new PropertyModel(recordModel,ID_PARAMETERS_TIMESTAMP));
timestamp.setOutputMarkupId(true);
eventDetailsPanel.add(timestamp);
final Label sessionIdentifier = new Label(ID_PARAMETERS_SESSION_IDENTIFIER, new PropertyModel(recordModel,ID_PARAMETERS_SESSION_IDENTIFIER));
sessionIdentifier.setOutputMarkupId(true);
eventDetailsPanel.add(sessionIdentifier);
final Label taskIdentifier = new Label(ID_PARAMETERS_TASK_IDENTIFIER, new PropertyModel(recordModel,ID_PARAMETERS_TASK_IDENTIFIER));
taskIdentifier.setOutputMarkupId(true);
eventDetailsPanel.add(taskIdentifier);
final Label taskOID = new Label(ID_PARAMETERS_TASK_OID, new PropertyModel(recordModel,ID_PARAMETERS_TASK_OID));
taskOID.setOutputMarkupId(true);
eventDetailsPanel.add(taskOID);
final Label hostIdentifier = new Label(ID_PARAMETERS_HOST_IDENTIFIER, new PropertyModel(recordModel,ID_PARAMETERS_HOST_IDENTIFIER));
hostIdentifier.setOutputMarkupId(true);
eventDetailsPanel.add(hostIdentifier);
final Label initiatorRef = new Label(ID_PARAMETERS_EVENT_INITIATOR,
new Model<>(WebModelServiceUtils.resolveReferenceName(recordModel.getObject().getInitiatorRef(), this,
createSimpleTask(ID_PARAMETERS_EVENT_INITIATOR),
new OperationResult(ID_PARAMETERS_EVENT_INITIATOR))));
initiatorRef.setOutputMarkupId(true);
eventDetailsPanel.add(initiatorRef);
final Label targetRef = new Label(ID_PARAMETERS_EVENT_TARGET,
new Model<>(WebModelServiceUtils.resolveReferenceName(recordModel.getObject().getTargetRef(), this,
createSimpleTask(ID_PARAMETERS_EVENT_TARGET),
new OperationResult(ID_PARAMETERS_EVENT_TARGET))));
targetRef.setOutputMarkupId(true);
eventDetailsPanel.add(targetRef);
IModel<String> targetOwnerRefModel = new IModel<String>() {
@Override
public String getObject() {
return WebModelServiceUtils.resolveReferenceName(recordModel.getObject().getTargetOwnerRef(),
PageAuditLogDetails.this,
createSimpleTask(OPERATION_RESOLVE_REFERENCE_NAME),
new OperationResult(OPERATION_RESOLVE_REFERENCE_NAME));
}
@Override
public void setObject(String s) {
}
@Override
public void detach() {
}
};
final Label targetOwnerRef = new Label(ID_PARAMETERS_EVENT_TARGET_OWNER , targetOwnerRefModel);
targetOwnerRef.setOutputMarkupId(true);
eventDetailsPanel.add(targetOwnerRef);
final Label eventType = new Label(ID_PARAMETERS_EVENT_TYPE , new PropertyModel(recordModel,ID_PARAMETERS_EVENT_TYPE));
eventType.setOutputMarkupId(true);
eventDetailsPanel.add(eventType);
final Label eventStage = new Label(ID_PARAMETERS_EVENT_STAGE , new PropertyModel(recordModel,ID_PARAMETERS_EVENT_STAGE));
eventStage.setOutputMarkupId(true);
eventDetailsPanel.add(eventStage);
final Label channel = new Label(ID_PARAMETERS_CHANNEL , new PropertyModel(recordModel,ID_PARAMETERS_CHANNEL));
channel.setOutputMarkupId(true);
eventDetailsPanel.add(channel);
final Label eventOutcome = new Label(ID_PARAMETERS_EVENT_OUTCOME , new PropertyModel(recordModel,ID_PARAMETERS_EVENT_OUTCOME));
eventOutcome.setOutputMarkupId(true);
eventDetailsPanel.add(eventOutcome);
final Label eventResult = new Label(ID_PARAMETERS_EVENT_RESULT , new PropertyModel(recordModel,ID_PARAMETERS_EVENT_RESULT));
eventResult.setOutputMarkupId(true);
eventDetailsPanel.add(eventResult);
final Label parameter = new Label(ID_PARAMETERS_PARAMETER , new PropertyModel(recordModel,ID_PARAMETERS_PARAMETER));
parameter.setOutputMarkupId(true);
eventDetailsPanel.add(parameter);
final Label message = new Label(ID_PARAMETERS_MESSAGE , new PropertyModel(recordModel,ID_PARAMETERS_MESSAGE));
message.setOutputMarkupId(true);
eventDetailsPanel.add(message);
ListView<AuditEventRecordItemValueDto> additionalItemsList = new ListView<AuditEventRecordItemValueDto>(
ID_ADDITIONAL_ITEM_LINE,
new AbstractReadOnlyModel<List<AuditEventRecordItemValueDto>>() {
@Override
public List<AuditEventRecordItemValueDto> getObject() {
List<AuditEventRecordItemValueDto> rv = new ArrayList<>();
for (AuditEventRecordItemType item : getSortedItems()) {
String currentName = getDisplayName(item.getName());
if (item instanceof AuditEventRecordPropertyType) {
for (String value : ((AuditEventRecordPropertyType) item).getValue()) {
rv.add(new AuditEventRecordItemValueDto(currentName, value));
currentName = null;
}
} else if (item instanceof AuditEventRecordReferenceType) {
for (AuditEventRecordReferenceValueType value : ((AuditEventRecordReferenceType) item).getValue()) {
rv.add(new AuditEventRecordItemValueDto(currentName, value.getTargetName() != null ?
value.getTargetName().getOrig() : value.getOid()));
currentName = null;
}
} else {
// should not occur
}
}
return rv;
}
// TODO take locale into account when sorting
private List<AuditEventRecordItemType> getSortedItems() {
AuditEventRecordType record = recordModel.getObject();
List<AuditEventRecordItemType> rv = new ArrayList<>();
rv.addAll(record.getProperty());
rv.addAll(record.getReference());
rv.sort((a, b) -> {
// explicitly enumerated are shown first; others are sorted by display name
int index_a = EXTENSION_ITEMS_ORDER.indexOf(a.getName());
int index_b = EXTENSION_ITEMS_ORDER.indexOf(b.getName());
if (index_a != -1 && index_b != -1) {
return Integer.compare(index_a, index_b);
} else if (index_a != -1) {
return -1;
} else if (index_b != -1) {
return 1;
}
String name_a = getDisplayName(a.getName());
String name_b = getDisplayName(b.getName());
return String.CASE_INSENSITIVE_ORDER.compare(name_a, name_b);
});
return rv;
}
private String getDisplayName(String nameKey) {
// null should not occur so we don't try to be nice when displaying it
return nameKey != null ? createStringResource(nameKey).getString() : "(null)";
}
}) {
@Override
protected void populateItem(ListItem<AuditEventRecordItemValueDto> item) {
item.add(new Label(ID_ITEM_NAME, new PropertyModel<String>(item.getModel(), AuditEventRecordItemValueDto.F_NAME)));
item.add(new Label(ID_ITEM_VALUE, new PropertyModel<String>(item.getModel(), AuditEventRecordItemValueDto.F_VALUE)));
}
};
WebMarkupContainer additionalItemsContainer = new WebMarkupContainer(ID_ADDITIONAL_ITEMS);
additionalItemsContainer.add(additionalItemsList);
additionalItemsContainer.add(new VisibleBehaviour(() -> !additionalItemsList.getModelObject().isEmpty()));
eventDetailsPanel.add(additionalItemsContainer);
}
private void initDeltasPanel(WebMarkupContainer eventPanel){
List<ObjectDeltaOperationType> deltas = recordModel.getObject().getDelta();
RepeatingView deltaScene = new RepeatingView(ID_DELTA_LIST_PANEL);
for(ObjectDeltaOperationType deltaOp :deltas){
ObjectDeltaOperationPanel deltaPanel = new ObjectDeltaOperationPanel(deltaScene.newChildId(), Model.of(deltaOp), this);
deltaPanel.setOutputMarkupId(true);
deltaScene.add(deltaPanel);
}
eventPanel.addOrReplace(deltaScene);
}
protected void initLayoutBackButton() {
AjaxButton back = new AjaxButton(ID_BUTTON_BACK, createStringResource("PageBase.button.back")) {
@Override
public void onClick(AjaxRequestTarget target) {
redirectBack();
}
};
add(back);
}
}