diff --git a/config/initial-objects/200-lookup-languages.xml b/config/initial-objects/200-lookup-languages.xml index d42f076a5ec..eb0f04eb61f 100644 --- a/config/initial-objects/200-lookup-languages.xml +++ b/config/initial-objects/200-lookup-languages.xml @@ -64,6 +64,10 @@ lt + + nl + + pl diff --git a/config/initial-objects/210-lookup-locales.xml b/config/initial-objects/210-lookup-locales.xml index d91cef3eee3..3b9a748de40 100644 --- a/config/initial-objects/210-lookup-locales.xml +++ b/config/initial-objects/210-lookup-locales.xml @@ -55,6 +55,10 @@ lt + + nl + + pl diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/component/MultivalueContainerListPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/component/MultivalueContainerListPanel.java index 0d8c27597df..e76b0ee58f6 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/component/MultivalueContainerListPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/component/MultivalueContainerListPanel.java @@ -1,547 +1,547 @@ -/* - * Copyright (c) 2018 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.gui.impl.component; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import com.evolveum.midpoint.model.api.AssignmentObjectRelation; -import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView; -import com.evolveum.midpoint.web.component.MultiCompositedButtonPanel; -import com.evolveum.midpoint.web.component.MultiFunctinalButtonDto; -import com.evolveum.midpoint.web.component.objectdetails.AssignmentHolderTypeMainPanel; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentHolderType; - -import org.apache.wicket.AttributeModifier; -import org.apache.wicket.Component; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; -import org.apache.wicket.markup.html.WebMarkupContainer; -import org.apache.wicket.markup.repeater.Item; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.PropertyModel; - -import com.evolveum.midpoint.gui.api.GuiStyleConstants; -import com.evolveum.midpoint.gui.api.component.BasePanel; -import com.evolveum.midpoint.gui.api.model.LoadableModel; -import com.evolveum.midpoint.gui.api.prism.PrismContainerWrapper; -import com.evolveum.midpoint.gui.api.util.WebComponentUtil; -import com.evolveum.midpoint.gui.api.util.WebPrismUtil; -import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; -import com.evolveum.midpoint.gui.impl.prism.PrismContainerValueWrapper; -import com.evolveum.midpoint.gui.impl.util.GuiImplUtil; -import com.evolveum.midpoint.prism.Containerable; -import com.evolveum.midpoint.prism.PrismContainerDefinition; -import com.evolveum.midpoint.prism.PrismContainerValue; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.query.ObjectPaging; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.web.component.MultifunctionalButton; -import com.evolveum.midpoint.web.component.data.BoxedTablePanel; -import com.evolveum.midpoint.web.component.data.Table; -import com.evolveum.midpoint.web.component.data.column.ColumnMenuAction; -import com.evolveum.midpoint.web.component.menu.cog.ButtonInlineMenuItem; -import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItem; -import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItemAction; -import com.evolveum.midpoint.web.component.objectdetails.FocusMainPanel; -import com.evolveum.midpoint.web.component.prism.ValueStatus; -import com.evolveum.midpoint.web.component.search.Search; -import com.evolveum.midpoint.web.component.search.SearchFormPanel; -import com.evolveum.midpoint.web.component.search.SearchItemDefinition; -import com.evolveum.midpoint.web.component.util.MultivalueContainerListDataProvider; -import com.evolveum.midpoint.web.component.util.VisibleBehaviour; -import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour; -import com.evolveum.midpoint.web.session.PageStorage; -import com.evolveum.midpoint.web.session.UserProfileStorage.TableId; -import com.evolveum.midpoint.xml.ns._public.common.common_3.DisplayType; - -/** - * @author skublik - */ - -public abstract class MultivalueContainerListPanel extends BasePanel> { - - private static final long serialVersionUID = 1L; - - private static final String DOT_CLASS = MultivalueContainerListPanel.class.getName() + "."; - private static final String OPERATION_CREATE_NEW_VALUE = DOT_CLASS + "createNewValue"; - - public static final String ID_ITEMS = "items"; - private static final String ID_ITEMS_TABLE = "itemsTable"; - public static final String ID_SEARCH_ITEM_PANEL = "search"; - - - private static final Trace LOGGER = TraceManager.getTrace(MultivalueContainerListPanel.class); - - private TableId tableId; - private PageStorage pageStorage; - - private LoadableModel searchModel = null; - - public MultivalueContainerListPanel(String id, IModel> model, TableId tableId, PageStorage pageStorage) { - super(id, model); - this.tableId = tableId; - this.pageStorage = pageStorage; - - searchModel = new LoadableModel(false) { - - private static final long serialVersionUID = 1L; - - @Override - protected Search load() { - if (model == null || model.getObject() == null){ - return null; - } - List availableDefs = initSearchableItems(model.getObject()); - - Search search = new Search(model.getObject().getCompileTimeClass(), availableDefs); - return search; - } - - - }; - } - - - public MultivalueContainerListPanel(String id, PrismContainerDefinition def, TableId tableId, PageStorage pageStorage) { - super(id); - this.tableId = tableId; - this.pageStorage = pageStorage; - - searchModel = new LoadableModel(false) { - - private static final long serialVersionUID = 1L; - - @Override - protected Search load() { - List availableDefs = initSearchableItems(def); - - Search search = new Search(def.getCompileTimeClass(), availableDefs); - return search; - } - - - }; - } - - protected abstract List initSearchableItems(PrismContainerDefinition containerDef); - - @Override - protected void onInitialize() { - super.onInitialize(); - - initPaging(); - initLayout(); - } - - private void initLayout() { - - initListPanel(); - setOutputMarkupId(true); - - } - - protected abstract void initPaging(); - - private void initListPanel() { - WebMarkupContainer itemsContainer = new WebMarkupContainer(ID_ITEMS); - itemsContainer.setOutputMarkupId(true); - itemsContainer.setOutputMarkupPlaceholderTag(true); - add(itemsContainer); - - BoxedTablePanel> itemTable = initItemTable(); - itemTable.setOutputMarkupId(true); - itemTable.setOutputMarkupPlaceholderTag(true); - itemsContainer.add(itemTable); - - WebMarkupContainer searchContainer = getSearchPanel(ID_SEARCH_ITEM_PANEL); - searchContainer.setOutputMarkupId(true); - searchContainer.setOutputMarkupPlaceholderTag(true); - itemsContainer.add(searchContainer); - itemsContainer.add(new VisibleEnableBehaviour() { - - private static final long serialVersionUID = 1L; - - @Override - public boolean isVisible() { - return isListPanelVisible(); - } - }); - - } - - protected boolean isListPanelVisible() { - return true; - } - - protected WebMarkupContainer getSearchPanel(String contentAreaId) { - return new WebMarkupContainer(contentAreaId); - } - - protected abstract boolean enableActionNewObject(); - - protected IModel>> loadValuesModel() { - if (getModel() == null) { - LOGGER.info("Parent model is null. Cannot load model for values for table: {}", tableId.name()); - } - - return new PropertyModel<>(getModel(), "values"); - } - - private BoxedTablePanel> initItemTable() { - - MultivalueContainerListDataProvider containersProvider = new MultivalueContainerListDataProvider(this, loadValuesModel()) { - private static final long serialVersionUID = 1L; - - @Override - protected void saveProviderPaging(ObjectQuery query, ObjectPaging paging) { - pageStorage.setPaging(paging); - } - - @Override - public ObjectQuery getQuery() { - return MultivalueContainerListPanel.this.createProviderQuery(); - } - - @Override - protected List> searchThroughList() { - List> resultList = super.searchThroughList(); - return postSearch(resultList); - } - - }; - - List, String>> columns = createColumns(); - - int itemPerPage = (int) getPageBase().getItemsPerPage(tableId); - BoxedTablePanel> itemTable = new BoxedTablePanel>(ID_ITEMS_TABLE, - containersProvider, columns, tableId, itemPerPage) { - private static final long serialVersionUID = 1L; - - @Override - protected WebMarkupContainer createHeader(String headerId) { - return MultivalueContainerListPanel.this.initSearch(headerId); - } - - @Override - public int getItemsPerPage() { - return getPageBase().getSessionStorage().getUserProfile().getTables() - .get(getTableId()); - } - - @Override - protected Item> customizeNewRowItem(Item> item, - IModel> model) { - item.add(AttributeModifier.append("class", new IModel() { - - private static final long serialVersionUID = 1L; - - @Override - public String getObject() { - return GuiImplUtil.getObjectStatus(model.getObject()); - } - })); - return item; - } - - @Override - protected WebMarkupContainer createButtonToolbar(String id) { - return initButtonToolbar(id); - } - - }; - itemTable.setOutputMarkupId(true); - itemTable.setCurrentPage(pageStorage != null ? pageStorage.getPaging() : null); - return itemTable; - - } - - protected WebMarkupContainer initButtonToolbar(String id) { - return getNewItemButton(id); - } - - protected List createNewButtonDescription() { - return null; - } - - - public MultiCompositedButtonPanel getNewItemButton(String id) { - MultiCompositedButtonPanel newObjectIcon = - new MultiCompositedButtonPanel(id, createNewButtonDescription()) { - private static final long serialVersionUID = 1L; - - @Override - protected void buttonClickPerformed(AjaxRequestTarget target, AssignmentObjectRelation relationSepc, CompiledObjectCollectionView collectionViews) { - newItemPerformed(target, relationSepc); - } - - @Override - protected boolean isDefaultButtonVisible(){ - return getNewObjectGenericButtonVisibility(); - } - - @Override - protected DisplayType getMainButtonDisplayType() { - return getNewObjectButtonDisplayType(); - } - - @Override - protected DisplayType getDefaultObjectButtonDisplayType() { - return getNewObjectButtonDisplayType(); - } - }; - newObjectIcon.add(AttributeModifier.append("class", "btn-group btn-margin-right")); - newObjectIcon.add(new VisibleEnableBehaviour() { - private static final long serialVersionUID = 1L; - - @Override - public boolean isVisible() { - return enableActionNewObject(); - } - - @Override - public boolean isEnabled() { - return isNewObjectButtonEnabled(); - } - }); -// newObjectIcon.add(AttributeModifier.append("class", createStyleClassModelForNewObjectIcon())); - return newObjectIcon; - } - - protected boolean isNewObjectButtonEnabled(){ - return true; - } - - - protected boolean getNewObjectGenericButtonVisibility(){ - return true; - } - - - protected DisplayType getNewObjectButtonDisplayType(){ - return WebComponentUtil.createDisplayType(GuiStyleConstants.CLASS_ADD_NEW_OBJECT, "green", createStringResource("MainObjectListPanel.newObject").getString()); - } - - - protected WebMarkupContainer initSearch(String headerId) { - SearchFormPanel searchPanel = new SearchFormPanel(headerId, searchModel) { - - private static final long serialVersionUID = 1L; - - @Override - protected void searchPerformed(ObjectQuery query, AjaxRequestTarget target) { - MultivalueContainerListPanel.this.searchPerformed(query, target); - } - }; - searchPanel.add(new VisibleBehaviour(() -> isSearchEnabled())); - return searchPanel; - } - - protected boolean isSearchEnabled(){ - return true; - } - - private void searchPerformed(ObjectQuery query, AjaxRequestTarget target) { - Table table = getItemTable(); - table.setCurrentPage(null); - target.add((Component) table); - target.add(getPageBase().getFeedbackPanel()); - - } - - private ObjectQuery getQuery() { - Search search = searchModel.getObject(); - ObjectQuery query = search.createObjectQuery(getPageBase().getPrismContext()); - return query; - } - - private MultivalueContainerListDataProvider getDataProvider() { - return (MultivalueContainerListDataProvider) getItemTable().getDataTable().getDataProvider(); - } - - - protected IModel createStyleClassModelForNewObjectIcon() { - return new IModel() { - private static final long serialVersionUID = 1L; - - @Override - public String getObject() { - return "btn btn-success btn-sm"; - } - }; - } - - protected abstract List> postSearch(List> items); - - private ObjectQuery createProviderQuery() { - ObjectQuery searchQuery = isSearchEnabled() ? getQuery() : null; - - ObjectQuery customQuery = createQuery(); - - if (searchQuery != null && searchQuery.getFilter() != null) { - if (customQuery != null && customQuery.getFilter() != null) { - return getPrismContext().queryFactory().createQuery(getPrismContext().queryFactory().createAnd(customQuery.getFilter(), searchQuery.getFilter())); - } - return searchQuery; - - } - return customQuery; - } - - protected abstract ObjectQuery createQuery(); - - protected abstract List, String>> createColumns(); - -// protected void newItemPerformed(AjaxRequestTarget target){} - - protected void newItemPerformed(AjaxRequestTarget target, AssignmentObjectRelation influencingObject){} - - public BoxedTablePanel> getItemTable() { - return (BoxedTablePanel>) get(createComponentPath(ID_ITEMS, ID_ITEMS_TABLE)); - } - - public void refreshTable(AjaxRequestTarget ajaxRequestTarget) { - ajaxRequestTarget.add(getItemContainer().addOrReplace(initItemTable())); - } - - public WebMarkupContainer getItemContainer() { - return (WebMarkupContainer) get(ID_ITEMS); - } - - public PrismObject getFocusObject(){ - AssignmentHolderTypeMainPanel mainPanel = findParent(AssignmentHolderTypeMainPanel.class); - if (mainPanel != null) { - return mainPanel.getObjectWrapper().getObject(); - } - return null; - } - - public List> getSelectedItems() { - BoxedTablePanel> itemsTable = getItemTable(); - MultivalueContainerListDataProvider itemsProvider = (MultivalueContainerListDataProvider) itemsTable.getDataTable() - .getDataProvider(); - return itemsProvider.getAvailableData().stream().filter(a -> a.isSelected()).collect(Collectors.toList()); - } - - public void reloadSavePreviewButtons(AjaxRequestTarget target){ - FocusMainPanel mainPanel = findParent(FocusMainPanel.class); - if (mainPanel != null) { - mainPanel.reloadSavePreviewButtons(target); - } - } - - public List> getPerformedSelectedItems(IModel> rowModel) { - List> performedItems = new ArrayList>(); - List> listItems = getSelectedItems(); - if((listItems!= null && !listItems.isEmpty()) || rowModel != null) { - if(rowModel == null) { - performedItems.addAll(listItems); - listItems.forEach(itemConfigurationTypeContainerValueWrapper -> { - itemConfigurationTypeContainerValueWrapper.setSelected(false); - }); - } else { - performedItems.add(rowModel.getObject()); - rowModel.getObject().setSelected(false); - } - } - return performedItems; - } - - //TODO generalize for properties - public PrismContainerValueWrapper createNewItemContainerValueWrapper( - PrismContainerValue newItem, - PrismContainerWrapper model, AjaxRequestTarget target) { - - return WebPrismUtil.createNewValueWrapper(model, newItem, getPageBase(), target); - - } - - public ColumnMenuAction> createDeleteColumnAction() { - return new ColumnMenuAction>() { - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget target) { - if (getRowModel() == null) { - deleteItemPerformed(target, getSelectedItems()); - } else { - List> toDelete = new ArrayList<>(); - toDelete.add(getRowModel().getObject()); - deleteItemPerformed(target, toDelete); - } - } - }; - } - - public ColumnMenuAction> createEditColumnAction() { - return new ColumnMenuAction>() { - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget target) { - itemPerformedForDefaultAction(target, getRowModel(), getSelectedItems()); - } - }; - } - - protected abstract void itemPerformedForDefaultAction(AjaxRequestTarget target, IModel> rowModel, List> listItems); - - protected void deleteItemPerformed(AjaxRequestTarget target, List> toDelete) { - if (toDelete == null || toDelete.isEmpty()){ - warn(createStringResource("MultivalueContainerListPanel.message.noItemsSelected").getString()); - target.add(getPageBase().getFeedbackPanel()); - return; - } - toDelete.forEach(value -> { - if (value.getStatus() == ValueStatus.ADDED) { - PrismContainerWrapper wrapper = getModelObject(); - wrapper.getValues().remove(value); - } else { - value.setStatus(ValueStatus.DELETED); - } - value.setSelected(false); - }); - refreshTable(target); - reloadSavePreviewButtons(target); - } - - public List getDefaultMenuActions() { - List menuItems = new ArrayList<>(); - menuItems.add(new ButtonInlineMenuItem(createStringResource("pageAdminFocus.button.delete")) { - private static final long serialVersionUID = 1L; - - @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_DELETE_MENU_ITEM; - } - - @Override - public InlineMenuItemAction initAction() { - return createDeleteColumnAction(); - } - }); - - menuItems.add(new ButtonInlineMenuItem(createStringResource("PageBase.button.edit")) { - private static final long serialVersionUID = 1L; - - @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_EDIT_MENU_ITEM; - } - - @Override - public InlineMenuItemAction initAction() { - return createEditColumnAction(); - } - }); - return menuItems; - } -} +/* + * Copyright (c) 2018 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.gui.impl.component; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.evolveum.midpoint.model.api.AssignmentObjectRelation; +import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView; +import com.evolveum.midpoint.web.component.MultiCompositedButtonPanel; +import com.evolveum.midpoint.web.component.MultiFunctinalButtonDto; +import com.evolveum.midpoint.web.component.objectdetails.AssignmentHolderTypeMainPanel; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentHolderType; + +import org.apache.wicket.AttributeModifier; +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.PropertyModel; + +import com.evolveum.midpoint.gui.api.GuiStyleConstants; +import com.evolveum.midpoint.gui.api.component.BasePanel; +import com.evolveum.midpoint.gui.api.model.LoadableModel; +import com.evolveum.midpoint.gui.api.prism.PrismContainerWrapper; +import com.evolveum.midpoint.gui.api.util.WebComponentUtil; +import com.evolveum.midpoint.gui.api.util.WebPrismUtil; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; +import com.evolveum.midpoint.gui.impl.prism.PrismContainerValueWrapper; +import com.evolveum.midpoint.gui.impl.util.GuiImplUtil; +import com.evolveum.midpoint.prism.Containerable; +import com.evolveum.midpoint.prism.PrismContainerDefinition; +import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.query.ObjectPaging; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.web.component.MultifunctionalButton; +import com.evolveum.midpoint.web.component.data.BoxedTablePanel; +import com.evolveum.midpoint.web.component.data.Table; +import com.evolveum.midpoint.web.component.data.column.ColumnMenuAction; +import com.evolveum.midpoint.web.component.menu.cog.ButtonInlineMenuItem; +import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItem; +import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItemAction; +import com.evolveum.midpoint.web.component.objectdetails.FocusMainPanel; +import com.evolveum.midpoint.web.component.prism.ValueStatus; +import com.evolveum.midpoint.web.component.search.Search; +import com.evolveum.midpoint.web.component.search.SearchFormPanel; +import com.evolveum.midpoint.web.component.search.SearchItemDefinition; +import com.evolveum.midpoint.web.component.util.MultivalueContainerListDataProvider; +import com.evolveum.midpoint.web.component.util.VisibleBehaviour; +import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour; +import com.evolveum.midpoint.web.session.PageStorage; +import com.evolveum.midpoint.web.session.UserProfileStorage.TableId; +import com.evolveum.midpoint.xml.ns._public.common.common_3.DisplayType; + +/** + * @author skublik + */ + +public abstract class MultivalueContainerListPanel extends BasePanel> { + + private static final long serialVersionUID = 1L; + + private static final String DOT_CLASS = MultivalueContainerListPanel.class.getName() + "."; + private static final String OPERATION_CREATE_NEW_VALUE = DOT_CLASS + "createNewValue"; + + public static final String ID_ITEMS = "items"; + private static final String ID_ITEMS_TABLE = "itemsTable"; + public static final String ID_SEARCH_ITEM_PANEL = "search"; + + + private static final Trace LOGGER = TraceManager.getTrace(MultivalueContainerListPanel.class); + + private TableId tableId; + private PageStorage pageStorage; + + private LoadableModel searchModel = null; + + public MultivalueContainerListPanel(String id, IModel> model, TableId tableId, PageStorage pageStorage) { + super(id, model); + this.tableId = tableId; + this.pageStorage = pageStorage; + + searchModel = new LoadableModel(false) { + + private static final long serialVersionUID = 1L; + + @Override + protected Search load() { + if (model == null || model.getObject() == null){ + return null; + } + List availableDefs = initSearchableItems(model.getObject()); + + Search search = new Search(model.getObject().getCompileTimeClass(), availableDefs); + return search; + } + + + }; + } + + + public MultivalueContainerListPanel(String id, PrismContainerDefinition def, TableId tableId, PageStorage pageStorage) { + super(id); + this.tableId = tableId; + this.pageStorage = pageStorage; + + searchModel = new LoadableModel(false) { + + private static final long serialVersionUID = 1L; + + @Override + protected Search load() { + List availableDefs = initSearchableItems(def); + + Search search = new Search(def.getCompileTimeClass(), availableDefs); + return search; + } + + + }; + } + + protected abstract List initSearchableItems(PrismContainerDefinition containerDef); + + @Override + protected void onInitialize() { + super.onInitialize(); + + initPaging(); + initLayout(); + } + + private void initLayout() { + + initListPanel(); + setOutputMarkupId(true); + + } + + protected abstract void initPaging(); + + private void initListPanel() { + WebMarkupContainer itemsContainer = new WebMarkupContainer(ID_ITEMS); + itemsContainer.setOutputMarkupId(true); + itemsContainer.setOutputMarkupPlaceholderTag(true); + add(itemsContainer); + + BoxedTablePanel> itemTable = initItemTable(); + itemTable.setOutputMarkupId(true); + itemTable.setOutputMarkupPlaceholderTag(true); + itemsContainer.add(itemTable); + + WebMarkupContainer searchContainer = getSearchPanel(ID_SEARCH_ITEM_PANEL); + searchContainer.setOutputMarkupId(true); + searchContainer.setOutputMarkupPlaceholderTag(true); + itemsContainer.add(searchContainer); + itemsContainer.add(new VisibleEnableBehaviour() { + + private static final long serialVersionUID = 1L; + + @Override + public boolean isVisible() { + return isListPanelVisible(); + } + }); + + } + + protected boolean isListPanelVisible() { + return true; + } + + protected WebMarkupContainer getSearchPanel(String contentAreaId) { + return new WebMarkupContainer(contentAreaId); + } + + protected abstract boolean enableActionNewObject(); + + protected IModel>> loadValuesModel() { + if (getModel() == null) { + LOGGER.info("Parent model is null. Cannot load model for values for table: {}", tableId.name()); + } + + return new PropertyModel<>(getModel(), "values"); + } + + private BoxedTablePanel> initItemTable() { + + MultivalueContainerListDataProvider containersProvider = new MultivalueContainerListDataProvider(this, loadValuesModel()) { + private static final long serialVersionUID = 1L; + + @Override + protected void saveProviderPaging(ObjectQuery query, ObjectPaging paging) { + pageStorage.setPaging(paging); + } + + @Override + public ObjectQuery getQuery() { + return MultivalueContainerListPanel.this.createProviderQuery(); + } + + @Override + protected List> searchThroughList() { + List> resultList = super.searchThroughList(); + return postSearch(resultList); + } + + }; + + List, String>> columns = createColumns(); + + int itemPerPage = (int) getPageBase().getItemsPerPage(tableId); + BoxedTablePanel> itemTable = new BoxedTablePanel>(ID_ITEMS_TABLE, + containersProvider, columns, tableId, itemPerPage) { + private static final long serialVersionUID = 1L; + + @Override + protected WebMarkupContainer createHeader(String headerId) { + return MultivalueContainerListPanel.this.initSearch(headerId); + } + + @Override + public int getItemsPerPage() { + return getPageBase().getSessionStorage().getUserProfile().getTables() + .get(getTableId()); + } + + @Override + protected Item> customizeNewRowItem(Item> item, + IModel> model) { + item.add(AttributeModifier.append("class", new IModel() { + + private static final long serialVersionUID = 1L; + + @Override + public String getObject() { + return GuiImplUtil.getObjectStatus(model.getObject()); + } + })); + return item; + } + + @Override + protected WebMarkupContainer createButtonToolbar(String id) { + return initButtonToolbar(id); + } + + }; + itemTable.setOutputMarkupId(true); + itemTable.setCurrentPage(pageStorage != null ? pageStorage.getPaging() : null); + return itemTable; + + } + + protected WebMarkupContainer initButtonToolbar(String id) { + return getNewItemButton(id); + } + + protected List createNewButtonDescription() { + return null; + } + + + public MultiCompositedButtonPanel getNewItemButton(String id) { + MultiCompositedButtonPanel newObjectIcon = + new MultiCompositedButtonPanel(id, createNewButtonDescription()) { + private static final long serialVersionUID = 1L; + + @Override + protected void buttonClickPerformed(AjaxRequestTarget target, AssignmentObjectRelation relationSepc, CompiledObjectCollectionView collectionViews) { + newItemPerformed(target, relationSepc); + } + + @Override + protected boolean isDefaultButtonVisible(){ + return getNewObjectGenericButtonVisibility(); + } + + @Override + protected DisplayType getMainButtonDisplayType() { + return getNewObjectButtonDisplayType(); + } + + @Override + protected DisplayType getDefaultObjectButtonDisplayType() { + return getNewObjectButtonDisplayType(); + } + }; + newObjectIcon.add(AttributeModifier.append("class", "btn-group btn-margin-right")); + newObjectIcon.add(new VisibleEnableBehaviour() { + private static final long serialVersionUID = 1L; + + @Override + public boolean isVisible() { + return enableActionNewObject(); + } + + @Override + public boolean isEnabled() { + return isNewObjectButtonEnabled(); + } + }); +// newObjectIcon.add(AttributeModifier.append("class", createStyleClassModelForNewObjectIcon())); + return newObjectIcon; + } + + protected boolean isNewObjectButtonEnabled(){ + return true; + } + + + protected boolean getNewObjectGenericButtonVisibility(){ + return true; + } + + + protected DisplayType getNewObjectButtonDisplayType(){ + return WebComponentUtil.createDisplayType(GuiStyleConstants.CLASS_ADD_NEW_OBJECT, "green", createStringResource("MainObjectListPanel.newObject").getString()); + } + + + protected WebMarkupContainer initSearch(String headerId) { + SearchFormPanel searchPanel = new SearchFormPanel(headerId, searchModel) { + + private static final long serialVersionUID = 1L; + + @Override + protected void searchPerformed(ObjectQuery query, AjaxRequestTarget target) { + MultivalueContainerListPanel.this.searchPerformed(query, target); + } + }; + searchPanel.add(new VisibleBehaviour(() -> isSearchEnabled())); + return searchPanel; + } + + protected boolean isSearchEnabled(){ + return true; + } + + private void searchPerformed(ObjectQuery query, AjaxRequestTarget target) { + Table table = getItemTable(); + table.setCurrentPage(null); + target.add((Component) table); + target.add(getPageBase().getFeedbackPanel()); + + } + + private ObjectQuery getQuery() { + Search search = searchModel.getObject(); + ObjectQuery query = search.createObjectQuery(getPageBase().getPrismContext()); + return query; + } + + private MultivalueContainerListDataProvider getDataProvider() { + return (MultivalueContainerListDataProvider) getItemTable().getDataTable().getDataProvider(); + } + + + protected IModel createStyleClassModelForNewObjectIcon() { + return new IModel() { + private static final long serialVersionUID = 1L; + + @Override + public String getObject() { + return "btn btn-success btn-sm"; + } + }; + } + + protected abstract List> postSearch(List> items); + + private ObjectQuery createProviderQuery() { + ObjectQuery searchQuery = isSearchEnabled() ? getQuery() : null; + + ObjectQuery customQuery = createQuery(); + + if (searchQuery != null && searchQuery.getFilter() != null) { + if (customQuery != null && customQuery.getFilter() != null) { + return getPrismContext().queryFactory().createQuery(getPrismContext().queryFactory().createAnd(customQuery.getFilter(), searchQuery.getFilter())); + } + return searchQuery; + + } + return customQuery; + } + + protected abstract ObjectQuery createQuery(); + + protected abstract List, String>> createColumns(); + +// protected void newItemPerformed(AjaxRequestTarget target){} + + protected void newItemPerformed(AjaxRequestTarget target, AssignmentObjectRelation influencingObject){} + + public BoxedTablePanel> getItemTable() { + return (BoxedTablePanel>) get(createComponentPath(ID_ITEMS, ID_ITEMS_TABLE)); + } + + public void refreshTable(AjaxRequestTarget ajaxRequestTarget) { + ajaxRequestTarget.add(getItemContainer().addOrReplace(initItemTable())); + } + + public WebMarkupContainer getItemContainer() { + return (WebMarkupContainer) get(ID_ITEMS); + } + + public PrismObject getFocusObject(){ + AssignmentHolderTypeMainPanel mainPanel = findParent(AssignmentHolderTypeMainPanel.class); + if (mainPanel != null) { + return mainPanel.getObjectWrapper().getObject(); + } + return null; + } + + public List> getSelectedItems() { + BoxedTablePanel> itemsTable = getItemTable(); + MultivalueContainerListDataProvider itemsProvider = (MultivalueContainerListDataProvider) itemsTable.getDataTable() + .getDataProvider(); + return itemsProvider.getAvailableData().stream().filter(a -> a.isSelected()).collect(Collectors.toList()); + } + + public void reloadSavePreviewButtons(AjaxRequestTarget target){ + FocusMainPanel mainPanel = findParent(FocusMainPanel.class); + if (mainPanel != null) { + mainPanel.reloadSavePreviewButtons(target); + } + } + + public List> getPerformedSelectedItems(IModel> rowModel) { + List> performedItems = new ArrayList>(); + List> listItems = getSelectedItems(); + if((listItems!= null && !listItems.isEmpty()) || rowModel != null) { + if(rowModel == null) { + performedItems.addAll(listItems); + listItems.forEach(itemConfigurationTypeContainerValueWrapper -> { + itemConfigurationTypeContainerValueWrapper.setSelected(false); + }); + } else { + performedItems.add(rowModel.getObject()); + rowModel.getObject().setSelected(false); + } + } + return performedItems; + } + + //TODO generalize for properties + public PrismContainerValueWrapper createNewItemContainerValueWrapper( + PrismContainerValue newItem, + PrismContainerWrapper model, AjaxRequestTarget target) { + + return WebPrismUtil.createNewValueWrapper(model, newItem, getPageBase(), target); + + } + + public ColumnMenuAction> createDeleteColumnAction() { + return new ColumnMenuAction>() { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + if (getRowModel() == null) { + deleteItemPerformed(target, getSelectedItems()); + } else { + List> toDelete = new ArrayList<>(); + toDelete.add(getRowModel().getObject()); + deleteItemPerformed(target, toDelete); + } + } + }; + } + + public ColumnMenuAction> createEditColumnAction() { + return new ColumnMenuAction>() { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + itemPerformedForDefaultAction(target, getRowModel(), getSelectedItems()); + } + }; + } + + protected abstract void itemPerformedForDefaultAction(AjaxRequestTarget target, IModel> rowModel, List> listItems); + + protected void deleteItemPerformed(AjaxRequestTarget target, List> toDelete) { + if (toDelete == null || toDelete.isEmpty()){ + warn(createStringResource("MultivalueContainerListPanel.message.noItemsSelected").getString()); + target.add(getPageBase().getFeedbackPanel()); + return; + } + toDelete.forEach(value -> { + if (value.getStatus() == ValueStatus.ADDED) { + PrismContainerWrapper wrapper = getModelObject(); + wrapper.getValues().remove(value); + } else { + value.setStatus(ValueStatus.DELETED); + } + value.setSelected(false); + }); + refreshTable(target); + reloadSavePreviewButtons(target); + } + + public List getDefaultMenuActions() { + List menuItems = new ArrayList<>(); + menuItems.add(new ButtonInlineMenuItem(createStringResource("pageAdminFocus.button.delete")) { + private static final long serialVersionUID = 1L; + + @Override + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_DELETE_MENU_ITEM); + } + + @Override + public InlineMenuItemAction initAction() { + return createDeleteColumnAction(); + } + }); + + menuItems.add(new ButtonInlineMenuItem(createStringResource("PageBase.button.edit")) { + private static final long serialVersionUID = 1L; + + @Override + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_EDIT_MENU_ITEM); + } + + @Override + public InlineMenuItemAction initAction() { + return createEditColumnAction(); + } + }); + return menuItems; + } +} diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/page/admin/configuration/component/NotificationConfigTabPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/page/admin/configuration/component/NotificationConfigTabPanel.java index 8bdbfa567ef..706659e0c1d 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/page/admin/configuration/component/NotificationConfigTabPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/page/admin/configuration/component/NotificationConfigTabPanel.java @@ -10,6 +10,8 @@ import java.util.List; import java.util.stream.Collectors; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; + import org.apache.commons.lang3.StringUtils; import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; @@ -445,8 +447,8 @@ private List getMenuActions() { private static final long serialVersionUID = 1L; @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_DELETE_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_DELETE_MENU_ITEM); } @Override @@ -459,8 +461,8 @@ public InlineMenuItemAction initAction() { private static final long serialVersionUID = 1L; @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_EDIT_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_EDIT_MENU_ITEM); } @Override diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentPanel.java index a426224b080..82e0c2bea8a 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentPanel.java @@ -848,8 +848,8 @@ private List getAssignmentMenu private static final long serialVersionUID = 1L; @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_DELETE_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_DELETE_MENU_ITEM); } @Override @@ -866,8 +866,8 @@ public InlineMenuItemAction initAction() { private static final long serialVersionUID = 1L; @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_DELETE_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_DELETE_MENU_ITEM); } @Override @@ -881,8 +881,8 @@ public InlineMenuItemAction initAction() { private static final long serialVersionUID = 1L; @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_EDIT_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_EDIT_MENU_ITEM); } @Override @@ -894,8 +894,8 @@ public InlineMenuItemAction initAction() { private static final long serialVersionUID = 1L; @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_NAVIGATE_ARROW; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_NAVIGATE_ARROW); } @Override diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/MultiButtonPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/MultiButtonPanel.java index 7685eb6bd70..6fc90113b5a 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/MultiButtonPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/MultiButtonPanel.java @@ -9,6 +9,8 @@ import com.evolveum.midpoint.gui.api.component.BasePanel; import com.evolveum.midpoint.web.component.AjaxIconButton; + +import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.behavior.AttributeAppender; import org.apache.wicket.markup.repeater.RepeatingView; @@ -47,14 +49,14 @@ private void initLayout() { add(buttons); for (int id = 0; id < numberOfButtons; id++) { - AjaxIconButton button = createButton(id, buttons.newChildId(), getModel()); + Component button = createButton(id, buttons.newChildId(), getModel()); if (button != null) { buttons.add(button); } } } - protected AjaxIconButton createButton(int index, String componentId, IModel model) { + protected Component createButton(int index, String componentId, IModel model) { return null; } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/column/InlineMenuButtonColumn.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/column/InlineMenuButtonColumn.java index b841b11f883..f015425bb0b 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/column/InlineMenuButtonColumn.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/column/InlineMenuButtonColumn.java @@ -8,6 +8,8 @@ package com.evolveum.midpoint.web.component.data.column; import com.evolveum.midpoint.gui.api.page.PageBase; +import com.evolveum.midpoint.gui.impl.component.AjaxCompositedIconButton; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; import com.evolveum.midpoint.web.component.AjaxIconButton; import com.evolveum.midpoint.web.component.data.MenuMultiButtonPanel; import com.evolveum.midpoint.web.component.dialog.ConfirmationPanel; @@ -20,6 +22,7 @@ import org.apache.wicket.Component; 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.AbstractColumn; import org.apache.wicket.markup.repeater.Item; @@ -112,16 +115,18 @@ private Component getPanel(String componentId, IModel rowModel, private static final long serialVersionUID = 1L; @Override - protected AjaxIconButton createButton(int index, String componentId, IModel model) { - AjaxIconButton btn = buildDefaultButton(componentId, - new Model<>(getButtonIconCss(index, buttonMenuItems)), - new Model<>(getButtonTitle(index, buttonMenuItems)), - new Model<>(getButtonCssClass()), - target -> { - setRowModelToButtonAction(rowModel, buttonMenuItems); - buttonMenuItemClickPerformed(index, buttonMenuItems, target); - }); - btn.showTitleAsLabel(false); + protected Component createButton(int index, String componentId, IModel model) { + CompositedIconBuilder builder = getIconCompositedBuilder(index, buttonMenuItems); + AjaxCompositedIconButton btn = new AjaxCompositedIconButton(componentId, builder.build(), + Model.of(getButtonTitle(index, buttonMenuItems))) { + @Override + public void onClick(AjaxRequestTarget target) { + setRowModelToButtonAction(rowModel, buttonMenuItems); + buttonMenuItemClickPerformed(index, buttonMenuItems, target); + } + }; + + btn.add(AttributeAppender.append("class", " btn btn-default btn-xs")); btn.add(new EnableBehaviour(() -> isButtonMenuItemEnabled(model))); return btn; @@ -209,11 +214,11 @@ private boolean isButtonEnabled(int id, List buttonMenuIte return buttonMenuItems.get(id).getEnabled().getObject().booleanValue(); } - private String getButtonIconCss(int id, List buttonMenuItems) { + private CompositedIconBuilder getIconCompositedBuilder(int id, List buttonMenuItems) { if (id >= buttonMenuItems.size()){ return null; } - return buttonMenuItems.get(id).getButtonIconCssClass() + " fa-fw"; + return buttonMenuItems.get(id).getIconCompositedBuilder(); // + " fa-fw"; } private String getButtonTitle(int id, List buttonMenuItems) { @@ -287,8 +292,8 @@ public InlineMenuItemAction initAction() { } @Override - public String getButtonIconCssClass() { - return ((ButtonInlineMenuItem) item).getButtonIconCssClass(); + public CompositedIconBuilder getIconCompositedBuilder() { + return ((ButtonInlineMenuItem) item).getIconCompositedBuilder(); } public IModel getConfirmationMessageModel() { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/menu/cog/ButtonInlineMenuItem.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/menu/cog/ButtonInlineMenuItem.java index f51a2150af3..1bbab47b975 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/menu/cog/ButtonInlineMenuItem.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/menu/cog/ButtonInlineMenuItem.java @@ -1,25 +1,36 @@ -/* - * Copyright (c) 2010-2018 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.web.component.menu.cog; - -import org.apache.wicket.model.IModel; - -/** - * Created by honchar - */ -public abstract class ButtonInlineMenuItem extends InlineMenuItem { - - public ButtonInlineMenuItem(IModel labelModel){ - super(labelModel); - } - - public ButtonInlineMenuItem(IModel labelModel, boolean isSubmit){ - super(labelModel, isSubmit); - } - - public abstract String getButtonIconCssClass(); -} +/* + * Copyright (c) 2010-2018 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.web.component.menu.cog; + +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; + +import com.evolveum.midpoint.gui.impl.component.icon.IconCssStyle; + +import org.apache.wicket.model.IModel; + +/** + * Created by honchar + */ +public abstract class ButtonInlineMenuItem extends InlineMenuItem { + + public ButtonInlineMenuItem(IModel labelModel){ + super(labelModel); + } + + public ButtonInlineMenuItem(IModel labelModel, boolean isSubmit){ + super(labelModel, isSubmit); + } + + public abstract CompositedIconBuilder getIconCompositedBuilder(); + + protected CompositedIconBuilder getDefaultCompositedIconBuilder(String basicIcon){ + CompositedIconBuilder builder = new CompositedIconBuilder(); + builder.setBasicIcon(basicIcon, IconCssStyle.IN_ROW_STYLE); + return builder; + } + +} diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusPersonasTabPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusPersonasTabPanel.java index ca12c55bcdd..b8b911ea896 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusPersonasTabPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusPersonasTabPanel.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; import com.evolveum.midpoint.web.component.util.SelectableBean; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; @@ -76,8 +77,8 @@ protected List createInlineMenu() { private static final long serialVersionUID = 1L; @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_NAVIGATE_ARROW; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_NAVIGATE_ARROW); } @Override diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusProjectionsTabPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusProjectionsTabPanel.java index 232e26a056d..98bf4ef2411 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusProjectionsTabPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/FocusProjectionsTabPanel.java @@ -9,14 +9,12 @@ import java.util.ArrayList; import java.util.List; -import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; import com.evolveum.midpoint.model.api.AssignmentObjectRelation; import com.evolveum.midpoint.web.component.search.SearchFactory; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -52,7 +50,6 @@ import com.evolveum.midpoint.gui.impl.component.data.column.AbstractItemWrapperColumn.ColumnType; import com.evolveum.midpoint.gui.impl.component.icon.CompositedIcon; import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; -import com.evolveum.midpoint.gui.impl.component.icon.IconCssStyle; import com.evolveum.midpoint.gui.impl.component.data.column.CompositedIconColumn; import com.evolveum.midpoint.gui.impl.component.data.column.PrismContainerWrapperColumn; import com.evolveum.midpoint.gui.impl.factory.ItemRealValueModel; @@ -63,16 +60,13 @@ import com.evolveum.midpoint.gui.impl.prism.PrismPropertyWrapper; import com.evolveum.midpoint.gui.impl.prism.PrismValueWrapper; import com.evolveum.midpoint.gui.impl.prism.ShadowPanel; -import com.evolveum.midpoint.prism.PrismContainer; import com.evolveum.midpoint.prism.PrismContainerDefinition; -import com.evolveum.midpoint.prism.PrismContainerValue; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismObjectDefinition; import com.evolveum.midpoint.prism.PrismPropertyDefinition; import com.evolveum.midpoint.prism.PrismPropertyValue; import com.evolveum.midpoint.prism.PrismReferenceDefinition; import com.evolveum.midpoint.prism.PrismValue; -import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; @@ -91,7 +85,6 @@ import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItem; import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItemAction; import com.evolveum.midpoint.web.component.search.SearchItemDefinition; -import com.evolveum.midpoint.web.model.PrismPropertyWrapperModel; import com.evolveum.midpoint.web.page.admin.PageAdminFocus; import com.evolveum.midpoint.web.page.admin.users.dto.UserDtoStatus; import com.evolveum.midpoint.web.session.PageStorage; @@ -505,8 +498,8 @@ public void onClick(AjaxRequestTarget target) { } @Override - public String getButtonIconCssClass() { - return "fa fa-check"; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder("fa fa-check"); } }; items.add(item); @@ -591,8 +584,8 @@ public void onClick(AjaxRequestTarget target) { private static final long serialVersionUID = 1L; @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_EDIT_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_EDIT_MENU_ITEM); } @Override diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/ObjectHistoryTabPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/ObjectHistoryTabPanel.java index f11ff8f9712..6549772150a 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/ObjectHistoryTabPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/ObjectHistoryTabPanel.java @@ -10,6 +10,7 @@ import java.util.List; +import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; @@ -95,7 +96,7 @@ public void populateItem(Item> cellItem, St private static final long serialVersionUID = 1L; @Override - protected AjaxIconButton createButton(int index, String componentId, IModel model) { + protected Component createButton(int index, String componentId, IModel model) { AjaxIconButton btn = null; switch (index) { case 0: diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/util/FocusListInlineMenuHelper.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/util/FocusListInlineMenuHelper.java index 64cc992b500..cf866455e5e 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/util/FocusListInlineMenuHelper.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/util/FocusListInlineMenuHelper.java @@ -11,6 +11,8 @@ 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.gui.impl.component.icon.CompositedIconBuilder; +import com.evolveum.midpoint.gui.impl.component.icon.IconCssStyle; import com.evolveum.midpoint.model.api.ModelExecuteOptions; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.schema.result.OperationResult; @@ -25,8 +27,11 @@ import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItem; import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItemAction; import com.evolveum.midpoint.web.page.admin.PageAdminObjectList; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; + import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.model.IModel; import org.jetbrains.annotations.NotNull; @@ -73,7 +78,7 @@ public FocusListInlineMenuHelper(@NotNull Class objectClass, @NotNull PageBas public List createRowActions() { List menu = new ArrayList<>(); - menu.add(new ButtonInlineMenuItem(parentPage.createStringResource("FocusListInlineMenuHelper.menu.enable")) { + ButtonInlineMenuItem enableItem = new ButtonInlineMenuItem(parentPage.createStringResource("FocusListInlineMenuHelper.menu.enable")) { private static final long serialVersionUID = 1L; @Override @@ -93,8 +98,8 @@ public void onClick(AjaxRequestTarget target) { } @Override - public String getButtonIconCssClass(){ - return GuiStyleConstants.CLASS_OBJECT_USER_ICON; + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_OBJECT_USER_ICON); } @Override @@ -103,9 +108,11 @@ public IModel getConfirmationMessageModel(){ return FocusListInlineMenuHelper.this.getConfirmationMessageModel((ColumnMenuAction) getAction(), actionName); } - }); + }; + enableItem.setVisibilityChecker(FocusListInlineMenuHelper::isObjectDisabled); + menu.add(enableItem); - menu.add(new InlineMenuItem(parentPage.createStringResource("FocusListInlineMenuHelper.menu.disable")) { + ButtonInlineMenuItem disableItem = new ButtonInlineMenuItem(parentPage.createStringResource("FocusListInlineMenuHelper.menu.disable")) { private static final long serialVersionUID = 1L; @Override @@ -130,7 +137,15 @@ public IModel getConfirmationMessageModel(){ return FocusListInlineMenuHelper.this.getConfirmationMessageModel((ColumnMenuAction) getAction(), actionName); } - }); + @Override + public CompositedIconBuilder getIconCompositedBuilder(){ + CompositedIconBuilder builder = getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_OBJECT_USER_ICON); + builder.appendLayerIcon(WebComponentUtil.createIconType(GuiStyleConstants.CLASS_BAN), IconCssStyle.BOTTOM_RIGHT_STYLE); + return builder; } + + }; + disableItem.setVisibilityChecker(FocusListInlineMenuHelper::isObjectEnabled); + menu.add(disableItem); menu.add(new ButtonInlineMenuItem(parentPage.createStringResource("FocusListInlineMenuHelper.menu.reconcile")) { private static final long serialVersionUID = 1L; @@ -152,8 +167,8 @@ public void onClick(AjaxRequestTarget target) { } @Override - public String getButtonIconCssClass(){ - return GuiStyleConstants.CLASS_RECONCILE_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_RECONCILE_MENU_ITEM); } @Override @@ -320,6 +335,21 @@ private List getObjectsToActOn(AjaxRequestTarget target, F selectedObject) { } } + public static boolean isObjectEnabled(IModel rowModel, boolean isHeader) { + if (rowModel == null || isHeader) { + return true; + } + FocusType focusObject = ((IModel>) rowModel).getObject().getValue(); + return focusObject != null && ActivationStatusType.ENABLED.equals(focusObject.getActivation().getEffectiveStatus()); + } + + public static boolean isObjectDisabled(IModel rowModel, boolean isHeader) { + if (rowModel == null || isHeader) { + return true; + } + FocusType focusObject = ((IModel>) rowModel).getObject().getValue(); + return focusObject != null && ActivationStatusType.DISABLED.equals(focusObject.getActivation().getEffectiveStatus()); + } protected boolean isShowConfirmationDialog(ColumnMenuAction action){ return false; } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemsPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemsPanel.java index fc2be5d62bc..9f2094cca37 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemsPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemsPanel.java @@ -11,17 +11,15 @@ import com.evolveum.midpoint.gui.api.component.ContainerableListPanel; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; import com.evolveum.midpoint.gui.impl.prism.PrismContainerValueWrapper; import com.evolveum.midpoint.prism.PrismConstants; import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.prism.query.ObjectFilter; -import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.CaseTypeUtil; -import com.evolveum.midpoint.schema.util.WorkItemId; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.web.component.data.column.*; import com.evolveum.midpoint.web.component.menu.cog.ButtonInlineMenuItem; @@ -31,7 +29,6 @@ import com.evolveum.midpoint.web.page.admin.workflow.PageAttorneySelection; import com.evolveum.midpoint.web.session.PageStorage; import com.evolveum.midpoint.web.session.UserProfileStorage; -import com.evolveum.midpoint.web.util.OnePageParameterEncoder; import com.evolveum.midpoint.wf.util.ApprovalUtils; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; @@ -220,8 +217,8 @@ public IModel getConfirmationMessageModel(){ } @Override - public String getButtonIconCssClass(){ - return GuiStyleConstants.CLASS_ICON_NO_OBJECTS; + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_ICON_NO_OBJECTS); } }); menu.add(new ButtonInlineMenuItem(createStringResource("pageWorkItem.button.approve")) { @@ -239,8 +236,8 @@ public void onClick(AjaxRequestTarget target) { } @Override - public String getButtonIconCssClass(){ - return GuiStyleConstants.CLASS_ICON_ACTIVATION_ACTIVE; + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_ICON_ACTIVATION_ACTIVE); } @Override diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCases.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCases.java index 48c33395eb8..7628e8506ce 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCases.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCases.java @@ -9,6 +9,7 @@ import com.evolveum.midpoint.gui.api.GuiStyleConstants; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; import com.evolveum.midpoint.prism.query.ObjectOrdering; import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.SelectorOptions; @@ -166,8 +167,8 @@ public IModel getEnabled() { } @Override - public String getButtonIconCssClass(){ - return GuiStyleConstants.CLASS_STOP_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_STOP_MENU_ITEM); } @Override @@ -200,8 +201,8 @@ public void onClick(AjaxRequestTarget target) { } @Override - public String getButtonIconCssClass(){ - return GuiStyleConstants.CLASS_DELETE_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_DELETE_MENU_ITEM); } @Override diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageWorkItemsClaimable.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageWorkItemsClaimable.java index 28938e41edd..d05c264de13 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageWorkItemsClaimable.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageWorkItemsClaimable.java @@ -10,6 +10,7 @@ import com.evolveum.midpoint.gui.api.GuiStyleConstants; import com.evolveum.midpoint.gui.api.component.ContainerableListPanel; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; import com.evolveum.midpoint.gui.impl.prism.PrismContainerValueWrapper; import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.schema.result.OperationResult; @@ -104,8 +105,8 @@ public IModel getConfirmationMessageModel(){ } @Override - public String getButtonIconCssClass(){ - return GuiStyleConstants.CLASS_ICON_CLAIM; + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_ICON_CLAIM); } }); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertCampaign.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertCampaign.java index c87fe5733c9..9de45786370 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertCampaign.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertCampaign.java @@ -323,7 +323,7 @@ public void populateItem(Item> cellItem, String comp private static final long serialVersionUID = 1L; @Override - protected AjaxIconButton createButton(int index, String componentId, IModel model) { + protected Component createButton(int index, String componentId, IModel model) { AjaxIconButton btn = null; if (index < responses) { btn = buildDefaultButton(componentId, null, new Model(availableResponses.getTitle(index)), diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertDecisions.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertDecisions.java index e8e5753cd39..8349a26c08c 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertDecisions.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertDecisions.java @@ -363,7 +363,7 @@ public void populateItem(Item> cellItem, String private static final long serialVersionUID = 1L; @Override - protected AjaxIconButton createButton(int index, String componentId, IModel model) { + protected Component createButton(int index, String componentId, IModel model) { AjaxIconButton btn; if (index < responses) { btn = buildDefaultButton(componentId, null, new Model(availableResponses.getTitle(index)), diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertDefinitions.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertDefinitions.java index 942a0418275..0b073a404f0 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertDefinitions.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/certification/PageCertDefinitions.java @@ -37,6 +37,8 @@ import com.evolveum.midpoint.web.util.OnePageParameterEncoder; import com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationCampaignType; import com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationDefinitionType; + +import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; @@ -162,7 +164,7 @@ public void populateItem(Item> model) { + protected Component createButton(int index, String componentId, IModel> model) { AjaxIconButton btn = null; switch (index) { case 0: diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsLoggedInUsersPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsLoggedInUsersPanel.java index d3231c5628d..0f6647b18a9 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsLoggedInUsersPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsLoggedInUsersPanel.java @@ -11,6 +11,7 @@ import com.evolveum.midpoint.gui.api.component.BasePanel; import com.evolveum.midpoint.gui.api.component.MainObjectListPanel; import com.evolveum.midpoint.gui.api.model.LoadableModel; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.web.component.data.BaseSortableDataProvider; @@ -127,8 +128,8 @@ private List initInlineMenu() { private static final long serialVersionUID = 1L; @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_ICON_SIGN_OUT; + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_ICON_SIGN_OUT); } @Override diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageCreatedReports.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageCreatedReports.java index 44251e00088..299766ea48a 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageCreatedReports.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageCreatedReports.java @@ -20,14 +20,14 @@ import javax.xml.namespace.QName; +import com.evolveum.midpoint.gui.api.GuiStyleConstants; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; import com.evolveum.midpoint.model.api.ModelAuthorizationAction; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItemAction; import com.evolveum.midpoint.web.component.util.SelectableBean; import com.evolveum.midpoint.web.page.admin.PageAdminObjectList; -import com.evolveum.midpoint.web.page.admin.configuration.PageDebugView; -import com.evolveum.midpoint.web.page.admin.configuration.PageEvaluateMapping; import com.evolveum.midpoint.web.page.admin.configuration.PageTraceView; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.commons.lang.StringUtils; @@ -357,8 +357,8 @@ public void onClick(AjaxRequestTarget target) { } @Override - public String getButtonIconCssClass() { - return "fa fa-minus"; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder("fa fa-minus"); } }); @@ -391,8 +391,8 @@ public void onClick(AjaxRequestTarget target) { } @Override - public String getButtonIconCssClass() { - return "fa fa-download"; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder("fa fa-download"); } @Override @@ -425,8 +425,8 @@ public void onClick(AjaxRequestTarget target) { } @Override - public String getButtonIconCssClass() { - return "fa fa-eye"; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder("fa fa-eye"); } @Override diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageReports.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageReports.java index e8cb34ef72b..0850372e815 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageReports.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageReports.java @@ -1,245 +1,246 @@ -/* - * Copyright (c) 2010-2017 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.web.page.admin.reports; - -import com.evolveum.midpoint.gui.api.GuiStyleConstants; -import com.evolveum.midpoint.gui.api.component.MainObjectListPanel; -import com.evolveum.midpoint.model.api.AssignmentObjectRelation; -import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView; -import com.evolveum.midpoint.prism.PrismContainer; -import com.evolveum.midpoint.schema.constants.ObjectTypes; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; -import com.evolveum.midpoint.security.api.AuthorizationConstants; -import com.evolveum.midpoint.task.api.Task; -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.application.Url; -import com.evolveum.midpoint.web.component.data.Table; -import com.evolveum.midpoint.web.component.data.column.ColumnMenuAction; -import com.evolveum.midpoint.web.component.menu.cog.ButtonInlineMenuItem; -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.SelectableBean; -import com.evolveum.midpoint.web.component.util.SelectableBeanImpl; -import com.evolveum.midpoint.web.page.admin.PageAdmin; -import com.evolveum.midpoint.web.page.admin.configuration.PageAdminConfiguration; -import com.evolveum.midpoint.web.page.admin.reports.component.RunReportPopupPanel; -import com.evolveum.midpoint.web.session.UserProfileStorage; -import com.evolveum.midpoint.web.util.OnePageParameterEncoder; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportEngineSelectionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportParameterType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportType; - -import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemObjectsType; - -import org.apache.wicket.ajax.AjaxRequestTarget; -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.form.Form; -import org.apache.wicket.request.mapper.parameter.PageParameters; - -import java.util.ArrayList; -import java.util.List; - - -/** - * @author lazyman - */ -@PageDescriptor( - urls = { - @Url(mountUrl = "/admin/reports", matchUrlForSecurity = "/admin/reports") - }, - action = { - @AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_REPORTS_ALL_URL, - label = PageAdminConfiguration.AUTH_CONFIGURATION_ALL_LABEL, - description = PageAdminConfiguration.AUTH_CONFIGURATION_ALL_DESCRIPTION), - @AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_REPORTS_URL, - label = "PageReports.auth.reports.label", - description = "PageReports.auth.reports.description")}) -public class PageReports extends PageAdmin { - - private static final Trace LOGGER = TraceManager.getTrace(PageReports.class); - - private static final String DOT_CLASS = PageReports.class.getName() + "."; - private static final String OPERATION_RUN_REPORT = DOT_CLASS + "runReport"; - - private static final String ID_MAIN_FORM = "mainForm"; - private static final String ID_REPORTS_TABLE = "reportsTable"; - - public PageReports() { - initLayout(); - } - - private void initLayout() { - Form mainForm = new com.evolveum.midpoint.web.component.form.Form(ID_MAIN_FORM); - add(mainForm); - - MainObjectListPanel table = - new MainObjectListPanel(ID_REPORTS_TABLE, ReportType.class, UserProfileStorage.TableId.PAGE_REPORTS, - null) { - - @Override - protected IColumn, String> createCheckboxColumn() { - return null; - } - - @Override - public void objectDetailsPerformed(AjaxRequestTarget target, ReportType report) { - if (report != null) { - PageReports.this.reportDetailsPerformed(target, report.getOid()); - } - } - - @Override - protected List, String>> createColumns() { - return PageReports.this.initColumns(); - } - - @Override - protected List createInlineMenu() { - return PageReports.this.createInlineMenu(); - } - - @Override - protected void newObjectPerformed(AjaxRequestTarget target, AssignmentObjectRelation relation, CompiledObjectCollectionView collectionView) { - navigateToNext(PageNewReport.class); - } - }; - table.setOutputMarkupId(true); - mainForm.add(table); - - } - - private List, String>> initColumns() { - List, String>> columns = new ArrayList<>(); - - IColumn column = new PropertyColumn(createStringResource("PageReports.table.description"), "value.description"); - columns.add(column); - - return columns; - } - - private void reportDetailsPerformed(AjaxRequestTarget target, String oid) { - PageParameters params = new PageParameters(); - params.add(OnePageParameterEncoder.PARAMETER, oid); - navigateToNext(PageCreatedReports.class, params); - } - - private List createInlineMenu(){ - List menu = new ArrayList<>(); - menu.add(new ButtonInlineMenuItem(createStringResource("PageReports.button.run")) { - private static final long serialVersionUID = 1L; - - @Override - public InlineMenuItemAction initAction() { - return new ColumnMenuAction>() { - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget target) { - ReportType report = getRowModel().getObject().getValue(); - runReportPerformed(target, report); - } - }; - } - - @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_START_MENU_ITEM; - } - - @Override - public boolean isHeaderMenuItem(){ - return false; - } - }); - menu.add(new ButtonInlineMenuItem(createStringResource("PageReports.button.configure")) { - private static final long serialVersionUID = 1L; - - @Override - public InlineMenuItemAction initAction() { - return new ColumnMenuAction>() { - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget target) { - ReportType reportObject = getRowModel().getObject().getValue(); - configurePerformed(target, reportObject); - } - }; - } - - @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_EDIT_MENU_ITEM; - } - - @Override - public boolean isHeaderMenuItem(){ - return false; - } - }); - return menu; - - } - - private void runConfirmPerformed(AjaxRequestTarget target, ReportType reportType, PrismContainer reportParam) { - OperationResult result = new OperationResult(OPERATION_RUN_REPORT); - Task task = createSimpleTask(OPERATION_RUN_REPORT); - task.getUpdatedTaskObject().asObjectable().getAssignment() - .add(ObjectTypeUtil.createAssignmentTo(SystemObjectsType.ARCHETYPE_REPORT_TASK.value(), ObjectTypes.ARCHETYPE, getPrismContext())); - task.getUpdatedTaskObject().asObjectable().getArchetypeRef() - .add(ObjectTypeUtil.createObjectRef(SystemObjectsType.ARCHETYPE_REPORT_TASK.value(), ObjectTypes.ARCHETYPE)); - try { - getReportManager().runReport(reportType.asPrismObject(), reportParam, task, result); - } catch (Exception ex) { - result.recordFatalError(ex); - } finally { - result.computeStatusIfUnknown(); - } - - showResult(result); - target.add(getFeedbackPanel(), get(createComponentPath(ID_MAIN_FORM))); - hideMainPopup(target); - - } - - protected void runReportPerformed(AjaxRequestTarget target, ReportType report) { - - if(report.getReportEngine() != null && report.getReportEngine().equals(ReportEngineSelectionType.DASHBOARD)) { - runConfirmPerformed(target, report, null); - return; - } - - RunReportPopupPanel runReportPopupPanel = new RunReportPopupPanel(getMainPopupBodyId(), report) { - - private static final long serialVersionUID = 1L; - - protected void runConfirmPerformed(AjaxRequestTarget target, ReportType reportType, PrismContainer reportParam) { - PageReports.this.runConfirmPerformed(target, reportType, reportParam); - hideMainPopup(target); - - } - }; - showMainPopup(runReportPopupPanel, target); - - } - - private void configurePerformed(AjaxRequestTarget target, ReportType report) { - PageParameters params = new PageParameters(); - params.add(OnePageParameterEncoder.PARAMETER, report.getOid()); - navigateToNext(PageReport.class, params); - } - - private Table getReportTable() { - return (Table) get(createComponentPath(ID_MAIN_FORM, ID_REPORTS_TABLE)); - } -} +/* + * Copyright (c) 2010-2017 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.web.page.admin.reports; + +import com.evolveum.midpoint.gui.api.GuiStyleConstants; +import com.evolveum.midpoint.gui.api.component.MainObjectListPanel; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; +import com.evolveum.midpoint.model.api.AssignmentObjectRelation; +import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView; +import com.evolveum.midpoint.prism.PrismContainer; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.security.api.AuthorizationConstants; +import com.evolveum.midpoint.task.api.Task; +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.application.Url; +import com.evolveum.midpoint.web.component.data.Table; +import com.evolveum.midpoint.web.component.data.column.ColumnMenuAction; +import com.evolveum.midpoint.web.component.menu.cog.ButtonInlineMenuItem; +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.SelectableBean; +import com.evolveum.midpoint.web.component.util.SelectableBeanImpl; +import com.evolveum.midpoint.web.page.admin.PageAdmin; +import com.evolveum.midpoint.web.page.admin.configuration.PageAdminConfiguration; +import com.evolveum.midpoint.web.page.admin.reports.component.RunReportPopupPanel; +import com.evolveum.midpoint.web.session.UserProfileStorage; +import com.evolveum.midpoint.web.util.OnePageParameterEncoder; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportEngineSelectionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportParameterType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportType; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemObjectsType; + +import org.apache.wicket.ajax.AjaxRequestTarget; +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.form.Form; +import org.apache.wicket.request.mapper.parameter.PageParameters; + +import java.util.ArrayList; +import java.util.List; + + +/** + * @author lazyman + */ +@PageDescriptor( + urls = { + @Url(mountUrl = "/admin/reports", matchUrlForSecurity = "/admin/reports") + }, + action = { + @AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_REPORTS_ALL_URL, + label = PageAdminConfiguration.AUTH_CONFIGURATION_ALL_LABEL, + description = PageAdminConfiguration.AUTH_CONFIGURATION_ALL_DESCRIPTION), + @AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_REPORTS_URL, + label = "PageReports.auth.reports.label", + description = "PageReports.auth.reports.description")}) +public class PageReports extends PageAdmin { + + private static final Trace LOGGER = TraceManager.getTrace(PageReports.class); + + private static final String DOT_CLASS = PageReports.class.getName() + "."; + private static final String OPERATION_RUN_REPORT = DOT_CLASS + "runReport"; + + private static final String ID_MAIN_FORM = "mainForm"; + private static final String ID_REPORTS_TABLE = "reportsTable"; + + public PageReports() { + initLayout(); + } + + private void initLayout() { + Form mainForm = new com.evolveum.midpoint.web.component.form.Form(ID_MAIN_FORM); + add(mainForm); + + MainObjectListPanel table = + new MainObjectListPanel(ID_REPORTS_TABLE, ReportType.class, UserProfileStorage.TableId.PAGE_REPORTS, + null) { + + @Override + protected IColumn, String> createCheckboxColumn() { + return null; + } + + @Override + public void objectDetailsPerformed(AjaxRequestTarget target, ReportType report) { + if (report != null) { + PageReports.this.reportDetailsPerformed(target, report.getOid()); + } + } + + @Override + protected List, String>> createColumns() { + return PageReports.this.initColumns(); + } + + @Override + protected List createInlineMenu() { + return PageReports.this.createInlineMenu(); + } + + @Override + protected void newObjectPerformed(AjaxRequestTarget target, AssignmentObjectRelation relation, CompiledObjectCollectionView collectionView) { + navigateToNext(PageNewReport.class); + } + }; + table.setOutputMarkupId(true); + mainForm.add(table); + + } + + private List, String>> initColumns() { + List, String>> columns = new ArrayList<>(); + + IColumn column = new PropertyColumn(createStringResource("PageReports.table.description"), "value.description"); + columns.add(column); + + return columns; + } + + private void reportDetailsPerformed(AjaxRequestTarget target, String oid) { + PageParameters params = new PageParameters(); + params.add(OnePageParameterEncoder.PARAMETER, oid); + navigateToNext(PageCreatedReports.class, params); + } + + private List createInlineMenu(){ + List menu = new ArrayList<>(); + menu.add(new ButtonInlineMenuItem(createStringResource("PageReports.button.run")) { + private static final long serialVersionUID = 1L; + + @Override + public InlineMenuItemAction initAction() { + return new ColumnMenuAction>() { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + ReportType report = getRowModel().getObject().getValue(); + runReportPerformed(target, report); + } + }; + } + + @Override + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_START_MENU_ITEM); + } + + @Override + public boolean isHeaderMenuItem(){ + return false; + } + }); + menu.add(new ButtonInlineMenuItem(createStringResource("PageReports.button.configure")) { + private static final long serialVersionUID = 1L; + + @Override + public InlineMenuItemAction initAction() { + return new ColumnMenuAction>() { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + ReportType reportObject = getRowModel().getObject().getValue(); + configurePerformed(target, reportObject); + } + }; + } + + @Override + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_EDIT_MENU_ITEM); + } + + @Override + public boolean isHeaderMenuItem(){ + return false; + } + }); + return menu; + + } + + private void runConfirmPerformed(AjaxRequestTarget target, ReportType reportType, PrismContainer reportParam) { + OperationResult result = new OperationResult(OPERATION_RUN_REPORT); + Task task = createSimpleTask(OPERATION_RUN_REPORT); + task.getUpdatedTaskObject().asObjectable().getAssignment() + .add(ObjectTypeUtil.createAssignmentTo(SystemObjectsType.ARCHETYPE_REPORT_TASK.value(), ObjectTypes.ARCHETYPE, getPrismContext())); + task.getUpdatedTaskObject().asObjectable().getArchetypeRef() + .add(ObjectTypeUtil.createObjectRef(SystemObjectsType.ARCHETYPE_REPORT_TASK.value(), ObjectTypes.ARCHETYPE)); + try { + getReportManager().runReport(reportType.asPrismObject(), reportParam, task, result); + } catch (Exception ex) { + result.recordFatalError(ex); + } finally { + result.computeStatusIfUnknown(); + } + + showResult(result); + target.add(getFeedbackPanel(), get(createComponentPath(ID_MAIN_FORM))); + hideMainPopup(target); + + } + + protected void runReportPerformed(AjaxRequestTarget target, ReportType report) { + + if(report.getReportEngine() != null && report.getReportEngine().equals(ReportEngineSelectionType.DASHBOARD)) { + runConfirmPerformed(target, report, null); + return; + } + + RunReportPopupPanel runReportPopupPanel = new RunReportPopupPanel(getMainPopupBodyId(), report) { + + private static final long serialVersionUID = 1L; + + protected void runConfirmPerformed(AjaxRequestTarget target, ReportType reportType, PrismContainer reportParam) { + PageReports.this.runConfirmPerformed(target, reportType, reportParam); + hideMainPopup(target); + + } + }; + showMainPopup(runReportPopupPanel, target); + + } + + private void configurePerformed(AjaxRequestTarget target, ReportType report) { + PageParameters params = new PageParameters(); + params.add(OnePageParameterEncoder.PARAMETER, report.getOid()); + navigateToNext(PageReport.class, params); + } + + private Table getReportTable() { + return (Table) get(createComponentPath(ID_MAIN_FORM, ID_REPORTS_TABLE)); + } +} diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/resources/PageResources.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/resources/PageResources.java index 62d254cb6b8..f8e259f64d1 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/resources/PageResources.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/resources/PageResources.java @@ -11,17 +11,14 @@ import java.util.Collection; import java.util.List; -import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.web.application.Url; import com.evolveum.midpoint.web.component.dialog.ConfirmationPanel; import com.evolveum.midpoint.web.component.menu.cog.ButtonInlineMenuItem; import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItemAction; -import com.evolveum.midpoint.web.component.search.*; import com.evolveum.midpoint.web.component.util.SelectableBean; import com.evolveum.midpoint.web.page.admin.PageAdminObjectList; -import com.evolveum.midpoint.web.session.PageStorage; -import com.evolveum.midpoint.web.session.SessionStorage; import com.evolveum.midpoint.web.session.UserProfileStorage; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.commons.lang.StringUtils; @@ -160,8 +157,8 @@ public boolean isHeaderMenuItem(){ } @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_TEST_CONNECTION_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_TEST_CONNECTION_MENU_ITEM); } }); @@ -187,8 +184,8 @@ public boolean isHeaderMenuItem(){ } @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_EDIT_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_EDIT_MENU_ITEM); } }); @@ -257,8 +254,8 @@ public void onClick(AjaxRequestTarget target) { } @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_DELETE_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_DELETE_MENU_ITEM); } }); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/resources/ResourceContentPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/resources/ResourceContentPanel.java index d0f79b92a49..4c93e486028 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/resources/ResourceContentPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/resources/ResourceContentPanel.java @@ -12,6 +12,7 @@ import com.evolveum.midpoint.gui.api.component.PendingOperationPanel; import com.evolveum.midpoint.gui.api.model.LoadableModel; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.path.ItemPath; @@ -42,7 +43,6 @@ import org.apache.wicket.model.IModel; import org.apache.wicket.model.PropertyModel; import org.apache.wicket.model.StringResourceModel; -import org.apache.wicket.model.util.ListModel; import org.apache.wicket.request.mapper.parameter.PageParameters; import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; @@ -850,8 +850,8 @@ public void onSubmit(AjaxRequestTarget target) { } @Override - public String getButtonIconCssClass(){ - return GuiStyleConstants.CLASS_IMPORT_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_IMPORT_MENU_ITEM); } }); @@ -907,8 +907,8 @@ protected void onSelectPerformed(AjaxRequestTarget target, FocusType focus) { } @Override - public String getButtonIconCssClass(){ - return GuiStyleConstants.CLASS_RECONCILE_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_RECONCILE_MENU_ITEM); } @Override diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/AbstractRoleMemberPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/AbstractRoleMemberPanel.java index 9220024ad29..1c19f036bcb 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/AbstractRoleMemberPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/AbstractRoleMemberPanel.java @@ -81,7 +81,6 @@ import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItem; import com.evolveum.midpoint.web.component.search.Search; import com.evolveum.midpoint.web.component.search.SearchFactory; -import com.evolveum.midpoint.web.component.util.EnableBehaviour; import com.evolveum.midpoint.web.component.util.VisibleBehaviour; import com.evolveum.midpoint.web.page.admin.configuration.component.ChooseTypePanel; import com.evolveum.midpoint.web.page.admin.dto.ObjectViewDto; @@ -442,8 +441,8 @@ public void onClick(AjaxRequestTarget target) { } @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_UNASSIGN; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_UNASSIGN); } }); } @@ -533,8 +532,8 @@ public void onClick(AjaxRequestTarget target) { // } @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_RECONCILE_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_RECONCILE_MENU_ITEM); } }); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageNodes.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageNodes.java index 93719092fa5..9ba776e6164 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageNodes.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageNodes.java @@ -1,487 +1,489 @@ -/* - * Copyright (c) 2020 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.web.page.admin.server; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; -import javax.xml.datatype.XMLGregorianCalendar; - -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.time.DurationFormatUtils; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; -import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; -import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.repeater.Item; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; - -import com.evolveum.midpoint.gui.api.GuiStyleConstants; -import com.evolveum.midpoint.gui.api.component.MainObjectListPanel; -import com.evolveum.midpoint.gui.api.util.WebComponentUtil; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.result.OperationResultStatus; -import com.evolveum.midpoint.security.api.AuthorizationConstants; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.MiscUtil; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.web.application.AuthorizationAction; -import com.evolveum.midpoint.web.application.PageDescriptor; -import com.evolveum.midpoint.web.application.Url; -import com.evolveum.midpoint.web.component.data.column.CheckBoxColumn; -import com.evolveum.midpoint.web.component.data.column.ColumnMenuAction; -import com.evolveum.midpoint.web.component.data.column.EnumPropertyColumn; -import com.evolveum.midpoint.web.component.menu.cog.ButtonInlineMenuItem; -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.SelectableBean; -import com.evolveum.midpoint.web.component.util.SelectableBeanImpl; -import com.evolveum.midpoint.web.page.admin.PageAdmin; -import com.evolveum.midpoint.web.session.UserProfileStorage; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; - -@PageDescriptor( - urls = { - @Url(mountUrl = "/admin/nodes", matchUrlForSecurity = "/admin/nodes") - }, - action = { - @AuthorizationAction(actionUri = PageAdminTasks.AUTHORIZATION_TASKS_ALL, - label = PageAdminTasks.AUTH_TASKS_ALL_LABEL, - description = PageAdminTasks.AUTH_TASKS_ALL_DESCRIPTION), - @AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_NODES_URL, - label = "PageNodes.auth.nodes.label", - description = "PageNodes.auth.nodes.description")}) -public class PageNodes extends PageAdmin { - - public static final long WAIT_FOR_TASK_STOP = 2000L; - private static final String ID_TABLE = "table"; - private static final String DOT_CLASS = PageNodes.class.getName() + "."; - private static final String OPERATION_DELETE_NODES = DOT_CLASS + "deleteNodes"; - private static final String OPERATION_START_SCHEDULERS = DOT_CLASS + "startSchedulers"; - private static final String OPERATION_STOP_SCHEDULERS_AND_TASKS = DOT_CLASS + "stopSchedulersAndTasks"; - private static final String OPERATION_STOP_SCHEDULERS = DOT_CLASS + "stopSchedulers"; - - public PageNodes() { - initLayout(); - } - - - private void initLayout() { - MainObjectListPanel table = new MainObjectListPanel(ID_TABLE, NodeType.class, UserProfileStorage.TableId.PAGE_TASKS_NODES_PANEL, null) { - - @Override - protected void objectDetailsPerformed(AjaxRequestTarget target, NodeType object) { - //nothing to do, details not enabled. - } - - @Override - protected boolean isObjectDetailsEnabled(IModel> rowModel) { - return false; - } - - @Override - protected boolean isCreateNewObjectEnabled() { - return false; - } - - @Override - protected List, String>> createColumns() { - return initNodeColumns(); - } - - @Override - protected List createInlineMenu() { - return createNodesInlineMenu(); - } - - }; - table.setOutputMarkupId(true); - add(table); - } - - private List, String>> initNodeColumns() { - List, String>> columns = new ArrayList<>(); - - columns.add(new EnumPropertyColumn>(createStringResource("pageTasks.node.executionStatus"), - SelectableBeanImpl.F_VALUE + "." + NodeType.F_EXECUTION_STATUS) { - - @SuppressWarnings("rawtypes") - @Override - protected String translate(Enum en) { - return createStringResource(en).getString(); - } - }); - - columns.add(new CheckBoxColumn>(createStringResource("pageTasks.node.actualNode")) { - private static final long serialVersionUID = 1L; - - @Override - protected IModel getEnabled(IModel> rowModel) { - return Model.of(Boolean.FALSE); - } - - @Override - protected IModel getCheckBoxValueModel(IModel> rowModel) { - return new IModel() { - - private static final long serialVersionUID = 1L; - - @Override - public Boolean getObject() { - if (getTaskManager().getNodeId() != null && rowModel != null - && rowModel.getObject() != null && rowModel.getObject().getValue() != null - && getTaskManager().getNodeId().equals(rowModel.getObject().getValue().getNodeIdentifier())) { - return Boolean.TRUE; - } - - return Boolean.FALSE; - } - }; - } - }); - - columns.add(new AbstractColumn, String>(createStringResource("pageTasks.node.contact")) { - - @Override - public void populateItem(Item>> item, String componentId, IModel> rowModel) { - item.add(new Label(componentId, () -> getContactLabel(rowModel))); - } - }); - - columns.add(new AbstractColumn, String>(createStringResource("pageTasks.node.lastCheckInTime")) { - - @Override - public void populateItem(Item>> item, String componentId, - final IModel> rowModel) { - item.add(new Label(componentId, (IModel) () -> getLastCheckInTime(rowModel))); - } - }); - CheckBoxColumn> check = new CheckBoxColumn<>(createStringResource("pageTasks.node.clustered"), SelectableBeanImpl.F_VALUE + "." + NodeType.F_CLUSTERED); - check.setEnabled(false); - columns.add(check); - columns.add(new AbstractColumn, String>(createStringResource("pageTasks.node.statusMessage")) { - @Override - public void populateItem(Item>> item, String componentId, IModel> rowModel) { - String statusMessage; - if (rowModel == null || rowModel.getObject() == null) { - statusMessage = ""; - } else { - NodeType node = rowModel.getObject().getValue(); - if (node.getConnectionResult() != null && node.getConnectionResult().getStatus() != OperationResultStatusType.SUCCESS && - StringUtils.isNotEmpty(node.getConnectionResult().getMessage())) { - statusMessage = node.getConnectionResult().getMessage(); - } else if (node.getErrorStatus() != null && node.getErrorStatus() != NodeErrorStatusType.OK) { - statusMessage = node.getErrorStatus().toString(); // TODO: explain and localize this - } else if (node.getExecutionStatus() == NodeExecutionStatusType.ERROR) { // error status not specified - statusMessage = "Unspecified error (or the node is just starting or shutting down)"; - } else { - statusMessage = ""; - } - } - - item.add(new Label(componentId, statusMessage)); - } - }); - - return columns; - } - - private String getContactLabel(IModel> model) { - - NodeType node = model.getObject().getValue(); - if (node == null) { - return null; - } - String url = node.getUrl(); - if (url != null) { - return url; - } - return node.getHostname(); - - } - - private String getLastCheckInTime(IModel> nodeModel) { - SelectableBean bean = nodeModel.getObject(); - if (bean == null) { - return ""; - } - NodeType node = bean.getValue(); - XMLGregorianCalendar xmlGregTime = node.getLastCheckInTime(); - if (xmlGregTime == null) { - return ""; - } - long time = MiscUtil.asDate(xmlGregTime).getTime(); - if (time == 0) { - return ""; - } - - return createStringResource("pageTasks.message.getLastCheckInTime", DurationFormatUtils.formatDurationWords(System.currentTimeMillis() - time, true, true)).getString(); - } - - private List createNodesInlineMenu() { - List items = new ArrayList<>(); - items.add(new ButtonInlineMenuItem(createStringResource("pageTasks.button.startScheduler")) { - private static final long serialVersionUID = 1L; - - @Override - public ColumnMenuAction> initAction() { - return new ColumnMenuAction>() { - - @Override - public void onClick(AjaxRequestTarget target) { - startSchedulersPerformed(target, getRowModel()); - } - }; - } - - @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_START_MENU_ITEM; - } - - @SuppressWarnings("unchecked") - @Override - public IModel getConfirmationMessageModel() { - String actionName = createStringResource("pageTasks.message.startSchedulerAction").getString(); - return PageNodes.this.getNodeConfirmationMessageModel((ColumnMenuAction>) getAction(), actionName); - } - }); - - items.add(new ButtonInlineMenuItem(createStringResource("pageTasks.button.stopScheduler")) { - private static final long serialVersionUID = 1L; - - @Override - public InlineMenuItemAction initAction() { - return new ColumnMenuAction>() { - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget target) { - stopSchedulersPerformed(target, getRowModel()); - } - }; - } - - @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_STOP_MENU_ITEM; - } - - @SuppressWarnings({ "unchecked"}) - @Override - public IModel getConfirmationMessageModel() { - String actionName = createStringResource("pageTasks.message.stopSchedulerAction").getString(); - return PageNodes.this.getNodeConfirmationMessageModel((ColumnMenuAction>) getAction(), actionName); - } - }); - - items.add(new InlineMenuItem(createStringResource("pageTasks.button.stopSchedulerAndTasks")) { - private static final long serialVersionUID = 1L; - - @Override - public InlineMenuItemAction initAction() { - return new ColumnMenuAction>() { - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget target) { - stopSchedulersAndTasksPerformed(target, getRowModel()); - } - }; - } - - @SuppressWarnings("unchecked") - @Override - public IModel getConfirmationMessageModel() { - String actionName = createStringResource("pageTasks.message.stopSchedulerTasksAction").getString(); - return PageNodes.this.getNodeConfirmationMessageModel((ColumnMenuAction>) getAction(), actionName); - } - }); - - items.add(new InlineMenuItem(createStringResource("pageTasks.button.deleteNode")) { - private static final long serialVersionUID = 1L; - - @Override - public InlineMenuItemAction initAction() { - return new ColumnMenuAction>() { - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget target) { - deleteNodesPerformed(target, getRowModel()); - } - }; - } - - @SuppressWarnings("unchecked") - @Override - public IModel getConfirmationMessageModel() { - String actionName = createStringResource("pageTasks.message.deleteAction").getString(); - return PageNodes.this.getNodeConfirmationMessageModel((ColumnMenuAction>) getAction(), actionName); - } - }); - - return items; - } - - private void startSchedulersPerformed(AjaxRequestTarget target, IModel> selectedNode) { - Task opTask = createSimpleTask(OPERATION_START_SCHEDULERS); - OperationResult result = opTask.getResult(); - - List selectedNodes = getSelectedNodes(target, selectedNode); - if (selectedNodes.isEmpty()) { - return; - } - try { - getTaskService().startSchedulers(getNodeIdentifiers(selectedNodes), opTask, result); - result.computeStatus(); - if (result.isSuccess()) { - result.recordStatus(OperationResultStatus.SUCCESS, - createStringResource("pageTasks.message.startSchedulersPerformed.success").getString()); - } - } catch (SecurityViolationException | ObjectNotFoundException | SchemaException | ExpressionEvaluationException - | RuntimeException | CommunicationException | ConfigurationException e) { - result.recordFatalError(createStringResource("pageTasks.message.startSchedulersPerformed.fatalError").getString(), e); - } - - showResult(result); - getTable().refreshTable(NodeType.class, target); - target.add(getTable()); - } - - private void stopSchedulersPerformed(AjaxRequestTarget target, IModel> model) { - List selectedNodes = getSelectedNodes(target, model); - if (CollectionUtils.isEmpty(selectedNodes)) { - return; - } - Task opTask = createSimpleTask(OPERATION_STOP_SCHEDULERS); - OperationResult result = opTask.getResult(); - try { - getTaskService().stopSchedulers(getNodeIdentifiers(selectedNodes), opTask, result); - result.computeStatus(); - if (result.isSuccess()) { - result.recordStatus(OperationResultStatus.SUCCESS, - createStringResource("pageTasks.message.stopSchedulersPerformed.success").getString()); - } - } catch (SecurityViolationException | ObjectNotFoundException | SchemaException | ExpressionEvaluationException - | RuntimeException | CommunicationException | ConfigurationException e) { - result.recordFatalError(createStringResource("pageTasks.message.stopSchedulersPerformed.fatalError").getString(), e); - } - showResult(result); - getTable().refreshTable(NodeType.class, target); - target.add(getTable()); - } - - private void stopSchedulersAndTasksPerformed(AjaxRequestTarget target, IModel> selectedNode) { - List selectedNodes = getSelectedNodes(target, selectedNode); - if (CollectionUtils.isEmpty(selectedNodes)) { - return; - } - - Task opTask = createSimpleTask(OPERATION_STOP_SCHEDULERS_AND_TASKS); - OperationResult result = opTask.getResult(); - try { - boolean suspended = getTaskService().stopSchedulersAndTasks(getNodeIdentifiers(selectedNodes), WAIT_FOR_TASK_STOP, opTask, result); - result.computeStatus(); - if (result.isSuccess()) { - if (suspended) { - result.recordStatus(OperationResultStatus.SUCCESS, - createStringResource("pageTasks.message.stopSchedulersAndTasksPerformed.success").getString()); - } else { - result.recordWarning( - createStringResource("pageTasks.message.stopSchedulersAndTasksPerformed.warning").getString()); - } - } - } catch (SecurityViolationException | ObjectNotFoundException | SchemaException | ExpressionEvaluationException - | RuntimeException | CommunicationException | ConfigurationException e) { - result.recordFatalError( - createStringResource("pageTasks.message.stopSchedulersAndTasksPerformed.fatalError").getString(), e); - } - showResult(result); - - // refresh feedback and table - getTable().refreshTable(NodeType.class, target); - target.add(getTable()); - - } - - private void deleteNodesPerformed(AjaxRequestTarget target, IModel> selectedNode) { - List selectedNodes = getSelectedNodes(target, selectedNode); - if (CollectionUtils.isEmpty(selectedNodes)) { - return; - } - - OperationResult result = new OperationResult(OPERATION_DELETE_NODES); - Task task = createSimpleTask(OPERATION_DELETE_NODES); - - for (NodeType nodeDto : selectedNodes) { - Collection> deltas = new ArrayList<>(); - deltas.add(getPrismContext().deltaFactory().object().createDeleteDelta(NodeType.class, nodeDto.getOid())); - try { - getModelService().executeChanges(deltas, null, task, result); - } catch (Exception e) { // until java 7 we do it in this way - result.recordFatalError(createStringResource("pageTasks.message.deleteNodesPerformed.fatalError").getString() - + nodeDto.getNodeIdentifier(), e); - } - } - - result.computeStatus(); - if (result.isSuccess()) { - result.recordStatus(OperationResultStatus.SUCCESS, - createStringResource("pageTasks.message.deleteNodesPerformed.success").getString()); - } - showResult(result); - - getTable().refreshTable(NodeType.class, target); - target.add(getTable()); - } - - private List getSelectedNodes(AjaxRequestTarget target, IModel> selectedNode) { - if (selectedNode != null) { - return Collections.singletonList(selectedNode.getObject().getValue()); - } - - List selectedNodes = getSelectedNodes(); - if (CollectionUtils.isEmpty(selectedNodes)) { - warn("PageNodes.nothing.selected"); - target.add(getFeedbackPanel()); - } - return selectedNodes; - } - - private List getNodeIdentifiers(List selectedNodes) { - return selectedNodes.stream().map(NodeType::getNodeIdentifier).collect(Collectors.toList()); - } - - private List getSelectedNodes() { - return getTable().getSelectedObjects(); - } - - @SuppressWarnings("unchecked") - private MainObjectListPanel getTable() { - return (MainObjectListPanel) get(ID_TABLE); - } - - private IModel getNodeConfirmationMessageModel(ColumnMenuAction> action, String actionName) { - if (action.getRowModel() == null) { - return createStringResource("pageTasks.message.confirmationMessageForMultipleNodeObject", actionName, - getTable().getSelectedObjectsCount()); - } else { - String objectName = WebComponentUtil.getName(action.getRowModel().getObject().getValue()); - return createStringResource("pageTasks.message.confirmationMessageForSingleNodeObject", actionName, objectName); - } - - } - -} +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.web.page.admin.server; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import javax.xml.datatype.XMLGregorianCalendar; + +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.time.DurationFormatUtils; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; +import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; + +import com.evolveum.midpoint.gui.api.GuiStyleConstants; +import com.evolveum.midpoint.gui.api.component.MainObjectListPanel; +import com.evolveum.midpoint.gui.api.util.WebComponentUtil; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.result.OperationResultStatus; +import com.evolveum.midpoint.security.api.AuthorizationConstants; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.web.application.AuthorizationAction; +import com.evolveum.midpoint.web.application.PageDescriptor; +import com.evolveum.midpoint.web.application.Url; +import com.evolveum.midpoint.web.component.data.column.CheckBoxColumn; +import com.evolveum.midpoint.web.component.data.column.ColumnMenuAction; +import com.evolveum.midpoint.web.component.data.column.EnumPropertyColumn; +import com.evolveum.midpoint.web.component.menu.cog.ButtonInlineMenuItem; +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.SelectableBean; +import com.evolveum.midpoint.web.component.util.SelectableBeanImpl; +import com.evolveum.midpoint.web.page.admin.PageAdmin; +import com.evolveum.midpoint.web.session.UserProfileStorage; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +@PageDescriptor( + urls = { + @Url(mountUrl = "/admin/nodes", matchUrlForSecurity = "/admin/nodes") + }, + action = { + @AuthorizationAction(actionUri = PageAdminTasks.AUTHORIZATION_TASKS_ALL, + label = PageAdminTasks.AUTH_TASKS_ALL_LABEL, + description = PageAdminTasks.AUTH_TASKS_ALL_DESCRIPTION), + @AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_NODES_URL, + label = "PageNodes.auth.nodes.label", + description = "PageNodes.auth.nodes.description")}) +public class PageNodes extends PageAdmin { + + public static final long WAIT_FOR_TASK_STOP = 2000L; + private static final String ID_TABLE = "table"; + private static final String DOT_CLASS = PageNodes.class.getName() + "."; + private static final String OPERATION_DELETE_NODES = DOT_CLASS + "deleteNodes"; + private static final String OPERATION_START_SCHEDULERS = DOT_CLASS + "startSchedulers"; + private static final String OPERATION_STOP_SCHEDULERS_AND_TASKS = DOT_CLASS + "stopSchedulersAndTasks"; + private static final String OPERATION_STOP_SCHEDULERS = DOT_CLASS + "stopSchedulers"; + + public PageNodes() { + initLayout(); + } + + + private void initLayout() { + MainObjectListPanel table = new MainObjectListPanel(ID_TABLE, NodeType.class, UserProfileStorage.TableId.PAGE_TASKS_NODES_PANEL, null) { + + @Override + protected void objectDetailsPerformed(AjaxRequestTarget target, NodeType object) { + //nothing to do, details not enabled. + } + + @Override + protected boolean isObjectDetailsEnabled(IModel> rowModel) { + return false; + } + + @Override + protected boolean isCreateNewObjectEnabled() { + return false; + } + + @Override + protected List, String>> createColumns() { + return initNodeColumns(); + } + + @Override + protected List createInlineMenu() { + return createNodesInlineMenu(); + } + + }; + table.setOutputMarkupId(true); + add(table); + } + + private List, String>> initNodeColumns() { + List, String>> columns = new ArrayList<>(); + + columns.add(new EnumPropertyColumn>(createStringResource("pageTasks.node.executionStatus"), + SelectableBeanImpl.F_VALUE + "." + NodeType.F_EXECUTION_STATUS) { + + @SuppressWarnings("rawtypes") + @Override + protected String translate(Enum en) { + return createStringResource(en).getString(); + } + }); + + columns.add(new CheckBoxColumn>(createStringResource("pageTasks.node.actualNode")) { + private static final long serialVersionUID = 1L; + + @Override + protected IModel getEnabled(IModel> rowModel) { + return Model.of(Boolean.FALSE); + } + + @Override + protected IModel getCheckBoxValueModel(IModel> rowModel) { + return new IModel() { + + private static final long serialVersionUID = 1L; + + @Override + public Boolean getObject() { + if (getTaskManager().getNodeId() != null && rowModel != null + && rowModel.getObject() != null && rowModel.getObject().getValue() != null + && getTaskManager().getNodeId().equals(rowModel.getObject().getValue().getNodeIdentifier())) { + return Boolean.TRUE; + } + + return Boolean.FALSE; + } + }; + } + }); + + columns.add(new AbstractColumn, String>(createStringResource("pageTasks.node.contact")) { + + @Override + public void populateItem(Item>> item, String componentId, IModel> rowModel) { + item.add(new Label(componentId, () -> getContactLabel(rowModel))); + } + }); + + columns.add(new AbstractColumn, String>(createStringResource("pageTasks.node.lastCheckInTime")) { + + @Override + public void populateItem(Item>> item, String componentId, + final IModel> rowModel) { + item.add(new Label(componentId, (IModel) () -> getLastCheckInTime(rowModel))); + } + }); + CheckBoxColumn> check = new CheckBoxColumn<>(createStringResource("pageTasks.node.clustered"), SelectableBeanImpl.F_VALUE + "." + NodeType.F_CLUSTERED); + check.setEnabled(false); + columns.add(check); + columns.add(new AbstractColumn, String>(createStringResource("pageTasks.node.statusMessage")) { + @Override + public void populateItem(Item>> item, String componentId, IModel> rowModel) { + String statusMessage; + if (rowModel == null || rowModel.getObject() == null) { + statusMessage = ""; + } else { + NodeType node = rowModel.getObject().getValue(); + if (node.getConnectionResult() != null && node.getConnectionResult().getStatus() != OperationResultStatusType.SUCCESS && + StringUtils.isNotEmpty(node.getConnectionResult().getMessage())) { + statusMessage = node.getConnectionResult().getMessage(); + } else if (node.getErrorStatus() != null && node.getErrorStatus() != NodeErrorStatusType.OK) { + statusMessage = node.getErrorStatus().toString(); // TODO: explain and localize this + } else if (node.getExecutionStatus() == NodeExecutionStatusType.ERROR) { // error status not specified + statusMessage = "Unspecified error (or the node is just starting or shutting down)"; + } else { + statusMessage = ""; + } + } + + item.add(new Label(componentId, statusMessage)); + } + }); + + return columns; + } + + private String getContactLabel(IModel> model) { + + NodeType node = model.getObject().getValue(); + if (node == null) { + return null; + } + String url = node.getUrl(); + if (url != null) { + return url; + } + return node.getHostname(); + + } + + private String getLastCheckInTime(IModel> nodeModel) { + SelectableBean bean = nodeModel.getObject(); + if (bean == null) { + return ""; + } + NodeType node = bean.getValue(); + XMLGregorianCalendar xmlGregTime = node.getLastCheckInTime(); + if (xmlGregTime == null) { + return ""; + } + long time = MiscUtil.asDate(xmlGregTime).getTime(); + if (time == 0) { + return ""; + } + + return createStringResource("pageTasks.message.getLastCheckInTime", DurationFormatUtils.formatDurationWords(System.currentTimeMillis() - time, true, true)).getString(); + } + + private List createNodesInlineMenu() { + List items = new ArrayList<>(); + items.add(new ButtonInlineMenuItem(createStringResource("pageTasks.button.startScheduler")) { + private static final long serialVersionUID = 1L; + + @Override + public ColumnMenuAction> initAction() { + return new ColumnMenuAction>() { + + @Override + public void onClick(AjaxRequestTarget target) { + startSchedulersPerformed(target, getRowModel()); + } + }; + } + + @Override + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_START_MENU_ITEM); + } + + @SuppressWarnings("unchecked") + @Override + public IModel getConfirmationMessageModel() { + String actionName = createStringResource("pageTasks.message.startSchedulerAction").getString(); + return PageNodes.this.getNodeConfirmationMessageModel((ColumnMenuAction>) getAction(), actionName); + } + }); + + items.add(new ButtonInlineMenuItem(createStringResource("pageTasks.button.stopScheduler")) { + private static final long serialVersionUID = 1L; + + @Override + public InlineMenuItemAction initAction() { + return new ColumnMenuAction>() { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + stopSchedulersPerformed(target, getRowModel()); + } + }; + } + + @Override + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_STOP_MENU_ITEM); + } + + @SuppressWarnings({ "unchecked"}) + @Override + public IModel getConfirmationMessageModel() { + String actionName = createStringResource("pageTasks.message.stopSchedulerAction").getString(); + return PageNodes.this.getNodeConfirmationMessageModel((ColumnMenuAction>) getAction(), actionName); + } + }); + + items.add(new InlineMenuItem(createStringResource("pageTasks.button.stopSchedulerAndTasks")) { + private static final long serialVersionUID = 1L; + + @Override + public InlineMenuItemAction initAction() { + return new ColumnMenuAction>() { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + stopSchedulersAndTasksPerformed(target, getRowModel()); + } + }; + } + + @SuppressWarnings("unchecked") + @Override + public IModel getConfirmationMessageModel() { + String actionName = createStringResource("pageTasks.message.stopSchedulerTasksAction").getString(); + return PageNodes.this.getNodeConfirmationMessageModel((ColumnMenuAction>) getAction(), actionName); + } + }); + + items.add(new InlineMenuItem(createStringResource("pageTasks.button.deleteNode")) { + private static final long serialVersionUID = 1L; + + @Override + public InlineMenuItemAction initAction() { + return new ColumnMenuAction>() { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + deleteNodesPerformed(target, getRowModel()); + } + }; + } + + @SuppressWarnings("unchecked") + @Override + public IModel getConfirmationMessageModel() { + String actionName = createStringResource("pageTasks.message.deleteAction").getString(); + return PageNodes.this.getNodeConfirmationMessageModel((ColumnMenuAction>) getAction(), actionName); + } + }); + + return items; + } + + private void startSchedulersPerformed(AjaxRequestTarget target, IModel> selectedNode) { + Task opTask = createSimpleTask(OPERATION_START_SCHEDULERS); + OperationResult result = opTask.getResult(); + + List selectedNodes = getSelectedNodes(target, selectedNode); + if (selectedNodes.isEmpty()) { + return; + } + try { + getTaskService().startSchedulers(getNodeIdentifiers(selectedNodes), opTask, result); + result.computeStatus(); + if (result.isSuccess()) { + result.recordStatus(OperationResultStatus.SUCCESS, + createStringResource("pageTasks.message.startSchedulersPerformed.success").getString()); + } + } catch (SecurityViolationException | ObjectNotFoundException | SchemaException | ExpressionEvaluationException + | RuntimeException | CommunicationException | ConfigurationException e) { + result.recordFatalError(createStringResource("pageTasks.message.startSchedulersPerformed.fatalError").getString(), e); + } + + showResult(result); + getTable().refreshTable(NodeType.class, target); + target.add(getTable()); + } + + private void stopSchedulersPerformed(AjaxRequestTarget target, IModel> model) { + List selectedNodes = getSelectedNodes(target, model); + if (CollectionUtils.isEmpty(selectedNodes)) { + return; + } + Task opTask = createSimpleTask(OPERATION_STOP_SCHEDULERS); + OperationResult result = opTask.getResult(); + try { + getTaskService().stopSchedulers(getNodeIdentifiers(selectedNodes), opTask, result); + result.computeStatus(); + if (result.isSuccess()) { + result.recordStatus(OperationResultStatus.SUCCESS, + createStringResource("pageTasks.message.stopSchedulersPerformed.success").getString()); + } + } catch (SecurityViolationException | ObjectNotFoundException | SchemaException | ExpressionEvaluationException + | RuntimeException | CommunicationException | ConfigurationException e) { + result.recordFatalError(createStringResource("pageTasks.message.stopSchedulersPerformed.fatalError").getString(), e); + } + showResult(result); + getTable().refreshTable(NodeType.class, target); + target.add(getTable()); + } + + private void stopSchedulersAndTasksPerformed(AjaxRequestTarget target, IModel> selectedNode) { + List selectedNodes = getSelectedNodes(target, selectedNode); + if (CollectionUtils.isEmpty(selectedNodes)) { + return; + } + + Task opTask = createSimpleTask(OPERATION_STOP_SCHEDULERS_AND_TASKS); + OperationResult result = opTask.getResult(); + try { + boolean suspended = getTaskService().stopSchedulersAndTasks(getNodeIdentifiers(selectedNodes), WAIT_FOR_TASK_STOP, opTask, result); + result.computeStatus(); + if (result.isSuccess()) { + if (suspended) { + result.recordStatus(OperationResultStatus.SUCCESS, + createStringResource("pageTasks.message.stopSchedulersAndTasksPerformed.success").getString()); + } else { + result.recordWarning( + createStringResource("pageTasks.message.stopSchedulersAndTasksPerformed.warning").getString()); + } + } + } catch (SecurityViolationException | ObjectNotFoundException | SchemaException | ExpressionEvaluationException + | RuntimeException | CommunicationException | ConfigurationException e) { + result.recordFatalError( + createStringResource("pageTasks.message.stopSchedulersAndTasksPerformed.fatalError").getString(), e); + } + showResult(result); + + // refresh feedback and table + getTable().refreshTable(NodeType.class, target); + target.add(getTable()); + + } + + private void deleteNodesPerformed(AjaxRequestTarget target, IModel> selectedNode) { + List selectedNodes = getSelectedNodes(target, selectedNode); + if (CollectionUtils.isEmpty(selectedNodes)) { + return; + } + + OperationResult result = new OperationResult(OPERATION_DELETE_NODES); + Task task = createSimpleTask(OPERATION_DELETE_NODES); + + for (NodeType nodeDto : selectedNodes) { + Collection> deltas = new ArrayList<>(); + deltas.add(getPrismContext().deltaFactory().object().createDeleteDelta(NodeType.class, nodeDto.getOid())); + try { + getModelService().executeChanges(deltas, null, task, result); + } catch (Exception e) { // until java 7 we do it in this way + result.recordFatalError(createStringResource("pageTasks.message.deleteNodesPerformed.fatalError").getString() + + nodeDto.getNodeIdentifier(), e); + } + } + + result.computeStatus(); + if (result.isSuccess()) { + result.recordStatus(OperationResultStatus.SUCCESS, + createStringResource("pageTasks.message.deleteNodesPerformed.success").getString()); + } + showResult(result); + + getTable().refreshTable(NodeType.class, target); + target.add(getTable()); + } + + private List getSelectedNodes(AjaxRequestTarget target, IModel> selectedNode) { + if (selectedNode != null) { + return Collections.singletonList(selectedNode.getObject().getValue()); + } + + List selectedNodes = getSelectedNodes(); + if (CollectionUtils.isEmpty(selectedNodes)) { + warn("PageNodes.nothing.selected"); + target.add(getFeedbackPanel()); + } + return selectedNodes; + } + + private List getNodeIdentifiers(List selectedNodes) { + return selectedNodes.stream().map(NodeType::getNodeIdentifier).collect(Collectors.toList()); + } + + private List getSelectedNodes() { + return getTable().getSelectedObjects(); + } + + @SuppressWarnings("unchecked") + private MainObjectListPanel getTable() { + return (MainObjectListPanel) get(ID_TABLE); + } + + private IModel getNodeConfirmationMessageModel(ColumnMenuAction> action, String actionName) { + if (action.getRowModel() == null) { + return createStringResource("pageTasks.message.confirmationMessageForMultipleNodeObject", actionName, + getTable().getSelectedObjectsCount()); + } else { + String objectName = WebComponentUtil.getName(action.getRowModel().getObject().getValue()); + return createStringResource("pageTasks.message.confirmationMessageForSingleNodeObject", actionName, objectName); + } + + } + +} diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskTablePanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskTablePanel.java index 10f06b43541..4cbae9cbeda 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskTablePanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskTablePanel.java @@ -11,6 +11,8 @@ import java.util.stream.Collectors; import javax.xml.datatype.XMLGregorianCalendar; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; + import org.apache.commons.collections.CollectionUtils; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -386,8 +388,8 @@ public void onClick(AjaxRequestTarget target) { } @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_SUSPEND_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_SUSPEND_MENU_ITEM); } @Override @@ -423,8 +425,8 @@ public void onClick(AjaxRequestTarget target) { } @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_RESUME_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_RESUME_MENU_ITEM); } @Override @@ -461,8 +463,8 @@ public void onClick(AjaxRequestTarget target) { } @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_START_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_START_MENU_ITEM); } @Override diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/PageUsers.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/PageUsers.java index 86df9955e29..34175b417e9 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/PageUsers.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/PageUsers.java @@ -13,9 +13,13 @@ import java.util.List; import com.evolveum.midpoint.gui.api.component.ObjectBrowserPanel; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; +import com.evolveum.midpoint.gui.impl.component.icon.IconCssStyle; import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.util.TaskTypeUtil; import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; @@ -26,8 +30,11 @@ import com.evolveum.midpoint.web.component.search.SearchFactory; import com.evolveum.midpoint.web.component.search.SearchItem; import com.evolveum.midpoint.web.component.search.SearchValue; +import com.evolveum.midpoint.web.component.util.FocusListInlineMenuHelper; import com.evolveum.midpoint.web.component.util.SelectableBeanImpl; import com.evolveum.midpoint.web.page.admin.PageAdminObjectList; +import com.evolveum.midpoint.web.page.admin.configuration.dto.DebugSearchDto; +import com.evolveum.midpoint.web.page.admin.server.TaskTablePanel; import com.evolveum.midpoint.web.session.PageStorage; import com.evolveum.midpoint.web.session.SessionStorage; import com.evolveum.midpoint.web.session.UserProfileStorage; @@ -175,7 +182,7 @@ public IModel getDataModel(IModel> rowModel) { @Override protected List createRowActions() { List menu = new ArrayList<>(); - menu.add(new ButtonInlineMenuItem(createStringResource("pageUsers.menu.enable")) { + ButtonInlineMenuItem enableItem = new ButtonInlineMenuItem(createStringResource("pageUsers.menu.enable")) { private static final long serialVersionUID = 1L; @Override @@ -195,8 +202,8 @@ public void onClick(AjaxRequestTarget target) { } @Override - public String getButtonIconCssClass(){ - return GuiStyleConstants.CLASS_OBJECT_USER_ICON; + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_OBJECT_USER_ICON); } @Override @@ -204,10 +211,11 @@ public IModel getConfirmationMessageModel(){ String actionName = createStringResource("pageUsers.message.enableAction").getString(); return PageUsers.this.getConfirmationMessageModel((ColumnMenuAction) getAction(), actionName); } + }; + enableItem.setVisibilityChecker(FocusListInlineMenuHelper::isObjectDisabled); + menu.add(enableItem); - }); - - menu.add(new InlineMenuItem(createStringResource("pageUsers.menu.disable")) { + ButtonInlineMenuItem disableItem = new ButtonInlineMenuItem(createStringResource("pageUsers.menu.disable")) { private static final long serialVersionUID = 1L; @Override @@ -227,13 +235,21 @@ public void onClick(AjaxRequestTarget target) { }; } + @Override + public CompositedIconBuilder getIconCompositedBuilder(){ + CompositedIconBuilder builder = getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_OBJECT_USER_ICON); + builder.appendLayerIcon(WebComponentUtil.createIconType(GuiStyleConstants.CLASS_BAN), IconCssStyle.BOTTOM_RIGHT_STYLE); + return builder; + } + @Override public IModel getConfirmationMessageModel() { String actionName = createStringResource("pageUsers.message.disableAction").getString(); return PageUsers.this.getConfirmationMessageModel((ColumnMenuAction) getAction(), actionName); } - - }); + }; + disableItem.setVisibilityChecker(FocusListInlineMenuHelper::isObjectEnabled); + menu.add(disableItem); menu.add(new ButtonInlineMenuItem(createStringResource("pageUsers.menu.reconcile")) { private static final long serialVersionUID = 1L; @@ -256,8 +272,8 @@ public void onClick(AjaxRequestTarget target) { } @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_RECONCILE_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder(){ + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_RECONCILE_MENU_ITEM); } @Override diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/valuePolicy/PageValuePolicies.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/valuePolicy/PageValuePolicies.java index 091c5ee688b..d1749b08bf9 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/valuePolicy/PageValuePolicies.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/valuePolicy/PageValuePolicies.java @@ -8,6 +8,7 @@ package com.evolveum.midpoint.web.page.admin.valuePolicy; import com.evolveum.midpoint.gui.api.GuiStyleConstants; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; import com.evolveum.midpoint.security.api.AuthorizationConstants; import com.evolveum.midpoint.web.application.AuthorizationAction; import com.evolveum.midpoint.web.application.PageDescriptor; @@ -16,7 +17,6 @@ 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.SelectableBean; -import com.evolveum.midpoint.web.component.util.SelectableBeanImpl; import com.evolveum.midpoint.web.page.admin.PageAdminObjectList; import com.evolveum.midpoint.web.session.UserProfileStorage; import com.evolveum.midpoint.web.util.OnePageParameterEncoder; @@ -100,8 +100,8 @@ private List createInlineMenu() { private static final long serialVersionUID = 1L; @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_DELETE_MENU_ITEM; + public CompositedIconBuilder getIconCompositedBuilder() { + return getDefaultCompositedIconBuilder(GuiStyleConstants.CLASS_DELETE_MENU_ITEM); } @Override diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/MidPointApplication.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/MidPointApplication.java index 95519680f60..b2a1ac91ff6 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/MidPointApplication.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/MidPointApplication.java @@ -34,7 +34,7 @@ import javax.servlet.ServletContext; import javax.xml.datatype.Duration; -import com.evolveum.midpoint.repo.cache.CacheRegistry; +import com.evolveum.midpoint.repo.cache.registry.CacheRegistry; import com.evolveum.midpoint.web.security.util.SecurityUtils; import org.apache.commons.configuration2.Configuration; import org.apache.commons.io.IOUtils; diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/SearchResultList.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/SearchResultList.java index 2e128a16f9d..743b49ddc9e 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/SearchResultList.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/SearchResultList.java @@ -6,8 +6,12 @@ */ package com.evolveum.midpoint.schema; +import com.evolveum.midpoint.prism.AbstractFreezable; +import com.evolveum.midpoint.prism.Freezable; import com.evolveum.midpoint.prism.util.CloneUtil; import com.evolveum.midpoint.util.ShortDumpable; +import com.evolveum.midpoint.util.annotation.Experimental; + import org.jetbrains.annotations.NotNull; import java.io.Serializable; @@ -17,11 +21,29 @@ * @author semancik * */ -public class SearchResultList implements List, Cloneable, Serializable, ShortDumpable { +public class SearchResultList extends AbstractFreezable implements List, Cloneable, Serializable, ShortDumpable { private List list = null; private SearchResultMetadata metadata = null; + @Override + protected void performFreeze() { + if (isMutable()) { + if (list != null) { + list = Collections.unmodifiableList(list); + } else { + list = Collections.emptyList(); + } + for (T item : list) { + if (item instanceof Freezable) { + ((Freezable) item).freeze(); + } + } + } else { + // no-op to avoid multiple wrapping of the list + } + } + public SearchResultList() { } public SearchResultList(List list) { @@ -203,10 +225,44 @@ public String toString() { } } + /** + * Just to emphasize the semantics. + * (Is this a good idea? Because then someone could think that clone() is a shallow clone!) + */ + @Experimental + public SearchResultList deepClone() { + return clone(); + } + + /** + * Just to indicate that clone() is a deep one :) + */ + @Experimental + public SearchResultList shallowClone() { + throw new UnsupportedOperationException(); + } + + /** + * Returns deep frozen list - either this or a clone. + */ + @Experimental + public SearchResultList toDeeplyFrozenList() { + if (isImmutable()) { + return this; + } else { + SearchResultList clone = deepClone(); + clone.freeze(); + return clone; + } + } + + /** + * This is actually a deep clone. + */ @SuppressWarnings("MethodDoesntCallSuperMethod") public SearchResultList clone() { SearchResultList clone = new SearchResultList<>(); - clone.metadata = this.metadata; // considered read-only object + clone.metadata = this.metadata; // considered read-only object if (this.list != null) { clone.list = new ArrayList<>(this.list.size()); for (T item : this.list) { diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/DiagnosticContextHolder.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/DiagnosticContextHolder.java index 2c2d2994ee0..cb9dfc28832 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/DiagnosticContextHolder.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/DiagnosticContextHolder.java @@ -15,19 +15,19 @@ */ public class DiagnosticContextHolder { - private static ThreadLocal> diagStack = new ThreadLocal<>(); + private static final ThreadLocal> DIAG_STACK_THREAD_LOCAL = new ThreadLocal<>(); public static void push(DiagnosticContext ctx) { - Deque stack = diagStack.get(); + Deque stack = DIAG_STACK_THREAD_LOCAL.get(); if (stack == null) { stack = new ArrayDeque<>(); - diagStack.set(stack); + DIAG_STACK_THREAD_LOCAL.set(stack); } stack.push(ctx); } public static DiagnosticContext pop() { - Deque stack = diagStack.get(); + Deque stack = DIAG_STACK_THREAD_LOCAL.get(); if (stack == null || stack.isEmpty()) { return null; } @@ -35,7 +35,7 @@ public static DiagnosticContext pop() { } public static DiagnosticContext get() { - Deque stack = diagStack.get(); + Deque stack = DIAG_STACK_THREAD_LOCAL.get(); if (stack == null) { return null; } diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/JAXBUtil.java b/infra/util/src/main/java/com/evolveum/midpoint/util/JAXBUtil.java index ed63cb3eb0a..ea171ee9122 100644 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/JAXBUtil.java +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/JAXBUtil.java @@ -216,6 +216,10 @@ public static Class findClassForType(QName typeName, Package pkg) { String namespace = PACKAGE_NAMESPACES.get(pkg); if (namespace == null) { XmlSchema xmlSchemaAnnotation = pkg.getAnnotation(XmlSchema.class); + if (xmlSchemaAnnotation == null) { + LOGGER.error("Package namespace unknown, there is also no XmlSchema annotation on package {}, type {}", pkg.getName(), typeName); + } + namespace = xmlSchemaAnnotation.namespace(); PACKAGE_NAMESPACES.put(pkg, namespace); } diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/caching/CacheConfiguration.java b/infra/util/src/main/java/com/evolveum/midpoint/util/caching/CacheConfiguration.java index 3545846c690..83ebb44f0d4 100644 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/caching/CacheConfiguration.java +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/caching/CacheConfiguration.java @@ -27,6 +27,13 @@ public class CacheConfiguration implements DebugDumpable { private Boolean tracePass; private StatisticsLevel statisticsLevel; private Boolean clusterwideInvalidation; + + /** + * Safe remote invalidation means that when object of given type is changed, we invalidate + * all queries related to this type on remote nodes (because we do not know the details about + * the modification). If safeRemoteInvalidation is false, we leave queries untouched, unless + * exact match on OID is found. + */ private Boolean safeRemoteInvalidation; private final Map, CacheObjectTypeConfiguration> objectTypes = new HashMap<>(); @@ -100,6 +107,7 @@ public void setStatisticsLevel(StatisticsLevel statisticsLevel) { this.statisticsLevel = statisticsLevel; } + @SuppressWarnings("unused") public Boolean getClusterwideInvalidation() { return clusterwideInvalidation; } @@ -108,6 +116,7 @@ public void setClusterwideInvalidation(Boolean clusterwideInvalidation) { this.clusterwideInvalidation = clusterwideInvalidation; } + @SuppressWarnings("unused") public Boolean getSafeRemoteInvalidation() { return safeRemoteInvalidation; } @@ -186,6 +195,7 @@ public void setStatisticsLevel(StatisticsLevel statisticsLevel) { this.statisticsLevel = statisticsLevel; } + @SuppressWarnings("unused") public Boolean getClusterwideInvalidation() { return clusterwideInvalidation; } @@ -194,6 +204,7 @@ public void setClusterwideInvalidation(Boolean clusterwideInvalidation) { this.clusterwideInvalidation = clusterwideInvalidation; } + @SuppressWarnings("unused") public Boolean getSafeRemoteInvalidation() { return safeRemoteInvalidation; } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/SystemObjectCache.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/SystemObjectCache.java index 915bddd1b49..0f039ba5521 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/SystemObjectCache.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/SystemObjectCache.java @@ -12,7 +12,7 @@ import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.repo.cache.CacheRegistry; +import com.evolveum.midpoint.repo.cache.registry.CacheRegistry; import com.evolveum.midpoint.repo.api.Cacheable; import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.SearchResultList; diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionFactory.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionFactory.java index 24aa9e55b97..5623d14fc19 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionFactory.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionFactory.java @@ -21,7 +21,7 @@ import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.repo.api.Cacheable; import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.repo.cache.CacheRegistry; +import com.evolveum.midpoint.repo.cache.registry.CacheRegistry; import com.evolveum.midpoint.repo.common.ObjectResolver; import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; import com.evolveum.midpoint.repo.common.expression.ExpressionSyntaxException; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelCrudService.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelCrudService.java index e752f71a682..c26f085d4bb 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelCrudService.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelCrudService.java @@ -150,7 +150,7 @@ public String addObject(PrismObject object, ModelExecu ModelImplUtils.resolveReferences(object, repository, false, false, EvaluationTimeType.IMPORT, true, prismContext, result); String oid; - RepositoryCache.enter(cacheConfigurationManager); + RepositoryCache.enterLocalCaches(cacheConfigurationManager); try { if (LOGGER.isTraceEnabled()) { @@ -177,7 +177,7 @@ public String addObject(PrismObject object, ModelExecu ModelImplUtils.recordFatalError(result, ex); throw ex; } finally { - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); } return oid; @@ -220,7 +220,7 @@ public void deleteObject(Class clazz, String oid, Mode OperationResult result = parentResult.createSubresult(DELETE_OBJECT); result.addParam(OperationResult.PARAM_OID, oid); - RepositoryCache.enter(cacheConfigurationManager); + RepositoryCache.enterLocalCaches(cacheConfigurationManager); try { ObjectDelta objectDelta = prismContext.deltaFactory().object().create(clazz, ChangeType.DELETE); @@ -240,7 +240,7 @@ public void deleteObject(Class clazz, String oid, Mode ModelImplUtils.recordFatalError(result, ex); throw new SystemException(ex.getMessage(), ex); } finally { - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); } } @@ -305,7 +305,7 @@ public void modifyObject(Class type, String oid, OperationResult result = parentResult.createSubresult(MODIFY_OBJECT); result.addArbitraryObjectCollectionAsParam("modifications", modifications); - RepositoryCache.enter(cacheConfigurationManager); + RepositoryCache.enterLocalCaches(cacheConfigurationManager); try { @@ -323,7 +323,7 @@ public void modifyObject(Class type, String oid, ModelImplUtils.recordFatalError(result, ex); throw ex; } finally { - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); } } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java index d3e76428772..1aac382eb40 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java @@ -30,13 +30,11 @@ import com.evolveum.midpoint.prism.path.*; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.prism.query.*; -import com.evolveum.midpoint.prism.query.builder.S_AtomicFilterExit; import com.evolveum.midpoint.prism.util.CloneUtil; import com.evolveum.midpoint.provisioning.api.*; import com.evolveum.midpoint.repo.api.PreconditionViolationException; import com.evolveum.midpoint.repo.api.RepoAddOptions; import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.repo.api.query.Query; import com.evolveum.midpoint.repo.cache.RepositoryCache; import com.evolveum.midpoint.schema.*; import com.evolveum.midpoint.schema.constants.ObjectTypes; @@ -67,7 +65,6 @@ import com.evolveum.midpoint.wf.util.ApprovalUtils; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.CompareResultType; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ImportOptionsType; -import com.evolveum.midpoint.xml.ns._public.common.audit_3.AuditEventStageType; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExecuteScriptType; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ScriptingExpressionType; @@ -1298,15 +1295,15 @@ public OperationResult testResource(String resourceOid, Task task) throws Object testResult = provisioning.testResource(resourceOid, task); } catch (ObjectNotFoundException ex) { LOGGER.error("Error testing resource OID: {}: Object not found: {} ", resourceOid, ex.getMessage(), ex); - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); throw ex; } catch (SystemException ex) { LOGGER.error("Error testing resource OID: {}: {} ", resourceOid, ex.getMessage(), ex); - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); throw ex; } catch (Exception ex) { LOGGER.error("Error testing resource OID: {}: {} ", resourceOid, ex.getMessage(), ex); - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); throw new SystemException(ex.getMessage(), ex); } finally { exitModelMethod(); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java index a919a2e305b..df9317431c7 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java @@ -223,7 +223,7 @@ public ModelContext previewChanges( LensContext context = null; try { - RepositoryCache.enter(cacheConfigurationManager); + RepositoryCache.enterLocalCaches(cacheConfigurationManager); // used cloned deltas instead of origin deltas, because some of the // values should be lost later.. context = contextFactory.createContext(clonedDeltas, options, task, result); @@ -233,7 +233,7 @@ public ModelContext previewChanges( } finally { LensUtil.reclaimSequences(context, cacheRepositoryService, task, result); - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); } return context; @@ -1365,7 +1365,7 @@ private String getClearValue(ProtectedStringType protectedString) throws SchemaE public List getDeputyAssignees(AbstractWorkItemType workItem, Task task, OperationResult parentResult) throws SchemaException { OperationResult result = parentResult.createMinorSubresult(GET_DEPUTY_ASSIGNEES); - RepositoryCache.enter(cacheConfigurationManager); + RepositoryCache.enterLocalCaches(cacheConfigurationManager); try { Set oidsToSkip = new HashSet<>(); List deputies = new ArrayList<>(); @@ -1377,7 +1377,7 @@ public List getDeputyAssignees(AbstractWorkItemType workIte result.recordFatalError(t.getMessage(), t); throw t; } finally { - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); } } @@ -1386,7 +1386,7 @@ public List getDeputyAssignees(AbstractWorkItemType workIte public List getDeputyAssignees(ObjectReferenceType assigneeRef, QName limitationItemName, Task task, OperationResult parentResult) throws SchemaException { OperationResult result = parentResult.createMinorSubresult(GET_DEPUTY_ASSIGNEES); - RepositoryCache.enter(cacheConfigurationManager); + RepositoryCache.enterLocalCaches(cacheConfigurationManager); try { Set oidsToSkip = new HashSet<>(); oidsToSkip.add(assigneeRef.getOid()); @@ -1398,7 +1398,7 @@ public List getDeputyAssignees(ObjectReferenceType assignee result.recordFatalError(t.getMessage(), t); throw t; } finally { - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/triggerSetter/TriggerCreatorGlobalState.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/triggerSetter/TriggerCreatorGlobalState.java index 1b0cae48c40..f6c717c8aab 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/triggerSetter/TriggerCreatorGlobalState.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/triggerSetter/TriggerCreatorGlobalState.java @@ -8,12 +8,11 @@ package com.evolveum.midpoint.model.impl.expr.triggerSetter; import com.evolveum.midpoint.CacheInvalidationContext; -import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionFactory; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.repo.api.Cacheable; import com.evolveum.midpoint.repo.api.DeleteObjectResult; -import com.evolveum.midpoint.repo.cache.CacheRegistry; -import com.evolveum.midpoint.repo.cache.RepositoryCache; +import com.evolveum.midpoint.repo.cache.invalidation.RepositoryCacheInvalidationDetails; +import com.evolveum.midpoint.repo.cache.registry.CacheRegistry; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.SingleCacheStateInformationType; @@ -62,8 +61,8 @@ public synchronized void invalidate(Class type, String oid, CacheInvalidation // We are interested in object deletion events; just to take care of situations when an object is deleted and // a new object (of the same name) is created immediately. boolean cleanupSpecificEntries = context != null && - context.getDetails() instanceof RepositoryCache.RepositoryCacheInvalidationDetails && - ((RepositoryCache.RepositoryCacheInvalidationDetails) context.getDetails()).getObject() instanceof DeleteObjectResult; + context.getDetails() instanceof RepositoryCacheInvalidationDetails && + ((RepositoryCacheInvalidationDetails) context.getDetails()).getObject() instanceof DeleteObjectResult; // We want to remove expired entries in regular intervals. Invalidation event arrival is quite good approximation. // However, there's EXPIRATION_INTERVAL present to avoid going through the entries at each invalidation event. diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentCollector.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentCollector.java index bc83484dc76..a255c27733e 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentCollector.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentCollector.java @@ -133,7 +133,7 @@ private Collection> ev Collection assignments, AssignmentOrigin origin, AssignmentEvaluator assignmentEvaluator, Task task, OperationResult result) { List> evaluatedAssignments = new ArrayList<>(); - RepositoryCache.enter(cacheConfigurationManager); + RepositoryCache.enterLocalCaches(cacheConfigurationManager); try { PrismContainerDefinition standardAssignmentDefinition = prismContext.getSchemaRegistry() .findObjectDefinitionByCompileTimeClass(AssignmentHolderType.class) @@ -153,7 +153,7 @@ private Collection> ev } } } finally { - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); } return evaluatedAssignments; } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkMedic.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkMedic.java index d83c56bb489..8bc7029d7c0 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkMedic.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkMedic.java @@ -70,13 +70,13 @@ public void enterModelMethod(boolean enterCache) { } if (enterCache) { - RepositoryCache.enter(cacheConfigurationManager); + RepositoryCache.enterLocalCaches(cacheConfigurationManager); } } public void exitModelMethod(boolean exitCache) { if (exitCache) { - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); } DiagnosticContext ctx = DiagnosticContextHolder.pop(); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/MidpointPasswordValidator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/MidpointPasswordValidator.java index eebb7c1d911..c441f49031b 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/MidpointPasswordValidator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/MidpointPasswordValidator.java @@ -6,14 +6,6 @@ */ package com.evolveum.midpoint.model.impl.security; -import com.evolveum.midpoint.model.api.authentication.GuiProfiledPrincipalManager; -import com.evolveum.midpoint.model.api.authentication.GuiProfiledPrincipal; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.security.api.ConnectionEnvironment; -import com.evolveum.midpoint.security.api.MidPointPrincipal; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; - import org.apache.wss4j.common.ext.WSSecurityException; import org.apache.wss4j.dom.handler.RequestData; import org.apache.wss4j.dom.message.token.UsernameToken; @@ -22,6 +14,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import com.evolveum.midpoint.model.api.authentication.GuiProfiledPrincipalManager; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.security.api.ConnectionEnvironment; +import com.evolveum.midpoint.security.api.MidPointPrincipal; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + @Service public class MidpointPasswordValidator extends UsernameTokenValidator { @@ -35,23 +34,22 @@ public Credential validate(Credential credential, RequestData data) throws WSSec recordAuthenticationSuccess(credential); return credentialToReturn; } catch (WSSecurityException ex) { - recordAuthenticatonError(credential, ex); + recordAuthenticationError(credential, ex); throw ex; } } - private void recordAuthenticationSuccess(Credential credential) throws WSSecurityException { + private void recordAuthenticationSuccess(Credential credential) throws WSSecurityException { MidPointPrincipal principal = resolveMidpointPrincipal(credential); ConnectionEnvironment connEnv = ConnectionEnvironment.create(SchemaConstants.CHANNEL_WEB_SERVICE_URI); - passwdEvaluator.recordPasswordAuthenticationSuccess(principal, connEnv, resolvePassowrd(principal)); + passwdEvaluator.recordPasswordAuthenticationSuccess(principal, connEnv, resolvePassword(principal)); } - private void recordAuthenticatonError(Credential credential, WSSecurityException originEx) throws WSSecurityException { - + private void recordAuthenticationError(Credential credential, WSSecurityException originEx) throws WSSecurityException { MidPointPrincipal principal = resolveMidpointPrincipal(credential); - PasswordType passwordType = resolvePassowrd(principal); + PasswordType passwordType = resolvePassword(principal); ConnectionEnvironment connEnv = ConnectionEnvironment.create(SchemaConstants.CHANNEL_WEB_SERVICE_URI); @@ -59,28 +57,24 @@ private void recordAuthenticatonError(Credential credential, WSSecurityException if (principal.getApplicableSecurityPolicy() != null) { CredentialsPolicyType credentialsPolicyType = principal.getApplicableSecurityPolicy().getCredentials(); - passwdPolicy = credentialsPolicyType.getPassword(); + passwdPolicy = credentialsPolicyType.getPassword(); } passwdEvaluator.recordPasswordAuthenticationFailure(principal, connEnv, passwordType, passwdPolicy, originEx.getMessage()); } - private MidPointPrincipal resolveMidpointPrincipal(Credential credential) throws WSSecurityException { + private MidPointPrincipal resolveMidpointPrincipal(Credential credential) throws WSSecurityException { UsernameToken usernameToken = credential.getUsernametoken(); String username = usernameToken.getName(); - GuiProfiledPrincipal principal = null; try { - principal = userService.getPrincipal(username, UserType.class); + return userService.getPrincipal(username, UserType.class); } catch (ObjectNotFoundException | SchemaException | CommunicationException | ConfigurationException | SecurityViolationException | ExpressionEvaluationException e) { - throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_AUTHENTICATION, e); } - - return principal; } - private PasswordType resolvePassowrd(MidPointPrincipal principal) { + private PasswordType resolvePassword(MidPointPrincipal principal) { FocusType user = principal.getFocus(); PasswordType passwordType = null; if (user.getCredentials() != null) { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/ReconciliationTaskHandler.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/ReconciliationTaskHandler.java index 107e8fc23ef..44e204cbd14 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/ReconciliationTaskHandler.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/ReconciliationTaskHandler.java @@ -762,7 +762,7 @@ private boolean scanForUnfinishedOperations(RunningTask task, String resourceOid long started = System.currentTimeMillis(); task.recordIterativeOperationStart(shadow.asObjectable()); - RepositoryCache.enter(cacheConfigurationManager); + RepositoryCache.enterLocalCaches(cacheConfigurationManager); OperationResult provisioningResult = new OperationResult(OperationConstants.RECONCILIATION+".finishOperation"); try { ProvisioningOperationOptions options = ProvisioningOperationOptions.createForceRetry(Boolean.TRUE); @@ -777,7 +777,7 @@ private boolean scanForUnfinishedOperations(RunningTask task, String resourceOid opResult.recordFatalError("Failed to finish operation with shadow: " + ObjectTypeUtil.toShortString(shadow.asObjectable()) +". Reason: " + ex.getMessage(), ex); } finally { task.markObjectActionExecutedBoundary(); - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); } task.incrementProgressAndStoreStatsIfNeeded(); diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestResources.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestResources.java index f1f7cca47dc..5f13d966440 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestResources.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestResources.java @@ -367,7 +367,7 @@ public void test100SearchResourcesNoFetch() throws Exception { assertSuccess(result); - assertCounterIncrement(InternalCounters.PRISM_OBJECT_CLONE_COUNT, 8); + assertCounterIncrement(InternalCounters.PRISM_OBJECT_CLONE_COUNT, 2); for (PrismObject resource: resources) { assertResource(resource, false); @@ -415,7 +415,7 @@ public void test102SearchResourcesNoFetchReadOnly() throws Exception { assertSuccess(result); - assertCounterIncrement(InternalCounters.PRISM_OBJECT_CLONE_COUNT, 4); + assertCounterIncrement(InternalCounters.PRISM_OBJECT_CLONE_COUNT, 2); for (PrismObject resource: resources) { assertResource(resource, false); @@ -774,14 +774,14 @@ public void test200GetResourceRawAfterSchema() throws Exception { OperationResult result = task.getResult(); assumeAssignmentPolicy(AssignmentPolicyEnforcementType.POSITIVE); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); Collection> options = SelectorOptions.createCollection(GetOperationOptions.createRaw()); // WHEN PrismObject resource = modelService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, options , task, result); // THEN - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); SqlRepoTestUtil.assertVersionProgress(null, resource.getVersion()); lastVersion = resource.getVersion(); displayValue("Initial version", lastVersion); @@ -1121,14 +1121,14 @@ private void singleModify(CarefulAnt ant, int iteration, Task task .createModifyDelta(RESOURCE_DUMMY_OID, itemDelta, ResourceType.class); Collection> deltas = MiscSchemaUtil.createCollection(objectDelta); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); ModelExecuteOptions options = ModelExecuteOptions.createRaw(); // WHEN modelService.executeChanges(deltas, options , task, result); // THEN - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); Collection> getOptions = SelectorOptions.createCollection(GetOperationOptions.createRaw()); PrismObject resourceAfter = modelService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, getOptions, task, result); SqlRepoTestUtil.assertVersionProgress(lastVersion, resourceAfter.getVersion()); @@ -1141,7 +1141,7 @@ private void singleModify(CarefulAnt ant, int iteration, Task task assertNotNull("No targetNamespace in schema after application of "+objectDelta, targetNamespace); } - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); ant.assertModification(resourceAfter, iteration); } diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/importer/AbstractImportTest.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/importer/AbstractImportTest.java index eed79775912..2c8a92926c3 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/importer/AbstractImportTest.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/importer/AbstractImportTest.java @@ -450,7 +450,7 @@ public void test030ImportResource() throws Exception { OperationResult result = task.getResult(); FileInputStream stream = new FileInputStream(getFile(RESOURCE_DUMMY_FILE_NAME, true)); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); dummyAuditService.clear(); // WHEN @@ -461,16 +461,16 @@ public void test030ImportResource() throws Exception { display("Result after import", result); TestUtil.assertSuccess("Import of " + RESOURCE_DUMMY_FILE_NAME + " has failed (result)", result, 2); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); importedRepoResource = repositoryService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, null, result); display("Imported resource (repo)", importedRepoResource); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); assertDummyResource(importedRepoResource, true); importedResource = modelService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, null, task, result); display("Imported resource (model)", importedResource); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); assertDummyResource(importedResource, false); ResourceType importedResourceType = importedResource.asObjectable(); @@ -479,7 +479,7 @@ public void test030ImportResource() throws Exception { // Read it from repo again. The read from model triggers schema fetch which increases version importedRepoResource = repositoryService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, null, result); display("Imported resource (repo2)", importedRepoResource); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); assertDummyResource(importedRepoResource, true); // Check audit @@ -502,7 +502,7 @@ public void test031ReimportResource() throws Exception { ImportOptionsType options = getDefaultImportOptions(); options.setOverwrite(true); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); dummyAuditService.clear(); // WHEN @@ -513,20 +513,20 @@ public void test031ReimportResource() throws Exception { display("Result after import", result); TestUtil.assertSuccess("Import of " + RESOURCE_DUMMY_CHANGED_FILE_NAME + " has failed (result)", result, 2); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); PrismObject repoResource = repositoryService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, null, result); display("Reimported resource (repo)", repoResource); assertDummyResource(repoResource, true); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); MidPointAsserts.assertVersionIncrease(importedRepoResource, repoResource); PrismObject resource = modelService.getObject(ResourceType.class, RESOURCE_DUMMY_OID, null, task, result); display("Reimported resource (model)", resource); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); assertDummyResource(resource, false); @@ -552,7 +552,7 @@ public void test032ImportResourceOidAndFilter() throws Exception { OperationResult result = task.getResult(); FileInputStream stream = new FileInputStream(getFile(RESOURCE_DERBY_FILE_NAME, false)); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); dummyAuditService.clear(); // WHEN @@ -563,24 +563,24 @@ public void test032ImportResourceOidAndFilter() throws Exception { display("Result after import", result); TestUtil.assertSuccess("Import of " + RESOURCE_DERBY_FILE_NAME + " has failed (result)", result, 2); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); importedRepoResource = repositoryService.getObject(ResourceType.class, RESOURCE_DERBY_OID, null, result); display("Imported resource (repo)", importedRepoResource); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); assertResource(importedRepoResource, "Embedded Test Derby: Import test", RESOURCE_DERBY_NAMESPACE, CONNECOTR_DBTABLE_OID); importedResource = modelService.getObject(ResourceType.class, RESOURCE_DERBY_OID, null, task, result); display("Imported resource (model)", importedResource); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); assertResource(importedResource, "Embedded Test Derby: Import test", RESOURCE_DERBY_NAMESPACE, CONNECOTR_DBTABLE_OID); // Read it from repo again. The read from model triggers schema fetch which increases version importedRepoResource = repositoryService.getObject(ResourceType.class, RESOURCE_DERBY_OID, null, result); display("Imported resource (repo2)", importedRepoResource); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); assertResource(importedRepoResource, "Embedded Test Derby: Import test", RESOURCE_DERBY_NAMESPACE, CONNECOTR_DBTABLE_OID); @@ -604,7 +604,7 @@ public void test033ImportResourceDummyRuntime() throws Exception { OperationResult result = task.getResult(); FileInputStream stream = new FileInputStream(getFile(RESOURCE_DUMMY_RUNTIME_FILE_NAME, false)); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); dummyAuditService.clear(); // WHEN @@ -615,22 +615,22 @@ public void test033ImportResourceDummyRuntime() throws Exception { display("Result after import", result); TestUtil.assertSuccess("Import of " + RESOURCE_DUMMY_RUNTIME_FILE_NAME + " has failed (result)", result, 2); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); importedRepoResource = repositoryService.getObject(ResourceType.class, RESOURCE_DUMMY_RUNTIME_OID, null, result); display("Imported resource (repo)", importedRepoResource); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); assertResource(importedRepoResource, "Dummy Resource (runtime)", MidPointConstants.NS_RI, null); importedResource = modelService.getObject(ResourceType.class, RESOURCE_DUMMY_RUNTIME_OID, null, task, result); display("Imported resource (model)", importedResource); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); assertResource(importedRepoResource, "Dummy Resource (runtime)", MidPointConstants.NS_RI, null); // Read it from repo again. The read from model triggers schema fetch which increases version importedRepoResource = repositoryService.getObject(ResourceType.class, RESOURCE_DUMMY_RUNTIME_OID, null, result); display("Imported resource (repo2)", importedRepoResource); - IntegrationTestTools.assertNoRepoCache(); + IntegrationTestTools.assertNoRepoThreadLocalCache(); assertResource(importedRepoResource, "Dummy Resource (runtime)", MidPointConstants.NS_RI, null); } diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/negative/TestAssignmentErrors.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/negative/TestAssignmentErrors.java index aaebd9357c8..4858625d5d2 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/negative/TestAssignmentErrors.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/negative/TestAssignmentErrors.java @@ -9,7 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.testng.AssertJUnit.*; -import static com.evolveum.midpoint.test.IntegrationTestTools.assertNoRepoCache; +import static com.evolveum.midpoint.test.IntegrationTestTools.assertNoRepoThreadLocalCache; import java.util.ArrayList; import java.util.Collection; @@ -655,7 +655,7 @@ private PrismObject setupUserAssignAccountDeletedShadowRecompute( result.computeStatus(); TestUtil.assertSuccess(result); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); dummyAuditService.clear(); return user; diff --git a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportHTMLCreateTaskHandler.java b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportHTMLCreateTaskHandler.java index 6e5fe682aba..944beb82b6d 100644 --- a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportHTMLCreateTaskHandler.java +++ b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportHTMLCreateTaskHandler.java @@ -220,7 +220,7 @@ public class ReportHTMLCreateTaskHandler extends ReportJasperCreateTaskHandler { put(FAMILY_NAME_COLUMN, UserType.F_FAMILY_NAME); put(FULL_NAME_COLUMN, UserType.F_FULL_NAME); put(EMAIL_COLUMN, UserType.F_EMAIL_ADDRESS); - put(ACCOUNTS_COLUMN, ItemPath.create(AbstractRoleType.F_LINK_REF, CUSTOM)); + put(ACCOUNTS_COLUMN, ItemPath.create(CUSTOM)); } }); @@ -751,7 +751,8 @@ private String getCustomValueForColumn(Item valueObject, String nameOfColumn) { if(!(valueObject instanceof PrismObject)){ return ""; } - return String.valueOf(((PrismObject)valueObject).getRealValues().size()); + Item item = ((PrismObject) valueObject).findItem(ItemPath.create(AbstractRoleType.F_LINK_REF)); + return String.valueOf(item.getRealValues().size()); case CURRENT_RUN_TIME_COLUMN: if(!(valueObject instanceof PrismObject) && !(((PrismObject)valueObject).getRealValue() instanceof TaskType)){ @@ -940,8 +941,10 @@ private String getLabelNameForCustom(String nameOfColumn) { switch (nameOfColumn) { case ACCOUNTS_COLUMN: key = "FocusType.accounts"; + break; case CURRENT_RUN_TIME_COLUMN: key = "TaskType.currentRunTime"; + break; } return getMessage(key); } diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConnectorManager.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConnectorManager.java index 938641ce982..5ade41255c9 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConnectorManager.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ConnectorManager.java @@ -41,7 +41,7 @@ import com.evolveum.midpoint.provisioning.ucf.api.ConnectorInstance; import com.evolveum.midpoint.provisioning.ucf.api.GenericFrameworkException; import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.repo.cache.CacheRegistry; +import com.evolveum.midpoint.repo.cache.registry.CacheRegistry; import com.evolveum.midpoint.repo.api.Cacheable; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.internals.InternalCounters; diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceCache.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceCache.java index 956b4d7c7f8..f8fb87a2b14 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceCache.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceCache.java @@ -11,7 +11,7 @@ import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.repo.api.Cacheable; import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.repo.cache.CacheRegistry; +import com.evolveum.midpoint.repo.cache.registry.CacheRegistry; import com.evolveum.midpoint.schema.internals.InternalMonitor; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.util.caching.CachePerformanceCollector; diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectConverter.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectConverter.java index 6d20028a77e..a91f6bf688f 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectConverter.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectConverter.java @@ -1289,7 +1289,7 @@ public SearchResultMetadata searchResourceObjects(final ProvisioningContext ctx, metadata = connector.search(objectClassDef, query, (shadow) -> { // in order to utilize the cache right from the beginning... - RepositoryCache.enter(cacheConfigurationManager); + RepositoryCache.enterLocalCaches(cacheConfigurationManager); try { int objectNumber = objectCounter.getAndIncrement(); @@ -1334,7 +1334,7 @@ public SearchResultMetadata searchResourceObjects(final ProvisioningContext ctx, parentResult.summarize(); } } finally { - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); if (requestedTracingHere && task instanceof RunningTask) { ((RunningTask) task).stopTracing(); } diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCache.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCache.java index a707279964a..3e13f5a2c03 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCache.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCache.java @@ -171,7 +171,11 @@ public PrismObject getShadow(String oid, PrismObject rep } throw e; } - shadowCaretaker.applyAttributesDefinition(ctx, repositoryShadow); + if (repositoryShadow.isImmutable()) { + repositoryShadow = shadowCaretaker.applyAttributesDefinitionToImmutable(ctx, repositoryShadow); + } else { + shadowCaretaker.applyAttributesDefinition(ctx, repositoryShadow); + } ResourceType resource = ctx.getResource(); XMLGregorianCalendar now = clock.currentTimeXMLGregorianCalendar(); diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCaretaker.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCaretaker.java index 654eb00a471..aedb025d238 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCaretaker.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCaretaker.java @@ -13,6 +13,8 @@ import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; +import com.evolveum.midpoint.util.Holder; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -155,9 +157,17 @@ private void applyAttributeDefi } } + public PrismObject applyAttributesDefinitionToImmutable(ProvisioningContext ctx, + PrismObject shadow) throws SchemaException, ConfigurationException, + ObjectNotFoundException, CommunicationException, ExpressionEvaluationException { + PrismObject mutableShadow = shadow.clone(); + applyAttributesDefinition(ctx, mutableShadow); + return mutableShadow.createImmutableClone(); + } + public ProvisioningContext applyAttributesDefinition(ProvisioningContext ctx, PrismObject shadow) throws SchemaException, ConfigurationException, - ObjectNotFoundException, CommunicationException, ExpressionEvaluationException { + ObjectNotFoundException, CommunicationException, ExpressionEvaluationException { ProvisioningContext subctx = ctx.spawn(shadow); subctx.assertDefinition(); RefinedObjectClassDefinition objectClassDefinition = subctx.getObjectClassDefinition(); diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummyParallelism.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummyParallelism.java index b16f0d31f70..80caf1fffba 100644 --- a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummyParallelism.java +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummyParallelism.java @@ -306,7 +306,7 @@ private PrismObject parallelModifyTest( Task localTask = createTask(testName + "-thread-" + i); OperationResult localResult = localTask.getResult(); - RepositoryCache.enter(cacheConfigurationManager); + RepositoryCache.enterLocalCaches(cacheConfigurationManager); // Playing with cache, trying to make a worst case PrismObject shadowBefore = repositoryService.getObject(ShadowType.class, accountMorganOid, null, localResult); @@ -328,7 +328,7 @@ private PrismObject parallelModifyTest( fail("Unexpected thread result status " + localResult.getStatus()); } - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); }, CONCURRENT_TEST_THREAD_COUNT, CONCURRENT_TEST_MAX_START_DELAY_FAST); @@ -363,7 +363,7 @@ public void test209ParallelDelete() throws Exception { Task localTask = createTask(testName + "-thread-" + i); OperationResult localResult = localTask.getResult(); - RepositoryCache.enter(cacheConfigurationManager); + RepositoryCache.enterLocalCaches(cacheConfigurationManager); try { display("Thread " + Thread.currentThread().getName() + " START"); @@ -383,7 +383,7 @@ public void test209ParallelDelete() throws Exception { // this is expected ... sometimes logger.info("Exception (maybe expected): {}: {}", e.getClass().getSimpleName(), e.getMessage()); } finally { - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); } }, CONCURRENT_TEST_THREAD_COUNT, CONCURRENT_TEST_MAX_START_DELAY_FAST); @@ -435,7 +435,7 @@ public void test210ParallelCreateSlow() throws Exception { // this is expected ... sometimes logger.info("Exception (maybe expected): {}: {}", e.getClass().getSimpleName(), e.getMessage()); } finally { - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); } }, CONCURRENT_TEST_THREAD_COUNT, null); @@ -506,7 +506,7 @@ private PrismObject parallelModifyTestSlow( Task localTask = createTask(testName + "-thread-" + i); OperationResult localResult = localTask.getResult(); - RepositoryCache.enter(cacheConfigurationManager); + RepositoryCache.enterLocalCaches(cacheConfigurationManager); // Playing with cache, trying to make a worst case PrismObject shadowBefore = repositoryService.getObject( ShadowType.class, accountElizabethOid, null, localResult); @@ -530,7 +530,7 @@ private PrismObject parallelModifyTestSlow( fail("Unexpected thread result status " + localResult.getStatus()); } - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); }, CONCURRENT_TEST_THREAD_COUNT, null); @@ -565,7 +565,7 @@ public void test229ParallelDeleteSlow() throws Exception { Task localTask = createTask(testName + "-thread-" + i); OperationResult localResult = localTask.getResult(); - RepositoryCache.enter(cacheConfigurationManager); + RepositoryCache.enterLocalCaches(cacheConfigurationManager); // Playing with cache, trying to make a worst case PrismObject shadowBefore = repositoryService.getObject(ShadowType.class, accountElizabethOid, null, localResult); @@ -588,7 +588,7 @@ public void test229ParallelDeleteSlow() throws Exception { // this is expected ... sometimes logger.info("Exception (maybe expected): {}: {}", e.getClass().getSimpleName(), e.getMessage()); } finally { - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); } }, CONCURRENT_TEST_THREAD_COUNT, null); diff --git a/provisioning/ucf-impl-connid/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/AbstractModificationConverter.java b/provisioning/ucf-impl-connid/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/AbstractModificationConverter.java index 87e0c3d8bd7..356850dc2b5 100644 --- a/provisioning/ucf-impl-connid/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/AbstractModificationConverter.java +++ b/provisioning/ucf-impl-connid/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/AbstractModificationConverter.java @@ -6,12 +6,7 @@ */ package com.evolveum.midpoint.provisioning.ucf.impl.connid; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import java.util.*; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; @@ -40,17 +35,11 @@ import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnectorType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.LockoutStatusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; /** * @author semancik - * */ public abstract class AbstractModificationConverter implements DebugDumpable { @@ -163,13 +152,13 @@ public void convert() throws SchemaException { ObjectClassComplexTypeDefinition structuralObjectClassDefinition = resourceSchema.findObjectClassDefinition(objectClassDef.getTypeName()); if (structuralObjectClassDefinition == null) { - throw new SchemaException("No definition of structural object class "+objectClassDef.getTypeName()+" in "+connectorDescription); + throw new SchemaException("No definition of structural object class " + objectClassDef.getTypeName() + " in " + connectorDescription); } - Map auxiliaryObjectClassMap = new HashMap<>(); + Map auxiliaryObjectClassMap = new HashMap<>(); if (auxiliaryObjectClassDelta != null) { // Auxiliary object class change means modification of __AUXILIARY_OBJECT_CLASS__ attribute collect(PredefinedAttributes.AUXILIARY_OBJECT_CLASS_NAME, auxiliaryObjectClassDelta, null, - (pvals,midPointAttributeName) -> covertAuxiliaryObjectClassValuesToConnId(pvals, midPointAttributeName, auxiliaryObjectClassMap)); + (pvals, midPointAttributeName) -> covertAuxiliaryObjectClassValuesToConnId(pvals, midPointAttributeName, auxiliaryObjectClassMap)); } for (Operation operation : changes) { @@ -182,7 +171,7 @@ public void convert() throws SchemaException { ResourceAttributeDefinition def = objectClassDef .findAttributeDefinition(delta.getElementName()); if (def == null) { - String message = "No definition for attribute "+delta.getElementName()+" used in modification delta"; + String message = "No definition for attribute " + delta.getElementName() + " used in modification delta"; throw new SchemaException(message); } try { @@ -202,7 +191,7 @@ public void convert() throws SchemaException { // auxiliary object class and the change of the attributes must be done in // one operation. Otherwise we get schema error. And as auxiliary object class // is removed, the attributes must be removed as well. - for (PrismPropertyValue auxPval: auxiliaryObjectClassDelta.getValuesToDelete()) { + for (PrismPropertyValue auxPval : auxiliaryObjectClassDelta.getValuesToDelete()) { ObjectClassComplexTypeDefinition auxDef = auxiliaryObjectClassMap.get(auxPval.getValue()); ResourceAttributeDefinition attrDef = auxDef.findAttributeDefinition(delta.getElementName()); if (attrDef != null) { @@ -218,7 +207,7 @@ public void convert() throws SchemaException { // auxiliary object class and the change of the attributes must be done in // one operation. Otherwise we get schema error. And as auxiliary object class // is added, the attributes must be added as well. - for (PrismPropertyValue auxPval: auxiliaryObjectClassDelta.getValuesToAdd()) { + for (PrismPropertyValue auxPval : auxiliaryObjectClassDelta.getValuesToAdd()) { ObjectClassComplexTypeDefinition auxOcDef = auxiliaryObjectClassMap.get(auxPval.getValue()); ResourceAttributeDefinition auxAttrDef = auxOcDef.findAttributeDefinition(delta.getElementName()); if (auxAttrDef != null) { @@ -251,7 +240,6 @@ public void convert() throws SchemaException { } - } private void convertFromActivation(PropertyDelta propDelta) throws SchemaException { @@ -303,12 +291,12 @@ protected void collectPassword(PropertyDelta passwordDelta) LOGGER.debug("Setting null password."); collectReplace(OperationalAttributes.PASSWORD_NAME, null); } else if (newPassword.getRealValue().canGetCleartext()) { - // We have password and we can get a cleartext value of the passowrd. This is normal case + // We have password and we can get a cleartext value of the password. This is normal case GuardedString guardedPassword = passwordToGuardedString(newPassword.getRealValue(), "new password"); collectReplace(OperationalAttributes.PASSWORD_NAME, guardedPassword); } else { // We have password, but we cannot get a cleartext value. Just to nothing. - LOGGER.debug("We would like to set password, but we do not have cleartext value. Skipping the opearation."); + LOGGER.debug("We would like to set password, but we do not have cleartext value. Skipping the operation."); } } @@ -319,12 +307,12 @@ protected GuardedString passwordToGuardedString(ProtectedStringType ps, String p @SuppressWarnings({ "rawtypes", "unchecked" }) private T getPropertyNewValue(PropertyDelta propertyDelta, Class clazz) throws SchemaException { PrismProperty> prop = propertyDelta.getPropertyNewMatchingPath(); - if (prop == null){ + if (prop == null) { return null; } PrismPropertyValue propValue = prop.getValue(clazz); - if (propValue == null){ + if (propValue == null) { return null; } @@ -333,7 +321,7 @@ private T getPropertyNewValue(PropertyDelta propertyDelta, Class clazz) t protected List covertAttributeValuesToConnId(Collection> pvals, QName midPointAttributeName) throws SchemaException { List connIdVals = new ArrayList<>(pvals.size()); - for (PrismPropertyValue pval: pvals) { + for (PrismPropertyValue pval : pvals) { connIdVals.add(covertAttributeValueToConnId(pval, midPointAttributeName)); } return connIdVals; @@ -343,13 +331,13 @@ protected Object covertAttributeValueToConnId(PrismPropertyValue pval, QN return ConnIdUtil.convertValueToConnId(pval, protector, midPointAttributeName); } - private List covertAuxiliaryObjectClassValuesToConnId(Collection> pvals, QName midPointAttributeName, Map auxiliaryObjectClassMap) throws SchemaException { + private List covertAuxiliaryObjectClassValuesToConnId(Collection> pvals, QName midPointAttributeName, Map auxiliaryObjectClassMap) throws SchemaException { List connIdVals = new ArrayList<>(pvals.size()); - for (PrismPropertyValue pval: pvals) { + for (PrismPropertyValue pval : pvals) { QName auxQName = pval.getValue(); ObjectClassComplexTypeDefinition auxDef = resourceSchema.findObjectClassDefinition(auxQName); if (auxDef == null) { - throw new SchemaException("Auxiliary object class "+auxQName+" not found in the schema"); + throw new SchemaException("Auxiliary object class " + auxQName + " not found in the schema"); } auxiliaryObjectClassMap.put(auxQName, auxDef); ObjectClass icfOc = connIdNameMapper.objectClassToConnId(pval.getValue(), resourceSchemaNamespace, connectorType, false); @@ -367,7 +355,7 @@ private PropertyDelta determineAuxilaryObjectClassDelta(Collection delta = ((PropertyModificationOperation)operation).getPropertyDelta(); + PropertyDelta delta = ((PropertyModificationOperation) operation).getPropertyDelta(); if (delta.getPath().equivalent(ShadowType.F_AUXILIARY_OBJECT_CLASS)) { auxiliaryObjectClassDelta = (PropertyDelta) delta; } diff --git a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/CacheDispatcher.java b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/CacheDispatcher.java index 58cebee7f38..02b0604dc89 100644 --- a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/CacheDispatcher.java +++ b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/CacheDispatcher.java @@ -7,16 +7,18 @@ package com.evolveum.midpoint.repo.api; -import com.evolveum.midpoint.CacheInvalidationContext; -import com.evolveum.midpoint.xml.ns._public.common.api_types_3.UserSessionManagementType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import org.jetbrains.annotations.Nullable; -import java.util.List; +import com.evolveum.midpoint.CacheInvalidationContext; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +/** + * Dispatches events to cache listeners (currently CacheRegistry and ClusterCacheListener). + */ public interface CacheDispatcher { void registerCacheListener(CacheListener cacheListener); + void unregisterCacheListener(CacheListener cacheListener); /** diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/RepositoryCache.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/RepositoryCache.java index d283ff1a244..ffb1fceac61 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/RepositoryCache.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/RepositoryCache.java @@ -6,1492 +6,205 @@ */ package com.evolveum.midpoint.repo.cache; -import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; +import static com.evolveum.midpoint.repo.cache.other.MonitoringUtil.repoOpEnd; +import static com.evolveum.midpoint.repo.cache.other.MonitoringUtil.repoOpStart; -import static com.evolveum.midpoint.repo.cache.RepositoryCache.PassReasonType.*; -import static com.evolveum.midpoint.schema.GetOperationOptions.*; -import static com.evolveum.midpoint.schema.SelectorOptions.findRootOptions; -import static com.evolveum.midpoint.schema.cache.CacheType.*; -import static com.evolveum.midpoint.schema.util.TraceUtil.isAtLeastMinimal; -import static com.evolveum.midpoint.schema.util.TraceUtil.isAtLeastNormal; - -import java.util.Objects; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.PreDestroy; +import com.evolveum.midpoint.repo.cache.global.GlobalObjectCache; +import com.evolveum.midpoint.repo.cache.global.GlobalQueryCache; +import com.evolveum.midpoint.repo.cache.global.GlobalVersionCache; +import com.evolveum.midpoint.repo.cache.handlers.*; +import com.evolveum.midpoint.repo.cache.local.LocalRepoCacheCollection; +import com.evolveum.midpoint.repo.cache.invalidation.Invalidator; +import com.evolveum.midpoint.repo.cache.registry.CacheRegistry; + import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.evolveum.midpoint.CacheInvalidationContext; import com.evolveum.midpoint.prism.Containerable; -import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.delta.ItemDelta; -import com.evolveum.midpoint.prism.match.MatchingRuleRegistry; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.repo.api.*; import com.evolveum.midpoint.repo.api.perf.PerformanceMonitor; import com.evolveum.midpoint.repo.api.query.ObjectFilterExpressionEvaluator; import com.evolveum.midpoint.schema.*; import com.evolveum.midpoint.schema.cache.CacheConfigurationManager; -import com.evolveum.midpoint.schema.result.CompiledTracingProfile; import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.DiagnosticContextHolder; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; -import com.evolveum.midpoint.schema.util.TraceUtil; -import com.evolveum.midpoint.util.caching.CacheConfiguration; -import com.evolveum.midpoint.util.caching.CacheConfiguration.CacheObjectTypeConfiguration; -import com.evolveum.midpoint.util.caching.CachePerformanceCollector; -import com.evolveum.midpoint.util.caching.CacheUtil; import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; /** - * Read-through write-through per-session repository cache. + * Read-through write-through repository cache. *

- * TODO doc - * TODO logging perf measurements - * - * @author Radovan Semancik + * This is an umbrella class providing RepositoryService and Cacheable interfaces. + * Majority of the work is delegated to operation handlers (and other classes). */ @Component(value = "cacheRepositoryService") public class RepositoryCache implements RepositoryService, Cacheable { - private static final Trace LOGGER = TraceManager.getTrace(RepositoryCache.class); - private static final Trace PERFORMANCE_ADVISOR = TraceManager.getPerformanceAdvisorTrace(); - - private static final String CLASS_NAME_WITH_DOT = RepositoryCache.class.getName() + "."; - private static final String GET_OBJECT = CLASS_NAME_WITH_DOT + "getObject"; - private static final String LIST_ACCOUNT_SHADOW_OWNER = CLASS_NAME_WITH_DOT + "listAccountShadowOwner"; - private static final String ADD_OBJECT = CLASS_NAME_WITH_DOT + "addObject"; - private static final String DELETE_OBJECT = CLASS_NAME_WITH_DOT + "deleteObject"; - private static final String SEARCH_OBJECTS = CLASS_NAME_WITH_DOT + "searchObjects"; - private static final String SEARCH_CONTAINERS = CLASS_NAME_WITH_DOT + "searchContainers"; - private static final String COUNT_CONTAINERS = CLASS_NAME_WITH_DOT + "countContainers"; - private static final String MODIFY_OBJECT = CLASS_NAME_WITH_DOT + "modifyObject"; - private static final String COUNT_OBJECTS = CLASS_NAME_WITH_DOT + "countObjects"; - private static final String GET_VERSION = CLASS_NAME_WITH_DOT + "getVersion"; - private static final String SEARCH_OBJECTS_ITERATIVE = CLASS_NAME_WITH_DOT + "searchObjectsIterative"; - private static final String SEARCH_SHADOW_OWNER = CLASS_NAME_WITH_DOT + "searchShadowOwner"; - private static final String ADVANCE_SEQUENCE = CLASS_NAME_WITH_DOT + "advanceSequence"; - private static final String RETURN_UNUSED_VALUES_TO_SEQUENCE = CLASS_NAME_WITH_DOT + "returnUnusedValuesToSequence"; + public static final String CLASS_NAME_WITH_DOT = RepositoryCache.class.getName() + "."; private static final String EXECUTE_QUERY_DIAGNOSTICS = CLASS_NAME_WITH_DOT + "executeQueryDiagnostics"; - private static final String ADD_DIAGNOSTIC_INFORMATION = CLASS_NAME_WITH_DOT + "addDiagnosticInformation"; - - private static final ConcurrentHashMap LOCAL_OBJECT_CACHE_INSTANCE = new ConcurrentHashMap<>(); - private static final ConcurrentHashMap LOCAL_VERSION_CACHE_INSTANCE = new ConcurrentHashMap<>(); - private static final ConcurrentHashMap LOCAL_QUERY_CACHE_INSTANCE = new ConcurrentHashMap<>(); - @Autowired private PrismContext prismContext; @Autowired private RepositoryService repositoryService; - @Autowired private CacheDispatcher cacheDispatcher; @Autowired private CacheRegistry cacheRegistry; - @Autowired private MatchingRuleRegistry matchingRuleRegistry; + + // individual caches @Autowired private GlobalQueryCache globalQueryCache; @Autowired private GlobalObjectCache globalObjectCache; @Autowired private GlobalVersionCache globalVersionCache; - @Autowired private CacheConfigurationManager cacheConfigurationManager; + @Autowired private LocalRepoCacheCollection localRepoCacheCollection; - private static final List> TYPES_ALWAYS_INVALIDATED_CLUSTERWIDE = Arrays.asList( - SystemConfigurationType.class, - FunctionLibraryType.class); + // handlers + @Autowired private GetObjectOpHandler getObjectOpHandler; + @Autowired private ModificationOpHandler modificationOpHandler; + @Autowired private SearchOpHandler searchOpHandler; + @Autowired private GetVersionOpHandler getVersionOpHandler; - private static final int QUERY_RESULT_SIZE_LIMIT = 100000; - - private static final Random RND = new Random(); - - private Integer modifyRandomDelayRange; + // other + @Autowired private Invalidator invalidator; public RepositoryCache() { } - private static LocalObjectCache getLocalObjectCache() { - return LOCAL_OBJECT_CACHE_INSTANCE.get(Thread.currentThread()); - } - - private static LocalVersionCache getLocalVersionCache() { - return LOCAL_VERSION_CACHE_INSTANCE.get(Thread.currentThread()); - } - - private static LocalQueryCache getLocalQueryCache() { - return LOCAL_QUERY_CACHE_INSTANCE.get(Thread.currentThread()); - } - - public static void init() { - } - - public static void destroy() { - LocalObjectCache.destroy(LOCAL_OBJECT_CACHE_INSTANCE, LOGGER); - LocalVersionCache.destroy(LOCAL_VERSION_CACHE_INSTANCE, LOGGER); - LocalQueryCache.destroy(LOCAL_QUERY_CACHE_INSTANCE, LOGGER); - } - - public static void enter(CacheConfigurationManager mgr) { - // let's compute configuration first -- an exception can be thrown there; so if it happens, none of the caches - // will be entered into upon exit of this method - CacheConfiguration objectCacheConfig = mgr.getConfiguration(LOCAL_REPO_OBJECT_CACHE); - CacheConfiguration versionCacheConfig = mgr.getConfiguration(LOCAL_REPO_VERSION_CACHE); - CacheConfiguration queryCacheConfig = mgr.getConfiguration(LOCAL_REPO_QUERY_CACHE); - - LocalObjectCache.enter(LOCAL_OBJECT_CACHE_INSTANCE, LocalObjectCache.class, objectCacheConfig, LOGGER); - LocalVersionCache.enter(LOCAL_VERSION_CACHE_INSTANCE, LocalVersionCache.class, versionCacheConfig, LOGGER); - LocalQueryCache.enter(LOCAL_QUERY_CACHE_INSTANCE, LocalQueryCache.class, queryCacheConfig, LOGGER); - } - - public static void exit() { - LocalObjectCache.exit(LOCAL_OBJECT_CACHE_INSTANCE, LOGGER); - LocalVersionCache.exit(LOCAL_VERSION_CACHE_INSTANCE, LOGGER); - LocalQueryCache.exit(LOCAL_QUERY_CACHE_INSTANCE, LOGGER); - } - - public static boolean exists() { - return LocalObjectCache.exists(LOCAL_OBJECT_CACHE_INSTANCE) || - LocalVersionCache.exists(LOCAL_VERSION_CACHE_INSTANCE) || - LocalQueryCache.exists(LOCAL_QUERY_CACHE_INSTANCE); - } - - @SuppressWarnings("unused") - public Integer getModifyRandomDelayRange() { - return modifyRandomDelayRange; + /** + * Enters thread-local caches. + */ + public static void enterLocalCaches(CacheConfigurationManager mgr) { + LocalRepoCacheCollection.enter(mgr); } - public void setModifyRandomDelayRange(Integer modifyRandomDelayRange) { - this.modifyRandomDelayRange = modifyRandomDelayRange; + /** + * Exits thread-local caches. + */ + public static void exitLocalCaches() { + LocalRepoCacheCollection.exit(); } - public static String debugDump() { - // TODO - return LocalObjectCache.debugDump(LOCAL_OBJECT_CACHE_INSTANCE) + "\n" + - LocalVersionCache.debugDump(LOCAL_VERSION_CACHE_INSTANCE) + "\n" + - LocalQueryCache.debugDump(LOCAL_QUERY_CACHE_INSTANCE); - } + //region --- GET, SEARCH and COUNT operations ------------------------------------------------------------------ @NotNull @Override public PrismObject getObject(Class type, String oid, - Collection> options, OperationResult parentResult) throws ObjectNotFoundException, SchemaException { - - OperationResult result = parentResult.subresult(GET_OBJECT) - .addQualifier(type.getSimpleName()) - .addParam("type", type) - .addParam("oid", oid) - .addArbitraryObjectCollectionAsParam("options", options) - .build(); - - TracingLevelType level = result.getTracingLevel(RepositoryGetObjectTraceType.class); - RepositoryGetObjectTraceType trace; - if (isAtLeastMinimal(level)) { - trace = new RepositoryGetObjectTraceType(prismContext) - .cache(true) - .objectType(prismContext.getSchemaRegistry().determineTypeForClass(type)) - .oid(oid) - .options(String.valueOf(options)); - result.addTrace(trace); - } else { - trace = null; - } - - PrismObject objectToReturn = null; - - try { - CachePerformanceCollector collector = CachePerformanceCollector.INSTANCE; - LocalObjectCache localObjectsCache = getLocalObjectCache(); - - Context global = new Context(globalObjectCache.getConfiguration(), type); - Context local = localObjectsCache != null ? - new Context(localObjectsCache.getConfiguration(), type) : - new Context(cacheConfigurationManager.getConfiguration(LOCAL_REPO_OBJECT_CACHE), type); - - /* - * Checks related to both caches - */ - - PassReason passReason = getPassReason(options, type); - if (passReason != null) { - // local nor global cache not interested in caching this object - if (localObjectsCache != null) { - localObjectsCache.registerPass(); - } - collector.registerPass(LocalObjectCache.class, type, local.statisticsLevel); - collector.registerPass(GlobalObjectCache.class, type, global.statisticsLevel); - log("Cache (local/global): PASS:{} getObject {} ({}, {})", local.tracePass || global.tracePass, passReason, oid, - type.getSimpleName(), options); - - if (trace != null) { - trace.setLocalCacheUse(passReason.toCacheUse()); - trace.setGlobalCacheUse(passReason.toCacheUse()); - } - objectToReturn = getObjectInternal(type, oid, options, result); - return objectToReturn; - } - - /* - * Let's try local cache - */ - boolean readOnly = isReadOnly(findRootOptions(options)); - - if (localObjectsCache == null) { - log("Cache (local): NULL getObject {} ({})", false, oid, type.getSimpleName()); - registerNotAvailable(LocalObjectCache.class, type, local.statisticsLevel); - if (trace != null) { - trace.setLocalCacheUse(createUse(CacheUseCategoryTraceType.NOT_AVAILABLE)); - } - } else { - //noinspection unchecked - PrismObject object = (PrismObject) (local.supports ? localObjectsCache.get(oid) : null); - if (object != null) { - localObjectsCache.registerHit(); - collector.registerHit(LocalObjectCache.class, type, local.statisticsLevel); - log("Cache (local): HIT {} getObject {} ({})", false, readOnly ? "" : "(clone)", oid, type.getSimpleName()); - if (trace != null) { - trace.setLocalCacheUse(createUse(CacheUseCategoryTraceType.HIT)); - } - objectToReturn = cloneIfNecessary(object, readOnly); - return objectToReturn; - } - if (local.supports) { - localObjectsCache.registerMiss(); - collector.registerMiss(LocalObjectCache.class, type, local.statisticsLevel); - log("Cache (local): MISS {} getObject ({})", local.traceMiss, oid, type.getSimpleName()); - if (trace != null) { - trace.setLocalCacheUse(createUse(CacheUseCategoryTraceType.MISS)); - } - } else { - localObjectsCache.registerPass(); - collector.registerPass(LocalObjectCache.class, type, local.statisticsLevel); - log("Cache (local): PASS:CONFIGURATION {} getObject ({})", local.tracePass, oid, type.getSimpleName()); - if (trace != null) { - trace.setLocalCacheUse(createUse(CacheUseCategoryTraceType.PASS, "configuration")); - } - } - } - - /* - * Then try global cache - */ - - if (!globalObjectCache.isAvailable()) { - collector.registerNotAvailable(GlobalObjectCache.class, type, global.statisticsLevel); - log("Cache (global): NOT_AVAILABLE {} getObject ({})", false, oid, type.getSimpleName()); - objectToReturn = getObjectInternal(type, oid, options, result); - locallyCacheObject(localObjectsCache, local.supports, objectToReturn, readOnly); - if (trace != null) { - trace.setGlobalCacheUse(createUse(CacheUseCategoryTraceType.NOT_AVAILABLE)); - } - return objectToReturn; - } else if (!global.supports) { - // caller is not interested in cached value, or global cache doesn't want to cache value - collector.registerPass(GlobalObjectCache.class, type, global.statisticsLevel); - log("Cache (global): PASS:CONFIGURATION {} getObject ({})", global.tracePass, oid, type.getSimpleName()); - objectToReturn = getObjectInternal(type, oid, options, result); - locallyCacheObject(localObjectsCache, local.supports, objectToReturn, readOnly); - if (trace != null) { - trace.setGlobalCacheUse(createUse(CacheUseCategoryTraceType.PASS, "configuration")); - } - return objectToReturn; - } - - assert global.cacheConfig != null && global.typeConfig != null; - - GlobalCacheObjectValue cacheObject = globalObjectCache.get(oid); - if (cacheObject == null) { - collector.registerMiss(GlobalObjectCache.class, type, global.statisticsLevel); - log("Cache (global): MISS getObject {} ({})", global.traceMiss, oid, type.getSimpleName()); - if (trace != null) { - trace.setGlobalCacheUse(createUse(CacheUseCategoryTraceType.MISS)); - } - objectToReturn = loadAndCacheObject(type, oid, options, readOnly, localObjectsCache, local.supports, result); - } else { - if (!shouldCheckVersion(cacheObject)) { - collector.registerHit(GlobalObjectCache.class, type, global.statisticsLevel); - log("Cache (global): HIT getObject {} ({})", false, oid, type.getSimpleName()); - if (trace != null) { - trace.setGlobalCacheUse(createUse(CacheUseCategoryTraceType.HIT)); - // todo object if needed - } - PrismObject object = cacheObject.getObject(); - locallyCacheObjectWithoutCloning(localObjectsCache, local.supports, object); - objectToReturn = cloneIfNecessary(object, readOnly); - } else { - if (hasVersionChanged(type, oid, cacheObject, result)) { - collector.registerMiss(GlobalObjectCache.class, type, global.statisticsLevel); - log("Cache (global): MISS because of version changed - getObject {} ({})", global.traceMiss, oid, - type.getSimpleName()); - if (trace != null) { - trace.setGlobalCacheUse(createUse(CacheUseCategoryTraceType.MISS, "version changed")); - // todo object if needed - } - objectToReturn = loadAndCacheObject(type, oid, options, readOnly, localObjectsCache, local.supports, result); - } else { - cacheObject.setTimeToCheckVersion(System.currentTimeMillis() + getTimeToVersionCheck(global.typeConfig, - global.cacheConfig)); // version matches, renew ttl - collector.registerWeakHit(GlobalObjectCache.class, type, global.statisticsLevel); - log("Cache (global): HIT with version check - getObject {} ({})", global.traceMiss, oid, - type.getSimpleName()); - if (trace != null) { - trace.setGlobalCacheUse(createUse(CacheUseCategoryTraceType.WEAK_HIT)); - } - PrismObject object = cacheObject.getObject(); - locallyCacheObjectWithoutCloning(localObjectsCache, local.supports, object); - objectToReturn = cloneIfNecessary(object, readOnly); - } - } - } - return objectToReturn; - } catch (ObjectNotFoundException e) { - if (isAllowNotFound(findRootOptions(options))) { - result.computeStatus(); - } else { - result.recordFatalError(e); - } - throw e; - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - if (objectToReturn != null) { - if (trace != null && isAtLeastNormal(level)) { - trace.setObjectRef(ObjectTypeUtil.createObjectRefWithFullObject(objectToReturn.clone(), prismContext)); - } - if (objectToReturn.getName() != null) { - result.addContext("objectName", objectToReturn.getName().getOrig()); - } - } - result.computeStatusIfUnknown(); - } - } - - private CacheUseTraceType createUse(CacheUseCategoryTraceType category) { - return new CacheUseTraceType().category(category); - } - - private CacheUseTraceType createUse(CacheUseCategoryTraceType category, String comment) { - return new CacheUseTraceType().category(category).comment(comment); - } - - private long getTimeToVersionCheck(@NotNull CacheObjectTypeConfiguration typeConfig, @NotNull CacheConfiguration cacheConfig) { - if (typeConfig.getEffectiveTimeToVersionCheck() != null) { - return typeConfig.getEffectiveTimeToVersionCheck() * 1000L; - } else if (typeConfig.getEffectiveTimeToLive() != null) { - return typeConfig.getEffectiveTimeToLive() * 1000L; - } else if (cacheConfig.getTimeToLive() != null) { - return cacheConfig.getTimeToLive() * 1000L; - } else { - return GlobalObjectCache.DEFAULT_TIME_TO_LIVE * 1000L; - } - } - - private void registerNotAvailable(Class cacheClass, Class type, CacheConfiguration.StatisticsLevel statisticsLevel) { - CachePerformanceCollector.INSTANCE.registerNotAvailable(cacheClass, type, statisticsLevel); - } - - private PrismObject cloneIfNecessary(PrismObject object, boolean readOnly) { - if (readOnly) { - return object; - } else { - // if client requested writable object, we need to provide him with a copy - return object.clone(); - } - } - - @NotNull - private PrismObject getObjectInternal(Class type, String oid, Collection> options, - OperationResult parentResult) throws SchemaException, ObjectNotFoundException { - Long startTime = repoOpStart(); - try { - return repositoryService.getObject(type, oid, options, parentResult); - } finally { - repoOpEnd(startTime); - } - } - - private Long repoOpStart() { - RepositoryPerformanceMonitor monitor = DiagnosticContextHolder.get(RepositoryPerformanceMonitor.class); - if (monitor == null) { - return null; - } else { - return System.currentTimeMillis(); - } - } - - private void repoOpEnd(Long startTime) { - RepositoryPerformanceMonitor monitor = DiagnosticContextHolder.get(RepositoryPerformanceMonitor.class); - if (monitor != null) { - monitor.recordRepoOperation(System.currentTimeMillis() - startTime); - } - } - - /* - * Tasks are usually rapidly changing. - * - * Cases are perhaps not changing that rapidly but these are objects that are used for communication of various parties; - * so - to avoid having stale data - we skip caching them altogether. - */ - private boolean alwaysNotCacheable(Class type) { - return type.equals(TaskType.class) || type.equals(CaseType.class); + Collection> options, OperationResult parentResult) + throws ObjectNotFoundException, SchemaException { + return getObjectOpHandler.getObject(type, oid, options, parentResult); } @Override - public String addObject(PrismObject object, RepoAddOptions options, OperationResult parentResult) - throws ObjectAlreadyExistsException, SchemaException { - OperationResult result = parentResult.subresult(ADD_OBJECT) - .addQualifier(object.asObjectable().getClass().getSimpleName()) - .addParam("type", object.getCompileTimeClass()) - .addArbitraryObjectAsParam("options", options) - .build(); - RepositoryAddTraceType trace; - TracingLevelType level = result.getTracingLevel(RepositoryAddTraceType.class); - if (isAtLeastMinimal(level)) { - trace = new RepositoryAddTraceType(prismContext) - .options(String.valueOf(options)); - result.addTrace(trace); - } else { - trace = null; - } - - try { - String oid; - Long startTime = repoOpStart(); - try { - oid = repositoryService.addObject(object, options, result); - } finally { - repoOpEnd(startTime); - } - // DON't cache the object here. The object may not have proper "JAXB" form, e.g. some pieces may be - // DOM element instead of JAXB elements. Not to cache it is safer and the performance loss - // is acceptable. - if (options != null && options.isOverwrite()) { - invalidateCacheEntries(object.getCompileTimeClass(), oid, - new ModifyObjectResult<>(object.getUserData(RepositoryService.KEY_ORIGINAL_OBJECT), object, - Collections.emptyList()), result); - } else { - // just for sure (the object should not be there but ...) - invalidateCacheEntries(object.getCompileTimeClass(), oid, new AddObjectResult<>(object), result); - } - if (trace != null) { - trace.setOid(oid); - if (isAtLeastNormal(level)) { - // We put the object into the trace here, because now it has OID set - trace.setObjectRef(ObjectTypeUtil.createObjectRefWithFullObject(object.clone(), prismContext)); - } - } - return oid; - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - result.computeStatusIfUnknown(); - } + public String getVersion(Class type, String oid, OperationResult parentResult) + throws ObjectNotFoundException, SchemaException { + return getVersionOpHandler.getVersion(type, oid, parentResult); } @NotNull @Override public SearchResultList> searchObjects(Class type, ObjectQuery query, Collection> options, OperationResult parentResult) throws SchemaException { - OperationResult result = parentResult.subresult(SEARCH_OBJECTS) - .addQualifier(type.getSimpleName()) - .addParam("type", type) - .addParam("query", query) - .addArbitraryObjectCollectionAsParam("options", options) - .build(); - - TracingLevelType level = result.getTracingLevel(RepositorySearchObjectsTraceType.class); - RepositorySearchObjectsTraceType trace; - if (isAtLeastMinimal(level)) { - trace = new RepositorySearchObjectsTraceType(prismContext) - .cache(true) - .objectType(prismContext.getSchemaRegistry().determineTypeForClass(type)) - .query(prismContext.getQueryConverter().createQueryType(query)) - .options(String.valueOf(options)); - result.addTrace(trace); - } else { - trace = null; - } - - Integer objectsFound = null; - - try { - CachePerformanceCollector collector = CachePerformanceCollector.INSTANCE; - - LocalQueryCache localQueryCache = getLocalQueryCache(); - - Context global = new Context(globalQueryCache.getConfiguration(), type); - Context local = localQueryCache != null ? - new Context(localQueryCache.getConfiguration(), type) : - new Context(cacheConfigurationManager.getConfiguration(LOCAL_REPO_QUERY_CACHE), type); - - /* - * Checks related to both caches - */ - - PassReason passReason = getPassReason(options, type); - if (passReason != null) { - if (localQueryCache != null) { - localQueryCache.registerPass(); - } - collector.registerPass(LocalQueryCache.class, type, local.statisticsLevel); - collector.registerPass(GlobalQueryCache.class, type, global.statisticsLevel); - log("Cache (local/global): PASS:{} searchObjects ({}, {})", local.tracePass || global.tracePass, passReason, - type.getSimpleName(), options); - CacheUseTraceType use = passReason.toCacheUse(); - SearchResultList> objects = searchObjectsInternal(type, query, options, result); - objectsFound = objects.size(); - return record(trace, use, use, objects, level, result.getTracingProfile()); - } - QueryKey key = new QueryKey(type, query); - - /* - * Let's try local cache - */ - - boolean readOnly = isReadOnly(findRootOptions(options)); - CacheUseTraceType localCacheUse; - - if (localQueryCache == null) { - log("Cache (local): NULL searchObjects ({})", false, type.getSimpleName()); - registerNotAvailable(LocalQueryCache.class, type, local.statisticsLevel); - localCacheUse = createUse(CacheUseCategoryTraceType.NOT_AVAILABLE); - } else { - SearchResultList queryResult = local.supports ? localQueryCache.get(key) : null; - if (queryResult != null) { - localQueryCache.registerHit(); - collector.registerHit(LocalQueryCache.class, type, local.statisticsLevel); - objectsFound = queryResult.size(); - if (readOnly) { - log("Cache: HIT searchObjects {} ({})", false, query, type.getSimpleName()); - //noinspection unchecked - return record(trace, createUse(CacheUseCategoryTraceType.HIT), null, queryResult, level, - result.getTracingProfile()); - } else { - log("Cache: HIT(clone) searchObjects {} ({})", false, query, type.getSimpleName()); - //noinspection unchecked - return record(trace, createUse(CacheUseCategoryTraceType.HIT), null, queryResult.clone(), level, - result.getTracingProfile()); - } - } - if (local.supports) { - localQueryCache.registerMiss(); - collector.registerMiss(LocalQueryCache.class, type, local.statisticsLevel); - log("Cache: MISS searchObjects {} ({})", local.traceMiss, query, type.getSimpleName()); - localCacheUse = createUse(CacheUseCategoryTraceType.MISS); - } else { - localQueryCache.registerPass(); - collector.registerPass(LocalQueryCache.class, type, local.statisticsLevel); - log("Cache: PASS:CONFIGURATION searchObjects {} ({})", local.tracePass, query, type.getSimpleName()); - localCacheUse = createUse(CacheUseCategoryTraceType.PASS, "configuration"); - } - } - - /* - * Then try global cache - */ - if (!globalQueryCache.isAvailable()) { - collector.registerNotAvailable(GlobalQueryCache.class, type, global.statisticsLevel); - log("Cache (global): NOT_AVAILABLE {} searchObjects ({})", false, query, type.getSimpleName()); - SearchResultList> objects = searchObjectsInternal(type, query, options, result); - locallyCacheSearchResult(localQueryCache, local.supports, key, readOnly, objects); - objectsFound = objects.size(); - return record(trace, localCacheUse, createUse(CacheUseCategoryTraceType.NOT_AVAILABLE), objects, level, - result.getTracingProfile()); - } else if (!global.supports) { - // caller is not interested in cached value, or global cache doesn't want to cache value - collector.registerPass(GlobalQueryCache.class, type, global.statisticsLevel); - log("Cache (global): PASS:CONFIGURATION {} searchObjects ({})", global.tracePass, query, type.getSimpleName()); - SearchResultList> objects = searchObjectsInternal(type, query, options, result); - locallyCacheSearchResult(localQueryCache, local.supports, key, readOnly, objects); - objectsFound = objects.size(); - return record(trace, localCacheUse, createUse(CacheUseCategoryTraceType.PASS, "configuration"), objects, level, - result.getTracingProfile()); - } - - assert global.cacheConfig != null && global.typeConfig != null; - - SearchResultList> searchResult = globalQueryCache.get(key); - - if (searchResult == null) { - collector.registerMiss(GlobalQueryCache.class, type, global.statisticsLevel); - log("Cache (global): MISS searchObjects {}", global.traceMiss, key); - searchResult = executeAndCacheSearch(key, options, readOnly, localQueryCache, local.supports, result); - record(trace, localCacheUse, createUse(CacheUseCategoryTraceType.MISS), searchResult, level, - result.getTracingProfile()); - } else { - collector.registerHit(GlobalQueryCache.class, type, global.statisticsLevel); - log("Cache (global): HIT searchObjects {}", false, key); - locallyCacheSearchResult(localQueryCache, local.supports, key, readOnly, searchResult); - searchResult = searchResult.clone(); // never return the value from the cache - record(trace, localCacheUse, createUse(CacheUseCategoryTraceType.HIT), searchResult, level, - result.getTracingProfile()); - } - objectsFound = searchResult.size(); - return readOnly ? searchResult : searchResult.clone(); - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - if (objectsFound != null) { - result.addReturn("objectsFound", objectsFound); - } - result.computeStatusIfUnknown(); - } - } - - private SearchResultList> record(RepositorySearchObjectsTraceType trace, - CacheUseTraceType localCacheUse, CacheUseTraceType globalCacheUse, SearchResultList> objectsFound, - TracingLevelType level, CompiledTracingProfile tracingProfile) { - if (trace != null) { - trace.setLocalCacheUse(localCacheUse); - trace.setGlobalCacheUse(globalCacheUse); - trace.setResultSize(objectsFound.size()); - recordObjectsFound(trace, objectsFound, level, tracingProfile); - } - return objectsFound; - } - - private void recordObjectsFound(RepositorySearchObjectsTraceType trace, - SearchResultList> objectsFound, TracingLevelType level, CompiledTracingProfile tracingProfile) { - if (isAtLeastNormal(level)) { - int maxFullObjects = defaultIfNull(tracingProfile.getDefinition().getRecordObjectsFound(), - TraceUtil.DEFAULT_RECORD_OBJECTS_FOUND); - int maxReferences = defaultIfNull(tracingProfile.getDefinition().getRecordObjectReferencesFound(), - TraceUtil.DEFAULT_RECORD_OBJECT_REFERENCES_FOUND); - int objectsToVisit = Math.min(objectsFound.size(), maxFullObjects + maxReferences); - for (int i = 0; i < objectsToVisit; i++) { - PrismObject object = objectsFound.get(i); - if (i < maxFullObjects) { - trace.getObjectRef().add(ObjectTypeUtil.createObjectRefWithFullObject(object.clone(), prismContext)); - } else { - trace.getObjectRef().add(ObjectTypeUtil.createObjectRef(object, prismContext)); - } - } - } - } - - private void locallyCacheSearchResult(LocalQueryCache cache, boolean supports, QueryKey key, - boolean readOnly, SearchResultList> objects) { - // TODO optimize cloning - if (cache != null && supports && objects.size() <= QUERY_RESULT_SIZE_LIMIT) { - cache.put(key, objects.clone()); - } - LocalObjectCache localObjectCache = getLocalObjectCache(); - if (localObjectCache != null) { - for (PrismObject object : objects) { - Class type = object.asObjectable().getClass(); - if (localObjectCache.supportsObjectType(type)) { - locallyCacheObject(localObjectCache, true, object, readOnly); - } - } - } - } - - @SuppressWarnings("unused") // todo optimize - private void globallyCacheSearchResult(QueryKey key, boolean readOnly, - SearchResultList> objects) { - SearchResultList> cloned = objects.clone(); - for (PrismObject object : cloned) { - globallyCacheObjectWithoutCloning(object); - globallyCacheObjectVersionWithoutCloning(object); - } - if (objects.size() <= QUERY_RESULT_SIZE_LIMIT) { - globalQueryCache.put(key, cloned); - } - } - - @NotNull - private SearchResultList> searchObjectsInternal(Class type, ObjectQuery query, - Collection> options, OperationResult parentResult) - throws SchemaException { - Long startTime = repoOpStart(); - try { - return repositoryService.searchObjects(type, query, options, parentResult); - } finally { - repoOpEnd(startTime); - } - } - - @Override - public SearchResultList searchContainers(Class type, ObjectQuery query, Collection> options, OperationResult parentResult) throws SchemaException { - OperationResult result = parentResult.subresult(SEARCH_CONTAINERS) - .addQualifier(type.getSimpleName()) - .addParam("type", type) - .addParam("query", query) - .addArbitraryObjectAsParam("options", options) - .build(); - Long startTime = repoOpStart(); - try { - return repositoryService.searchContainers(type, query, options, result); - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - repoOpEnd(startTime); - result.computeStatusIfUnknown(); - } + return searchOpHandler.searchObjects(type, query, options, parentResult); } @Override public SearchResultMetadata searchObjectsIterative(Class type, ObjectQuery query, ResultHandler handler, Collection> options, boolean strictlySequential, OperationResult parentResult) throws SchemaException { - - OperationResult result = parentResult.subresult(SEARCH_OBJECTS_ITERATIVE) - .addQualifier(type.getSimpleName()) - .addParam("type", type) - .addParam("query", query) - .addArbitraryObjectCollectionAsParam("options", options) - .build(); - TracingLevelType level = result.getTracingLevel(RepositorySearchObjectsTraceType.class); - RepositorySearchObjectsTraceType trace; - if (isAtLeastMinimal(level)) { - trace = new RepositorySearchObjectsTraceType(prismContext) - .cache(true) - .objectType(prismContext.getSchemaRegistry().determineTypeForClass(type)) - .query(prismContext.getQueryConverter().createQueryType(query)) - .options(String.valueOf(options)); - result.addTrace(trace); - } else { - trace = null; - } - - SearchResultList> objectsFound = new SearchResultList<>(); - AtomicBoolean interrupted = new AtomicBoolean(false); - - ResultHandler watchingHandler = (object, result1) -> { - objectsFound.add(object); - boolean cont = handler.handle(object, result1); - if (!cont) { - interrupted.set(true); - } - return cont; - }; - - try { - CachePerformanceCollector collector = CachePerformanceCollector.INSTANCE; - - LocalQueryCache localQueryCache = getLocalQueryCache(); - - Context global = new Context(globalQueryCache.getConfiguration(), type); - Context local = localQueryCache != null ? - new Context(localQueryCache.getConfiguration(), type) : - new Context(cacheConfigurationManager.getConfiguration(LOCAL_REPO_QUERY_CACHE), type); - - /* - * Checks related to both caches - */ - - PassReason passReason = getPassReason(options, type); - if (passReason != null) { - if (localQueryCache != null) { - localQueryCache.registerPass(); - } - collector.registerPass(LocalQueryCache.class, type, local.statisticsLevel); - collector.registerPass(GlobalQueryCache.class, type, global.statisticsLevel); - log("Cache (local/global): PASS:{} searchObjectsIterative ({}, {})", local.tracePass || global.tracePass, - passReason, type.getSimpleName(), options); - if (trace != null) { - CacheUseTraceType use = passReason.toCacheUse(); - trace.setLocalCacheUse(use); - trace.setGlobalCacheUse(use); - } - return searchObjectsIterativeInternal(type, query, watchingHandler, options, strictlySequential, result); - } - QueryKey key = new QueryKey(type, query); - - /* - * Let's try local cache - */ - - boolean readOnly = isReadOnly(findRootOptions(options)); - CacheUseTraceType localCacheUse; - - if (localQueryCache == null) { - log("Cache (local): NULL searchObjectsIterative ({})", false, type.getSimpleName()); - registerNotAvailable(LocalQueryCache.class, type, local.statisticsLevel); - localCacheUse = createUse(CacheUseCategoryTraceType.NOT_AVAILABLE); - } else { - //noinspection unchecked - SearchResultList> queryResult = local.supports ? localQueryCache.get(key) : null; - if (queryResult != null) { - localQueryCache.registerHit(); - collector.registerHit(LocalQueryCache.class, type, local.statisticsLevel); - if (trace != null) { - trace.setLocalCacheUse(createUse(CacheUseCategoryTraceType.HIT)); - } - if (readOnly) { - log("Cache: HIT searchObjectsIterative {} ({})", false, query, type.getSimpleName()); - return iterateOverQueryResult(queryResult, watchingHandler, result, false); - } else { - log("Cache: HIT(clone) searchObjectsIterative {} ({})", false, query, type.getSimpleName()); - return iterateOverQueryResult(queryResult, watchingHandler, result, true); - } - } - if (local.supports) { - localQueryCache.registerMiss(); - collector.registerMiss(LocalQueryCache.class, type, local.statisticsLevel); - log("Cache: MISS searchObjectsIterative {} ({})", local.traceMiss, query, type.getSimpleName()); - localCacheUse = createUse(CacheUseCategoryTraceType.MISS); - } else { - localQueryCache.registerPass(); - collector.registerPass(LocalQueryCache.class, type, local.statisticsLevel); - log("Cache: PASS:CONFIGURATION searchObjectsIterative {} ({})", local.tracePass, query, type.getSimpleName()); - localCacheUse = createUse(CacheUseCategoryTraceType.PASS, "configuration"); - } - } - - /* - * Then try global cache - */ - if (!globalQueryCache.isAvailable()) { - collector.registerNotAvailable(GlobalQueryCache.class, type, global.statisticsLevel); - log("Cache (global): NOT_AVAILABLE {} searchObjectsIterative ({})", false, query, type.getSimpleName()); - CollectingHandler collectingHandler = new CollectingHandler<>(watchingHandler); - SearchResultMetadata metadata = searchObjectsIterativeInternal(type, query, collectingHandler, options, - strictlySequential, result); - if (collectingHandler.isResultAvailable()) { - locallyCacheSearchResult(localQueryCache, local.supports, key, readOnly, collectingHandler.getObjects()); - } - if (trace != null) { - trace.setLocalCacheUse(localCacheUse); - trace.setGlobalCacheUse(createUse(CacheUseCategoryTraceType.NOT_AVAILABLE)); - } - return metadata; - } else if (!global.supports) { - // caller is not interested in cached value, or global cache doesn't want to cache value - collector.registerPass(GlobalQueryCache.class, type, global.statisticsLevel); - log("Cache (global): PASS:CONFIGURATION {} searchObjectsIterative ({})", global.tracePass, query, - type.getSimpleName()); - CollectingHandler collectingHandler = new CollectingHandler<>(watchingHandler); - SearchResultMetadata metadata = searchObjectsIterativeInternal(type, query, collectingHandler, options, - strictlySequential, result); - if (collectingHandler.isResultAvailable()) { - locallyCacheSearchResult(localQueryCache, local.supports, key, readOnly, collectingHandler.getObjects()); - } - if (trace != null) { - trace.setLocalCacheUse(localCacheUse); - trace.setGlobalCacheUse(createUse(CacheUseCategoryTraceType.PASS, "configuration")); - } - return metadata; - } - - assert global.cacheConfig != null && global.typeConfig != null; - - SearchResultList> searchResult = globalQueryCache.get(key); - SearchResultMetadata metadata; - - if (searchResult == null) { - collector.registerMiss(GlobalQueryCache.class, type, global.statisticsLevel); - log("Cache (global): MISS searchObjectsIterative {}", global.traceMiss, key); - if (trace != null) { - trace.setLocalCacheUse(localCacheUse); - trace.setGlobalCacheUse(createUse(CacheUseCategoryTraceType.MISS)); - } - metadata = executeAndCacheSearchIterative(type, key, watchingHandler, options, strictlySequential, readOnly, - localQueryCache, - local.supports, result); - } else { - collector.registerHit(GlobalQueryCache.class, type, global.statisticsLevel); - log("Cache (global): HIT searchObjectsIterative {}", false, key); - if (trace != null) { - trace.setLocalCacheUse(localCacheUse); - trace.setGlobalCacheUse(createUse(CacheUseCategoryTraceType.HIT)); - } - locallyCacheSearchResult(localQueryCache, local.supports, key, readOnly, searchResult); - iterateOverQueryResult(searchResult, watchingHandler, result, !readOnly); - metadata = searchResult.getMetadata(); - } - return metadata; - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - if (isAtLeastNormal(level)) { - recordObjectsFound(trace, objectsFound, level, result.getTracingProfile()); - } - result.addReturn("objectsFound", objectsFound.size()); - result.addReturn("interrupted", interrupted.get()); - result.computeStatusIfUnknown(); - } + return searchOpHandler.searchObjectsIterative(type, query, handler, options, strictlySequential, parentResult); } - private SearchResultMetadata searchObjectsIterativeInternal(Class type, ObjectQuery query, - ResultHandler handler, Collection> options, - boolean strictlySequential, OperationResult parentResult) throws SchemaException { - Long startTime = repoOpStart(); - try { - return repositoryService.searchObjectsIterative(type, query, handler, options, strictlySequential, parentResult); - } finally { - repoOpEnd(startTime); - } - } - - // type is there to allow T matching in the method body - private SearchResultMetadata executeAndCacheSearchIterative(Class type, QueryKey key, - ResultHandler handler, Collection> options, - boolean strictlySequential, boolean readOnly, LocalQueryCache localCache, - boolean localCacheSupports, OperationResult result) - throws SchemaException { - try { - CollectingHandler collectingHandler = new CollectingHandler<>(handler); - SearchResultMetadata metadata = searchObjectsIterativeInternal(type, key.getQuery(), collectingHandler, options, - strictlySequential, result); - if (collectingHandler.isResultAvailable()) { // todo optimize cloning here - locallyCacheSearchResult(localCache, localCacheSupports, key, readOnly, collectingHandler.getObjects()); - globallyCacheSearchResult(key, readOnly, collectingHandler.getObjects()); - } - return metadata; - } catch (SchemaException ex) { - globalQueryCache.remove(key); - throw ex; - } - } - - private SearchResultMetadata iterateOverQueryResult(SearchResultList> queryResult, - ResultHandler handler, OperationResult parentResult, boolean clone) { - OperationResult result = parentResult.subresult(RepositoryCache.class.getName() + ".iterateOverQueryResult") - .setMinor() - .addParam("objects", queryResult.size()) - .addArbitraryObjectAsParam("handler", handler) - .build(); - try { - for (PrismObject object : queryResult) { - PrismObject objectToHandle = clone ? object.clone() : object; - if (!handler.handle(objectToHandle, parentResult)) { - break; - } - } - // todo Should be metadata influenced by the number of handler executions? - // ...and is it correct to return cached metadata at all? - return queryResult.getMetadata() != null ? queryResult.getMetadata().clone() : null; - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - result.computeStatusIfUnknown(); - } - } - - private void cacheLoadedObject(PrismObject object, boolean readOnly, - LocalObjectCache localObjectCache) { - Class objectType = object.asObjectable().getClass(); - boolean putIntoLocal = localObjectCache != null && localObjectCache.supportsObjectType(objectType); - boolean putIntoGlobal = globalObjectCache.isAvailable() && globalObjectCache.supportsObjectType(objectType); - if (putIntoLocal || putIntoGlobal) { - PrismObject objectToCache = prepareObjectToCache(object, readOnly); - if (putIntoLocal) { - locallyCacheObjectWithoutCloning(localObjectCache, true, objectToCache); - } - if (putIntoGlobal) { - globallyCacheObjectWithoutCloning(objectToCache); - } - } - LocalVersionCache localVersionCache = getLocalVersionCache(); - if (localVersionCache != null && localVersionCache.supportsObjectType(objectType)) { - localVersionCache.put(object.getOid(), object.getVersion()); - } - if (globalVersionCache.isAvailable() && globalVersionCache.supportsObjectType(objectType)) { - globalVersionCache.put(object); - } - + @Override + public SearchResultList searchContainers(Class type, ObjectQuery query, + Collection> options, OperationResult parentResult) throws SchemaException { + return searchOpHandler.searchContainers(type, query, options, parentResult); } @Override - public int countContainers(Class type, ObjectQuery query, - Collection> options, OperationResult parentResult) { - OperationResult result = parentResult.subresult(COUNT_CONTAINERS) - .addQualifier(type.getSimpleName()) - .addParam("type", type) - .addParam("query", query) - .addArbitraryObjectCollectionAsParam("options", options) - .build(); - log("Cache: PASS countContainers ({})", false, type.getSimpleName()); - Long startTime = repoOpStart(); - try { - return repositoryService.countContainers(type, query, options, result); - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - repoOpEnd(startTime); - result.computeStatusIfUnknown(); - } + public PrismObject searchShadowOwner( + String shadowOid, Collection> options, OperationResult parentResult) { + return searchOpHandler.searchShadowOwner(shadowOid, options, parentResult); } @Override public int countObjects(Class type, ObjectQuery query, Collection> options, OperationResult parentResult) - throws SchemaException { - // TODO use cached query result if applicable - OperationResult result = parentResult.subresult(COUNT_OBJECTS) - .addQualifier(type.getSimpleName()) - .addParam("type", type) - .addParam("query", query) - .addArbitraryObjectCollectionAsParam("options", options) - .build(); - log("Cache: PASS countObjects ({})", false, type.getSimpleName()); - Long startTime = repoOpStart(); - try { - return repositoryService.countObjects(type, query, options, result); - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - repoOpEnd(startTime); - result.computeStatusIfUnknown(); - } - } - - @NotNull - public ModifyObjectResult modifyObject(Class type, String oid, Collection modifications, - OperationResult parentResult) throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException { - return modifyObject(type, oid, modifications, null, parentResult); - } - - @NotNull - @Override - public ModifyObjectResult modifyObject(Class type, String oid, Collection modifications, - RepoModifyOptions options, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException { - try { - return modifyObject(type, oid, modifications, null, options, parentResult); - } catch (PreconditionViolationException e) { - throw new AssertionError(e); - } - } - - private void delay(Integer delayRange) { - if (delayRange == null) { - return; - } - int delay = RND.nextInt(delayRange); - try { - Thread.sleep(delay); - } catch (InterruptedException e) { - // Nothing to do - } - } - - @NotNull - @Override - public ModifyObjectResult modifyObject(@NotNull Class type, @NotNull String oid, - @NotNull Collection modifications, - ModificationPrecondition precondition, RepoModifyOptions options, OperationResult parentResult) - throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException, PreconditionViolationException { - - OperationResult result = parentResult.subresult(MODIFY_OBJECT) - .addQualifier(type.getSimpleName()) - .addParam("type", type) - .addParam("oid", oid) - .addArbitraryObjectAsParam("options", options) - .build(); - - RepositoryModifyTraceType trace; - if (result.isTraced()) { - trace = new RepositoryModifyTraceType(prismContext) - .cache(true) - .objectType(prismContext.getSchemaRegistry().determineTypeForClass(type)) - .oid(oid) - .options(String.valueOf(options)); - for (ItemDelta modification : modifications) { - // todo only if configured? - trace.getModification().addAll(DeltaConvertor.toItemDeltaTypes(modification)); - } - result.addTrace(trace); - } else { - trace = null; - } - - try { - delay(modifyRandomDelayRange); - Long startTime = repoOpStart(); - ModifyObjectResult modifyInfo = null; - try { - modifyInfo = repositoryService.modifyObject(type, oid, modifications, precondition, options, result); - return modifyInfo; - } finally { - repoOpEnd(startTime); - // this changes the object. We are too lazy to apply changes ourselves, so just invalidate - // the object in cache - invalidateCacheEntries(type, oid, modifyInfo, result); - } - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - result.computeStatusIfUnknown(); - } - } - - private void invalidateCacheEntries(Class type, String oid, Object additionalInfo, OperationResult parentResult) { - OperationResult result = parentResult.subresult(CLASS_NAME_WITH_DOT + "invalidateCacheEntries") - .setMinor() - .addParam("type", type) - .addParam("oid", oid) - .addParam("additionalInfo", additionalInfo != null ? additionalInfo.getClass().getSimpleName() : "none") - .build(); - try { - LocalObjectCache localObjectCache = getLocalObjectCache(); - if (localObjectCache != null) { - localObjectCache.remove(oid); - } - LocalVersionCache localVersionCache = getLocalVersionCache(); - if (localVersionCache != null) { - localVersionCache.remove(oid); - } - LocalQueryCache localQueryCache = getLocalQueryCache(); - if (localQueryCache != null) { - clearQueryResultsLocally(localQueryCache, type, oid, additionalInfo, matchingRuleRegistry); - } - boolean clusterwide = TYPES_ALWAYS_INVALIDATED_CLUSTERWIDE.contains(type) || - globalObjectCache.isClusterwideInvalidation(type) || - globalVersionCache.isClusterwideInvalidation(type) || - globalQueryCache.isClusterwideInvalidation(type); - cacheDispatcher.dispatchInvalidation(type, oid, clusterwide, - new CacheInvalidationContext(false, new RepositoryCacheInvalidationDetails(additionalInfo))); - } catch (Throwable t) { - result.recordFatalError(t); - throw t; // Really? We want the operation to proceed anyway. But OTOH we want to be sure devel team gets notified about this. - } finally { - result.computeStatusIfUnknown(); - } - } - - // This is what is called from cache dispatcher (on local node with the full context; on remote nodes with reduced context) - @Override - public void invalidate(Class type, String oid, CacheInvalidationContext context) { - if (type == null) { - globalObjectCache.clear(); - globalVersionCache.clear(); - globalQueryCache.clear(); - } else { - globalObjectCache.remove(type, oid); - globalVersionCache.remove(type, oid); - if (ObjectType.class.isAssignableFrom(type)) { - //noinspection unchecked - clearQueryResultsGlobally((Class) type, oid, context); - } - } + throws SchemaException { + return searchOpHandler.countObjects(type, query, options, parentResult); } - private void clearQueryResultsLocally(LocalQueryCache cache, Class type, String oid, - Object additionalInfo, MatchingRuleRegistry matchingRuleRegistry) { - // TODO implement more efficiently - - ChangeDescription change = ChangeDescription.getFrom(type, oid, additionalInfo, true); - - long start = System.currentTimeMillis(); - int all = 0; - int removed = 0; - Iterator> iterator = cache.getEntryIterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - QueryKey queryKey = entry.getKey(); - all++; - if (change.mayAffect(queryKey, entry.getValue(), matchingRuleRegistry)) { - LOGGER.trace("Removing (from local cache) query for type={}, change={}: {}", type, change, queryKey.getQuery()); - iterator.remove(); - removed++; - } - } - LOGGER.trace("Removed (from local cache) {} (of {}) query result entries of type {} in {} ms", removed, all, type, System.currentTimeMillis() - start); + @Override + public int countContainers(Class type, ObjectQuery query, + Collection> options, OperationResult parentResult) { + return searchOpHandler.countContainers(type, query, options, parentResult); } + //endregion - private void clearQueryResultsGlobally(Class type, String oid, CacheInvalidationContext context) { - // TODO implement more efficiently + //region --- ADD, MODIFY, DELETE and other modifications ------------------------------------------------------- - boolean safeInvalidation = !context.isFromRemoteNode() || globalQueryCache.isSafeRemoteInvalidation(type); - ChangeDescription change = ChangeDescription.getFrom(type, oid, context, safeInvalidation); - - long start = System.currentTimeMillis(); - AtomicInteger all = new AtomicInteger(0); - AtomicInteger removed = new AtomicInteger(0); + @Override + public String addObject(PrismObject object, RepoAddOptions options, OperationResult parentResult) + throws ObjectAlreadyExistsException, SchemaException { + return modificationOpHandler.addObject(object, options, parentResult); + } - globalQueryCache.invokeAll(entry -> { - QueryKey queryKey = entry.getKey(); - all.incrementAndGet(); - if (change.mayAffect(queryKey, entry.getValue().getResult(), matchingRuleRegistry)) { - LOGGER.trace("Removing (from global cache) query for type={}, change={}: {}", type, change, queryKey.getQuery()); - entry.remove(); - removed.incrementAndGet(); - } - return null; - }); - LOGGER.trace("Removed (from global cache) {} (of {}) query result entries of type {} in {} ms", removed, all, type, System.currentTimeMillis() - start); + @NotNull + public ModifyObjectResult modifyObject(Class type, String oid, Collection modifications, + OperationResult parentResult) throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException { + return modifyObject(type, oid, modifications, null, parentResult); } @NotNull @Override - public DeleteObjectResult deleteObject(Class type, String oid, OperationResult parentResult) - throws ObjectNotFoundException { - OperationResult result = parentResult.subresult(DELETE_OBJECT) - .addQualifier(type.getSimpleName()) - .addParam("type", type) - .addParam("oid", oid) - .build(); - - RepositoryDeleteTraceType trace; - if (result.isTraced()) { - trace = new RepositoryDeleteTraceType(prismContext) - .cache(true) - .objectType(prismContext.getSchemaRegistry().determineTypeForClass(type)) - .oid(oid); - result.addTrace(trace); - } else { - trace = null; - } - Long startTime = repoOpStart(); - DeleteObjectResult deleteInfo = null; + public ModifyObjectResult modifyObject(Class type, String oid, Collection modifications, + RepoModifyOptions options, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException { try { - try { - deleteInfo = repositoryService.deleteObject(type, oid, result); - } finally { - repoOpEnd(startTime); - invalidateCacheEntries(type, oid, deleteInfo, result); - } - return deleteInfo; - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - result.computeStatusIfUnknown(); + return modifyObject(type, oid, modifications, null, options, parentResult); + } catch (PreconditionViolationException e) { + throw new AssertionError(e); } } + @NotNull @Override - public PrismObject searchShadowOwner( - String shadowOid, Collection> options, OperationResult parentResult) { - OperationResult result = parentResult.subresult(SEARCH_SHADOW_OWNER) - .addParam("shadowOid", shadowOid) - .addArbitraryObjectCollectionAsParam("options", options) - .build(); - try { - // TODO cache the search operation? - PrismObject ownerObject; - Long startTime = repoOpStart(); - try { - ownerObject = repositoryService.searchShadowOwner(shadowOid, options, result); - } finally { - repoOpEnd(startTime); - } - if (ownerObject != null && getPassReason(options, FocusType.class) == null) { - boolean readOnly = isReadOnly(findRootOptions(options)); - LocalObjectCache localObjectCache = getLocalObjectCache(); - cacheLoadedObject(ownerObject, readOnly, localObjectCache); - } - return ownerObject; - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - result.computeStatusIfUnknown(); - } + public ModifyObjectResult modifyObject(@NotNull Class type, @NotNull String oid, + @NotNull Collection modifications, ModificationPrecondition precondition, + RepoModifyOptions options, OperationResult parentResult) + throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException, PreconditionViolationException { + return modificationOpHandler.modifyObject(type, oid, modifications, precondition, options, parentResult); } - private PassReason getPassReason(Collection> options, Class objectType) { - if (alwaysNotCacheable(objectType)) { - return new PassReason(NOT_CACHEABLE_TYPE); - } - if (options == null || options.isEmpty()) { - return null; - } - if (options.size() > 1) { - //LOGGER.info("Cache: PASS REASON: size>1: {}", options); - return new PassReason(MULTIPLE_OPTIONS); - } - SelectorOptions selectorOptions = options.iterator().next(); - if (!selectorOptions.isRoot()) { - //LOGGER.info("Cache: PASS REASON: !root: {}", options); - return new PassReason(NON_ROOT_OPTIONS); - } - if (selectorOptions.getOptions() == null) { - return null; - } - Long staleness = selectorOptions.getOptions().getStaleness(); - if (staleness != null && staleness == 0) { - return new PassReason(ZERO_STALENESS_REQUESTED); - } - GetOperationOptions cloned = selectorOptions.getOptions().clone(); + @NotNull + @Override + public DeleteObjectResult deleteObject(Class type, String oid, OperationResult parentResult) + throws ObjectNotFoundException { + return modificationOpHandler.deleteObject(type, oid, parentResult); + } - // Eliminate harmless options - cloned.setAllowNotFound(null); - cloned.setExecutionPhase(null); - cloned.setReadOnly(null); - cloned.setNoFetch(null); - cloned.setPointInTimeType(null); // This is not used by repository anyway. - // We know the staleness is not zero, so caching is (in principle) allowed. - // More detailed treatment of staleness is not yet available. - cloned.setStaleness(null); - if (cloned.equals(GetOperationOptions.EMPTY)) { - return null; - } - if (cloned.equals(createRetrieve(RetrieveOption.INCLUDE))) { - if (SelectorOptions.isRetrievedFullyByDefault(objectType)) { - return null; - } else { - //LOGGER.info("Cache: PASS REASON: INCLUDE for {}: {}", objectType, options); - return new PassReason(INCLUDE_OPTION_PRESENT); - } - } - //LOGGER.info("Cache: PASS REASON: other: {}", options); - return new PassReason(UNSUPPORTED_OPTION, cloned.toString()); + @Override + public long advanceSequence(String oid, OperationResult parentResult) throws ObjectNotFoundException, + SchemaException { + return modificationOpHandler.advanceSequence(oid, parentResult); } @Override - public String getVersion(Class type, String oid, OperationResult parentResult) + public void returnUnusedValuesToSequence(String oid, Collection unusedValues, OperationResult parentResult) throws ObjectNotFoundException, SchemaException { - OperationResult result = parentResult.subresult(GET_VERSION) - .addQualifier(type.getSimpleName()) - .addParam("type", type) - .addParam("oid", oid) - .build(); - RepositoryGetVersionTraceType trace; - if (result.isTraced()) { - trace = new RepositoryGetVersionTraceType(prismContext) - .cache(true) - .objectType(prismContext.getSchemaRegistry().determineTypeForClass(type)) - .oid(oid); - result.addTrace(trace); - } else { - trace = null; - } - - try { - CachePerformanceCollector collector = CachePerformanceCollector.INSTANCE; - LocalVersionCache localCache = getLocalVersionCache(); - Context globalVersionContext = new Context(globalVersionCache.getConfiguration(), type); - Context local = localCache != null ? - new Context(localCache.getConfiguration(), type) : - new Context(cacheConfigurationManager.getConfiguration(LOCAL_REPO_VERSION_CACHE), type); - - if (alwaysNotCacheable(type)) { - if (localCache != null) { - localCache.registerPass(); - } - collector.registerPass(LocalVersionCache.class, type, local.statisticsLevel); - collector.registerPass(GlobalVersionCache.class, type, globalVersionContext.statisticsLevel); - log("Cache: PASS (local) getVersion {} ({})", local.tracePass, oid, type.getSimpleName()); - if (trace != null) { - trace.setLocalCacheUse(createUse(CacheUseCategoryTraceType.PASS, "object type")); - trace.setGlobalCacheUse(createUse(CacheUseCategoryTraceType.PASS, "object type")); - } - Long startTime = repoOpStart(); - try { - return repositoryService.getVersion(type, oid, result); - } finally { - repoOpEnd(startTime); - } - } - CacheUseTraceType localCacheUse; - if (localCache == null) { - log("Cache: NULL {} ({})", false, oid, type.getSimpleName()); - registerNotAvailable(LocalVersionCache.class, type, local.statisticsLevel); - localCacheUse = createUse(CacheUseCategoryTraceType.NOT_AVAILABLE); - } else { - String version = local.supports ? localCache.get(oid) : null; - if (version != null) { - localCache.registerHit(); - collector.registerHit(LocalVersionCache.class, type, local.statisticsLevel); - log("Cache: HIT (local) getVersion {} ({})", false, oid, type.getSimpleName()); - record(trace, createUse(CacheUseCategoryTraceType.HIT), null, version); - return version; - } - if (local.supports) { - localCache.registerMiss(); - collector.registerMiss(LocalVersionCache.class, type, local.statisticsLevel); - log("Cache: MISS (local) getVersion {} ({})", local.traceMiss, oid, type.getSimpleName()); - localCacheUse = createUse(CacheUseCategoryTraceType.MISS); - } else { - localCache.registerPass(); - collector.registerPass(LocalVersionCache.class, type, local.statisticsLevel); - log("Cache: PASS (local) (cfg) getVersion {} ({})", local.tracePass, oid, type.getSimpleName()); - localCacheUse = createUse(CacheUseCategoryTraceType.PASS, "configuration"); - } - } - - CacheUseTraceType globalCacheUse; - String version = null; - - // try global version cache - if (!globalVersionCache.isAvailable()) { - collector.registerNotAvailable(GlobalVersionCache.class, type, globalVersionContext.statisticsLevel); - log("Cache (global:version): NOT_AVAILABLE {} getVersion ({})", false, oid, type.getSimpleName()); - globalCacheUse = createUse(CacheUseCategoryTraceType.NOT_AVAILABLE); - } else if (!globalVersionContext.supports) { - // caller is not interested in cached value, or global cache doesn't want to cache value - collector.registerPass(GlobalVersionCache.class, type, globalVersionContext.statisticsLevel); - log("Cache (global:version): PASS:CONFIGURATION {} getVersion ({})", globalVersionContext.tracePass, oid, type.getSimpleName()); - globalCacheUse = createUse(CacheUseCategoryTraceType.PASS, "configuration"); - } else { - version = globalVersionCache.get(oid); - if (version != null) { - collector.registerHit(GlobalVersionCache.class, type, globalVersionContext.statisticsLevel); - globalCacheUse = createUse(CacheUseCategoryTraceType.HIT); - } else { - collector.registerMiss(GlobalVersionCache.class, type, globalVersionContext.statisticsLevel); - globalCacheUse = createUse(CacheUseCategoryTraceType.MISS); - } - } - - // try global object cache (cache use information will be overwritten) - if (version == null) { - Context globalObjectContext = new Context(globalObjectCache.getConfiguration(), type); - if (!globalObjectCache.isAvailable()) { - collector.registerNotAvailable(GlobalObjectCache.class, type, globalObjectContext.statisticsLevel); - log("Cache (global:object): NOT_AVAILABLE {} getVersion ({})", false, oid, type.getSimpleName()); - globalCacheUse = createUse(CacheUseCategoryTraceType.NOT_AVAILABLE); - } else if (!globalObjectContext.supports) { - // caller is not interested in cached value, or global cache doesn't want to cache value - collector.registerPass(GlobalObjectCache.class, type, globalObjectContext.statisticsLevel); - log("Cache (global:object): PASS:CONFIGURATION {} getVersion ({})", globalObjectContext.tracePass, - oid, type.getSimpleName()); - globalCacheUse = createUse(CacheUseCategoryTraceType.PASS, "configuration"); - } else { - GlobalCacheObjectValue cacheObject = globalObjectCache.get(oid); - if (cacheObject != null) { - collector.registerHit(GlobalObjectCache.class, type, globalObjectContext.statisticsLevel); - globalCacheUse = createUse(CacheUseCategoryTraceType.HIT); - version = cacheObject.getObjectVersion(); - } else { - // Is this relevant? We missed ... but we wanted only the version, not the whole object. - // (And we don't load the object after this miss.) - collector.registerMiss(GlobalObjectCache.class, type, globalObjectContext.statisticsLevel); - globalCacheUse = createUse(CacheUseCategoryTraceType.MISS); - } - } - } - - if (version == null) { - Long startTime = repoOpStart(); - try { - version = repositoryService.getVersion(type, oid, result); - } finally { - repoOpEnd(startTime); - } - } - cacheObjectVersionLocal(localCache, local.supports, oid, version); - cacheObjectVersionGlobal(globalVersionContext.supports, oid, type, version); - record(trace, localCacheUse, globalCacheUse, version); - return version; - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - result.computeStatusIfUnknown(); - } + modificationOpHandler.returnUnusedValuesToSequence(oid, unusedValues, parentResult); } - private void record(RepositoryGetVersionTraceType trace, CacheUseTraceType localCacheUse, CacheUseTraceType globalCacheUse, - String version) { - if (trace != null) { - trace.setLocalCacheUse(localCacheUse); - trace.setGlobalCacheUse(globalCacheUse); - trace.setVersion(version); - } + @Override + public void addDiagnosticInformation(Class type, String oid, DiagnosticInformationType information, + OperationResult parentResult) throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException { + modificationOpHandler.addDiagnosticInformation(type, oid, information, parentResult); } + //endregion + + //region --- Other methods (delegated directly to repository service) ------------------------------------------ + @Override public RepositoryDiag getRepositoryDiag() { Long startTime = repoOpStart(); @@ -1522,30 +235,6 @@ public void testOrgClosureConsistency(boolean repairIfNecessary, OperationResult } } - private void locallyCacheObject(LocalObjectCache cache, boolean supports, PrismObject object, boolean readOnly) { - if (cache != null && supports) { - cache.put(object.getOid(), prepareObjectToCache(object, readOnly)); - } - } - - private void locallyCacheObjectWithoutCloning(LocalObjectCache cache, boolean supports, PrismObject object) { - if (cache != null && supports) { - cache.put(object.getOid(), object); - } - } - - private void cacheObjectVersionLocal(LocalVersionCache cache, boolean supports, String oid, String version) { - if (cache != null && supports) { - cache.put(oid, version); - } - } - - private void cacheObjectVersionGlobal(boolean supports, String oid, Class type, String version) { - if (supports) { - globalVersionCache.put(oid, type, version); - } - } - @Override public boolean isAnySubordinate(String upperOrgOid, Collection lowerObjectOids) throws SchemaException { @@ -1581,7 +270,9 @@ public boolean isAncestor(PrismObject object, String o @Override public boolean selectorMatches(ObjectSelectorType objectSelector, - PrismObject object, ObjectFilterExpressionEvaluator filterEvaluator, Trace logger, String logMessagePrefix) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + PrismObject object, ObjectFilterExpressionEvaluator filterEvaluator, Trace logger, String logMessagePrefix) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, + ConfigurationException, SecurityViolationException { Long startTime = repoOpStart(); try { return repositoryService.selectorMatches(objectSelector, object, filterEvaluator, logger, logMessagePrefix); @@ -1590,55 +281,6 @@ public boolean selectorMatches(ObjectSelectorType objectS } } - private void log(String message, boolean info, Object... params) { - CacheUtil.log(LOGGER, PERFORMANCE_ADVISOR, message, info, params); - } - - @Override - public long advanceSequence(String oid, OperationResult parentResult) throws ObjectNotFoundException, - SchemaException { - OperationResult result = parentResult.subresult(ADVANCE_SEQUENCE) - .addParam("oid", oid) - .build(); - try { - Long startTime = repoOpStart(); - try { - return repositoryService.advanceSequence(oid, result); - } finally { - repoOpEnd(startTime); - invalidateCacheEntries(SequenceType.class, oid, null, result); - } - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - result.computeStatusIfUnknown(); - } - } - - @Override - public void returnUnusedValuesToSequence(String oid, Collection unusedValues, OperationResult parentResult) - throws ObjectNotFoundException, SchemaException { - OperationResult result = parentResult.subresult(RETURN_UNUSED_VALUES_TO_SEQUENCE) - .addParam("oid", oid) - .addArbitraryObjectCollectionAsParam("unusedValues", unusedValues) - .build(); - try { - Long startTime = repoOpStart(); - try { - repositoryService.returnUnusedValuesToSequence(oid, unusedValues, result); - } finally { - repoOpEnd(startTime); - invalidateCacheEntries(SequenceType.class, oid, null, result); - } - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - result.computeStatusIfUnknown(); - } - } - @Override public RepositoryQueryDiagResponse executeQueryDiagnostics(RepositoryQueryDiagRequest request, OperationResult parentResult) { OperationResult result = parentResult.subresult(EXECUTE_QUERY_DIAGNOSTICS) @@ -1678,20 +320,6 @@ public FullTextSearchConfigurationType getFullTextSearchConfiguration() { } } - @Override - public void postInit(OperationResult result) throws SchemaException { - repositoryService.postInit(result); // TODO resolve somehow multiple calls to repositoryService postInit method - globalObjectCache.initialize(); - globalVersionCache.initialize(); - globalQueryCache.initialize(); - cacheRegistry.registerCacheableService(this); - } - - @PreDestroy - public void unregister() { - cacheRegistry.unregisterCacheableService(this); - } - @Override public ConflictWatcher createAndRegisterConflictWatcher(@NotNull String oid) { return repositoryService.createAndRegisterConflictWatcher(oid); @@ -1707,223 +335,39 @@ public boolean hasConflict(ConflictWatcher watcher, OperationResult result) { return repositoryService.hasConflict(watcher, result); } - private boolean hasVersionChanged(Class objectType, String oid, GlobalCacheObjectValue object, OperationResult result) - throws ObjectNotFoundException, SchemaException { - - try { - String version = repositoryService.getVersion(objectType, oid, result); - return !Objects.equals(version, object.getObjectVersion()); - } catch (ObjectNotFoundException | SchemaException ex) { - globalObjectCache.remove(oid); - globalVersionCache.remove(oid); - throw ex; - } - } - - private boolean shouldCheckVersion(GlobalCacheObjectValue object) { - return object.getTimeToCheckVersion() < System.currentTimeMillis(); - } - - private PrismObject loadAndCacheObject(Class objectClass, String oid, - Collection> options, boolean readOnly, LocalObjectCache localCache, - boolean localCacheSupports, OperationResult result) - throws ObjectNotFoundException, SchemaException { - try { - PrismObject object = getObjectInternal(objectClass, oid, options, result); - PrismObject objectToCache = prepareObjectToCache(object, readOnly); - globallyCacheObjectWithoutCloning(objectToCache); - globallyCacheObjectVersionWithoutCloning(objectToCache); - locallyCacheObjectWithoutCloning(localCache, localCacheSupports, objectToCache); - return object; - } catch (ObjectNotFoundException | SchemaException ex) { - globalObjectCache.remove(oid); - globalVersionCache.remove(oid); - throw ex; - } - } - - private void globallyCacheObjectWithoutCloning(PrismObject objectToCache) { - CacheConfiguration cacheConfiguration = globalObjectCache.getConfiguration(); - Class type = objectToCache.asObjectable().getClass(); - CacheObjectTypeConfiguration typeConfiguration = globalObjectCache.getConfiguration(type); - if (cacheConfiguration != null && cacheConfiguration.supportsObjectType(type)) { - long ttl = System.currentTimeMillis() + getTimeToVersionCheck(typeConfiguration, cacheConfiguration); - globalObjectCache.put(new GlobalCacheObjectValue<>(objectToCache, ttl)); - } - } - - private void globallyCacheObjectVersionWithoutCloning(PrismObject objectToCache) { - CacheConfiguration cacheConfiguration = globalVersionCache.getConfiguration(); - Class type = objectToCache.asObjectable().getClass(); - CacheObjectTypeConfiguration typeConfiguration = globalVersionCache.getConfiguration(type); - if (cacheConfiguration != null && cacheConfiguration.supportsObjectType(type)) { - globalVersionCache.put(objectToCache); - } - } - - private SearchResultList> executeAndCacheSearch(QueryKey key, - Collection> options, boolean readOnly, LocalQueryCache localCache, - boolean localCacheSupports, OperationResult result) - throws SchemaException { - try { - //noinspection unchecked - SearchResultList> searchResult = (SearchResultList) searchObjectsInternal(key.getType(), key.getQuery(), options, result); - locallyCacheSearchResult(localCache, localCacheSupports, key, readOnly, searchResult); - globallyCacheSearchResult(key, readOnly, searchResult); - return searchResult; - } catch (SchemaException ex) { - globalQueryCache.remove(key); - throw ex; - } - } - - @NotNull - private PrismObject prepareObjectToCache(PrismObject object, boolean readOnly) { - PrismObject objectToCache; - if (readOnly) { - object.freeze(); - objectToCache = object; - } else { - // We are going to return the object (as mutable), so we must store a clone - objectToCache = object.clone(); - } - return objectToCache; - } - - @Override - public void addDiagnosticInformation(Class type, String oid, DiagnosticInformationType information, - OperationResult parentResult) throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException { - OperationResult result = parentResult.subresult(ADD_DIAGNOSTIC_INFORMATION) - .addQualifier(type.getSimpleName()) - .addParam("type", type) - .addParam("oid", oid) - .build(); - try { - delay(modifyRandomDelayRange); - Long startTime = repoOpStart(); - try { - repositoryService.addDiagnosticInformation(type, oid, information, result); - } finally { - repoOpEnd(startTime); - // this changes the object. We are too lazy to apply changes ourselves, so just invalidate - // the object in cache - // TODO specify additional info more precisely (but currently we use this method only in connection with TaskType - // and this kind of object is not cached anyway, so let's ignore this - invalidateCacheEntries(type, oid, null, result); - } - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - result.computeStatusIfUnknown(); - } - } - @Override public PerformanceMonitor getPerformanceMonitor() { return repositoryService.getPerformanceMonitor(); } + //endregion - private static class Context { - CacheConfiguration cacheConfig; - CacheObjectTypeConfiguration typeConfig; - boolean supports; - CacheConfiguration.StatisticsLevel statisticsLevel; - boolean traceMiss; - boolean tracePass; - - Context(CacheConfiguration configuration, Class type) { - if (configuration != null) { - cacheConfig = configuration; - typeConfig = configuration.getForObjectType(type); - supports = configuration.supportsObjectType(type); - statisticsLevel = CacheConfiguration.getStatisticsLevel(typeConfig, cacheConfig); - traceMiss = CacheConfiguration.getTraceMiss(typeConfig, cacheConfig); - tracePass = CacheConfiguration.getTracePass(typeConfig, cacheConfig); - } - } - } - - enum PassReasonType { - NOT_CACHEABLE_TYPE, MULTIPLE_OPTIONS, NON_ROOT_OPTIONS, UNSUPPORTED_OPTION, INCLUDE_OPTION_PRESENT, ZERO_STALENESS_REQUESTED - } - - private static final class PassReason { - private final PassReasonType type; - private final String comment; - - private PassReason(PassReasonType type) { - this.type = type; - this.comment = null; - } - - private PassReason(PassReasonType type, String comment) { - this.type = type; - this.comment = comment; - } - - private CacheUseTraceType toCacheUse() { - return new CacheUseTraceType() - .category(CacheUseCategoryTraceType.PASS) - .comment(type + (comment != null ? ": " + comment : "")); - } + @Override + public void postInit(OperationResult result) throws SchemaException { + repositoryService.postInit(result); // TODO resolve somehow multiple calls to repositoryService postInit method + globalObjectCache.initialize(); + globalVersionCache.initialize(); + globalQueryCache.initialize(); + cacheRegistry.registerCacheableService(this); } - private final class CollectingHandler implements ResultHandler { - - private boolean overflown = false; - private final SearchResultList> objects = new SearchResultList<>(); - private final ResultHandler originalHandler; - - private CollectingHandler(ResultHandler handler) { - originalHandler = handler; - } - - @Override - public boolean handle(PrismObject object, OperationResult parentResult) { - if (objects.size() < QUERY_RESULT_SIZE_LIMIT) { - objects.add(object.clone()); // todo optimize on read only option - } else { - overflown = true; - } - return originalHandler.handle(object, parentResult); - } - - private SearchResultList> getObjects() { - return overflown ? null : objects; - } - - private boolean isResultAvailable() { - return !overflown; - } + @PreDestroy + public void unregister() { + cacheRegistry.unregisterCacheableService(this); } - public static final class RepositoryCacheInvalidationDetails implements CacheInvalidationDetails { - private final Object details; - - private RepositoryCacheInvalidationDetails(Object details) { - this.details = details; - } + //region Cacheable interface - public Object getObject() { - return details; - } + // This is what is called from cache dispatcher (on local node with the full context; on remote nodes with reduced context) + @Override + public void invalidate(Class type, String oid, CacheInvalidationContext context) { + invalidator.invalidate(type, oid, context); } @NotNull @Override public Collection getStateInformation() { List rv = new ArrayList<>(); - rv.add(new SingleCacheStateInformationType(prismContext) - .name(LocalObjectCache.class.getName()) - .size(LocalObjectCache.getTotalSize(LOCAL_OBJECT_CACHE_INSTANCE))); - rv.add(new SingleCacheStateInformationType(prismContext) - .name(LocalVersionCache.class.getName()) - .size(LocalVersionCache.getTotalSize(LOCAL_VERSION_CACHE_INSTANCE))); - rv.add(new SingleCacheStateInformationType(prismContext) - .name(LocalQueryCache.class.getName()) - .size(LocalQueryCache.getTotalSize(LOCAL_QUERY_CACHE_INSTANCE)) - .secondarySize(LocalQueryCache.getTotalCachedObjects(LOCAL_QUERY_CACHE_INSTANCE))); + localRepoCacheCollection.getStateInformation(rv); rv.addAll(globalObjectCache.getStateInformation()); rv.addAll(globalVersionCache.getStateInformation()); rv.addAll(globalQueryCache.getStateInformation()); @@ -1932,11 +376,18 @@ public Collection getStateInformation() { @Override public void dumpContent() { - LocalObjectCache.dumpContent(LOCAL_OBJECT_CACHE_INSTANCE); - LocalVersionCache.dumpContent(LOCAL_VERSION_CACHE_INSTANCE); - LocalQueryCache.dumpContent(LOCAL_QUERY_CACHE_INSTANCE); + localRepoCacheCollection.dumpContent(); globalObjectCache.dumpContent(); globalVersionCache.dumpContent(); globalQueryCache.dumpContent(); } + //endregion + + //region Instrumentation + + public void setModifyRandomDelayRange(Integer modifyRandomDelayRange) { + modificationOpHandler.setModifyRandomDelayRange(modifyRandomDelayRange); + } + + //endregion } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheCounterManager.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/counters/CounterManagerImpl.java similarity index 90% rename from repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheCounterManager.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/counters/CounterManagerImpl.java index 8519c45bb91..1be3e3b2fae 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheCounterManager.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/counters/CounterManagerImpl.java @@ -1,10 +1,10 @@ /* - * Copyright (c) 2010-2018 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.repo.cache; +package com.evolveum.midpoint.repo.cache.counters; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -27,17 +27,22 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.TimeIntervalType; /** - * @author katka + * An implementation of CounterManager. Keeps track of counters used e.g. to implement + * policy rules thresholds. + * + * Note that this class resides in repo-cache module almost by accident and perhaps should + * be moved to a more appropriate place. * + * @author katka */ @Component -public class CacheCounterManager implements CounterManager { +public class CounterManagerImpl implements CounterManager { @Autowired private Clock clock; - private static final Trace LOGGER = TraceManager.getTrace(CacheCounterManager.class); + private static final Trace LOGGER = TraceManager.getTrace(CounterManagerImpl.class); - private Map countersMap = new ConcurrentHashMap<>(); + private final Map countersMap = new ConcurrentHashMap<>(); @Override public synchronized void cleanupCounters(String taskOid) { diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/AbstractGlobalCache.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/AbstractGlobalCache.java similarity index 71% rename from repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/AbstractGlobalCache.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/AbstractGlobalCache.java index 4f521d45116..eb6e077304b 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/AbstractGlobalCache.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/AbstractGlobalCache.java @@ -1,11 +1,11 @@ /* - * Copyright (c) 2010-2019 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.repo.cache; +package com.evolveum.midpoint.repo.cache.global; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.schema.cache.CacheConfigurationManager; @@ -19,33 +19,22 @@ import org.springframework.beans.factory.annotation.Autowired; /** - * + * Superclass for global caches handling objects, versions, and queries. */ public abstract class AbstractGlobalCache { private static final Trace LOGGER = TraceManager.getTrace(AbstractGlobalCache.class); - static final int DEFAULT_TIME_TO_LIVE = 60; // see also default-caching-profile.xml in resources + public static final int DEFAULT_TIME_TO_LIVE = 60; // see also default-caching-profile.xml in resources @Autowired protected CacheConfigurationManager configurationManager; @Autowired protected PrismContext prismContext; - boolean supportsObjectType(Class type) { - CacheType cacheType = getCacheType(); - CacheConfiguration configuration = configurationManager.getConfiguration(cacheType); - if (configuration != null) { - return configuration.supportsObjectType(type); - } else { - LOGGER.warn("Global cache configuration for {} not found", cacheType); - return false; - } - } - - protected CacheConfiguration getConfiguration() { + public CacheConfiguration getConfiguration() { return configurationManager.getConfiguration(getCacheType()); } - protected CacheObjectTypeConfiguration getConfiguration(Class type) { + public CacheObjectTypeConfiguration getConfiguration(Class type) { CacheConfiguration configuration = getConfiguration(); return configuration != null ? configuration.getForObjectType(type) : null; } @@ -61,7 +50,7 @@ long getCapacity() { } } - protected long getExpiryTime(Class type) { + long getExpiryTime(Class type) { CacheObjectTypeConfiguration configuration = getConfiguration(type); if (configuration == null) { return Expiry.NO_CACHE; @@ -74,12 +63,12 @@ protected long getExpiryTime(Class type) { protected abstract CacheType getCacheType(); - boolean isClusterwideInvalidation(Class type) { + public boolean hasClusterwideInvalidationFor(Class type) { CacheConfiguration configuration = getConfiguration(); return configuration != null && configuration.isClusterwideInvalidation(type); } - boolean isSafeRemoteInvalidation(Class type) { + public boolean shouldDoSafeRemoteInvalidationFor(Class type) { CacheConfiguration configuration = getConfiguration(); return configuration != null && configuration.isSafeRemoteInvalidation(type); } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/AbstractGlobalCacheValue.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/AbstractGlobalCacheValue.java similarity index 91% rename from repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/AbstractGlobalCacheValue.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/AbstractGlobalCacheValue.java index 8107d4b2652..0f8c90e8bd1 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/AbstractGlobalCacheValue.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/AbstractGlobalCacheValue.java @@ -5,7 +5,7 @@ * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.repo.cache; +package com.evolveum.midpoint.repo.cache.global; class AbstractGlobalCacheValue { diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheObjectValue.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalCacheObjectValue.java similarity index 50% rename from repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheObjectValue.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalCacheObjectValue.java index d8d40023ebf..bcfbf312538 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheObjectValue.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalCacheObjectValue.java @@ -1,33 +1,29 @@ /* - * Copyright (c) 2010-2018 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.repo.cache; +package com.evolveum.midpoint.repo.cache.global; + +import org.jetbrains.annotations.NotNull; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import org.jetbrains.annotations.NotNull; - /** * Created by Viliam Repan (lazyman). */ -class GlobalCacheObjectValue extends AbstractGlobalCacheValue { +public class GlobalCacheObjectValue extends AbstractGlobalCacheValue { @NotNull private final PrismObject object; - private long timeToCheckVersion; + private volatile long checkVersionTime; - GlobalCacheObjectValue(@NotNull PrismObject object, long timeToCheckVersion) { + public GlobalCacheObjectValue(@NotNull PrismObject object, long checkVersionTime) { this.object = object; - this.timeToCheckVersion = timeToCheckVersion; - } - - long getTimeToCheckVersion() { - return timeToCheckVersion; + this.checkVersionTime = checkVersionTime; } String getObjectOid() { @@ -38,21 +34,26 @@ Class getObjectType() { return object.getCompileTimeClass(); } - String getObjectVersion() { + public String getObjectVersion() { return object.getVersion(); } - @NotNull PrismObject getObject() { + @NotNull + public PrismObject getObject() { return object; // cloning is done in RepositoryCache } - void setTimeToCheckVersion(long timeToCheckVersion) { - this.timeToCheckVersion = timeToCheckVersion; + public void setCheckVersionTime(long checkVersionTime) { + this.checkVersionTime = checkVersionTime; + } + + public boolean shouldCheckVersion() { + return System.currentTimeMillis() > checkVersionTime; } @Override public String toString() { - return "GlobalCacheObjectValue{" + "timeToCheckVersion=" + timeToCheckVersion + " (" + (timeToCheckVersion-System.currentTimeMillis()) + " left)" + return "GlobalCacheObjectValue{" + "checkVersionTime=" + checkVersionTime + " (" + (checkVersionTime -System.currentTimeMillis()) + " left)" + ", object=" + object + " (version " + object.getVersion() + ")}"; } } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheObjectVersionValue.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalCacheObjectVersionValue.java similarity index 90% rename from repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheObjectVersionValue.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalCacheObjectVersionValue.java index 11dc9491122..cf517a0b4a9 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheObjectVersionValue.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalCacheObjectVersionValue.java @@ -1,15 +1,16 @@ /* - * Copyright (c) 2010-2019 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.repo.cache; +package com.evolveum.midpoint.repo.cache.global; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import org.jetbrains.annotations.NotNull; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + public class GlobalCacheObjectVersionValue extends AbstractGlobalCacheValue { @NotNull private final Class objectType; diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheQueryValue.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalCacheQueryValue.java similarity index 89% rename from repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheQueryValue.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalCacheQueryValue.java index 051d2a71bca..729d4358587 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalCacheQueryValue.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalCacheQueryValue.java @@ -1,16 +1,15 @@ /* - * Copyright (c) 2010-2019 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.repo.cache; - -import com.evolveum.midpoint.schema.SearchResultList; +package com.evolveum.midpoint.repo.cache.global; import org.jetbrains.annotations.NotNull; +import com.evolveum.midpoint.schema.SearchResultList; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; public class GlobalCacheQueryValue extends AbstractGlobalCacheValue { diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalObjectCache.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalObjectCache.java similarity index 95% rename from repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalObjectCache.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalObjectCache.java index c785f245596..e7c9a3ad0b8 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalObjectCache.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalObjectCache.java @@ -1,11 +1,11 @@ /* - * Copyright (c) 2010-2019 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.repo.cache; +package com.evolveum.midpoint.repo.cache.global; import com.evolveum.midpoint.schema.cache.CacheType; import com.evolveum.midpoint.util.logging.Trace; @@ -103,6 +103,7 @@ public void remove(@NotNull Class type, String oid) { public void put(GlobalCacheObjectValue cacheObject) { if (cache != null) { + cacheObject.getObject().checkImmutable(); cache.put(cacheObject.getObjectOid(), cacheObject); } } @@ -119,7 +120,7 @@ public void clear() { } } - Collection getStateInformation() { + public Collection getStateInformation() { Map, Integer> counts = new HashMap<>(); AtomicInteger size = new AtomicInteger(0); if (cache != null) { @@ -142,7 +143,7 @@ Collection getStateInformation() { } } - void dumpContent() { + public void dumpContent() { if (cache != null && LOGGER_CONTENT.isInfoEnabled()) { cache.invokeAll(cache.keys(), e -> { String key = e.getKey(); diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalQueryCache.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalQueryCache.java similarity index 93% rename from repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalQueryCache.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalQueryCache.java index 3136bfa1784..f8f0096bc68 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalQueryCache.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalQueryCache.java @@ -1,13 +1,14 @@ /* - * Copyright (c) 2010-2019 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.repo.cache; +package com.evolveum.midpoint.repo.cache.global; import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.repo.cache.local.QueryKey; import com.evolveum.midpoint.schema.SearchResultList; import com.evolveum.midpoint.schema.cache.CacheType; import com.evolveum.midpoint.util.logging.Trace; @@ -95,12 +96,13 @@ public void remove(QueryKey cacheKey) { public void put(QueryKey key, @NotNull SearchResultList> cacheObject) { if (cache != null) { + cacheObject.checkImmutable(); //noinspection unchecked cache.put(key, new GlobalCacheQueryValue(cacheObject)); } } - void invokeAll(EntryProcessor entryProcessor) { + public void invokeAll(EntryProcessor entryProcessor) { if (cache != null) { cache.invokeAll(cache.keys(), entryProcessor); } @@ -122,7 +124,7 @@ public void clear() { } } - Collection getStateInformation() { + public Collection getStateInformation() { Map, MutablePair> counts = new HashMap<>(); AtomicInteger queries = new AtomicInteger(0); AtomicInteger objects = new AtomicInteger(0); @@ -157,7 +159,7 @@ Collection getStateInformation() { } } - void dumpContent() { + public void dumpContent() { if (cache != null && LOGGER_CONTENT.isInfoEnabled()) { cache.invokeAll(cache.keys(), e -> { QueryKey key = e.getKey(); diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalVersionCache.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalVersionCache.java similarity index 96% rename from repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalVersionCache.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalVersionCache.java index 7ee451c4b61..d7a557c97f2 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/GlobalVersionCache.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/global/GlobalVersionCache.java @@ -1,11 +1,11 @@ /* - * Copyright (c) 2010-2019 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.repo.cache; +package com.evolveum.midpoint.repo.cache.global; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.schema.cache.CacheType; @@ -116,7 +116,7 @@ public void clear() { } } - Collection getStateInformation() { + public Collection getStateInformation() { Map, Integer> counts = new HashMap<>(); AtomicInteger size = new AtomicInteger(0); if (cache != null) { @@ -151,7 +151,7 @@ public void put(String oid, Class type, String version) { } } - void dumpContent() { + public void dumpContent() { if (cache != null && LOGGER_CONTENT.isInfoEnabled()) { cache.invokeAll(cache.keys(), e -> { LOGGER_CONTENT.info("Cached version: {}: {} (cached {} ms ago)", e.getKey(), e.getValue(), e.getValue().getAge()); diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/AddObjectResult.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/AddObjectResult.java similarity index 50% rename from repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/AddObjectResult.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/AddObjectResult.java index 00184bd8998..9a3a9a3600c 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/AddObjectResult.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/AddObjectResult.java @@ -1,24 +1,27 @@ /* - * Copyright (c) 2010-2019 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.repo.cache; +package com.evolveum.midpoint.repo.cache.handlers; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import org.jetbrains.annotations.NotNull; /** - * + * Result of the addObject operation. It is analogous to {@link com.evolveum.midpoint.repo.api.ModifyObjectResult} + * and {@link com.evolveum.midpoint.repo.api.DeleteObjectResult} but cannot be returned by addObject, + * because this operation returns String. This long-time convention cannot be changed easily, so we use + * this workaround. */ -class AddObjectResult { +public class AddObjectResult { @NotNull private final PrismObject object; - AddObjectResult(PrismObject object) { + AddObjectResult(@NotNull PrismObject object) { this.object = object; } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/BaseOpHandler.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/BaseOpHandler.java new file mode 100644 index 00000000000..291fb7a88a3 --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/BaseOpHandler.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.handlers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.match.MatchingRuleRegistry; +import com.evolveum.midpoint.repo.api.CacheDispatcher; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.repo.cache.global.GlobalObjectCache; +import com.evolveum.midpoint.repo.cache.global.GlobalQueryCache; +import com.evolveum.midpoint.repo.cache.global.GlobalVersionCache; +import com.evolveum.midpoint.repo.cache.invalidation.Invalidator; +import com.evolveum.midpoint.repo.cache.registry.CacheRegistry; +import com.evolveum.midpoint.schema.cache.CacheConfigurationManager; + +/** + * Base for all operation handlers. + */ +@Component +abstract public class BaseOpHandler { + + @Autowired PrismContext prismContext; + @Autowired RepositoryService repositoryService; + @Autowired CacheDispatcher cacheDispatcher; + @Autowired CacheRegistry cacheRegistry; + @Autowired MatchingRuleRegistry matchingRuleRegistry; + @Autowired GlobalQueryCache globalQueryCache; + @Autowired GlobalObjectCache globalObjectCache; + @Autowired GlobalVersionCache globalVersionCache; + @Autowired CacheConfigurationManager cacheConfigurationManager; + @Autowired Invalidator invalidator; + @Autowired CacheSetAccessInfoFactory cacheSetAccessInfoFactory; + @Autowired CacheUpdater cacheUpdater; +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CacheAccessInfo.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CacheAccessInfo.java new file mode 100644 index 00000000000..75ea154eb34 --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CacheAccessInfo.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.handlers; + +import com.evolveum.midpoint.util.caching.CacheConfiguration; + +/** + * Information needed to access a specific cache. + */ +class CacheAccessInfo { + + /** + * Configuration of the cache as such. + */ + final CacheConfiguration cacheConfig; + + /** + * Configuration of the cache, specific to given object type. + */ + final CacheConfiguration.CacheObjectTypeConfiguration typeConfig; + + /** + * Is the cache available? + */ + final boolean available; + + /** + * Information if the cache supports given object type. + */ + final boolean supports; + + /** + * Statistics level on which we should report events related to the given cache + type of objects. + */ + final CacheConfiguration.StatisticsLevel statisticsLevel; + + /** + * Should we log MISS events for this cache/type? + */ + final boolean traceMiss; + + /** + * Should we log PASS events for this cache/type? + */ + final boolean tracePass; + + /** + * The cache itself. + * It is null if and only if available is false. + */ + final C cache; + + CacheAccessInfo(C cache, CacheConfiguration configuration, Class type, boolean available) { + this.available = available; + this.cache = cache; + + if (configuration != null) { + cacheConfig = configuration; + typeConfig = configuration.getForObjectType(type); + supports = configuration.supportsObjectType(type); + statisticsLevel = CacheConfiguration.getStatisticsLevel(typeConfig, cacheConfig); + traceMiss = CacheConfiguration.getTraceMiss(typeConfig, cacheConfig); + tracePass = CacheConfiguration.getTracePass(typeConfig, cacheConfig); + } else { + cacheConfig = null; + typeConfig = null; + supports = false; + statisticsLevel = null; + traceMiss = false; + tracePass = false; + } + } +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CacheSetAccessInfo.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CacheSetAccessInfo.java new file mode 100644 index 00000000000..907441d0c55 --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CacheSetAccessInfo.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.handlers; + +import com.evolveum.midpoint.util.annotation.Experimental; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.repo.cache.global.GlobalObjectCache; +import com.evolveum.midpoint.repo.cache.global.GlobalQueryCache; +import com.evolveum.midpoint.repo.cache.global.GlobalVersionCache; +import com.evolveum.midpoint.repo.cache.local.LocalObjectCache; +import com.evolveum.midpoint.repo.cache.local.LocalQueryCache; +import com.evolveum.midpoint.repo.cache.local.LocalVersionCache; + +/** + * CacheAccessInfo for all six caches. + */ +public class CacheSetAccessInfo { + + @NotNull final CacheAccessInfo localObject; + @NotNull final CacheAccessInfo localVersion; + @NotNull final CacheAccessInfo localQuery; + @NotNull final CacheAccessInfo globalObject; + @NotNull final CacheAccessInfo globalVersion; + @NotNull final CacheAccessInfo globalQuery; + + public CacheSetAccessInfo(@NotNull CacheAccessInfo localObject, + @NotNull CacheAccessInfo localVersion, + @NotNull CacheAccessInfo localQuery, + @NotNull CacheAccessInfo globalObject, + @NotNull CacheAccessInfo globalVersion, + @NotNull CacheAccessInfo globalQuery) { + this.localObject = localObject; + this.localVersion = localVersion; + this.localQuery = localQuery; + this.globalObject = globalObject; + this.globalVersion = globalVersion; + this.globalQuery = globalQuery; + } +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CacheSetAccessInfoFactory.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CacheSetAccessInfoFactory.java new file mode 100644 index 00000000000..36172ba347b --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CacheSetAccessInfoFactory.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.handlers; + +import com.evolveum.midpoint.repo.cache.global.GlobalObjectCache; + +import com.evolveum.midpoint.repo.cache.global.GlobalQueryCache; +import com.evolveum.midpoint.repo.cache.global.GlobalVersionCache; + +import com.evolveum.midpoint.repo.cache.local.LocalObjectCache; +import com.evolveum.midpoint.repo.cache.local.LocalQueryCache; +import com.evolveum.midpoint.repo.cache.local.LocalVersionCache; +import com.evolveum.midpoint.schema.cache.CacheConfigurationManager; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import static com.evolveum.midpoint.repo.cache.local.LocalRepoCacheCollection.*; +import static com.evolveum.midpoint.schema.cache.CacheType.*; + +/** + * Creates CacheSetAccessInfo objects. + */ +@Component +public class CacheSetAccessInfoFactory { + + @Autowired GlobalObjectCache globalObjectCache; + @Autowired GlobalVersionCache globalVersionCache; + @Autowired GlobalQueryCache globalQueryCache; + @Autowired CacheConfigurationManager cacheConfigurationManager; + + CacheSetAccessInfo determine(Class type) { + + CacheAccessInfo globalObject = new CacheAccessInfo<>(globalObjectCache, globalObjectCache.getConfiguration(), type, globalObjectCache.isAvailable()); + CacheAccessInfo globalVersion = new CacheAccessInfo<>(globalVersionCache, globalVersionCache.getConfiguration(), type, globalVersionCache.isAvailable()); + CacheAccessInfo globalQuery = new CacheAccessInfo<>(globalQueryCache, globalQueryCache.getConfiguration(), type, globalQueryCache.isAvailable()); + + LocalObjectCache localObjectCache = getLocalObjectCache(); + LocalVersionCache localVersionCache = getLocalVersionCache(); + LocalQueryCache localQueryCache = getLocalQueryCache(); + + CacheAccessInfo localObject = localObjectCache != null ? + new CacheAccessInfo<>(localObjectCache, localObjectCache.getConfiguration(), type, true) : + new CacheAccessInfo<>(null, cacheConfigurationManager.getConfiguration(LOCAL_REPO_OBJECT_CACHE), type, false); + CacheAccessInfo localVersion = localVersionCache != null ? + new CacheAccessInfo<>(localVersionCache, localVersionCache.getConfiguration(), type, true) : + new CacheAccessInfo<>(null, cacheConfigurationManager.getConfiguration(LOCAL_REPO_VERSION_CACHE), type, false); + CacheAccessInfo localQuery = localQueryCache != null ? + new CacheAccessInfo<>(localQueryCache, localQueryCache.getConfiguration(), type, true) : + new CacheAccessInfo<>(null, cacheConfigurationManager.getConfiguration(LOCAL_REPO_QUERY_CACHE), type, false); + + return new CacheSetAccessInfo(localObject, localVersion, localQuery, globalObject, globalVersion, globalQuery); + } +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CacheUpdater.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CacheUpdater.java new file mode 100644 index 00000000000..bc9371d4f1a --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CacheUpdater.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.handlers; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.repo.cache.global.GlobalCacheObjectValue; +import com.evolveum.midpoint.repo.cache.global.GlobalObjectCache; +import com.evolveum.midpoint.repo.cache.global.GlobalQueryCache; +import com.evolveum.midpoint.repo.cache.global.GlobalVersionCache; +import com.evolveum.midpoint.repo.cache.local.LocalObjectCache; +import com.evolveum.midpoint.repo.cache.local.LocalQueryCache; +import com.evolveum.midpoint.repo.cache.local.LocalVersionCache; +import com.evolveum.midpoint.repo.cache.local.QueryKey; +import com.evolveum.midpoint.schema.SearchResultList; +import com.evolveum.midpoint.util.caching.CacheConfiguration; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +import static com.evolveum.midpoint.repo.cache.handlers.SearchOpHandler.QUERY_RESULT_SIZE_LIMIT; +import static com.evolveum.midpoint.repo.cache.local.LocalRepoCacheCollection.getLocalObjectCache; +import static com.evolveum.midpoint.repo.cache.local.LocalRepoCacheCollection.getLocalVersionCache; + +/** + * Responsible for inserting things into caches (in all possible flavors). + * All this logic was concentrated here in facilitate its consolidation. + * + * Methods naming: "store" "Immutable"? (what) "To" (what) + * + * Notes: + * - All methods here are conditional. Unconditional insertions are treated directly by caches. + * - Version caches do not need to distinguish mutable and immutable objects. + */ +@Component +class CacheUpdater { + + @Autowired private GlobalObjectCache globalObjectCache; + @Autowired private GlobalVersionCache globalVersionCache; + + //region Lists of objects + + void storeSearchResultToAll(QueryKey key, + SearchResultList> list, CacheSetAccessInfo caches) { + // The condition is there to avoid needless object cloning. + if (isQueryCacheableLocally(list, caches.localQuery) || areObjectsCacheableLocally(caches.localObject) + || isQueryCacheableGlobally(list, caches.globalQuery) || areObjectsCacheableGlobally(caches.globalObject)) { + SearchResultList> immutableObjectList = list.toDeeplyFrozenList(); + storeImmutableSearchResultToAllLocal(key, immutableObjectList, true, caches); + storeImmutableSearchResultToAllGlobal(key, immutableObjectList, true, caches); + } else { + // For simplicity we ignore the situation if versions are cacheable but objects are not. + // In such cases object versions will not be cached. + } + } + + void storeImmutableSearchResultToAllLocal(QueryKey key, + SearchResultList> immutableList, boolean isComplete, CacheSetAccessInfo caches) { + if (isComplete && isQueryCacheableLocally(immutableList, caches.localQuery)) { + caches.localQuery.cache.put(key, immutableList); + } + storeImmutableObjectsToObjectAndVersionLocal(immutableList); + } + + private void storeImmutableObjectsToObjectAndVersionLocal(List> immutableObjects) { + LocalObjectCache localObjectCache = getLocalObjectCache(); + if (localObjectCache != null) { + for (PrismObject immutableObject : immutableObjects) { + Class type = immutableObject.asObjectable().getClass(); + if (localObjectCache.supportsObjectType(type)) { + localObjectCache.put(immutableObject); // no need to clone immutable object + } + } + } + + LocalVersionCache localVersionCache = getLocalVersionCache(); + if (localVersionCache != null) { + for (PrismObject immutableObject : immutableObjects) { + Class type = immutableObject.asObjectable().getClass(); + if (localVersionCache.supportsObjectType(type)) { + localVersionCache.put(immutableObject); + } + } + } + } + + void storeImmutableSearchResultToAllGlobal(QueryKey key, + SearchResultList> immutableList, boolean isComplete, CacheSetAccessInfo caches) { + if (isComplete && isQueryCacheableGlobally(immutableList, caches.globalQuery)) { + caches.globalQuery.cache.put(key, immutableList); + } + storeImmutableObjectsToObjectAndVersionGlobal(immutableList); + } + + private void storeImmutableObjectsToObjectAndVersionGlobal(List> immutableObjects) { + if (globalObjectCache.isAvailable()) { + for (PrismObject immutableObject : immutableObjects) { + storeImmutableObjectToObjectGlobal(immutableObject); + } + } + if (globalVersionCache.isAvailable()) { + for (PrismObject immutableObject : immutableObjects) { + storeObjectToVersionGlobal(immutableObject); + } + } + } + + private boolean isQueryCacheableLocally(SearchResultList> list, + CacheAccessInfo localQuery) { + return localQuery.available && localQuery.supports && list.size() <= QUERY_RESULT_SIZE_LIMIT; + } + + private boolean areObjectsCacheableLocally(CacheAccessInfo localObject) { + return localObject.available && localObject.supports; + } + + private boolean isQueryCacheableGlobally(SearchResultList> list, + CacheAccessInfo globalQuery) { + return globalQuery.available && globalQuery.supports && list.size() <= QUERY_RESULT_SIZE_LIMIT; + } + + private boolean areObjectsCacheableGlobally(CacheAccessInfo globalObject) { + return globalObject.available && globalObject.supports; + } + + //endregion + + //region Single objects (content + version) + + void storeImmutableObjectToObjectAndVersionLocal(PrismObject immutable, CacheSetAccessInfo caches) { + storeImmutableObjectToObjectLocal(immutable, caches); + storeObjectToVersionLocal(immutable, caches.localVersion); + } + + // Assumption: object will be returned by the RepositoryCache + void storeLoadedObjectToAll(PrismObject object, CacheSetAccessInfo caches, boolean readOnly) { + boolean putIntoLocalObject = areObjectsCacheableLocally(caches.localObject); + boolean putIntoGlobalObject = areObjectsCacheableGlobally(caches.globalObject); + if (putIntoLocalObject || putIntoGlobalObject) { + @NotNull PrismObject immutable; + if (readOnly) { + // We are going to return the object as read-only, so we can cache the same (frozen) object as we are returning. + immutable = object; + } else { + // We are going to return the object as mutable, so we must cache the frozen clone of the retrieved object. + immutable = object.clone(); + } + immutable.freeze(); + + if (putIntoLocalObject) { + caches.localObject.cache.put(immutable); + } + if (putIntoGlobalObject) { + storeImmutableObjectToObjectGlobal(immutable); + } + } + + storeObjectToVersionLocal(object, caches.localVersion); + storeObjectToVersionGlobal(object, caches.globalVersion); + } + //endregion + + //region Single objects (content) + + void storeImmutableObjectToObjectLocal(PrismObject immutable, CacheSetAccessInfo caches) { + if (areObjectsCacheableLocally(caches.localObject)) { + caches.localObject.cache.put(immutable); // no need to clone immutable object + } + } + + void storeImmutableObjectToObjectGlobal(PrismObject immutable) { + CacheConfiguration cacheConfiguration = globalObjectCache.getConfiguration(); + Class type = immutable.asObjectable().getClass(); + CacheConfiguration.CacheObjectTypeConfiguration typeConfiguration = globalObjectCache.getConfiguration(type); + if (cacheConfiguration != null && cacheConfiguration.supportsObjectType(type)) { + long nextVersionCheckTime = computeNextVersionCheckTime(typeConfiguration, cacheConfiguration); + globalObjectCache.put(new GlobalCacheObjectValue<>(immutable, nextVersionCheckTime)); + } + } + + //endregion + + //region Single objects (version only) + + private void storeObjectToVersionGlobal(PrismObject object) { + CacheConfiguration cacheConfiguration = globalVersionCache.getConfiguration(); + Class type = object.asObjectable().getClass(); + if (cacheConfiguration != null && cacheConfiguration.supportsObjectType(type)) { + globalVersionCache.put(object); + } + } + + void storeObjectToVersionGlobal(PrismObject object, CacheAccessInfo globalVersion) { + if (globalVersion.available && globalVersion.supports) { + globalVersion.cache.put(object); + } + } + + void storeObjectToVersionLocal(PrismObject object, CacheAccessInfo localVersion) { + storeVersionToVersionLocal(object.getOid(), object.getVersion(), localVersion); + } + + void storeVersionToVersionLocal(String oid, String version, CacheAccessInfo localVersion) { + if (localVersion.available && localVersion.supports) { + localVersion.cache.put(oid, version); + } + } + + void storeVersionToVersionGlobal(@NotNull Class type, String oid, String version, + CacheAccessInfo globalVersion) { + if (globalVersion.available && globalVersion.supports) { + globalVersionCache.put(oid, type, version); + } + } + + //endregion + + void updateTimeToVersionCheck(GlobalCacheObjectValue cachedValue, + CacheAccessInfo globalObject) { + assert globalObject.typeConfig != null && globalObject.cacheConfig != null; + long newTimeToVersionCheck = computeNextVersionCheckTime(globalObject.typeConfig, globalObject.cacheConfig); + cachedValue.setCheckVersionTime(newTimeToVersionCheck); + } + + private long computeNextVersionCheckTime(@NotNull CacheConfiguration.CacheObjectTypeConfiguration typeConfig, + @NotNull CacheConfiguration cacheConfig) { + return System.currentTimeMillis() + computeTimeToVersionCheck(typeConfig, cacheConfig); + } + + private long computeTimeToVersionCheck(@NotNull CacheConfiguration.CacheObjectTypeConfiguration typeConfig, + @NotNull CacheConfiguration cacheConfig) { + if (typeConfig.getEffectiveTimeToVersionCheck() != null) { + return typeConfig.getEffectiveTimeToVersionCheck() * 1000L; + } else if (typeConfig.getEffectiveTimeToLive() != null) { + return typeConfig.getEffectiveTimeToLive() * 1000L; + } else if (cacheConfig.getTimeToLive() != null) { + return cacheConfig.getTimeToLive() * 1000L; + } else { + return GlobalObjectCache.DEFAULT_TIME_TO_LIVE * 1000L; + } + } +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CachedOpExecution.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CachedOpExecution.java new file mode 100644 index 00000000000..4569cb24abd --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CachedOpExecution.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.handlers; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.repo.cache.global.AbstractGlobalCache; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.util.caching.AbstractThreadLocalCache; +import com.evolveum.midpoint.util.caching.CachePerformanceCollector; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +import static com.evolveum.midpoint.repo.cache.other.MonitoringUtil.log; +import static com.evolveum.midpoint.schema.GetOperationOptions.isReadOnly; +import static com.evolveum.midpoint.schema.SelectorOptions.findRootOptions; +import static com.evolveum.midpoint.xml.ns._public.common.common_3.CacheUseCategoryTraceType.*; +import static com.evolveum.midpoint.xml.ns._public.common.common_3.CacheUseCategoryTraceType.MISS; + +/** + * Single execution of cached operation (getObject, getVersion, searchObjects, searchObjectsIteratively). + * + * It is hard to split responsibilities between this class and CachedOpHandler. But, generally, here + * are auxiliary methods - in particular, + * 1) performance monitoring and logging ones, + * 2) preparation of result to be returned - cloning as needed (plus recording it as needed). + * + * The CachedOpHandler contains the main caching-related logic. + * + * Instances of this type should be immutable (should contain only final fields). + */ +abstract class CachedOpExecution { + + /** + * Object type (input parameter). + */ + @NotNull final Class type; + + /** + * Options (input parameter). + */ + @Nullable final Collection> options; + + /** + * Is the READ-ONLY option set? + */ + final boolean readOnly; + + /** + * Operation result - a child of parentResult input parameter. + */ + @NotNull final OperationResult result; + + /** + * Trace created for the operation result (if any). + */ + @Nullable final RT trace; + + /** + * Tracing level (if any). + */ + @Nullable final TracingLevelType tracingLevel; + + /** + * Operation name e.g. getObject. + */ + @NotNull private final String opName; + + /** + * Access information for all the caches. + */ + @NotNull final CacheSetAccessInfo caches; + + /** + * Access information for related local cache (object, version, query). + */ + @NotNull final CacheAccessInfo local; + + /** + * Access information for related global cache (object, version, query). + */ + @NotNull final CacheAccessInfo global; + + @NotNull final PrismContext prismContext; + + CachedOpExecution(@NotNull Class type, + @Nullable Collection> options, + @NotNull OperationResult result, + @NotNull CacheSetAccessInfo caches, + @NotNull CacheAccessInfo local, + @NotNull CacheAccessInfo global, + @Nullable RT trace, + @Nullable TracingLevelType tracingLevel, + @NotNull PrismContext prismContext, + @NotNull String opName) { + this.type = type; + this.options = options; + this.result = result; + this.caches = caches; + this.local = local; + this.global = global; + this.trace = trace; + this.tracingLevel = tracingLevel; + this.readOnly = isReadOnly(findRootOptions(options)); + this.prismContext = prismContext; + this.opName = opName; + } + + void reportLocalAndGlobalPass(PassReason passReason) { + if (local.cache != null) { + local.cache.registerPass(); + } + CachePerformanceCollector.INSTANCE.registerPass(getLocalCacheClass(), type, local.statisticsLevel); + CachePerformanceCollector.INSTANCE.registerPass(getGlobalCacheClass(), type, global.statisticsLevel); + log("Cache (local/global): PASS:{} {} {}", local.tracePass || global.tracePass, passReason, + opName, getDescription()); + if (trace != null) { + CacheUseTraceType use = passReason.toCacheUse(); + trace.setLocalCacheUse(use); + trace.setGlobalCacheUse(use); + } + } + + void reportLocalNotAvailable() { + log("Cache (local): NULL {} {}", false, opName, getDescription()); + CachePerformanceCollector.INSTANCE.registerNotAvailable(getLocalCacheClass(), type, local.statisticsLevel); + if (trace != null) { + trace.setLocalCacheUse(createUse(NOT_AVAILABLE)); + } + } + + void reportLocalPass() { + local.cache.registerPass(); + CachePerformanceCollector.INSTANCE.registerPass(getLocalCacheClass(), type, local.statisticsLevel); + log("Cache: PASS:CONFIGURATION {} {}", local.tracePass, opName, getDescription()); + if (trace != null) { + trace.setLocalCacheUse(createUse(PASS, "configuration")); + } + } + + void reportLocalMiss() { + local.cache.registerMiss(); + CachePerformanceCollector.INSTANCE.registerMiss(getLocalCacheClass(), type, local.statisticsLevel); + log("Cache: MISS {} {}", local.traceMiss, opName, getDescription()); + if (trace != null) { + trace.setLocalCacheUse(createUse(MISS)); + } + } + + private void reportLocalHitNoClone() { + local.cache.registerHit(); + CachePerformanceCollector.INSTANCE.registerHit(getLocalCacheClass(), type, local.statisticsLevel); + log("Cache: HIT {} {}", false, opName, getDescription()); + if (trace != null) { + trace.setLocalCacheUse(createUse(HIT)); + } + } + + private void reportLocalHitWithClone() { + local.cache.registerHit(); + CachePerformanceCollector.INSTANCE.registerHit(getLocalCacheClass(), type, local.statisticsLevel); + log("Cache: HIT(clone) {} {}", false, opName, getDescription()); + if (trace != null) { + trace.setLocalCacheUse(createUse(HIT)); + } + } + + void reportLocalHit() { + if (readOnly) { + reportLocalHitNoClone(); + } else { + reportLocalHitWithClone(); + } + } + + void reportGlobalNotAvailable() { + CachePerformanceCollector.INSTANCE.registerNotAvailable(getGlobalCacheClass(), type, global.statisticsLevel); + log("Cache (global): NOT_AVAILABLE {} {}", false, opName, getDescription()); + if (trace != null) { + trace.setGlobalCacheUse(createUse(NOT_AVAILABLE)); + } + } + + void reportGlobalPass() { + CachePerformanceCollector.INSTANCE.registerPass(getGlobalCacheClass(), type, global.statisticsLevel); + log("Cache (global): PASS:CONFIGURATION {} {}", global.tracePass, opName, getDescription()); + if (trace != null) { + trace.setGlobalCacheUse(createUse(PASS, "configuration")); + } + } + + void reportGlobalHit() { + CachePerformanceCollector.INSTANCE.registerHit(getGlobalCacheClass(), type, global.statisticsLevel); + log("Cache (global): HIT {} {}", false, opName, getDescription()); + if (trace != null) { + trace.setGlobalCacheUse(createUse(HIT)); + } + } + + void reportGlobalMiss() { + CachePerformanceCollector.INSTANCE.registerMiss(getGlobalCacheClass(), type, global.statisticsLevel); + log("Cache (global): MISS {} {}", global.traceMiss, opName, getDescription()); + if (trace != null) { + trace.setGlobalCacheUse(createUse(MISS)); + } + } + + CacheUseTraceType createUse(CacheUseCategoryTraceType category) { + return new CacheUseTraceType(prismContext).category(category); + } + + CacheUseTraceType createUse(CacheUseCategoryTraceType category, String comment) { + return new CacheUseTraceType(prismContext).category(category).comment(comment); + } + + abstract String getDescription(); + abstract Class getLocalCacheClass(); + abstract Class getGlobalCacheClass(); +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CachedOpHandler.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CachedOpHandler.java new file mode 100644 index 00000000000..6e8877b3d22 --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/CachedOpHandler.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.handlers; + +/** + * Superclass for handlers for caching operations - getObject, getVersion, search. + * Currently there's not nothing here so we might consider removing this class. + */ +abstract class CachedOpHandler extends BaseOpHandler { + +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/GetObjectOpExecution.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/GetObjectOpExecution.java new file mode 100644 index 00000000000..e7add4343ca --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/GetObjectOpExecution.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.handlers; + +import static com.evolveum.midpoint.repo.cache.other.MonitoringUtil.log; +import static com.evolveum.midpoint.schema.util.TraceUtil.isAtLeastNormal; +import static com.evolveum.midpoint.xml.ns._public.common.common_3.CacheUseCategoryTraceType.MISS; +import static com.evolveum.midpoint.xml.ns._public.common.common_3.CacheUseCategoryTraceType.WEAK_HIT; + +import java.util.Collection; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.repo.cache.global.GlobalObjectCache; +import com.evolveum.midpoint.repo.cache.local.LocalObjectCache; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.util.caching.CachePerformanceCollector; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.RepositoryGetObjectTraceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TracingLevelType; + +import org.jetbrains.annotations.NotNull; + +/** + * Execution of getObject operation. + */ +class GetObjectOpExecution + extends CachedOpExecution { + + final String oid; + + GetObjectOpExecution(Class type, String oid, Collection> options, OperationResult result, + RepositoryGetObjectTraceType trace, TracingLevelType tracingLevel, + PrismContext prismContext, CacheSetAccessInfo caches) { + super(type, options, result, caches, caches.localObject, caches.globalObject, trace, tracingLevel, prismContext, "getObject"); + this.oid = oid; + } + + @Override + String getDescription() { + return type.getSimpleName() + ":" + oid; + } + + @Override + Class getLocalCacheClass() { + return LocalObjectCache.class; + } + + @Override + Class getGlobalCacheClass() { + return GlobalObjectCache.class; + } + + void reportGlobalVersionChangedMiss() { + CachePerformanceCollector.INSTANCE.registerMiss(GlobalObjectCache.class, type, global.statisticsLevel); + log("Cache (global): MISS because of version changed - getObject {}", global.traceMiss, getDescription()); + if (trace != null) { + trace.setGlobalCacheUse(createUse(MISS, "version changed")); + // todo object if needed + } + } + + void reportGlobalWeakHit() { + CachePerformanceCollector.INSTANCE.registerWeakHit(GlobalObjectCache.class, type, global.statisticsLevel); + log("Cache (global): HIT with version check - getObject {}", global.traceMiss, getDescription()); + if (trace != null) { + trace.setGlobalCacheUse(createUse(WEAK_HIT)); + } + } + + private void recordResult(PrismObject objectToReturn) { + if (objectToReturn != null) { + if (trace != null && isAtLeastNormal(tracingLevel)) { + trace.setObjectRef(ObjectTypeUtil.createObjectRefWithFullObject(objectToReturn.clone(), prismContext)); + } + if (objectToReturn.getName() != null) { + result.addContext("objectName", objectToReturn.getName().getOrig()); + } + } + } + + @NotNull PrismObject prepareReturnValueAsIs(PrismObject object) { + recordResult(object); + return object; + } + + @NotNull PrismObject prepareReturnValueWhenImmutable(PrismObject immutable) { + immutable.checkImmutable(); + recordResult(immutable); + if (readOnly) { + return immutable; + } else { + return immutable.clone(); + } + } +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/GetObjectOpHandler.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/GetObjectOpHandler.java new file mode 100644 index 00000000000..d101b8d02bb --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/GetObjectOpHandler.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.handlers; + +import static com.evolveum.midpoint.repo.cache.RepositoryCache.CLASS_NAME_WITH_DOT; +import static com.evolveum.midpoint.repo.cache.other.MonitoringUtil.repoOpEnd; +import static com.evolveum.midpoint.repo.cache.other.MonitoringUtil.repoOpStart; +import static com.evolveum.midpoint.schema.GetOperationOptions.isAllowNotFound; +import static com.evolveum.midpoint.schema.SelectorOptions.findRootOptions; +import static com.evolveum.midpoint.schema.util.TraceUtil.isAtLeastMinimal; + +import java.util.Collection; +import java.util.Objects; + +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.repo.cache.global.GlobalCacheObjectValue; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.RepositoryGetObjectTraceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TracingLevelType; + +/** + * Handles getObject calls. + */ +@Component +public class GetObjectOpHandler extends CachedOpHandler { + + private static final String GET_OBJECT = CLASS_NAME_WITH_DOT + "getObject"; + + @NotNull + public PrismObject getObject(Class type, String oid, + Collection> options, OperationResult parentResult) + throws ObjectNotFoundException, SchemaException { + + GetObjectOpExecution exec = initializeExecution(type, oid, options, parentResult); + + try { + // Checks related to both caches + PassReason passReason = PassReason.determine(options, type); + if (passReason != null) { // local nor global cache not interested in caching this object + exec.reportLocalAndGlobalPass(passReason); + PrismObject loaded = getObjectInternal(type, oid, options, exec.result); + return exec.prepareReturnValueAsIs(loaded); + } + + // Let's try local cache first + if (!exec.local.available) { + exec.reportLocalNotAvailable(); + } else if (!exec.local.supports) { + exec.reportLocalPass(); + } else { + PrismObject cachedObject = exec.local.cache.get(oid); + if (cachedObject != null) { + exec.reportLocalHit(); + return exec.prepareReturnValueWhenImmutable(cachedObject); + } else { + exec.reportLocalMiss(); + } + } + + // Then try global cache + if (!exec.global.available) { + exec.reportGlobalNotAvailable(); + PrismObject object = executeAndCache(exec); + return exec.prepareReturnValueAsIs(object); + } else if (!exec.global.supports) { + exec.reportGlobalPass(); + PrismObject object = executeAndCache(exec); + return exec.prepareReturnValueAsIs(object); + } + + GlobalCacheObjectValue cachedValue = globalObjectCache.get(oid); + if (cachedValue == null) { + exec.reportGlobalMiss(); + PrismObject object = executeAndCache(exec); + return exec.prepareReturnValueAsIs(object); + } else { + PrismObject cachedObject = cachedValue.getObject(); + if (!cachedValue.shouldCheckVersion()) { + exec.reportGlobalHit(); + cacheUpdater.storeImmutableObjectToObjectAndVersionLocal(cachedObject, exec.caches); + return exec.prepareReturnValueWhenImmutable(cachedObject); + } else { + if (hasVersionChanged(type, oid, cachedValue, exec.result)) { + exec.reportGlobalVersionChangedMiss(); + PrismObject object = executeAndCache(exec); + return exec.prepareReturnValueAsIs(object); + } else { // version matches, renew ttl + exec.reportGlobalWeakHit(); + cacheUpdater.updateTimeToVersionCheck(cachedValue, exec.global); + cacheUpdater.storeImmutableObjectToObjectAndVersionLocal(cachedObject, exec.caches); + return exec.prepareReturnValueWhenImmutable(cachedObject); + } + } + } + } catch (ObjectNotFoundException e) { + if (isAllowNotFound(findRootOptions(options))) { + exec.result.computeStatus(); + } else { + exec.result.recordFatalError(e); + } + throw e; + } catch (Throwable t) { + exec.result.recordFatalError(t); + throw t; + } finally { + exec.result.computeStatusIfUnknown(); + } + } + + private GetObjectOpExecution initializeExecution(Class type, String oid, + Collection> options, OperationResult parentResult) { + OperationResult result = parentResult.subresult(GET_OBJECT) + .addQualifier(type.getSimpleName()) + .addParam("type", type) + .addParam("oid", oid) + .addArbitraryObjectCollectionAsParam("options", options) + .build(); + + TracingLevelType tracingLevel = result.getTracingLevel(RepositoryGetObjectTraceType.class); + RepositoryGetObjectTraceType trace; + if (isAtLeastMinimal(tracingLevel)) { + trace = new RepositoryGetObjectTraceType(prismContext) + .cache(true) + .objectType(prismContext.getSchemaRegistry().determineTypeForClass(type)) + .oid(oid) + .options(String.valueOf(options)); + result.addTrace(trace); + } else { + trace = null; + } + + CacheSetAccessInfo caches = cacheSetAccessInfoFactory.determine(type); + return new GetObjectOpExecution<>(type, oid, options, result, trace, tracingLevel, prismContext, caches); + } + + @NotNull + private PrismObject getObjectInternal(Class type, String oid, Collection> options, + OperationResult parentResult) throws SchemaException, ObjectNotFoundException { + Long startTime = repoOpStart(); + try { + return repositoryService.getObject(type, oid, options, parentResult); + } finally { + repoOpEnd(startTime); + } + } + + // returns directly returnable object (frozen if readonly, mutable if not readonly) + private PrismObject executeAndCache(GetObjectOpExecution exec) + throws SchemaException, ObjectNotFoundException { + try { + PrismObject object = getObjectInternal(exec.type, exec.oid, exec.options, exec.result); + PrismObject immutable = toImmutable(object); + cacheUpdater.storeImmutableObjectToObjectLocal(immutable, exec.caches); + cacheUpdater.storeImmutableObjectToObjectGlobal(immutable); + cacheUpdater.storeObjectToVersionGlobal(immutable, exec.caches.globalVersion); + cacheUpdater.storeObjectToVersionLocal(immutable, exec.caches.localVersion); + if (exec.readOnly) { + return immutable; + } else { + return object.cloneIfImmutable(); + } + } catch (ObjectNotFoundException | SchemaException ex) { + globalObjectCache.remove(exec.oid); + globalVersionCache.remove(exec.oid); + throw ex; + } + } + + private PrismObject toImmutable(PrismObject object) { + if (object.isImmutable()) { + return object; + } else { + PrismObject clone = object.clone(); + clone.freeze(); + return clone; + } + } + + private boolean hasVersionChanged(Class objectType, String oid, + GlobalCacheObjectValue object, OperationResult result) throws ObjectNotFoundException, SchemaException { + try { + // TODO shouldn't we record repoOpStart/repoOpEnd? + String version = repositoryService.getVersion(objectType, oid, result); + return !Objects.equals(version, object.getObjectVersion()); + } catch (ObjectNotFoundException | SchemaException ex) { + globalObjectCache.remove(oid); + globalVersionCache.remove(oid); + throw ex; + } + } +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/GetVersionOpExecution.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/GetVersionOpExecution.java new file mode 100644 index 00000000000..06b75459a43 --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/GetVersionOpExecution.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.handlers; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.repo.cache.global.GlobalVersionCache; +import com.evolveum.midpoint.repo.cache.local.LocalVersionCache; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.RepositoryGetVersionTraceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TracingLevelType; + +/** + * Execution of getVersion operation. + */ +class GetVersionOpExecution + extends CachedOpExecution { + + final String oid; + + GetVersionOpExecution(Class type, String oid, OperationResult result, + RepositoryGetVersionTraceType trace, TracingLevelType tracingLevel, + PrismContext prismContext, CacheSetAccessInfo caches) { + super(type, null, result, caches, caches.localVersion, caches.globalVersion, trace, tracingLevel, prismContext, "getVersion"); + this.oid = oid; + } + + @Override + String getDescription() { + return type.getSimpleName() + ":" + oid; + } + + @Override + Class getLocalCacheClass() { + return LocalVersionCache.class; + } + + @Override + Class getGlobalCacheClass() { + return GlobalVersionCache.class; + } + + private void recordResult(String version) { + if (trace != null) { + trace.setVersion(version); + } + result.addReturn("version", version); + } + + String prepareReturnValue(String version) { + recordResult(version); + return version; + } +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/GetVersionOpHandler.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/GetVersionOpHandler.java new file mode 100644 index 00000000000..19dd90d531a --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/GetVersionOpHandler.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.handlers; + +import static com.evolveum.midpoint.repo.cache.RepositoryCache.CLASS_NAME_WITH_DOT; +import static com.evolveum.midpoint.repo.cache.other.MonitoringUtil.repoOpEnd; +import static com.evolveum.midpoint.repo.cache.other.MonitoringUtil.repoOpStart; + +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.RepositoryGetVersionTraceType; + +/** + * Handler for getVersion operation. + */ +@Component +public class GetVersionOpHandler extends CachedOpHandler { + + private static final String GET_VERSION = CLASS_NAME_WITH_DOT + "getVersion"; + + public String getVersion(Class type, String oid, OperationResult parentResult) + throws ObjectNotFoundException, SchemaException { + + GetVersionOpExecution exec = initializeExecution(type, oid, parentResult); + + try { + + PassReason passReason = PassReason.determine(null, type); + if (passReason != null) { + exec.reportLocalAndGlobalPass(passReason); + String loaded = getVersionInternal(type, oid, exec.result); + return exec.prepareReturnValue(loaded); + } + + if (!exec.local.available) { + exec.reportLocalNotAvailable(); + } else if (!exec.local.supports) { + exec.reportLocalPass(); + } else { + String cachedVersion = exec.local.cache.get(oid); + if (cachedVersion != null) { + exec.reportLocalHit(); + return exec.prepareReturnValue(cachedVersion); + } else { + exec.reportLocalMiss(); + } + } + + if (!exec.global.available) { + exec.reportGlobalNotAvailable(); + } else if (!exec.global.supports) { + exec.reportGlobalPass(); + } else { + String cachedVersion = globalVersionCache.get(oid); + if (cachedVersion != null) { + exec.reportGlobalHit(); + cacheUpdater.storeVersionToVersionLocal(exec.oid, cachedVersion, exec.local); + return exec.prepareReturnValue(cachedVersion); + } else { + exec.reportGlobalMiss(); + } + } + + String version = getVersionInternal(type, oid, exec.result); + + cacheUpdater.storeVersionToVersionGlobal(exec.type, exec.oid, version, exec.global); + cacheUpdater.storeVersionToVersionLocal(exec.oid, version, exec.local); + return exec.prepareReturnValue(version); + } catch (Throwable t) { + exec.result.recordFatalError(t); + throw t; + } finally { + exec.result.computeStatusIfUnknown(); + } + } + + private GetVersionOpExecution initializeExecution(Class type, String oid, + OperationResult parentResult) { + OperationResult result = parentResult.subresult(GET_VERSION) + .addQualifier(type.getSimpleName()) + .addParam("type", type) + .addParam("oid", oid) + .build(); + + RepositoryGetVersionTraceType trace; + if (result.isTraced()) { + trace = new RepositoryGetVersionTraceType(prismContext) + .cache(true) + .objectType(prismContext.getSchemaRegistry().determineTypeForClass(type)) + .oid(oid); + result.addTrace(trace); + } else { + trace = null; + } + + CacheSetAccessInfo caches = cacheSetAccessInfoFactory.determine(type); + return new GetVersionOpExecution<>(type, oid, result, trace, null, prismContext, caches); + } + + private String getVersionInternal(Class type, String oid, OperationResult result) + throws ObjectNotFoundException, SchemaException { + Long startTime = repoOpStart(); + try { + return repositoryService.getVersion(type, oid, result); + } finally { + repoOpEnd(startTime); + } + } +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/ModificationOpHandler.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/ModificationOpHandler.java new file mode 100644 index 00000000000..9610ad77e10 --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/ModificationOpHandler.java @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.handlers; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.repo.api.*; +import com.evolveum.midpoint.repo.cache.other.MonitoringUtil; +import com.evolveum.midpoint.schema.DeltaConvertor; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.Collections; +import java.util.Random; + +import static com.evolveum.midpoint.repo.cache.RepositoryCache.CLASS_NAME_WITH_DOT; +import static com.evolveum.midpoint.schema.util.TraceUtil.isAtLeastMinimal; +import static com.evolveum.midpoint.schema.util.TraceUtil.isAtLeastNormal; + +/** + * Handles modification operations: add, modify, delete, and a couple of others. + * What they have in common is that they invalidate cache entries. + */ +@Component +public class ModificationOpHandler extends BaseOpHandler { + + private static final String ADD_OBJECT = CLASS_NAME_WITH_DOT + "addObject"; + private static final String MODIFY_OBJECT = CLASS_NAME_WITH_DOT + "modifyObject"; + private static final String DELETE_OBJECT = CLASS_NAME_WITH_DOT + "deleteObject"; + private static final String ADVANCE_SEQUENCE = CLASS_NAME_WITH_DOT + "advanceSequence"; + private static final String RETURN_UNUSED_VALUES_TO_SEQUENCE = CLASS_NAME_WITH_DOT + "returnUnusedValuesToSequence"; + private static final String ADD_DIAGNOSTIC_INFORMATION = CLASS_NAME_WITH_DOT + "addDiagnosticInformation"; + + private Integer modifyRandomDelayRange; + + private static final Random RND = new Random(); + + public String addObject(PrismObject object, RepoAddOptions options, OperationResult parentResult) + throws ObjectAlreadyExistsException, SchemaException { + OperationResult result = parentResult.subresult(ADD_OBJECT) + .addQualifier(object.asObjectable().getClass().getSimpleName()) + .addParam("type", object.getCompileTimeClass()) + .addArbitraryObjectAsParam("options", options) + .build(); + RepositoryAddTraceType trace; + TracingLevelType level = result.getTracingLevel(RepositoryAddTraceType.class); + if (isAtLeastMinimal(level)) { + trace = new RepositoryAddTraceType(prismContext) + .options(String.valueOf(options)); + result.addTrace(trace); + } else { + trace = null; + } + + try { + String oid; + Long startTime = MonitoringUtil.repoOpStart(); + try { + oid = repositoryService.addObject(object, options, result); + } finally { + MonitoringUtil.repoOpEnd(startTime); + } + // DON't cache the object here. The object may not have proper "JAXB" form, e.g. some pieces may be + // DOM element instead of JAXB elements. Not to cache it is safer and the performance loss + // is acceptable. + if (options != null && options.isOverwrite()) { + invalidator.invalidateCacheEntries(object.getCompileTimeClass(), oid, + new ModifyObjectResult<>(object.getUserData(RepositoryService.KEY_ORIGINAL_OBJECT), object, + Collections.emptyList()), result); + } else { + // just for sure (the object should not be there but ...) + invalidator.invalidateCacheEntries(object.getCompileTimeClass(), oid, new AddObjectResult<>(object), result); + } + if (trace != null) { + trace.setOid(oid); + if (isAtLeastNormal(level)) { + // We put the object into the trace here, because now it has OID set + trace.setObjectRef(ObjectTypeUtil.createObjectRefWithFullObject(object.clone(), prismContext)); + } + } + return oid; + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + result.computeStatusIfUnknown(); + } + } + + @NotNull + public ModifyObjectResult modifyObject(@NotNull Class type, @NotNull String oid, + @NotNull Collection modifications, + ModificationPrecondition precondition, RepoModifyOptions options, OperationResult parentResult) + throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException, PreconditionViolationException { + + OperationResult result = parentResult.subresult(MODIFY_OBJECT) + .addQualifier(type.getSimpleName()) + .addParam("type", type) + .addParam("oid", oid) + .addArbitraryObjectAsParam("options", options) + .build(); + + if (result.isTraced()) { + RepositoryModifyTraceType trace = new RepositoryModifyTraceType(prismContext) + .cache(true) + .objectType(prismContext.getSchemaRegistry().determineTypeForClass(type)) + .oid(oid) + .options(String.valueOf(options)); + for (ItemDelta modification : modifications) { + // todo only if configured? + trace.getModification().addAll(DeltaConvertor.toItemDeltaTypes(modification)); + } + result.addTrace(trace); + } + + try { + randomDelay(); + Long startTime = MonitoringUtil.repoOpStart(); + ModifyObjectResult modifyInfo = null; + try { + modifyInfo = repositoryService.modifyObject(type, oid, modifications, precondition, options, result); + return modifyInfo; + } finally { + MonitoringUtil.repoOpEnd(startTime); + // this changes the object. We are too lazy to apply changes ourselves, so just invalidate + // the object in cache + invalidator.invalidateCacheEntries(type, oid, modifyInfo, result); + } + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + result.computeStatusIfUnknown(); + } + } + + @NotNull + public DeleteObjectResult deleteObject(Class type, String oid, OperationResult parentResult) + throws ObjectNotFoundException { + OperationResult result = parentResult.subresult(DELETE_OBJECT) + .addQualifier(type.getSimpleName()) + .addParam("type", type) + .addParam("oid", oid) + .build(); + + if (result.isTraced()) { + RepositoryDeleteTraceType trace = new RepositoryDeleteTraceType(prismContext) + .cache(true) + .objectType(prismContext.getSchemaRegistry().determineTypeForClass(type)) + .oid(oid); + result.addTrace(trace); + } + Long startTime = MonitoringUtil.repoOpStart(); + DeleteObjectResult deleteInfo = null; + try { + try { + deleteInfo = repositoryService.deleteObject(type, oid, result); + } finally { + MonitoringUtil.repoOpEnd(startTime); + invalidator.invalidateCacheEntries(type, oid, deleteInfo, result); + } + return deleteInfo; + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + result.computeStatusIfUnknown(); + } + } + + public long advanceSequence(String oid, OperationResult parentResult) throws ObjectNotFoundException, + SchemaException { + OperationResult result = parentResult.subresult(ADVANCE_SEQUENCE) + .addParam("oid", oid) + .build(); + try { + Long startTime = MonitoringUtil.repoOpStart(); + try { + return repositoryService.advanceSequence(oid, result); + } finally { + MonitoringUtil.repoOpEnd(startTime); + invalidator.invalidateCacheEntries(SequenceType.class, oid, null, result); + } + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + result.computeStatusIfUnknown(); + } + } + + public void returnUnusedValuesToSequence(String oid, Collection unusedValues, OperationResult parentResult) + throws ObjectNotFoundException, SchemaException { + OperationResult result = parentResult.subresult(RETURN_UNUSED_VALUES_TO_SEQUENCE) + .addParam("oid", oid) + .addArbitraryObjectCollectionAsParam("unusedValues", unusedValues) + .build(); + try { + Long startTime = MonitoringUtil.repoOpStart(); + try { + repositoryService.returnUnusedValuesToSequence(oid, unusedValues, result); + } finally { + MonitoringUtil.repoOpEnd(startTime); + invalidator.invalidateCacheEntries(SequenceType.class, oid, null, result); + } + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + result.computeStatusIfUnknown(); + } + } + + + public void addDiagnosticInformation(Class type, String oid, DiagnosticInformationType information, + OperationResult parentResult) throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException { + OperationResult result = parentResult.subresult(ADD_DIAGNOSTIC_INFORMATION) + .addQualifier(type.getSimpleName()) + .addParam("type", type) + .addParam("oid", oid) + .build(); + try { + randomDelay(); + Long startTime = MonitoringUtil.repoOpStart(); + try { + repositoryService.addDiagnosticInformation(type, oid, information, result); + } finally { + MonitoringUtil.repoOpEnd(startTime); + // this changes the object. We are too lazy to apply changes ourselves, so just invalidate + // the object in cache + // TODO specify additional info more precisely (but currently we use this method only in connection with TaskType + // and this kind of object is not cached anyway, so let's ignore this + invalidator.invalidateCacheEntries(type, oid, null, result); + } + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + result.computeStatusIfUnknown(); + } + } + + public void setModifyRandomDelayRange(Integer modifyRandomDelayRange) { + this.modifyRandomDelayRange = modifyRandomDelayRange; + } + + private void randomDelay() { + if (modifyRandomDelayRange == null) { + return; + } + int delay = RND.nextInt(modifyRandomDelayRange); + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + // Nothing to do + } + } +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/PassReason.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/PassReason.java new file mode 100644 index 00000000000..13a2f293081 --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/PassReason.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.handlers; + +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.RetrieveOption; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CacheUseCategoryTraceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CacheUseTraceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; + +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +import static com.evolveum.midpoint.repo.cache.handlers.PassReason.PassReasonType.*; +import static com.evolveum.midpoint.repo.cache.handlers.PassReason.PassReasonType.UNSUPPORTED_OPTION; +import static com.evolveum.midpoint.schema.GetOperationOptions.createRetrieve; + +/** + * Reason why an operation request passes a cache. + */ +final class PassReason { + + /** + * Categorization of why a request passes the cache. + */ + private final PassReasonType type; + + /** + * More detailed information on the pass reason. For the time being it is provided as plain text. + */ + private final String comment; + + enum PassReasonType { + NOT_CACHEABLE_TYPE, MULTIPLE_OPTIONS, NON_ROOT_OPTIONS, UNSUPPORTED_OPTION, INCLUDE_OPTION_PRESENT, ZERO_STALENESS_REQUESTED + } + + private PassReason(PassReasonType type) { + this.type = type; + this.comment = null; + } + + private PassReason(PassReasonType type, String comment) { + this.type = type; + this.comment = comment; + } + + /** + * Main entry point. By looking at situation we determine if there's a reason to pass the cache. + */ + @Nullable + static PassReason determine(Collection> options, Class objectType) { + if (alwaysNotCacheable(objectType)) { + return new PassReason(NOT_CACHEABLE_TYPE); + } + if (options == null || options.isEmpty()) { + return null; + } + if (options.size() > 1) { + return new PassReason(MULTIPLE_OPTIONS); + } + SelectorOptions selectorOptions = options.iterator().next(); + if (!selectorOptions.isRoot()) { + return new PassReason(NON_ROOT_OPTIONS); + } + if (selectorOptions.getOptions() == null) { + return null; + } + Long staleness = selectorOptions.getOptions().getStaleness(); + if (staleness != null && staleness == 0) { + return new PassReason(ZERO_STALENESS_REQUESTED); + } + GetOperationOptions cloned = selectorOptions.getOptions().clone(); + + // Eliminate harmless options + cloned.setAllowNotFound(null); + cloned.setExecutionPhase(null); + cloned.setReadOnly(null); + cloned.setNoFetch(null); + cloned.setPointInTimeType(null); // This is not used by repository anyway. + // We know the staleness is not zero, so caching is (in principle) allowed. + // More detailed treatment of staleness is not yet available. + cloned.setStaleness(null); + if (cloned.equals(GetOperationOptions.EMPTY)) { + return null; + } + if (cloned.equals(createRetrieve(RetrieveOption.INCLUDE))) { + if (SelectorOptions.isRetrievedFullyByDefault(objectType)) { + return null; + } else { + return new PassReason(INCLUDE_OPTION_PRESENT); + } + } + return new PassReason(UNSUPPORTED_OPTION, cloned.toString()); + } + + /** + * Main reason of cache pass: + * + * Tasks are usually rapidly changing. + * + * Cases are perhaps not changing that rapidly but these are objects that are used for communication of various parties; + * so - to avoid having stale data - we skip caching them altogether. + */ + static boolean alwaysNotCacheable(Class type) { + return type.equals(TaskType.class) || type.equals(CaseType.class); + } + + CacheUseTraceType toCacheUse() { + return new CacheUseTraceType() + .category(CacheUseCategoryTraceType.PASS) + .comment(type + (comment != null ? ": " + comment : "")); + } +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/SearchOpExecution.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/SearchOpExecution.java new file mode 100644 index 00000000000..3e4c5769f01 --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/SearchOpExecution.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.handlers; + +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; + +import static com.evolveum.midpoint.schema.util.TraceUtil.isAtLeastNormal; + +import java.util.Collection; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.repo.cache.global.GlobalQueryCache; +import com.evolveum.midpoint.repo.cache.local.LocalQueryCache; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.SearchResultList; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.result.CompiledTracingProfile; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.schema.util.TraceUtil; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.RepositorySearchObjectsTraceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TracingLevelType; + +import org.jetbrains.annotations.NotNull; + +/** + * Context of searchObjects/searchObjectsIteratively operation. + * + * Responsible also for reporting on operation execution. + */ +class SearchOpExecution + extends CachedOpExecution { + + final ObjectQuery query; + + SearchOpExecution(Class type, Collection> options, OperationResult result, + ObjectQuery query, RepositorySearchObjectsTraceType trace, TracingLevelType tracingLevel, + PrismContext prismContext, CacheSetAccessInfo caches, String opName) { + super(type, options, result, caches, caches.localQuery, caches.globalQuery, trace, tracingLevel, prismContext, opName); + this.query = query; + } + + void recordResult(SearchResultList> objectsFound) { + result.addReturn("objectsFound", objectsFound.size()); + if (trace != null) { + trace.setResultSize(objectsFound.size()); + recordObjectsFound(objectsFound); + } + } + + private void recordObjectsFound(SearchResultList> objectsFound) { + if (isAtLeastNormal(tracingLevel)) { + CompiledTracingProfile tracingProfile = result.getTracingProfile(); + int maxFullObjects = defaultIfNull(tracingProfile.getDefinition().getRecordObjectsFound(), + TraceUtil.DEFAULT_RECORD_OBJECTS_FOUND); + int maxReferences = defaultIfNull(tracingProfile.getDefinition().getRecordObjectReferencesFound(), + TraceUtil.DEFAULT_RECORD_OBJECT_REFERENCES_FOUND); + int objectsToVisit = Math.min(objectsFound.size(), maxFullObjects + maxReferences); + assert trace != null; + for (int i = 0; i < objectsToVisit; i++) { + PrismObject object = objectsFound.get(i); + if (i < maxFullObjects) { + trace.getObjectRef().add(ObjectTypeUtil.createObjectRefWithFullObject(object.clone(), prismContext)); + } else { + trace.getObjectRef().add(ObjectTypeUtil.createObjectRef(object, prismContext)); + } + } + } + } + + @NotNull SearchResultList> prepareReturnValueAsIs(SearchResultList> list) { + recordResult(list); + return list; + } + + @NotNull SearchResultList> prepareReturnValueWhenImmutable(SearchResultList> immutable) { + immutable.checkImmutable(); + recordResult(immutable); + if (readOnly) { + return immutable; + } else { + return immutable.deepClone(); + } + } + + @Override + String getDescription() { + return type.getSimpleName() + ": " + query; + } + + @Override + Class getLocalCacheClass() { + return LocalQueryCache.class; + } + + @Override + Class getGlobalCacheClass() { + return GlobalQueryCache.class; + } +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/SearchOpHandler.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/SearchOpHandler.java new file mode 100644 index 00000000000..c91f0161377 --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/handlers/SearchOpHandler.java @@ -0,0 +1,431 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.handlers; + +import static com.evolveum.midpoint.repo.cache.RepositoryCache.CLASS_NAME_WITH_DOT; +import static com.evolveum.midpoint.repo.cache.other.MonitoringUtil.repoOpEnd; +import static com.evolveum.midpoint.repo.cache.other.MonitoringUtil.repoOpStart; +import static com.evolveum.midpoint.schema.GetOperationOptions.isReadOnly; +import static com.evolveum.midpoint.schema.SelectorOptions.findRootOptions; +import static com.evolveum.midpoint.schema.util.TraceUtil.isAtLeastMinimal; + +import java.util.Collection; + +import com.evolveum.midpoint.repo.cache.other.MonitoringUtil; + +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.prism.Containerable; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.repo.cache.RepositoryCache; +import com.evolveum.midpoint.repo.cache.local.QueryKey; +import com.evolveum.midpoint.schema.*; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.RepositorySearchObjectsTraceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TracingLevelType; + +/** + * Handler for searchObjects/searchObjectsIterative operations. + */ +@Component +public class SearchOpHandler extends CachedOpHandler { + + private static final String SEARCH_OBJECTS = "searchObjects"; + private static final String SEARCH_OBJECTS_ITERATIVE = "searchObjectsIterative"; + + private static final String OP_SEARCH_CONTAINERS = CLASS_NAME_WITH_DOT + "searchContainers"; + private static final String OP_SEARCH_SHADOW_OWNER = CLASS_NAME_WITH_DOT + "searchShadowOwner"; + private static final String OP_COUNT_CONTAINERS = CLASS_NAME_WITH_DOT + "countContainers"; + private static final String OP_COUNT_OBJECTS = CLASS_NAME_WITH_DOT + "countObjects"; + + static final int QUERY_RESULT_SIZE_LIMIT = 100000; + + private static final String OP_ITERATE_OVER_QUERY_RESULT = RepositoryCache.class.getName() + ".iterateOverQueryResult"; + + @NotNull + public SearchResultList> searchObjects(Class type, ObjectQuery query, + Collection> options, OperationResult parentResult) throws SchemaException { + + SearchOpExecution exec = initializeExecution(type, query, options, parentResult, SEARCH_OBJECTS); + + try { + // Checks related to both caches + PassReason passReason = PassReason.determine(options, type); + if (passReason != null) { + exec.reportLocalAndGlobalPass(passReason); + SearchResultList> objects = searchObjectsInternal(type, query, options, exec.result); + return exec.prepareReturnValueAsIs(objects); + } + QueryKey key = new QueryKey<>(type, query); + + // Let's try local cache + if (!exec.local.available) { + exec.reportLocalNotAvailable(); + } else if (!exec.local.supports) { + exec.reportLocalPass(); + } else { + SearchResultList> cachedResult = exec.local.cache.get(key); + if (cachedResult != null) { + exec.reportLocalHit(); + return exec.prepareReturnValueWhenImmutable(cachedResult); + } else { + exec.reportLocalMiss(); + } + } + + // Then try global cache + if (!exec.global.available) { + exec.reportGlobalNotAvailable(); + SearchResultList> objects = executeAndCacheSearch(exec, key); + return exec.prepareReturnValueAsIs(objects); + } else if (!exec.global.supports) { + exec.reportGlobalPass(); + SearchResultList> objects = executeAndCacheSearch(exec, key); + return exec.prepareReturnValueAsIs(objects); + } + + SearchResultList> cachedResult = globalQueryCache.get(key); + if (cachedResult != null) { + exec.reportGlobalHit(); + cacheUpdater.storeImmutableSearchResultToAllLocal(key, cachedResult, true, exec.caches); + return exec.prepareReturnValueWhenImmutable(cachedResult); + } else { + exec.reportGlobalMiss(); + SearchResultList> objects = executeAndCacheSearch(exec, key); + return exec.prepareReturnValueAsIs(objects); + } + } catch (Throwable t) { + exec.result.recordFatalError(t); + throw t; + } finally { + exec.result.computeStatusIfUnknown(); + } + } + + private static class WatchingHandler implements ResultHandler { + + private final ResultHandler innerHandler; + private final SearchResultList> objectsHandled = new SearchResultList<>(); + private boolean interrupted; + + private WatchingHandler(ResultHandler innerHandler) { + this.innerHandler = innerHandler; + } + + @Override + public boolean handle(PrismObject object, OperationResult result) { + objectsHandled.add(object); + boolean cont = innerHandler.handle(object, result); + if (!cont) { + interrupted = true; + } + return cont; + } + } + + public SearchResultMetadata searchObjectsIterative(Class type, ObjectQuery query, + ResultHandler handler, Collection> options, + boolean strictlySequential, OperationResult parentResult) throws SchemaException { + + SearchOpExecution exec = initializeExecution(type, query, options, parentResult, SEARCH_OBJECTS_ITERATIVE); + WatchingHandler watchingHandler = new WatchingHandler<>(handler); + + try { + // Checks related to both caches + PassReason passReason = PassReason.determine(options, type); + if (passReason != null) { + exec.reportLocalAndGlobalPass(passReason); + return searchObjectsIterativeInternal(type, query, watchingHandler, options, strictlySequential, exec.result); + } + QueryKey key = new QueryKey<>(type, query); + + // Let's try local cache + if (!exec.local.available) { + exec.reportLocalNotAvailable(); + } else if (!exec.local.supports) { + exec.reportLocalPass(); + } else { + SearchResultList> cachedResult = exec.local.cache.get(key); + if (cachedResult != null) { + exec.reportLocalHit(); + return iterateOverImmutableQueryResult(exec, cachedResult, watchingHandler); + } else { + exec.reportLocalMiss(); + } + } + + // Then try global cache + if (!exec.global.available) { + exec.reportGlobalNotAvailable(); + return executeAndCacheSearchIterative(exec, key, watchingHandler, strictlySequential); + } else if (!exec.global.supports) { + exec.reportGlobalPass(); + return executeAndCacheSearchIterative(exec, key, watchingHandler, strictlySequential); + } + + SearchResultList> cachedResult = globalQueryCache.get(key); + if (cachedResult != null) { + exec.reportGlobalHit(); + cachedResult.checkImmutable(); + cacheUpdater.storeImmutableSearchResultToAllLocal(key, cachedResult, true, exec.caches); + iterateOverImmutableQueryResult(exec, cachedResult, watchingHandler); + return cachedResult.getMetadata(); + } else { + exec.reportGlobalMiss(); + return executeAndCacheSearchIterative(exec, key, watchingHandler, strictlySequential); + } + } catch (Throwable t) { + exec.result.recordFatalError(t); + throw t; + } finally { + exec.recordResult(watchingHandler.objectsHandled); + exec.result.addReturn("interrupted", watchingHandler.interrupted); + exec.result.computeStatusIfUnknown(); + } + } + + private SearchOpExecution initializeExecution(Class type, ObjectQuery query, + Collection> options, OperationResult parentResult, String opName) + throws SchemaException { + OperationResult result = parentResult.subresult(CLASS_NAME_WITH_DOT + opName) + .addQualifier(type.getSimpleName()) + .addParam("type", type) + .addParam("query", query) + .addArbitraryObjectCollectionAsParam("options", options) + .build(); + + TracingLevelType level = result.getTracingLevel(RepositorySearchObjectsTraceType.class); + RepositorySearchObjectsTraceType trace; + if (isAtLeastMinimal(level)) { + trace = new RepositorySearchObjectsTraceType(prismContext) + .cache(true) + .objectType(prismContext.getSchemaRegistry().determineTypeForClass(type)) + .query(prismContext.getQueryConverter().createQueryType(query)) + .options(String.valueOf(options)); + result.addTrace(trace); + } else { + trace = null; + } + CacheSetAccessInfo caches = cacheSetAccessInfoFactory.determine(type); + return new SearchOpExecution<>(type, options, result, query, trace, level, prismContext, caches, opName); + } + + // returns directly returnable list (frozen if readonly, mutable if not readonly) + private SearchResultList> executeAndCacheSearch(SearchOpExecution exec, QueryKey key) + throws SchemaException { + try { + SearchResultList> objects = searchObjectsInternal(key.getType(), key.getQuery(), exec.options, exec.result); + if (exec.readOnly) { + SearchResultList> immutableObjectList = objects.toDeeplyFrozenList(); + cacheUpdater.storeImmutableSearchResultToAllLocal(key, immutableObjectList, true, exec.caches); + cacheUpdater.storeImmutableSearchResultToAllGlobal(key, immutableObjectList, true, exec.caches); + return immutableObjectList; + } else { + cacheUpdater.storeSearchResultToAll(key, objects, exec.caches); + return objects; + } + } catch (SchemaException ex) { + globalQueryCache.remove(key); + throw ex; + } + } + + private SearchResultMetadata executeAndCacheSearchIterative(SearchOpExecution exec, QueryKey key, + WatchingHandler watchingHandler, boolean strictlySequential) throws SchemaException { + try { + CollectingHandler collectingHandler = new CollectingHandler<>(watchingHandler); + SearchResultMetadata metadata = searchObjectsIterativeInternal(exec.type, exec.query, collectingHandler, exec.options, + strictlySequential, exec.result); + SearchResultList> list = collectingHandler.getObjects(); + if (list != null) { // todo optimize cloning here + SearchResultList> immutableList = list.toDeeplyFrozenList(); + cacheUpdater.storeImmutableSearchResultToAllLocal(key, immutableList, !watchingHandler.interrupted, exec.caches); + cacheUpdater.storeImmutableSearchResultToAllGlobal(key, immutableList, !watchingHandler.interrupted, exec.caches); + } + return metadata; + } catch (SchemaException ex) { + globalQueryCache.remove(key); + throw ex; + } + } + + private static final class CollectingHandler implements ResultHandler { + + private boolean overflown = false; + private final SearchResultList> objects = new SearchResultList<>(); + private final ResultHandler originalHandler; + + private CollectingHandler(ResultHandler handler) { + originalHandler = handler; + } + + @Override + public boolean handle(PrismObject object, OperationResult parentResult) { + if (objects.size() < QUERY_RESULT_SIZE_LIMIT) { + objects.add(object.clone()); // todo optimize on read only option + } else { + overflown = true; + } + return originalHandler.handle(object, parentResult); + } + + private SearchResultList> getObjects() { + return overflown ? null : objects; + } + } + + @NotNull + private SearchResultList> searchObjectsInternal(Class type, ObjectQuery query, + Collection> options, OperationResult parentResult) + throws SchemaException { + Long startTime = repoOpStart(); + try { + return repositoryService.searchObjects(type, query, options, parentResult); + } finally { + repoOpEnd(startTime); + } + } + + private SearchResultMetadata searchObjectsIterativeInternal(Class type, ObjectQuery query, + ResultHandler handler, Collection> options, + boolean strictlySequential, OperationResult parentResult) throws SchemaException { + Long startTime = repoOpStart(); + try { + return repositoryService.searchObjectsIterative(type, query, handler, options, strictlySequential, parentResult); + } finally { + repoOpEnd(startTime); + } + } + + private SearchResultMetadata iterateOverImmutableQueryResult( + SearchOpExecution exec, SearchResultList> immutableList, ResultHandler handler) { + OperationResult result = exec.result.subresult(OP_ITERATE_OVER_QUERY_RESULT) + .setMinor() + .addParam("objects", immutableList.size()) + .addArbitraryObjectAsParam("handler", handler) + .build(); + try { + for (PrismObject immutableObject : immutableList) { + immutableObject.checkImmutable(); + PrismObject objectToHandle = exec.readOnly ? immutableObject : immutableObject.clone(); + if (!handler.handle(objectToHandle, result)) { + break; + } + } + // todo Should be metadata influenced by the number of handler executions? + // ...and is it correct to return cached metadata at all? + return immutableList.getMetadata() != null ? immutableList.getMetadata().clone() : null; + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + result.computeStatusIfUnknown(); + } + } + + public PrismObject searchShadowOwner( + String shadowOid, Collection> options, OperationResult parentResult) { + OperationResult result = parentResult.subresult(OP_SEARCH_SHADOW_OWNER) + .addParam("shadowOid", shadowOid) + .addArbitraryObjectCollectionAsParam("options", options) + .build(); + try { + // TODO cache the search operation? + PrismObject ownerObject; + Long startTime = repoOpStart(); + try { + ownerObject = repositoryService.searchShadowOwner(shadowOid, options, result); + } finally { + repoOpEnd(startTime); + } + if (ownerObject != null) { + Class type = ownerObject.getCompileTimeClass(); + if (type != null && PassReason.determine(options, type) == null) { + boolean readOnly = isReadOnly(findRootOptions(options)); + CacheSetAccessInfo caches = cacheSetAccessInfoFactory.determine(type); + cacheUpdater.storeLoadedObjectToAll(ownerObject, caches, readOnly); + } + } + + return ownerObject; + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + result.computeStatusIfUnknown(); + } + } + + public SearchResultList searchContainers(Class type, ObjectQuery query, Collection> options, OperationResult parentResult) throws SchemaException { + OperationResult result = parentResult.subresult(OP_SEARCH_CONTAINERS) + .addQualifier(type.getSimpleName()) + .addParam("type", type) + .addParam("query", query) + .addArbitraryObjectAsParam("options", options) + .build(); + Long startTime = repoOpStart(); + try { + return repositoryService.searchContainers(type, query, options, result); + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + repoOpEnd(startTime); + result.computeStatusIfUnknown(); + } + } + + public int countContainers(Class type, ObjectQuery query, + Collection> options, OperationResult parentResult) { + OperationResult result = parentResult.subresult(OP_COUNT_CONTAINERS) + .addQualifier(type.getSimpleName()) + .addParam("type", type) + .addParam("query", query) + .addArbitraryObjectCollectionAsParam("options", options) + .build(); + MonitoringUtil.log("Cache: PASS countContainers ({})", false, type.getSimpleName()); + Long startTime = repoOpStart(); + try { + return repositoryService.countContainers(type, query, options, result); + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + repoOpEnd(startTime); + result.computeStatusIfUnknown(); + } + } + + public int countObjects(Class type, ObjectQuery query, + Collection> options, OperationResult parentResult) + throws SchemaException { + // TODO use cached query result if applicable + OperationResult result = parentResult.subresult(OP_COUNT_OBJECTS) + .addQualifier(type.getSimpleName()) + .addParam("type", type) + .addParam("query", query) + .addArbitraryObjectCollectionAsParam("options", options) + .build(); + MonitoringUtil.log("Cache: PASS countObjects ({})", false, type.getSimpleName()); + Long startTime = repoOpStart(); + try { + return repositoryService.countObjects(type, query, options, result); + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + repoOpEnd(startTime); + result.computeStatusIfUnknown(); + } + } +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/ChangeDescription.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/invalidation/ChangeDescription.java similarity index 86% rename from repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/ChangeDescription.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/invalidation/ChangeDescription.java index b5bbdc497b5..df32685b7d3 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/ChangeDescription.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/invalidation/ChangeDescription.java @@ -1,11 +1,11 @@ /* - * Copyright (c) 2010-2019 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.repo.cache; +package com.evolveum.midpoint.repo.cache.invalidation; import com.evolveum.midpoint.CacheInvalidationContext; import com.evolveum.midpoint.prism.PrismObject; @@ -14,6 +14,8 @@ import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.repo.api.DeleteObjectResult; import com.evolveum.midpoint.repo.api.ModifyObjectResult; +import com.evolveum.midpoint.repo.cache.handlers.AddObjectResult; +import com.evolveum.midpoint.repo.cache.local.QueryKey; import com.evolveum.midpoint.schema.SearchResultList; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; @@ -34,8 +36,15 @@ public abstract class ChangeDescription { private static final Trace LOGGER = TraceManager.getTrace(ChangeDescription.class); - protected Class type; // changed object type - protected String oid; // changed object oid + /** + * Type of the changed object. + */ + final protected Class type; + + /** + * OID of the changed object. + */ + final protected String oid; ChangeDescription(Class type, String oid) { this.type = type; @@ -46,9 +55,9 @@ public abstract class ChangeDescription { * Describes an OBJECT ADD operation. */ static class Add extends ChangeDescription { - private AddObjectResult addInfo; + private final AddObjectResult addInfo; - Add(Class type, String oid, AddObjectResult addInfo) { + private Add(Class type, String oid, AddObjectResult addInfo) { super(type, oid); this.addInfo = addInfo; } @@ -68,9 +77,9 @@ public boolean mayMatchAfterChange(@NotNull ObjectFilter filter, SearchResultLis * Describes an OBJECT MODIFY operation. */ static class Modify extends ChangeDescription { - private ModifyObjectResult modifyInfo; + private final ModifyObjectResult modifyInfo; - Modify(Class type, String oid, ModifyObjectResult modifyInfo) { + private Modify(Class type, String oid, ModifyObjectResult modifyInfo) { super(type, oid); this.modifyInfo = modifyInfo; } @@ -101,7 +110,7 @@ public String toString() { * Describes an OBJECT DELETE operation. */ static class Delete extends ChangeDescription { - Delete(Class type, String oid) { + private Delete(Class type, String oid) { super(type, oid); } @@ -120,10 +129,10 @@ public String toString() { } } - static final class Any extends ChangeDescription { + static final class Unknown extends ChangeDescription { private final boolean safeInvalidation; - private Any(Class type, String oid, boolean safeInvalidation) { + private Unknown(Class type, String oid, boolean safeInvalidation) { super(type, oid); this.safeInvalidation = safeInvalidation; } @@ -149,8 +158,8 @@ public String toString() { public static ChangeDescription getFrom(Class type, String oid, CacheInvalidationContext context, boolean safeInvalidation) { Object additionalInfo; if (context != null) { - additionalInfo = context.getDetails() instanceof RepositoryCache.RepositoryCacheInvalidationDetails ? - ((RepositoryCache.RepositoryCacheInvalidationDetails) context.getDetails()).getObject() : null; + additionalInfo = context.getDetails() instanceof RepositoryCacheInvalidationDetails ? + ((RepositoryCacheInvalidationDetails) context.getDetails()).getObject() : null; } else { additionalInfo = null; } @@ -170,7 +179,7 @@ public static ChangeDescription getFrom(Class type, String boolean isTricky = LookupTableType.class.equals(type) || AccessCertificationCampaignType.class.equals(type); if (isTricky || additionalInfo == null) { - return new Any(type, oid, safeInvalidation); + return new Unknown(type, oid, safeInvalidation); } else if (additionalInfo instanceof AddObjectResult) { return new Add(type, oid, (AddObjectResult) additionalInfo); } else if (additionalInfo instanceof ModifyObjectResult) { @@ -182,7 +191,7 @@ public static ChangeDescription getFrom(Class type, String } } - private boolean queryTypeMatches(QueryKey queryKey) { + private boolean queryTypeMatches(QueryKey queryKey) { return queryKey.getType().isAssignableFrom(type); } @@ -190,7 +199,7 @@ private boolean queryTypeMatches(QueryKey queryKey) { * Returns true if the given change may affect the result of a given query. * Better be conservative and say "true" even if we are not sure. */ - boolean mayAffect(QueryKey queryKey, SearchResultList list, MatchingRuleRegistry matchingRuleRegistry) { + boolean mayAffect(QueryKey queryKey, SearchResultList list, MatchingRuleRegistry matchingRuleRegistry) { if (!queryTypeMatches(queryKey)) { return false; } @@ -237,5 +246,4 @@ private static boolean listContainsOid(SearchResultList list, String oid) { } return false; } - } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/invalidation/Invalidator.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/invalidation/Invalidator.java new file mode 100644 index 00000000000..5c6ca55c83f --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/invalidation/Invalidator.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.invalidation; + +import com.evolveum.midpoint.CacheInvalidationContext; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.match.MatchingRuleRegistry; +import com.evolveum.midpoint.repo.api.CacheDispatcher; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.repo.cache.global.GlobalObjectCache; +import com.evolveum.midpoint.repo.cache.global.GlobalQueryCache; +import com.evolveum.midpoint.repo.cache.global.GlobalVersionCache; +import com.evolveum.midpoint.repo.cache.local.LocalObjectCache; +import com.evolveum.midpoint.repo.cache.local.LocalQueryCache; +import com.evolveum.midpoint.repo.cache.local.LocalVersionCache; +import com.evolveum.midpoint.repo.cache.local.QueryKey; +import com.evolveum.midpoint.repo.cache.registry.CacheRegistry; +import com.evolveum.midpoint.schema.SearchResultList; +import com.evolveum.midpoint.schema.cache.CacheConfigurationManager; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.FunctionLibraryType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.evolveum.midpoint.repo.cache.RepositoryCache.*; +import static com.evolveum.midpoint.repo.cache.local.LocalRepoCacheCollection.*; + +/** + * Contains functionality related to cache entry invalidation. + */ +@Component +public class Invalidator { + + public static final Trace LOGGER = TraceManager.getTrace(Invalidator.class); + + private static final List> TYPES_ALWAYS_INVALIDATED_CLUSTERWIDE = Arrays.asList( + SystemConfigurationType.class, + FunctionLibraryType.class); + + @Autowired private GlobalQueryCache globalQueryCache; + @Autowired private GlobalObjectCache globalObjectCache; + @Autowired private GlobalVersionCache globalVersionCache; + @Autowired PrismContext prismContext; + @Autowired RepositoryService repositoryService; + @Autowired CacheDispatcher cacheDispatcher; + @Autowired CacheRegistry cacheRegistry; + @Autowired MatchingRuleRegistry matchingRuleRegistry; + @Autowired CacheConfigurationManager cacheConfigurationManager; + @Autowired Invalidator invalidator; + + // This is what is called from cache dispatcher (on local node with the full context; on remote nodes with reduced context) + public void invalidate(Class type, String oid, CacheInvalidationContext context) { + if (type == null) { + globalObjectCache.clear(); + globalVersionCache.clear(); + globalQueryCache.clear(); + } else { + globalObjectCache.remove(type, oid); + globalVersionCache.remove(type, oid); + if (ObjectType.class.isAssignableFrom(type)) { + //noinspection unchecked + clearQueryResultsGlobally((Class) type, oid, context); + } + } + } + + public void invalidateCacheEntries(Class type, String oid, Object additionalInfo, OperationResult parentResult) { + OperationResult result = parentResult.subresult(CLASS_NAME_WITH_DOT + "invalidateCacheEntries") + .setMinor() + .addParam("type", type) + .addParam("oid", oid) + .addParam("additionalInfo", additionalInfo != null ? additionalInfo.getClass().getSimpleName() : "none") + .build(); + try { + LocalObjectCache localObjectCache = getLocalObjectCache(); + if (localObjectCache != null) { + localObjectCache.remove(oid); + } + LocalVersionCache localVersionCache = getLocalVersionCache(); + if (localVersionCache != null) { + localVersionCache.remove(oid); + } + LocalQueryCache localQueryCache = getLocalQueryCache(); + if (localQueryCache != null) { + clearQueryResultsLocally(localQueryCache, type, oid, additionalInfo, matchingRuleRegistry); + } + boolean clusterwide = TYPES_ALWAYS_INVALIDATED_CLUSTERWIDE.contains(type) || + globalObjectCache.hasClusterwideInvalidationFor(type) || + globalVersionCache.hasClusterwideInvalidationFor(type) || + globalQueryCache.hasClusterwideInvalidationFor(type); + cacheDispatcher.dispatchInvalidation(type, oid, clusterwide, + new CacheInvalidationContext(false, new RepositoryCacheInvalidationDetails(additionalInfo))); + } catch (Throwable t) { + result.recordFatalError(t); + throw t; // Really? We want the operation to proceed anyway. But OTOH we want to be sure devel team gets notified about this. + } finally { + result.computeStatusIfUnknown(); + } + } + + private void clearQueryResultsLocally(LocalQueryCache cache, Class type, String oid, + Object additionalInfo, MatchingRuleRegistry matchingRuleRegistry) { + // TODO implement more efficiently + + ChangeDescription change = ChangeDescription.getFrom(type, oid, additionalInfo, true); + + long start = System.currentTimeMillis(); + int all = 0; + int removed = 0; + Iterator> iterator = cache.getEntryIterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + QueryKey queryKey = entry.getKey(); + all++; + if (change.mayAffect(queryKey, entry.getValue(), matchingRuleRegistry)) { + LOGGER.trace("Removing (from local cache) query for type={}, change={}: {}", type, change, queryKey.getQuery()); + iterator.remove(); + removed++; + } + } + LOGGER.trace("Removed (from local cache) {} (of {}) query result entries of type {} in {} ms", removed, all, type, System.currentTimeMillis() - start); + } + + private void clearQueryResultsGlobally(Class type, String oid, CacheInvalidationContext context) { + // TODO implement more efficiently + + // Safe invalidation means we evict queries without looking at details of the change. + boolean safeIfUnknown = !context.isFromRemoteNode() || globalQueryCache.shouldDoSafeRemoteInvalidationFor(type); + ChangeDescription change = ChangeDescription.getFrom(type, oid, context, safeIfUnknown); + + long start = System.currentTimeMillis(); + AtomicInteger all = new AtomicInteger(0); + AtomicInteger removed = new AtomicInteger(0); + + globalQueryCache.invokeAll(entry -> { + QueryKey queryKey = entry.getKey(); + all.incrementAndGet(); + if (change.mayAffect(queryKey, entry.getValue().getResult(), matchingRuleRegistry)) { + LOGGER.trace("Removing (from global cache) query for type={}, change={}: {}", type, change, queryKey.getQuery()); + entry.remove(); + removed.incrementAndGet(); + } + return null; + }); + LOGGER.trace("Removed (from global cache) {} (of {}) query result entries of type {} in {} ms", removed, all, type, System.currentTimeMillis() - start); + } + +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/invalidation/RepositoryCacheInvalidationDetails.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/invalidation/RepositoryCacheInvalidationDetails.java new file mode 100644 index 00000000000..df02ad4abc8 --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/invalidation/RepositoryCacheInvalidationDetails.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.invalidation; + +import com.evolveum.midpoint.repo.api.CacheInvalidationDetails; +import com.evolveum.midpoint.util.annotation.Experimental; + +/** + * TODO + */ +@Experimental +public final class RepositoryCacheInvalidationDetails implements CacheInvalidationDetails { + private final Object details; + + RepositoryCacheInvalidationDetails(Object details) { + this.details = details; + } + + public Object getObject() { + return details; + } +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalObjectCache.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/local/LocalObjectCache.java similarity index 76% rename from repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalObjectCache.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/local/LocalObjectCache.java index ea959110a17..8f6321d82d0 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalObjectCache.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/local/LocalObjectCache.java @@ -1,11 +1,11 @@ /* - * Copyright (c) 2010-2019 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.repo.cache; +package com.evolveum.midpoint.repo.cache.local; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.util.caching.AbstractThreadLocalCache; @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap; /** - * + * Thread-local cache for storing objects. */ public class LocalObjectCache extends AbstractThreadLocalCache { @@ -25,11 +25,17 @@ public class LocalObjectCache extends AbstractThreadLocalCache { private final Map> data = new ConcurrentHashMap<>(); - public PrismObject get(String oid) { - return data.get(oid); + public PrismObject get(String oid) { + //noinspection unchecked + return (PrismObject) data.get(oid); + } + + public void put(PrismObject object) { + put(object.getOid(), object); } public void put(String oid, PrismObject object) { + object.checkImmutable(); data.put(oid, object); } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalQueryCache.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/local/LocalQueryCache.java similarity index 72% rename from repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalQueryCache.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/local/LocalQueryCache.java index cba25e2edbe..63f81fa734b 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalQueryCache.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/local/LocalQueryCache.java @@ -1,17 +1,20 @@ /* - * Copyright (c) 2010-2019 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.repo.cache; +package com.evolveum.midpoint.repo.cache.local; +import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.schema.SearchResultList; import com.evolveum.midpoint.util.caching.AbstractThreadLocalCache; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + import org.jetbrains.annotations.NotNull; import java.util.Iterator; @@ -19,7 +22,7 @@ import java.util.concurrent.ConcurrentHashMap; /** - * + * Thread-local cache for storing query results. */ public class LocalQueryCache extends AbstractThreadLocalCache { @@ -27,12 +30,14 @@ public class LocalQueryCache extends AbstractThreadLocalCache { private final Map data = new ConcurrentHashMap<>(); - public SearchResultList get(QueryKey key) { + public SearchResultList> get(QueryKey key) { + //noinspection unchecked return data.get(key); } - public void put(QueryKey key, @NotNull SearchResultList objects) { - data.put(key, objects); + public void put(QueryKey key, @NotNull SearchResultList list) { + list.checkImmutable(); + data.put(key, list); } public void remove(QueryKey key) { @@ -57,7 +62,7 @@ public void dumpContent(String threadName) { } @SuppressWarnings("SameParameterValue") - static int getTotalCachedObjects(ConcurrentHashMap cacheInstances) { + public static int getTotalCachedObjects(ConcurrentHashMap cacheInstances) { int rv = 0; for (LocalQueryCache cacheInstance : cacheInstances.values()) { rv += cacheInstance.getCachedObjects(); @@ -73,7 +78,7 @@ private int getCachedObjects() { return rv; } - Iterator> getEntryIterator() { + public Iterator> getEntryIterator() { return data.entrySet().iterator(); } } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/local/LocalRepoCacheCollection.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/local/LocalRepoCacheCollection.java new file mode 100644 index 00000000000..8243b4d7bee --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/local/LocalRepoCacheCollection.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.local; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.schema.cache.CacheConfigurationManager; +import com.evolveum.midpoint.util.caching.CacheConfiguration; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SingleCacheStateInformationType; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import static com.evolveum.midpoint.schema.cache.CacheType.*; + +/** + * Set of three thread-local repo caches (object, version, query). + */ +@Component +public class LocalRepoCacheCollection { + + public static final Trace LOGGER = TraceManager.getTrace(LocalRepoCacheCollection.class); + + @Autowired private PrismContext prismContext; + + private static final ConcurrentHashMap LOCAL_OBJECT_CACHE_INSTANCE = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap LOCAL_VERSION_CACHE_INSTANCE = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap LOCAL_QUERY_CACHE_INSTANCE = new ConcurrentHashMap<>(); + + public static LocalObjectCache getLocalObjectCache() { + return LOCAL_OBJECT_CACHE_INSTANCE.get(Thread.currentThread()); + } + + public static LocalVersionCache getLocalVersionCache() { + return LOCAL_VERSION_CACHE_INSTANCE.get(Thread.currentThread()); + } + + public static LocalQueryCache getLocalQueryCache() { + return LOCAL_QUERY_CACHE_INSTANCE.get(Thread.currentThread()); + } + + public static void destroy() { + LocalObjectCache.destroy(LOCAL_OBJECT_CACHE_INSTANCE, LOGGER); + LocalVersionCache.destroy(LOCAL_VERSION_CACHE_INSTANCE, LOGGER); + LocalQueryCache.destroy(LOCAL_QUERY_CACHE_INSTANCE, LOGGER); + } + + public static void enter(CacheConfigurationManager mgr) { + // let's compute configuration first -- an exception can be thrown there; so if it happens, none of the caches + // will be entered into upon exit of this method + CacheConfiguration objectCacheConfig = mgr.getConfiguration(LOCAL_REPO_OBJECT_CACHE); + CacheConfiguration versionCacheConfig = mgr.getConfiguration(LOCAL_REPO_VERSION_CACHE); + CacheConfiguration queryCacheConfig = mgr.getConfiguration(LOCAL_REPO_QUERY_CACHE); + + LocalObjectCache.enter(LOCAL_OBJECT_CACHE_INSTANCE, LocalObjectCache.class, objectCacheConfig, LOGGER); + LocalVersionCache.enter(LOCAL_VERSION_CACHE_INSTANCE, LocalVersionCache.class, versionCacheConfig, LOGGER); + LocalQueryCache.enter(LOCAL_QUERY_CACHE_INSTANCE, LocalQueryCache.class, queryCacheConfig, LOGGER); + } + + public static void exit() { + LocalObjectCache.exit(LOCAL_OBJECT_CACHE_INSTANCE, LOGGER); + LocalVersionCache.exit(LOCAL_VERSION_CACHE_INSTANCE, LOGGER); + LocalQueryCache.exit(LOCAL_QUERY_CACHE_INSTANCE, LOGGER); + } + + public static boolean exists() { + return LocalObjectCache.exists(LOCAL_OBJECT_CACHE_INSTANCE) || + LocalVersionCache.exists(LOCAL_VERSION_CACHE_INSTANCE) || + LocalQueryCache.exists(LOCAL_QUERY_CACHE_INSTANCE); + } + + public static String debugDump() { + // TODO + return LocalObjectCache.debugDump(LOCAL_OBJECT_CACHE_INSTANCE) + "\n" + + LocalVersionCache.debugDump(LOCAL_VERSION_CACHE_INSTANCE) + "\n" + + LocalQueryCache.debugDump(LOCAL_QUERY_CACHE_INSTANCE); + } + + public void getStateInformation(List rv) { + rv.add(new SingleCacheStateInformationType(prismContext) + .name(LocalObjectCache.class.getName()) + .size(LocalObjectCache.getTotalSize(LOCAL_OBJECT_CACHE_INSTANCE))); + rv.add(new SingleCacheStateInformationType(prismContext) + .name(LocalVersionCache.class.getName()) + .size(LocalVersionCache.getTotalSize(LOCAL_VERSION_CACHE_INSTANCE))); + rv.add(new SingleCacheStateInformationType(prismContext) + .name(LocalQueryCache.class.getName()) + .size(LocalQueryCache.getTotalSize(LOCAL_QUERY_CACHE_INSTANCE)) + .secondarySize(LocalQueryCache.getTotalCachedObjects(LOCAL_QUERY_CACHE_INSTANCE))); + + } + + public void dumpContent() { + LocalObjectCache.dumpContent(LOCAL_OBJECT_CACHE_INSTANCE); + LocalVersionCache.dumpContent(LOCAL_VERSION_CACHE_INSTANCE); + LocalQueryCache.dumpContent(LOCAL_QUERY_CACHE_INSTANCE); + } +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalVersionCache.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/local/LocalVersionCache.java similarity index 81% rename from repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalVersionCache.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/local/LocalVersionCache.java index 25a87ac5f13..d3ddf969f36 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/LocalVersionCache.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/local/LocalVersionCache.java @@ -1,12 +1,13 @@ /* - * Copyright (c) 2010-2019 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.repo.cache; +package com.evolveum.midpoint.repo.cache.local; +import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.util.caching.AbstractThreadLocalCache; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; @@ -15,7 +16,7 @@ import java.util.concurrent.ConcurrentHashMap; /** - * + * Thread-local cache for object version. */ public class LocalVersionCache extends AbstractThreadLocalCache { @@ -27,6 +28,10 @@ public String get(String oid) { return data.get(oid); } + public void put(PrismObject object) { + put(object.getOid(), object.getVersion()); + } + public void put(String oid, String version) { data.put(oid, version); } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/QueryKey.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/local/QueryKey.java similarity index 79% rename from repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/QueryKey.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/local/QueryKey.java index 807e230dd08..935f8e079da 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/QueryKey.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/local/QueryKey.java @@ -1,11 +1,11 @@ /* - * Copyright (c) 2010-2019 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.repo.cache; +package com.evolveum.midpoint.repo.cache.local; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; @@ -18,13 +18,13 @@ * Key for repository query cache. The query is stored as a clone, in order to make sure it won't be * changed during the lifetime of the cache entry. */ -public class QueryKey { +public class QueryKey { - @NotNull private final Class type; + @NotNull private final Class type; private final ObjectQuery query; private Integer cachedHashCode; - QueryKey(@NotNull Class type, ObjectQuery query) { + public QueryKey(@NotNull Class type, ObjectQuery query) { this.type = type; this.query = query != null ? query.clone() : null; } @@ -36,7 +36,7 @@ public boolean equals(Object o) { } else if (!(o instanceof QueryKey)) { return false; } else { - QueryKey queryKey = (QueryKey) o; + QueryKey queryKey = (QueryKey) o; return Objects.equals(type, queryKey.type) && Objects.equals(query, queryKey.query); } @@ -50,7 +50,7 @@ public int hashCode() { return cachedHashCode; } - @NotNull public Class getType() { + @NotNull public Class getType() { return type; } diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/other/MonitoringUtil.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/other/MonitoringUtil.java new file mode 100644 index 00000000000..48c5c9615e0 --- /dev/null +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/other/MonitoringUtil.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.cache.other; + +import com.evolveum.midpoint.repo.api.RepositoryPerformanceMonitor; +import com.evolveum.midpoint.repo.cache.RepositoryCache; +import com.evolveum.midpoint.schema.util.DiagnosticContextHolder; +import com.evolveum.midpoint.util.caching.CacheUtil; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; + +/** + * Utility methods related to operations and cache performance monitoring. + */ +public class MonitoringUtil { + + private static final Trace REPO_CACHE_LOGGER = TraceManager.getTrace(RepositoryCache.class); + private static final Trace PERFORMANCE_ADVISOR = TraceManager.getPerformanceAdvisorTrace(); + + public static Long repoOpStart() { + RepositoryPerformanceMonitor monitor = DiagnosticContextHolder.get(RepositoryPerformanceMonitor.class); + if (monitor == null) { + return null; + } else { + return System.currentTimeMillis(); + } + } + + public static void repoOpEnd(Long startTime) { + RepositoryPerformanceMonitor monitor = DiagnosticContextHolder.get(RepositoryPerformanceMonitor.class); + if (monitor != null) { + monitor.recordRepoOperation(System.currentTimeMillis() - startTime); + } + } + + public static void log(String message, boolean info, Object... params) { + CacheUtil.log(REPO_CACHE_LOGGER, PERFORMANCE_ADVISOR, message, info, params); + } +} diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheDispatcherImpl.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/registry/CacheDispatcherImpl.java similarity index 78% rename from repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheDispatcherImpl.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/registry/CacheDispatcherImpl.java index cb659c14ef6..859634eb208 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheDispatcherImpl.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/registry/CacheDispatcherImpl.java @@ -1,11 +1,11 @@ /* - * Copyright (c) 2010-2018 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.repo.cache; +package com.evolveum.midpoint.repo.cache.registry; import com.evolveum.midpoint.CacheInvalidationContext; import com.evolveum.midpoint.repo.api.CacheDispatcher; @@ -20,6 +20,16 @@ import org.jetbrains.annotations.Nullable; import org.springframework.stereotype.Component; +/** + * Dispatches cache-related events - mainly invalidation ones - to all relevant listeners: + * {@link CacheRegistry} (grouping local cacheable services) and ClusterCacheListener (for + * inter-node distribution). + * + * Could be reworked in the future. + * + * Note that this class resides in repo-cache module almost by accident and perhaps should + * be moved to a more appropriate place. + */ @Component public class CacheDispatcherImpl implements CacheDispatcher { diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheRegistry.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/registry/CacheRegistry.java similarity index 89% rename from repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheRegistry.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/registry/CacheRegistry.java index 591f779ba30..078ba2c56ba 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheRegistry.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/registry/CacheRegistry.java @@ -1,10 +1,10 @@ /* - * Copyright (c) 2010-2019 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.repo.cache; +package com.evolveum.midpoint.repo.cache.registry; import java.util.ArrayList; import java.util.List; @@ -22,6 +22,12 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.CachesStateInformationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +/** + * Registry of all local cacheable services. + * + * Note that this class resides in repo-cache module almost by accident and perhaps should + * be moved to a more appropriate place. + */ @Component public class CacheRegistry implements CacheListener { @@ -50,10 +56,6 @@ public synchronized void unregisterCacheableService(Cacheable cacheableService) cacheableServices.remove(cacheableService); } - public List getCacheableServices() { - return cacheableServices; - } - @Override public void invalidate(Class type, String oid, boolean clusterwide, CacheInvalidationContext context) { diff --git a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/SystemConfigurationCacheableAdapter.java b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/SystemConfigurationCacheableAdapter.java index b3327950b81..f0b0dd45a8c 100644 --- a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/SystemConfigurationCacheableAdapter.java +++ b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/SystemConfigurationCacheableAdapter.java @@ -10,7 +10,7 @@ import com.evolveum.midpoint.CacheInvalidationContext; import com.evolveum.midpoint.repo.api.Cacheable; import com.evolveum.midpoint.repo.api.SystemConfigurationChangeDispatcher; -import com.evolveum.midpoint.repo.cache.CacheRegistry; +import com.evolveum.midpoint.repo.cache.registry.CacheRegistry; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.util.logging.LoggingUtils; import com.evolveum.midpoint.util.logging.Trace; @@ -27,7 +27,8 @@ import java.util.Collections; /** - * @author mederly + * Adapter from SystemConfigurationChangeDispatcher to Cacheable. Distributes events about system configuration + * invalidation changes. */ @Component public class SystemConfigurationCacheableAdapter implements Cacheable { diff --git a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionFactory.java b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionFactory.java index a5fc736084f..031db5ca40f 100644 --- a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionFactory.java +++ b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionFactory.java @@ -20,7 +20,7 @@ import com.evolveum.midpoint.common.LocalizationService; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.repo.api.Cacheable; -import com.evolveum.midpoint.repo.cache.CacheRegistry; +import com.evolveum.midpoint.repo.cache.registry.CacheRegistry; import com.evolveum.midpoint.repo.common.ObjectResolver; import com.evolveum.midpoint.schema.expression.ExpressionProfile; import com.evolveum.midpoint.schema.result.OperationResult; diff --git a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/task/AbstractSearchIterativeResultHandler.java b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/task/AbstractSearchIterativeResultHandler.java index 914d890db04..d8bb48407aa 100644 --- a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/task/AbstractSearchIterativeResultHandler.java +++ b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/task/AbstractSearchIterativeResultHandler.java @@ -27,14 +27,6 @@ import com.evolveum.midpoint.schema.ResultHandler; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.util.exception.CommonException; -import com.evolveum.midpoint.util.exception.CommunicationException; -import com.evolveum.midpoint.util.exception.ConfigurationException; -import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; -import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.PolicyViolationException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; @@ -314,7 +306,7 @@ private void processRequest(ProcessingRequest request, RunningTask workerTask, O long startTime = System.currentTimeMillis(); - RepositoryCache.enter(taskManager.getCacheConfigurationManager()); + RepositoryCache.enterLocalCaches(taskManager.getCacheConfigurationManager()); try { if (!isNonScavengingWorker()) { // todo configure this somehow @@ -370,7 +362,7 @@ private void processRequest(ProcessingRequest request, RunningTask workerTask, O cont = processError(object, workerTask, e, result); } finally { - RepositoryCache.exit(); + RepositoryCache.exitLocalCaches(); workerTask.stopDynamicProfiling(); workerTask.stopTracing(); diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/IntegrationTestTools.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/IntegrationTestTools.java index 244e0e0f701..c60132bccaf 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/IntegrationTestTools.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/IntegrationTestTools.java @@ -12,6 +12,8 @@ import java.util.stream.Collectors; import javax.xml.namespace.QName; +import com.evolveum.midpoint.repo.cache.local.LocalRepoCacheCollection; + import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.opends.server.types.Entry; @@ -38,7 +40,6 @@ import com.evolveum.midpoint.prism.util.PrismTestUtil; import com.evolveum.midpoint.prism.util.PrismUtil; import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.repo.cache.RepositoryCache; import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.SearchResultList; import com.evolveum.midpoint.schema.constants.ConnectorTestOperation; @@ -782,9 +783,9 @@ public static void assertNotInMessageRecursive(Throwable e, String substring) { } } - public static void assertNoRepoCache() { - if (RepositoryCache.exists()) { - AssertJUnit.fail("Cache exists! " + RepositoryCache.debugDump()); + public static void assertNoRepoThreadLocalCache() { + if (LocalRepoCacheCollection.exists()) { + AssertJUnit.fail("Cache exists! " + LocalRepoCacheCollection.debugDump()); } } diff --git a/testing/sanity/src/test/java/com/evolveum/midpoint/testing/sanity/TestSanity.java b/testing/sanity/src/test/java/com/evolveum/midpoint/testing/sanity/TestSanity.java index c00cfd3415f..a017567e1b8 100644 --- a/testing/sanity/src/test/java/com/evolveum/midpoint/testing/sanity/TestSanity.java +++ b/testing/sanity/src/test/java/com/evolveum/midpoint/testing/sanity/TestSanity.java @@ -352,7 +352,7 @@ public void test000Integrity() throws Exception { assertNotNull("No my:shipState definition", shipStateDefinition); assertEquals("Wrong maxOccurs in my:shipState definition", 1, shipStateDefinition.getMaxOccurs()); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); Task task = taskManager.createTaskInstance(TestSanity.class.getName() + ".test000Integrity"); OperationResult result = task.getResult(); @@ -363,7 +363,7 @@ public void test000Integrity() throws Exception { display("Imported OpenDJ resource (repository)", openDjResource); AssertJUnit.assertEquals(RESOURCE_OPENDJ_OID, openDjResource.getOid()); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); String ldapConnectorOid = openDjResource.asObjectable().getConnectorRef().getOid(); PrismObject ldapConnector = repositoryService.getObject(ConnectorType.class, ldapConnectorOid, null, result); @@ -407,14 +407,14 @@ public void test001SelfTests() { public void test001TestConnectionOpenDJ() throws Exception { // GIVEN try { - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); // WHEN OperationResultType result = modelWeb.testResource(RESOURCE_OPENDJ_OID); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("testResource result:", result, SchemaConstants.C_RESULT); @@ -425,7 +425,7 @@ public void test001TestConnectionOpenDJ() throws Exception { PrismObject resourceOpenDjRepo = repositoryService.getObject(ResourceType.class, RESOURCE_OPENDJ_OID, null, opResult); resourceTypeOpenDjrepo = resourceOpenDjRepo.asObjectable(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); assertEquals(RESOURCE_OPENDJ_OID, resourceTypeOpenDjrepo.getOid()); display("Initialized OpenDJ resource (respository)", resourceTypeOpenDjrepo); assertNotNull("Resource schema was not generated", resourceTypeOpenDjrepo.getSchema()); @@ -580,7 +580,7 @@ public void test002AddDerbyResource() throws Exception { OperationResult result = createOperationResult(); checkRepoOpenDjResource(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); PrismObject resource = PrismTestUtil.parseObject(new File(RESOURCE_DERBY_FILENAME)); assertParentConsistency(resource); @@ -600,7 +600,7 @@ public void test002AddDerbyResource() throws Exception { PrismObject derbyResource = repositoryService.getObject(ResourceType.class, RESOURCE_DERBY_OID, null, result); AssertJUnit.assertEquals(RESOURCE_DERBY_OID, derbyResource.getOid()); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); String dbConnectorOid = derbyResource.asObjectable().getConnectorRef().getOid(); PrismObject dbConnector = repositoryService.getObject(ConnectorType.class, dbConnectorOid, null, result); @@ -684,14 +684,14 @@ public void test003TestConnectionDerby() throws Exception { // GIVEN checkRepoDerbyResource(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); // WHEN OperationResultType result = modelWeb.testResource(RESOURCE_DERBY_OID); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("testResource result:", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("testResource has failed", result.getPartialResults().get(0)); @@ -702,7 +702,7 @@ public void test003TestConnectionDerby() throws Exception { resourceDerby = rObject.asObjectable(); checkDerbyResource(rObject, "repository(after test)"); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); assertEquals(RESOURCE_DERBY_OID, resourceDerby.getOid()); display("Initialized Derby resource (respository)", resourceDerby); assertNotNull("Resource schema was not generated", resourceDerby.getSchema()); @@ -732,7 +732,7 @@ public void test004Capabilities() checkRepoOpenDjResource(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); Holder resultHolder = new Holder<>(); Holder objectHolder = new Holder<>(); @@ -747,7 +747,7 @@ public void test004Capabilities() // THEN display("Resource", resource); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); CapabilityCollectionType nativeCapabilities = resource.getCapabilities().getNative(); List capabilities = nativeCapabilities.getAny(); @@ -855,7 +855,7 @@ public void test006reimportResourceDummy() throws Exception { public void test010AddUser() throws Exception { // GIVEN checkRepoOpenDjResource(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); PrismObject user = PrismTestUtil.parseObject(USER_JACK_FILE); UserType userType = user.asObjectable(); @@ -876,7 +876,7 @@ public void test010AddUser() throws Exception { // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("addObject result:", resultHolder.value, SchemaConstants.C_RESULT); TestUtil.assertSuccess("addObject has failed", resultHolder.value); @@ -905,7 +905,7 @@ public void test013AddOpenDjAccountToUser() throws Exception { try { // GIVEN checkRepoOpenDjResource(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); // IMPORTANT! SWITCHING OFF ASSIGNMENT ENFORCEMENT HERE! setAssignmentEnforcement(AssignmentPolicyEnforcementType.NONE); @@ -921,7 +921,7 @@ public void test013AddOpenDjAccountToUser() throws Exception { // THEN then(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("modifyObject has failed", result); @@ -985,7 +985,7 @@ public void test013AddOpenDjAccountToUser() throws Exception { // Use getObject to test fetch of complete shadow - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); Holder resultHolder = new Holder<>(); Holder objectHolder = new Holder<>(); @@ -996,7 +996,7 @@ public void test013AddOpenDjAccountToUser() throws Exception { options, objectHolder, resultHolder); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("getObject result", resultHolder.value, SchemaConstants.C_RESULT); TestUtil.assertSuccess("getObject has failed", resultHolder.value); @@ -1042,7 +1042,7 @@ public void test014AddDerbyAccountToUser() throws IOException, JAXBException, Fa // GIVEN checkRepoDerbyResource(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); ObjectDeltaType objectChange = unmarshalValueFromFile( REQUEST_USER_MODIFY_ADD_ACCOUNT_DERBY_FILENAME, ObjectDeltaType.class); @@ -1051,7 +1051,7 @@ public void test014AddDerbyAccountToUser() throws IOException, JAXBException, Fa OperationResultType result = modifyObjectViaModelWS(objectChange); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("modifyObject has failed", result); @@ -1118,7 +1118,7 @@ public void test014AddDerbyAccountToUser() throws IOException, JAXBException, Fa // Use getObject to test fetch of complete shadow - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); Holder resultHolder = new Holder<>(); Holder objectHolder = new Holder<>(); @@ -1129,7 +1129,7 @@ public void test014AddDerbyAccountToUser() throws IOException, JAXBException, Fa options, objectHolder, resultHolder); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("getObject result", resultHolder.value, SchemaConstants.C_RESULT); TestUtil.assertSuccess("getObject has failed", resultHolder.value); @@ -1149,7 +1149,7 @@ public void test014AddDerbyAccountToUser() throws IOException, JAXBException, Fa public void test015AccountOwner() throws FaultMessage, ObjectNotFoundException, SchemaException { // GIVEN checkRepoOpenDjResource(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); Holder resultHolder = new Holder<>(); Holder userHolder = new Holder<>(); @@ -1232,7 +1232,7 @@ public boolean handle(PrismObject prismObject, OperationResult paren public void test020ModifyUser() throws Exception { // GIVEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); ObjectDeltaType objectChange = unmarshalValueFromFile( REQUEST_USER_MODIFY_FULLNAME_LOCALITY_FILENAME, ObjectDeltaType.class); @@ -1241,7 +1241,7 @@ public void test020ModifyUser() throws Exception { OperationResultType result = modifyObjectViaModelWS(objectChange); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result:", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("modifyObject has failed", result); @@ -1332,7 +1332,7 @@ public void test022ChangeUserPassword() throws Exception { REQUEST_USER_MODIFY_PASSWORD_FILENAME, ObjectDeltaType.class); System.out.println("In modification: " + objectChange.getItemDelta().get(0).getValue().get(0)); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); // WHEN ObjectTypes.USER.getTypeQName(), OperationResultType result = modifyObjectViaModelWS(objectChange); @@ -1374,7 +1374,7 @@ public void test023ChangeUserPasswordJAXB() throws Exception { } private void assertUserPasswordChange(String expectedUserPassword, OperationResultType result) throws ObjectNotFoundException, SchemaException, DirectoryException, EncryptionException { - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result:", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("modifyObject has failed", result); @@ -1436,7 +1436,7 @@ public void test028ModifyAccountDjExplicitType() throws Exception { public void testModifyAccountDjRoomNumber(File reqFile, String expectedVal) throws Exception { // GIVEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); ObjectDeltaType objectChange = unmarshalValueFromFile(reqFile, ObjectDeltaType.class); objectChange.setOid(accountShadowOidOpendj); @@ -1445,7 +1445,7 @@ public void testModifyAccountDjRoomNumber(File reqFile, String expectedVal) thro OperationResultType result = modifyObjectViaModelWS(objectChange); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result:", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("modifyObject has failed", result); @@ -1470,7 +1470,7 @@ public void testModifyAccountDjRoomNumber(File reqFile, String expectedVal) thro @Test public void test029ModifyAccountDjBadPath() throws Exception { // GIVEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); ObjectDeltaType objectChange = unmarshalValueFromFile( REQUEST_ACCOUNT_MODIFY_BAD_PATH_FILE, ObjectDeltaType.class); @@ -1489,7 +1489,7 @@ public void test029ModifyAccountDjBadPath() throws Exception { } // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result:", result, SchemaConstants.C_RESULT); TestUtil.assertFailure(result); @@ -1527,7 +1527,7 @@ public void test030DisableUser() throws Exception { displayValue("ds-pwp-account-disabled before change", pwpAccountDisabled); assertTrue("LDAP account is not enabled (precondition)", openDJController.isAccountEnabled(entry)); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); // WHEN when(); @@ -1535,7 +1535,7 @@ public void test030DisableUser() throws Exception { // THEN then(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result:", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("modifyObject has failed", result); @@ -1588,7 +1588,7 @@ public void test030DisableUser() throws Exception { Holder resultHolder = new Holder<>(); Holder objectHolder = new Holder<>(); SelectorQualifiedGetOptionsType options = new SelectorQualifiedGetOptionsType(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); // WHEN when(); @@ -1597,7 +1597,7 @@ public void test030DisableUser() throws Exception { // THEN then(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("getObject result", resultHolder.value, SchemaConstants.C_RESULT); TestUtil.assertSuccess("getObject has failed", resultHolder.value); @@ -1629,13 +1629,13 @@ public void test031EnableUser() throws Exception { // GIVEN ObjectDeltaType objectChange = unmarshalValueFromFile( REQUEST_USER_MODIFY_ACTIVATION_ENABLE_FILENAME, ObjectDeltaType.class); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); // WHEN ObjectTypes.USER.getTypeQName(), OperationResultType result = modifyObjectViaModelWS(objectChange); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result:", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("modifyObject has failed", result); @@ -1680,14 +1680,14 @@ public void test031EnableUser() throws Exception { Holder resultHolder = new Holder<>(); Holder objectHolder = new Holder<>(); SelectorQualifiedGetOptionsType options = new SelectorQualifiedGetOptionsType(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); // WHEN modelWeb.getObject(ObjectTypes.SHADOW.getTypeQName(), accountShadowOidOpendj, options, objectHolder, resultHolder); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("getObject result", resultHolder.value, SchemaConstants.C_RESULT); TestUtil.assertSuccess("getObject has failed", resultHolder.value); @@ -1742,13 +1742,13 @@ public void test040UnlinkDerbyAccountFromUser() objectChange.setChangeType(ChangeTypeType.MODIFY); objectChange.setObjectType(UserType.COMPLEX_TYPE); displayJaxb("modifyObject input", objectChange, new QName(SchemaConstants.NS_C, "change")); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); // WHEN ObjectTypes.USER.getTypeQName(), OperationResultType result = modifyObjectViaModelWS(objectChange); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("modifyObject has failed", result); @@ -1778,13 +1778,13 @@ public void test041DeleteDerbyAccount() throws FaultMessage, SchemaException, SQLException { // GIVEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); // WHEN OperationResultType result = deleteObjectViaModelWS(ObjectTypes.SHADOW.getTypeQName(), accountShadowOidDerby); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("deleteObject result", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("deleteObject has failed", result); @@ -1824,7 +1824,7 @@ private OperationResultType deleteObjectViaModelWS(QName typeQName, String oid) @Test public void test047RenameUser() throws Exception { // GIVEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); ObjectDeltaType objectChange = unmarshalValueFromFile( REQUEST_USER_MODIFY_NAME_FILENAME, ObjectDeltaType.class); @@ -1833,7 +1833,7 @@ public void test047RenameUser() throws Exception { OperationResultType result = modifyObjectViaModelWS(objectChange); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result:", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("modifyObject has failed", result); @@ -1883,7 +1883,7 @@ public void test047RenameUser() throws Exception { @Test public void test048ModifyUserRemoveGivenName() throws Exception { // GIVEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); ObjectDeltaType objectChange = unmarshalValueFromFile( REQUEST_USER_MODIFY_GIVENNAME_FILENAME, ObjectDeltaType.class); displayJaxb("objectChange:", objectChange, SchemaConstants.T_OBJECT_DELTA); @@ -1892,7 +1892,7 @@ public void test048ModifyUserRemoveGivenName() throws Exception { OperationResultType result = modifyObjectViaModelWS(objectChange); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result:", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("modifyObject has failed", result); @@ -1941,13 +1941,13 @@ public void test048ModifyUserRemoveGivenName() throws Exception { public void test049DeleteUser() throws SchemaException, FaultMessage, DirectoryException { // GIVEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); // WHEN OperationResultType result = deleteObjectViaModelWS(ObjectTypes.USER.getTypeQName(), USER_JACK_OID); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("deleteObject result", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("deleteObject has failed", result); @@ -1998,11 +1998,11 @@ public void test100AssignRolePirate() throws Exception { OperationResultType result = new OperationResultType(); Holder resultHolder = new Holder<>(result); Holder oidHolder = new Holder<>(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); addObjectViaModelWS(userType, null, oidHolder, resultHolder); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); TestUtil.assertSuccess("addObject has failed", resultHolder.value); ObjectDeltaType objectChange = unmarshalValueFromFile( @@ -2012,7 +2012,7 @@ public void test100AssignRolePirate() throws Exception { result = modifyObjectViaModelWS(objectChange); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("modifyObject has failed", result); @@ -2077,7 +2077,7 @@ public void test100AssignRolePirate() throws Exception { public void test101AccountOwnerAfterRole() throws Exception { // GIVEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); Holder resultHolder = new Holder<>(); Holder userHolder = new Holder<>(); @@ -2107,7 +2107,7 @@ public void test102AssignRoleCaptain() throws Exception { OperationResultType result = modifyObjectViaModelWS(objectChange); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("modifyObject has failed", result); @@ -2185,7 +2185,7 @@ public void test103AssignRoleCaptainAgain() throws Exception { OperationResultType result = modifyObjectViaModelWS(objectChange); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("modifyObject has failed", result); @@ -2289,7 +2289,7 @@ public void test105ModifyAccount() throws Exception { modelService.executeChanges(deltas, null, task, parentResult); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("modifyObject has failed", result); @@ -2335,7 +2335,7 @@ public void test104AssignRoleJudge() throws Exception { OperationResultType result = new OperationResultType(); Holder resultHolder = new Holder<>(result); Holder oidHolder = new Holder<>(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); ObjectDeltaType objectChange = unmarshalValueFromFile( REQUEST_USER_MODIFY_ADD_ROLE_JUDGE_FILENAME, ObjectDeltaType.class); @@ -2351,7 +2351,7 @@ public void test104AssignRoleJudge() throws Exception { // TODO: check if the fault is the right one } - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("modifyObject has failed", result); @@ -2372,7 +2372,7 @@ public void test104AssignRoleJudge() throws Exception { @Test public void test107UnassignRolePirate() throws Exception { // GIVEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); ObjectDeltaType objectChange = unmarshalValueFromFile( REQUEST_USER_MODIFY_DELETE_ROLE_PIRATE_FILENAME, ObjectDeltaType.class); @@ -2381,7 +2381,7 @@ public void test107UnassignRolePirate() throws Exception { OperationResultType result = modifyObjectViaModelWS(objectChange); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("modifyObject has failed", result); @@ -2448,7 +2448,7 @@ public void test107UnassignRolePirate() throws Exception { @Test public void test108UnassignRoleCaptain() throws Exception { // GIVEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); ObjectDeltaType objectChange = unmarshalValueFromFile( REQUEST_USER_MODIFY_DELETE_ROLE_CAPTAIN_1_FILENAME, ObjectDeltaType.class); @@ -2457,7 +2457,7 @@ public void test108UnassignRoleCaptain() throws Exception { OperationResultType result = modifyObjectViaModelWS(objectChange); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result", result, SchemaConstants.C_RESULT); TestUtil.assertSuccess("modifyObject has failed", result); @@ -2530,7 +2530,7 @@ public void test108UnassignRoleCaptain() throws Exception { public void test109UnassignRoleCaptainAgain() throws Exception { // GIVEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); ObjectDeltaType objectChange = unmarshalValueFromFile( REQUEST_USER_MODIFY_DELETE_ROLE_CAPTAIN_2_FILENAME, ObjectDeltaType.class); @@ -2539,7 +2539,7 @@ public void test109UnassignRoleCaptainAgain() throws Exception { OperationResultType result = modifyObjectViaModelWS(objectChange); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("modifyObject result", result, SchemaConstants.C_RESULT); //TODO TODO TODO TODO operation result from repostiory.getObject is unknown...find out why.. @@ -2755,7 +2755,7 @@ public void test302LiveSyncModify() throws Exception { @Test public void test303LiveSyncLink() throws Exception { // GIVEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); PrismObject user = PrismTestUtil.parseObject(USER_E_LINK_ACTION_FILE); UserType userType = user.asObjectable(); final String userOid = userType.getOid(); @@ -2768,7 +2768,7 @@ public void test303LiveSyncLink() throws Exception { display("Adding user object", userType); addObjectViaModelWS(userType, null, oidHolder, resultHolder); //check results - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("addObject result:", resultHolder.value, SchemaConstants.C_RESULT); TestUtil.assertSuccess("addObject has failed", resultHolder.value); // AssertJUnit.assertEquals(userOid, oidHolder.value); @@ -2898,7 +2898,7 @@ public void test399LiveSyncCleanup() throws Exception { public void test400ImportFromResource() throws Exception { // GIVEN checkAllShadows(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); OperationResult result = createOperationResult(); @@ -2916,7 +2916,7 @@ public void test400ImportFromResource() throws Exception { // THEN then(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("importFromResource result", taskType.getResult(), SchemaConstants.C_RESULT); AssertJUnit.assertEquals("importFromResource has failed", OperationResultStatusType.IN_PROGRESS, taskType.getResult().getStatus()); // Convert the returned TaskType to a more usable Task @@ -2944,7 +2944,7 @@ public boolean check() throws CommonException { Holder resultHolder = new Holder<>(); Holder objectHolder = new Holder<>(); OperationResult opResult = new OperationResult("import check"); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); SelectorQualifiedGetOptionsType options = new SelectorQualifiedGetOptionsType(); try { modelWeb.getObject(ObjectTypes.TASK.getTypeQName(), taskOid, @@ -2952,7 +2952,7 @@ public boolean check() throws CommonException { } catch (FaultMessage faultMessage) { throw new SystemException(faultMessage); } - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); // display("getObject result (wait loop)",resultHolder.value); TestUtil.assertSuccess("getObject has failed", resultHolder.value); Task task = taskManager.createTaskInstance((PrismObject) objectHolder.value.asPrismObject(), opResult); @@ -2979,12 +2979,12 @@ public void timeout() { Holder objectHolder = new Holder<>(); Holder resultHolder = new Holder<>(); SelectorQualifiedGetOptionsType options = new SelectorQualifiedGetOptionsType(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); modelWeb.getObject(ObjectTypes.TASK.getTypeQName(), task.getOid(), options, objectHolder, resultHolder); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); TestUtil.assertSuccess("getObject has failed", resultHolder.value); task = taskManager.createTaskInstance((PrismObject) objectHolder.value.asPrismObject(), result); @@ -3030,12 +3030,12 @@ public void timeout() { } Holder listHolder = new Holder<>(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); modelWeb.searchObjects(ObjectTypes.USER.getTypeQName(), null, null, listHolder, resultHolder); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); ObjectListType uobjects = listHolder.value; TestUtil.assertSuccess("listObjects has failed", resultHolder.value); AssertJUnit.assertFalse("No users created", uobjects.getObject().isEmpty()); @@ -3816,11 +3816,11 @@ private UserType searchUserByName(String name) throws Exception { OperationResultType resultType = new OperationResultType(); Holder resultHolder = new Holder<>(resultType); Holder listHolder = new Holder<>(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); modelWeb.searchObjects(ObjectTypes.USER.getTypeQName(), query, null, listHolder, resultHolder); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); ObjectListType objects = listHolder.value; TestUtil.assertSuccess("searchObjects has failed", resultHolder.value); AssertJUnit.assertEquals("User not found (or found too many)", 1, objects.getObject().size()); diff --git a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestConsistencyMechanism.java b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestConsistencyMechanism.java index 454adca3c57..63c5e7083ff 100644 --- a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestConsistencyMechanism.java +++ b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestConsistencyMechanism.java @@ -9,7 +9,7 @@ import static org.testng.AssertJUnit.*; import static com.evolveum.midpoint.prism.util.PrismTestUtil.getPrismContext; -import static com.evolveum.midpoint.test.IntegrationTestTools.assertNoRepoCache; +import static com.evolveum.midpoint.test.IntegrationTestTools.assertNoRepoThreadLocalCache; import static com.evolveum.midpoint.test.IntegrationTestTools.displayJaxb; import java.io.File; @@ -303,7 +303,7 @@ public void test000Integrity() throws Exception { assertNotNull("No my:shipState definition", shipStateDefinition); assertEquals("Wrong maxOccurs in my:shipState definition", 1, shipStateDefinition.getMaxOccurs()); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); OperationResult result = createOperationResult(); @@ -313,7 +313,7 @@ public void test000Integrity() throws Exception { RESOURCE_OPENDJ_OID, null, result); display("Imported OpenDJ resource (repository)", openDjResource); AssertJUnit.assertEquals(RESOURCE_OPENDJ_OID, openDjResource.getOid()); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); String ldapConnectorOid = openDjResource.asObjectable().getConnectorRef().getOid(); PrismObject ldapConnector = repositoryService.getObject(ConnectorType.class, @@ -329,14 +329,14 @@ public void test001TestConnectionOpenDJ() throws Exception { Task task = taskManager.createTaskInstance(); // GIVEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); // WHEN OperationResultType result = modelWeb.testResource(RESOURCE_OPENDJ_OID); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); displayJaxb("testResource result:", result, SchemaConstants.C_RESULT); @@ -348,7 +348,7 @@ public void test001TestConnectionOpenDJ() throws Exception { RESOURCE_OPENDJ_OID, null, opResult); resourceTypeOpenDjrepo = resourceOpenDjRepo.asObjectable(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); assertEquals(RESOURCE_OPENDJ_OID, resourceTypeOpenDjrepo.getOid()); display("Initialized OpenDJ resource (respository)", resourceTypeOpenDjrepo); assertNotNull("Resource schema was not generated", resourceTypeOpenDjrepo.getSchema()); @@ -432,7 +432,7 @@ public void test110PrepareOpenDjWithJackieAccounts() throws Exception { OpenDJController.assertAttribute(entry, "sn", "Sparrow"); OpenDJController.assertAttribute(entry, "cn", "Jack Sparrow"); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); // check full (model) shadow PrismObject modelShadow = getShadowModel(ACCOUNT_JACKIE_OID); @@ -472,7 +472,7 @@ public void test111prepareOpenDjWithDenielsAccounts() throws Exception { OpenDJController.assertAttribute(entry, "sn", "Deniels"); OpenDJController.assertAttribute(entry, "cn", "Jack Deniels"); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); // check full (model) shadow PrismObject modelShadow = getShadowModel(ACCOUNT_DENIELS_OID); @@ -506,7 +506,7 @@ public void test120AddAccountAlreadyExistLinked() throws Exception { // GIVEN //adding user jakie. we already have user jack with the account identifier jackie. - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); addObject(USER_JACKIE_FILE, task, parentResult); PrismObject userJackieBefore = getUser(USER_JACKIE_OID); UserAsserter.forUser(userJackieBefore).assertLinks(0); @@ -2775,7 +2775,7 @@ private UserType testAddUserToRepo(String fileName) ConfigurationException, PolicyViolationException, SecurityViolationException { checkRepoOpenDjResource(); - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); PrismObject user = PrismTestUtil.parseObject(new File(fileName)); UserType userType = user.asObjectable(); @@ -2795,7 +2795,7 @@ private UserType testAddUserToRepo(String fileName) modelService.executeChanges(deltas, null, task, result); // THEN - assertNoRepoCache(); + assertNoRepoThreadLocalCache(); return userType; }