/*
* Copyright (c) 2010-2015 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.configuration;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import javax.xml.namespace.QName;
import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl;
import com.evolveum.midpoint.prism.query.builder.QueryBuilder;
import com.evolveum.midpoint.prism.query.builder.S_AtomicFilterEntry;
import com.evolveum.midpoint.util.exception.CommonException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.RestartResponseException;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteTextField;
import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable;
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.form.DropDownChoice;
import org.apache.wicket.markup.html.form.EnumChoiceRenderer;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
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 org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.util.file.File;
import org.apache.wicket.util.string.Strings;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.IValidator;
import com.evolveum.midpoint.common.configuration.api.MidpointConfiguration;
import com.evolveum.midpoint.gui.api.model.LoadableModel;
import com.evolveum.midpoint.gui.api.util.WebComponentUtil;
import com.evolveum.midpoint.prism.Definition;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.polystring.PolyStringNormalizer;
import com.evolveum.midpoint.prism.query.AndFilter;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.schema.AbstractSummarizingResultHandler;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.ResultHandler;
import com.evolveum.midpoint.schema.SchemaConstantsGenerated;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.processor.ResourceSchema;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.security.api.AuthorizationConstants;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
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.application.AuthorizationAction;
import com.evolveum.midpoint.web.application.PageDescriptor;
import com.evolveum.midpoint.web.component.AjaxDownloadBehaviorFromFile;
import com.evolveum.midpoint.web.component.BasicSearchPanel;
import com.evolveum.midpoint.web.component.data.ObjectDataProvider;
import com.evolveum.midpoint.web.component.data.TablePanel;
import com.evolveum.midpoint.web.component.data.column.LinkColumn;
import com.evolveum.midpoint.web.component.input.ChoiceableChoiceRenderer;
import com.evolveum.midpoint.web.component.input.StringChoiceRenderer;
import com.evolveum.midpoint.web.component.util.SelectableBean;
import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour;
import com.evolveum.midpoint.web.page.admin.configuration.component.AceEditorDialog;
import com.evolveum.midpoint.web.page.admin.configuration.dto.AccountDetailsSearchDto;
import com.evolveum.midpoint.web.page.admin.configuration.dto.ResourceItemDto;
import com.evolveum.midpoint.web.page.admin.home.PageDashboard;
import com.evolveum.midpoint.web.page.admin.roles.PageRole;
import com.evolveum.midpoint.web.page.admin.users.PageOrgUnit;
import com.evolveum.midpoint.web.page.admin.users.PageUser;
import com.evolveum.midpoint.web.session.ConfigurationStorage;
import com.evolveum.midpoint.web.session.UserProfileStorage;
import com.evolveum.midpoint.web.util.OnePageParameterEncoder;
import com.evolveum.midpoint.web.util.SearchFormEnterBehavior;
import com.evolveum.midpoint.xml.ns._public.common.common_3.FailedOperationTypeType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationResultType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectTypeDefinitionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.RoleType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SchemaHandlingType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SynchronizationSituationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
/**
* @author lazyman
*/
@PageDescriptor(url = "/admin/config/sync/accounts", action = {
@AuthorizationAction(actionUri = PageAdminConfiguration.AUTH_CONFIGURATION_ALL,
label = PageAdminConfiguration.AUTH_CONFIGURATION_ALL_LABEL, description = PageAdminConfiguration.AUTH_CONFIGURATION_ALL_DESCRIPTION),
@AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_CONFIGURATION_SYNCHRONIZATION_ACCOUNTS_URL,
label = "PageAccounts.auth.configSyncAccounts.label", description = "PageAccounts.auth.configSyncAccounts.description")})
public class PageAccounts extends PageAdminConfiguration {
private static final Trace LOGGER = TraceManager.getTrace(PageAccounts.class);
private static final String DOT_CLASS = PageAccounts.class.getName() + ".";
private static final String OPERATION_LOAD_RESOURCES = DOT_CLASS + "loadResources";
private static final String OPERATION_LOAD_ACCOUNTS = DOT_CLASS + "loadAccounts";
private static final String OPERATION_EXPORT = DOT_CLASS + "export";
private static final String OPERATION_EXPORT_ACCOUNT = DOT_CLASS + "exportAccount";
private static final String OPERATION_GET_TOTALS = DOT_CLASS + "getTotals";
private static final String OPERATION_GET_INTENTS = DOT_CLASS + "getResourceIntentList";
private static final String OPERATION_GET_OBJECT_CLASS = DOT_CLASS + "getResourceObjectClassList";
private static final String OPERATION_LOAD_ACCOUNT_OWNER = DOT_CLASS + "loadAccountOwner";
private static final String ID_MAIN_FORM = "mainForm";
private static final String ID_FORM_ACCOUNT = "accountForm";
private static final String ID_RESOURCES = "resources";
private static final String ID_LIST_SYNC_DETAILS = "listSyncDetails";
private static final String ID_EXPORT = "export";
private static final String ID_ACCOUNTS = "accounts";
private static final String ID_CLEAR_EXPORT = "clearExport";
private static final String ID_FILES_CONTAINER = "filesContainer";
private static final String ID_FILES = "files";
private static final String ID_FILE = "file";
private static final String ID_FILE_NAME = "fileName";
private static final String ID_TOTALS = "totals";
private static final String ID_TOTAL = "total";
private static final String ID_DELETED = "deleted";
private static final String ID_UNMATCHED = "unmatched";
private static final String ID_DISPUTED = "disputed";
private static final String ID_LINKED = "linked";
private static final String ID_UNLINKED = "unlinked";
private static final String ID_ACCOUNTS_CONTAINER = "accountsContainer";
private static final String ID_NOTHING = "nothing";
private static final String ID_SEARCH_FORM = "searchForm";
private static final String ID_SEARCH_KIND = "kindSearch";
private static final String ID_SEARCH_INTENT = "intentSearch";
private static final String ID_SEARCH_OBJECT_CLASS = "objectClassSearch";
private static final String ID_SEARCH_BASIC = "basicSearch";
private static final String ID_SEARCH_FAILED_OPERATION_TYPE = "failedOperationSearch";
private static final String ID_RESULT_DIALOG = "resultPopup";
private static final Integer AUTO_COMPLETE_LIST_SIZE = 10;
private IModel<List<ResourceItemDto>> resourcesModel;
private LoadableModel<List<String>> filesModel;
private IModel<AccountDetailsSearchDto> searchModel;
private IModel<ResourceItemDto> resourceModel = new Model<>();
private LoadableModel<Integer> totalModel;
private LoadableModel<Integer> deletedModel;
private LoadableModel<Integer> unmatchedModel;
private LoadableModel<Integer> disputedModel;
private LoadableModel<Integer> linkedModel;
private LoadableModel<Integer> unlinkedModel;
private LoadableModel<Integer> nothingModel;
private File downloadFile;
public PageAccounts() {
searchModel = new LoadableModel<AccountDetailsSearchDto>() {
@Override
protected AccountDetailsSearchDto load() {
ConfigurationStorage storage = getSessionStorage().getConfiguration();
AccountDetailsSearchDto dto = storage.getAccountSearchDto();
if(dto == null){
dto = new AccountDetailsSearchDto();
}
return dto;
}
};
resourcesModel = new LoadableModel<List<ResourceItemDto>>() {
@Override
protected List<ResourceItemDto> load() {
return loadResources();
}
};
initLayout();
}
private void initLayout() {
Form form = new Form(ID_MAIN_FORM);
form.setOutputMarkupId(true);
add(form);
Form accForm = new Form(ID_FORM_ACCOUNT);
accForm.setOutputMarkupId(true);
add(accForm);
Form searchForm = new Form(ID_SEARCH_FORM);
initSearchForm(searchForm);
searchForm.setOutputMarkupPlaceholderTag(true);
searchForm.setOutputMarkupId(true);
searchForm.add(new VisibleEnableBehaviour() {
@Override
public boolean isVisible() {
return resourceModel.getObject() != null;
}
});
add(searchForm);
DropDownChoice<ResourceItemDto> resources = new DropDownChoice<>(
ID_RESOURCES, resourceModel, resourcesModel,
new ChoiceableChoiceRenderer<ResourceItemDto>());
form.add(resources);
initLinks(form, accForm);
initTotals(form);
final AjaxDownloadBehaviorFromFile ajaxDownloadBehavior = new AjaxDownloadBehaviorFromFile(true) {
@Override
protected File initFile() {
return downloadFile;
}
};
ajaxDownloadBehavior.setRemoveFile(false);
form.add(ajaxDownloadBehavior);
WebMarkupContainer filesContainer = new WebMarkupContainer(ID_FILES_CONTAINER);
filesContainer.setOutputMarkupId(true);
accForm.add(filesContainer);
ModalWindow resultPopup = createModalWindow(ID_RESULT_DIALOG, createStringResource("PageAccounts.result.popoup"), 1100, 560);
resultPopup.setContent(new AceEditorDialog(resultPopup.getContentId()));
add(resultPopup);
filesModel = createFilesModel();
ListView<String> files = new ListView<String>(ID_FILES, filesModel) {
@Override
protected void populateItem(final ListItem<String> item) {
AjaxLink file = new AjaxLink(ID_FILE) {
@Override
public void onClick(AjaxRequestTarget target) {
downloadPerformed(target, item.getModelObject(), ajaxDownloadBehavior);
}
};
file.add(new Label(ID_FILE_NAME, item.getModelObject()));
item.add(file);
}
};
files.setRenderBodyOnly(true);
filesContainer.add(files);
WebMarkupContainer accountsContainer = new WebMarkupContainer(ID_ACCOUNTS_CONTAINER);
accountsContainer.setOutputMarkupId(true);
accForm.add(accountsContainer);
ObjectDataProvider provider = new ObjectDataProvider(this, ShadowType.class);
provider.setOptions(SelectorOptions.createCollection(GetOperationOptions.createRaw()));
provider.setQuery(ObjectQuery.createObjectQuery(createResourceQueryFilter()));
TablePanel accounts = new TablePanel(ID_ACCOUNTS, provider, createAccountsColumns(),
UserProfileStorage.TableId.CONF_PAGE_ACCOUNTS, getItemsPerPage(UserProfileStorage.TableId.CONF_PAGE_ACCOUNTS));
accounts.add(new VisibleEnableBehaviour() {
@Override
public boolean isVisible() {
return resourceModel.getObject() != null;
}
});
accounts.setItemsPerPage(50);
accountsContainer.add(accounts);
}
private void initSearchForm(Form searchForm){
BasicSearchPanel<AccountDetailsSearchDto> basicSearch = new BasicSearchPanel<AccountDetailsSearchDto>(ID_SEARCH_BASIC) {
@Override
protected IModel<String> createSearchTextModel() {
return new PropertyModel<>(searchModel, AccountDetailsSearchDto.F_SEARCH_TEXT);
}
@Override
protected void searchPerformed(AjaxRequestTarget target) {
PageAccounts.this.searchPerformed(target);
}
@Override
protected void clearSearchPerformed(AjaxRequestTarget target) {
PageAccounts.this.clearSearchPerformed(target);
}
};
basicSearch.setOutputMarkupId(true);
searchForm.add(basicSearch);
DropDownChoice failedOperationType = new DropDownChoice<>(ID_SEARCH_FAILED_OPERATION_TYPE,
new PropertyModel<FailedOperationTypeType>(searchModel, AccountDetailsSearchDto.F_FAILED_OPERATION_TYPE),
WebComponentUtil.createReadonlyModelFromEnum(FailedOperationTypeType.class), new EnumChoiceRenderer<FailedOperationTypeType>(this));
failedOperationType.add(new OnChangeAjaxBehavior() {
@Override
protected void onUpdate(AjaxRequestTarget target) {
searchPerformed(target);
}
});
failedOperationType.setOutputMarkupId(true);
failedOperationType.setNullValid(true);
searchForm.add(failedOperationType);
DropDownChoice kind = new DropDownChoice<>(ID_SEARCH_KIND,
new PropertyModel<ShadowKindType>(searchModel, AccountDetailsSearchDto.F_KIND),
WebComponentUtil.createReadonlyModelFromEnum(ShadowKindType.class), new EnumChoiceRenderer<ShadowKindType>(this));
kind.add(new OnChangeAjaxBehavior() {
@Override
protected void onUpdate(AjaxRequestTarget target) {
searchPerformed(target);
}
});
kind.setOutputMarkupId(true);
kind.setNullValid(true);
searchForm.add(kind);
DropDownChoice intent = new DropDownChoice<>(ID_SEARCH_INTENT,
new PropertyModel<String>(searchModel, AccountDetailsSearchDto.F_INTENT),
createIntentChoices(), new StringChoiceRenderer(null));
intent.setNullValid(true);
intent.add(new OnChangeAjaxBehavior() {
@Override
protected void onUpdate(AjaxRequestTarget target) {
searchPerformed(target);
}
});
intent.setOutputMarkupId(true);
searchForm.add(intent);
AutoCompleteTextField<String> objectClass = new AutoCompleteTextField<String>(ID_SEARCH_OBJECT_CLASS,
new PropertyModel<String>(searchModel, AccountDetailsSearchDto.F_OBJECT_CLASS)) {
@Override
protected Iterator<String> getChoices(String input) {
if(Strings.isEmpty(input)){
List<String> emptyList = Collections.emptyList();
return emptyList.iterator();
}
AccountDetailsSearchDto dto = searchModel.getObject();
List<QName> accountObjectClassList = dto.getObjectClassList();
List<String> choices = new ArrayList<>(AUTO_COMPLETE_LIST_SIZE);
for(QName s: accountObjectClassList){
if(s.getLocalPart().toLowerCase().startsWith(input.toLowerCase())){
choices.add(s.getLocalPart());
if(choices.size() == AUTO_COMPLETE_LIST_SIZE){
break;
}
}
}
return choices.iterator();
}
};
objectClass.add(AttributeModifier.replace("placeholder", createStringResource("PageAccounts.accounts.objectClass")));
objectClass.setOutputMarkupId(true);
objectClass.add(createObjectClassValidator());
objectClass.add(new SearchFormEnterBehavior(basicSearch.getSearchButton()));
searchForm.add(objectClass);
}
private IValidator<String> createObjectClassValidator(){
return new IValidator<String>() {
@Override
public void validate(IValidatable<String> validatable) {
String value = validatable.getValue();
AccountDetailsSearchDto dto = searchModel.getObject();
List<QName> accountObjectClassList = dto.getObjectClassList();
List<String> accountObjectClassListString = new ArrayList<>();
for(QName objectClass: accountObjectClassList){
accountObjectClassListString.add(objectClass.getLocalPart());
}
if(!accountObjectClassListString.contains(value)){
error(createStringResource("PageAccounts.message.validationError", value).getString());
}
}
};
}
private IModel<List<String>> createIntentChoices(){
return new AbstractReadOnlyModel<List<String>>() {
@Override
public List<String> getObject() {
List<String> intentList = new ArrayList<>();
ResourceItemDto dto = resourceModel.getObject();
PrismObject<ResourceType> resourcePrism;
if(dto == null){
return intentList;
}
String oid = dto.getOid();
OperationResult result = new OperationResult(OPERATION_GET_INTENTS);
try {
resourcePrism = getModelService().getObject(ResourceType.class, oid, null,
createSimpleTask(OPERATION_GET_INTENTS), result);
ResourceType resource = resourcePrism.asObjectable();
SchemaHandlingType schemaHandling = resource.getSchemaHandling();
for(ResourceObjectTypeDefinitionType r: schemaHandling.getObjectType()){
intentList.add(r.getIntent());
}
} catch (Exception e){
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't load intents from resource.", e);
error("Couldn't load intents from resource.");
return null;
}
return intentList;
}
};
}
private void initTotals(Form form) {
WebMarkupContainer totals = new WebMarkupContainer(ID_TOTALS);
totals.setOutputMarkupId(true);
totalModel = createTotalModel();
deletedModel = createTotalsModel(SynchronizationSituationType.DELETED);
unmatchedModel = createTotalsModel(SynchronizationSituationType.UNMATCHED);
disputedModel = createTotalsModel(SynchronizationSituationType.DISPUTED);
linkedModel = createTotalsModel(SynchronizationSituationType.LINKED);
unlinkedModel = createTotalsModel(SynchronizationSituationType.UNLINKED);
nothingModel = createTotalsModel(null);
totals.add(new Label(ID_TOTAL, totalModel));
totals.add(new Label(ID_DELETED, deletedModel));
totals.add(new Label(ID_UNMATCHED, unmatchedModel));
totals.add(new Label(ID_DISPUTED, disputedModel));
totals.add(new Label(ID_LINKED, linkedModel));
totals.add(new Label(ID_UNLINKED, unlinkedModel));
totals.add(new Label(ID_NOTHING, nothingModel));
form.add(totals);
}
private LoadableModel<Integer> createTotalModel() {
return new LoadableModel<Integer>(false) {
@Override
protected Integer load() {
int total = 0;
total += deletedModel.getObject();
total += unmatchedModel.getObject();
total += disputedModel.getObject();
total += linkedModel.getObject();
total += unlinkedModel.getObject();
total += nothingModel.getObject();
return total;
}
};
}
private void refreshSyncTotalsModels() {
totalModel.reset();
deletedModel.reset();
unmatchedModel.reset();
disputedModel.reset();
linkedModel.reset();
unlinkedModel.reset();
nothingModel.reset();
}
private LoadableModel<Integer> createTotalsModel(final SynchronizationSituationType situation) {
return new LoadableModel<Integer>(false) {
@Override
protected Integer load() {
ObjectFilter resourceFilter = createResourceQueryFilter();
if (resourceFilter == null) {
return 0;
}
ObjectFilter filter = createObjectQuery().getFilter();
Collection<SelectorOptions<GetOperationOptions>> options =
SelectorOptions.createCollection(GetOperationOptions.createRaw());
Task task = createSimpleTask(OPERATION_GET_TOTALS);
OperationResult result = new OperationResult(OPERATION_GET_TOTALS);
try {
ObjectFilter situationFilter = QueryBuilder.queryFor(ShadowType.class, getPrismContext())
.item(ShadowType.F_SYNCHRONIZATION_SITUATION).eq(situation)
.buildFilter();
ObjectQuery query = ObjectQuery.createObjectQuery(AndFilter.createAnd(filter, situationFilter));
return getModelService().countObjects(ShadowType.class, query, options, task, result);
} catch (CommonException|RuntimeException ex) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't count shadows", ex);
}
return 0;
}
};
}
private LoadableModel<List<String>> createFilesModel() {
return new LoadableModel<List<String>>(false) {
@Override
protected List<String> load() {
String[] filesArray;
try {
MidpointConfiguration config = getMidpointConfiguration();
File exportFolder = new File(config.getMidpointHome() + "/export");
filesArray = exportFolder.list(new FilenameFilter() {
@Override
public boolean accept(java.io.File dir, String name) {
return name.endsWith("xml");
}
});
} catch (Exception ex) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't list files", ex);
getSession().error("Couldn't list files, reason: " + ex.getMessage());
throw new RestartResponseException(PageDashboard.class);
}
if (filesArray == null) {
return new ArrayList<>();
}
List<String> list = Arrays.asList(filesArray);
Collections.sort(list);
return list;
}
};
}
private void initLinks(Form form, Form accForm) {
AjaxSubmitLink listSyncDetails = new AjaxSubmitLink(ID_LIST_SYNC_DETAILS) {
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
listSyncDetailsPerformed(target);
}
@Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
target.add(getFeedbackPanel());
}
};
form.add(listSyncDetails);
AjaxSubmitLink export = new AjaxSubmitLink(ID_EXPORT) {
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
exportPerformed(target);
}
@Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
target.add(getFeedbackPanel());
}
};
form.add(export);
AjaxLink clearExport = new AjaxLink(ID_CLEAR_EXPORT) {
@Override
public void onClick(AjaxRequestTarget target) {
clearExportPerformed(target);
}
};
accForm.add(clearExport);
}
private List<IColumn> createAccountsColumns() {
List<IColumn> columns = new ArrayList<>();
columns.add(new PropertyColumn(createStringResource("PageAccounts.accounts.oid"),
SelectableBean.F_VALUE + ".oid"));
columns.add(new PropertyColumn<>(createStringResource("PageAccounts.accounts.name"),
ShadowType.F_NAME.getLocalPart(), SelectableBean.F_VALUE + ".name"));
columns.add(new PropertyColumn<>(createStringResource("PageAccounts.accounts.kind"),
ShadowType.F_KIND.getLocalPart(), SelectableBean.F_VALUE + ".kind"));
columns.add(new PropertyColumn<>(createStringResource("PageAccounts.accounts.intent"),
ShadowType.F_INTENT.getLocalPart(), SelectableBean.F_VALUE + ".intent"));
columns.add(new PropertyColumn<QName, String>(createStringResource("PageAccounts.accounts.objectClass"),
ShadowType.F_OBJECT_CLASS.getLocalPart(), SelectableBean.F_VALUE + ".objectClass.localPart"));
columns.add(new PropertyColumn<>(createStringResource("PageAccounts.accounts.synchronizationSituation"),
ShadowType.F_SYNCHRONIZATION_SITUATION.getLocalPart(), SelectableBean.F_VALUE + ".synchronizationSituation"));
columns.add(new PropertyColumn<>(createStringResource("PageAccounts.accounts.synchronizationTimestamp"),
ShadowType.F_SYNCHRONIZATION_TIMESTAMP.getLocalPart(), SelectableBean.F_VALUE + ".synchronizationTimestamp"));
// columns.add(new PropertyColumn<>(createStringResource("PageAccounts.accounts.result"),
// ShadowType.F_RESULT.getLocalPart(), SelectableBean.F_VALUE + ".result.status"));
columns.add(new LinkColumn<SelectableBean>(createStringResource("PageAccounts.accounts.result")){
@Override
protected IModel<String> createLinkModel(final IModel<SelectableBean> rowModel){
return new AbstractReadOnlyModel<String>() {
@Override
public String getObject() {
OperationResultType result = getResult(rowModel);
if (result == null){
return "";
}
return createStringResource("OperationResultStatusType." + result.getStatus()).getObject();
}
};
}
@Override
public void onClick(AjaxRequestTarget target, IModel<SelectableBean> rowModel) {
showShadowResult(target, rowModel);
}
});
columns.add(new LinkColumn<SelectableBean>(createStringResource("PageAccounts.accounts.owner")){
@Override
protected IModel<String> createLinkModel(final IModel<SelectableBean> rowModel){
return new AbstractReadOnlyModel<String>() {
@Override
public String getObject() {
FocusType focus = loadShadowOwner(rowModel);
return WebComponentUtil.getName(focus);
}
};
}
@Override
public void onClick(AjaxRequestTarget target, IModel<SelectableBean> rowModel) {
ownerDetailsPerformed(target, rowModel);
}
});
return columns;
}
private void showShadowResult(AjaxRequestTarget target, IModel<SelectableBean> rowModel){
OperationResultType result = getResult(rowModel);
String xml;
ModalWindow aceDialog = (ModalWindow) get(createComponentPath(ID_RESULT_DIALOG));
AceEditorDialog aceEditor = (AceEditorDialog) aceDialog.get(aceDialog.getContentId());
try {
xml = getPrismContext().xmlSerializer().serializeRealValue(result, ShadowType.F_RESULT);
aceEditor.updateModel(new Model<String>(xml));
} catch (SchemaException e) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't parse result", e);
aceEditor.updateModel(new Model<String>("Unable to show result. For more information see logs."));
}
aceDialog.show(target);
target.add(getFeedbackPanel());
}
private ObjectFilter createResourceAndQueryFilter(){
return AndFilter.createAnd(createResourceQueryFilter());
}
private ObjectFilter createResourceQueryFilter() {
ResourceItemDto dto = resourceModel.getObject();
if (dto == null) {
return null;
}
String oid = dto.getOid();
return QueryBuilder.queryFor(ShadowType.class, getPrismContext())
.item(ShadowType.F_RESOURCE_REF).ref(oid)
.buildFilter();
}
private ObjectQuery appendResourceQueryFilter(S_AtomicFilterEntry q) {
ResourceItemDto dto = resourceModel.getObject();
if (dto == null) {
return q.all().build(); // TODO ok?
} else {
return q.item(ShadowType.F_RESOURCE_REF).ref(dto.getOid()).build();
}
}
private List<ResourceItemDto> loadResources() {
List<ResourceItemDto> resources = new ArrayList<>();
OperationResult result = new OperationResult(OPERATION_LOAD_RESOURCES);
try {
List<PrismObject<ResourceType>> objects = getModelService().searchObjects(ResourceType.class, null, SelectorOptions.createCollection(GetOperationOptions.createNoFetch()),
createSimpleTask(OPERATION_LOAD_RESOURCES), result);
if (objects != null) {
for (PrismObject<ResourceType> object : objects) {
StringBuilder nameBuilder = new StringBuilder(WebComponentUtil.getName(object));
PrismProperty<OperationResultType> fetchResult = object.findProperty(ResourceType.F_FETCH_RESULT);
if (fetchResult != null){
nameBuilder.append(" (");
nameBuilder.append(fetchResult.getRealValue().getStatus());
nameBuilder.append(")");
}
resources.add(new ResourceItemDto(object.getOid(), nameBuilder.toString()));
}
}
result.recordSuccess();
} catch (Exception ex) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't load resources", ex);
result.recordFatalError("Couldn't load resources, reason: " + ex.getMessage(), ex);
} finally {
if (result.isUnknown()) {
result.computeStatus();
}
}
Collections.sort(resources);
if (!WebComponentUtil.isSuccessOrHandledError(result)) {
showResult(result, false);
throw new RestartResponseException(PageDashboard.class);
}
return resources;
}
private Component getAccountsContainer(){
return get(createComponentPath(ID_FORM_ACCOUNT, ID_ACCOUNTS_CONTAINER));
}
private TablePanel getAccountsTable(){
return (TablePanel) get(createComponentPath(ID_FORM_ACCOUNT, ID_ACCOUNTS_CONTAINER, ID_ACCOUNTS));
}
private Form getSearchPanel(){
return (Form) get(ID_SEARCH_FORM);
}
private Component getTotalsPanel(){
return get(createComponentPath(ID_MAIN_FORM, ID_TOTALS));
}
private void loadResourceObjectClass(){
AccountDetailsSearchDto dto = searchModel.getObject();
PrismObject<ResourceType> resourcePrism;
OperationResult result = new OperationResult(OPERATION_GET_OBJECT_CLASS);
List<QName> accountObjectClassList = new ArrayList<>();
ResourceItemDto resourceDto = resourceModel.getObject();
String oid = resourceDto.getOid();
try {
resourcePrism = getModelService().getObject(ResourceType.class, oid, null,
createSimpleTask(OPERATION_GET_INTENTS), result);
ResourceSchema schema = RefinedResourceSchemaImpl.getResourceSchema(resourcePrism, getPrismContext());
schema.getObjectClassDefinitions();
for(Definition def: schema.getDefinitions()){
accountObjectClassList.add(def.getTypeName());
}
dto.setObjectClassList(accountObjectClassList);
} catch (Exception e){
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't load object class list from resource.", e);
result.recordFatalError("Couldn't load object class list from resource.: " +e.getMessage(), e);
showResult(result, false);
resourceModel.setObject(null);
new RestartResponseException(PageAccounts.this);
}
}
private void listSyncDetailsPerformed(AjaxRequestTarget target) {
refreshSyncTotalsModels();
if(resourceModel.getObject() == null){
warn(getString("pageAccounts.message.resourceNotSelected"));
refreshEverything(target);
return;
}
loadResourceObjectClass();
TablePanel table = getAccountsTable();
ObjectDataProvider provider = (ObjectDataProvider) table.getDataTable().getDataProvider();
provider.setQuery(createObjectQuery());
table.getDataTable().setCurrentPage(0);
refreshEverything(target);
}
private void refreshEverything(AjaxRequestTarget target){
target.add(getAccountsContainer(),
getTotalsPanel(),
getFeedbackPanel(),
getSearchPanel(),
get(createComponentPath(ID_SEARCH_FORM, ID_SEARCH_INTENT)));
}
private void exportPerformed(AjaxRequestTarget target) {
if(resourceModel.getObject() == null){
warn(getString("pageAccounts.message.resourceNotSelected"));
refreshEverything(target);
return;
}
String fileName = "accounts-" + WebComponentUtil.formatDate("yyyy-MM-dd-HH-mm-ss", new Date()) + ".xml";
OperationResult result = new OperationResult(OPERATION_EXPORT);
Writer writer = null;
try {
Task task = createSimpleTask(OPERATION_EXPORT);
writer = createWriter(fileName);
writeHeader(writer);
final Writer handlerWriter = writer;
ResultHandler handler = new AbstractSummarizingResultHandler() {
@Override
protected boolean handleObject(PrismObject object, OperationResult parentResult) {
OperationResult result = parentResult.createMinorSubresult(OPERATION_EXPORT_ACCOUNT);
try {
String xml = getPrismContext().serializeObjectToString(object, PrismContext.LANG_XML);
handlerWriter.write(xml);
result.computeStatus();
} catch (Exception ex) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't serialize account", ex);
result.recordFatalError("Couldn't serialize account.", ex);
return false;
}
return true;
}
};
try {
ObjectQuery query = ObjectQuery.createObjectQuery(createResourceAndQueryFilter());
getModelService().searchObjectsIterative(ShadowType.class, query, handler,
SelectorOptions.createCollection(GetOperationOptions.createRaw()), task, result);
} finally {
writeFooter(writer);
}
result.recomputeStatus();
} catch (Exception ex) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't export accounts", ex);
error(getString("PageAccounts.exportException", ex.getMessage()));
} finally {
IOUtils.closeQuietly(writer);
}
filesModel.reset();
success(getString("PageAccounts.message.success.export", fileName));
target.add(getFeedbackPanel(), get(createComponentPath(ID_FORM_ACCOUNT, ID_FILES_CONTAINER)));
}
private Writer createWriter(String fileName) throws IOException {
//todo improve!!!!!!!
MidpointConfiguration config = getMidpointConfiguration();
File exportFolder = new File(config.getMidpointHome() + "/export/");
File file = new File(exportFolder, fileName);
try {
if (!exportFolder.exists() || !exportFolder.isDirectory()) {
exportFolder.mkdir();
}
file.createNewFile();
} catch (Exception ex) {
error(getString("PageAccounts.exportFileDoesntExist", file.getAbsolutePath()));
throw ex;
}
return new OutputStreamWriter(new FileOutputStream(file), "utf-8");
}
private void writeHeader(Writer writer) throws IOException {
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\"?>\n");
sb.append("<objects xmlns=\"");
sb.append(SchemaConstantsGenerated.NS_COMMON);
sb.append("\">\n");
writer.write(sb.toString());
}
private void writeFooter(Writer writer) throws IOException {
writer.write("</objects>\n");
}
private void clearExportPerformed(AjaxRequestTarget target) {
try {
MidpointConfiguration config = getMidpointConfiguration();
File exportFolder = new File(config.getMidpointHome() + "/export");
java.io.File[] files = exportFolder.listFiles();
if (files == null) {
return;
}
for (java.io.File file : files) {
file.delete();
}
} catch (Exception ex) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't delete export files", ex);
error("Couldn't delete export files, reason: " + ex.getMessage());
}
filesModel.reset();
success(getString("PageAccounts.message.success.clearExport"));
target.add(getFeedbackPanel(), get(createComponentPath(ID_FORM_ACCOUNT, ID_FILES_CONTAINER)));
}
private void downloadPerformed(AjaxRequestTarget target, String fileName,
AjaxDownloadBehaviorFromFile downloadBehavior) {
MidpointConfiguration config = getMidpointConfiguration();
downloadFile = new File(config.getMidpointHome() + "/export/" + fileName);
downloadBehavior.initiate(target);
}
private void searchPerformed(AjaxRequestTarget target){
refreshSyncTotalsModels();
ObjectQuery query = createObjectQuery();
TablePanel panel = getAccountsTable();
DataTable table = panel.getDataTable();
ObjectDataProvider provider = (ObjectDataProvider)table.getDataProvider();
provider.setQuery(query);
ConfigurationStorage storage = getSessionStorage().getConfiguration();
storage.setAccountSearchDto(searchModel.getObject());
storage.setAccountDetailsPaging(null);
panel.setCurrentPage(null);
target.add(getTotalsPanel());
target.add(getFeedbackPanel());
target.add(getAccountsContainer());
}
private ObjectQuery createObjectQuery() {
AccountDetailsSearchDto dto = searchModel.getObject();
String searchText = dto.getText();
ShadowKindType kind = dto.getKind();
String intent = dto.getIntent();
String objectClass = dto.getObjectClass();
FailedOperationTypeType failedOperatonType = dto.getFailedOperationType();
S_AtomicFilterEntry q = QueryBuilder.queryFor(ShadowType.class, getPrismContext());
if (StringUtils.isNotEmpty(searchText)) {
PolyStringNormalizer normalizer = getPrismContext().getDefaultPolyStringNormalizer();
String normalized = normalizer.normalize(searchText);
q = q.item(ShadowType.F_NAME).contains(normalized).matchingNorm().and();
}
if (kind != null) {
q = q.item(ShadowType.F_KIND).eq(kind).and();
}
if (StringUtils.isNotEmpty(intent)) {
q = q.item(ShadowType.F_INTENT).eq(intent).and();
}
if (failedOperatonType != null){
q = q.item(ShadowType.F_FAILED_OPERATION_TYPE).eq(failedOperatonType).and();
}
if (StringUtils.isNotEmpty(objectClass)) {
QName objClass = new QName(objectClass);
for (QName qn: dto.getObjectClassList()) {
if (objectClass.equals(qn.getLocalPart())){
objClass = qn;
}
}
q = q.item(ShadowType.F_OBJECT_CLASS).eq(objClass).and();
}
return appendResourceQueryFilter(q);
}
private void clearSearchPerformed(AjaxRequestTarget target){
refreshSyncTotalsModels();
searchModel.setObject(new AccountDetailsSearchDto());
TablePanel panel = getAccountsTable();
DataTable table = panel.getDataTable();
ObjectDataProvider provider = (ObjectDataProvider) table.getDataProvider();
provider.setQuery(ObjectQuery.createObjectQuery(createResourceQueryFilter()));
ConfigurationStorage storage = getSessionStorage().getConfiguration();
storage.setAccountSearchDto(searchModel.getObject());
storage.setAccountDetailsPaging(null);
panel.setCurrentPage(storage.getAccountDetailsPaging());
target.add(getTotalsPanel());
target.add(getSearchPanel());
target.add(getAccountsContainer());
}
private OperationResultType getResult(IModel<SelectableBean> model){
ShadowType shadow = getShadow(model);
return shadow.getResult();
}
private ShadowType getShadow(IModel<SelectableBean> model){
if(model == null || model.getObject() == null || model.getObject().getValue() == null){
return null;
}
return (ShadowType) model.getObject().getValue();
}
private <F extends FocusType> F loadShadowOwner(IModel<SelectableBean> model){
F owner = null;
ShadowType shadow = getShadow(model);
String shadowOid;
if(shadow != null){
shadowOid = shadow.getOid();
} else {
return null;
}
Task task = createSimpleTask(OPERATION_LOAD_ACCOUNT_OWNER);
OperationResult result = new OperationResult(OPERATION_LOAD_ACCOUNT_OWNER);
try {
PrismObject prismOwner = getModelService().searchShadowOwner(shadowOid, null, task, result);
if(prismOwner != null){
owner = (F) prismOwner.asObjectable();
}
} catch (ObjectNotFoundException exception){
//owner was not found, it's possible and it's ok on unlinked accounts
} catch (Exception ex){
result.recordFatalError(getString("PageAccounts.message.ownerNotFound", shadowOid), ex);
LoggingUtils.logUnexpectedException(LOGGER, "Could not load owner of account with oid: " + shadowOid, ex);
} finally {
result.computeStatusIfUnknown();
}
if(WebComponentUtil.showResultInPage(result)){
showResult(result, false);
}
return owner;
}
private <F extends FocusType> void ownerDetailsPerformed(AjaxRequestTarget target, IModel<SelectableBean> model){
F focus = loadShadowOwner(model);
if(focus == null){
error(getString("PageAccounts.message.cantShowOwner"));
target.add(getFeedbackPanel());
return;
}
PageParameters parameters = new PageParameters();
parameters.add(OnePageParameterEncoder.PARAMETER, focus.getOid());
if (focus instanceof UserType){
navigateToNext(PageUser.class, parameters);
} else if (focus instanceof RoleType){
navigateToNext(PageRole.class, parameters);
} else if (focus instanceof OrgType) {
navigateToNext(PageOrgUnit.class, parameters);
} else {
error(getString("PageAccounts.message.unsupportedOwnerType"));
target.add(getFeedbackPanel());
return;
}
}
}