/* * Copyright (c) 2010-2016 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.evolveum.midpoint.web.component.data; import com.evolveum.midpoint.gui.api.page.PageBase; import com.evolveum.midpoint.model.api.*; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.query.ObjectOrdering; import com.evolveum.midpoint.prism.query.ObjectPaging; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.query.OrderDirection; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.SchemaConstantsGenerated; import com.evolveum.midpoint.task.api.TaskManager; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.web.page.PageDialog; import com.evolveum.midpoint.web.security.MidPointApplication; import com.evolveum.midpoint.wf.api.WorkflowManager; import org.apache.commons.lang.Validate; import org.apache.wicket.Component; import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder; import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider; import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.jetbrains.annotations.NotNull; import javax.xml.namespace.QName; import java.io.Serializable; import java.util.*; import static com.evolveum.midpoint.gui.api.util.WebComponentUtil.safeLongToInteger; /** * @author lazyman */ public abstract class BaseSortableDataProvider<T extends Serializable> extends SortableDataProvider<T, String> { private static final Trace LOGGER = TraceManager.getTrace(BaseSortableDataProvider.class); private Component component; private List<T> availableData; private ObjectQuery query; // after this amount of time cached size will be removed // from cache and replaced by new value, time in seconds private Map<Serializable, CachedSize> cache = new HashMap<Serializable, CachedSize>(); private int cacheCleanupThreshold = 60; private boolean useCache; public BaseSortableDataProvider(Component component) { this(component, false, true); } public BaseSortableDataProvider(Component component, boolean useCache) { this(component, useCache, true); } public BaseSortableDataProvider(Component component, boolean useCache, boolean useDefaultSortingField) { Validate.notNull(component, "Component must not be null."); this.component = component; this.useCache = useCache; if (useDefaultSortingField) { setSort("name", SortOrder.ASCENDING); } } protected ModelService getModel() { MidPointApplication application = (MidPointApplication) MidPointApplication.get(); return application.getModel(); } protected RepositoryService getRepositoryService() { MidPointApplication application = (MidPointApplication) MidPointApplication.get(); return application.getRepositoryService(); } protected TaskManager getTaskManager() { MidPointApplication application = (MidPointApplication) MidPointApplication.get(); return application.getTaskManager(); } protected PrismContext getPrismContext() { MidPointApplication application = (MidPointApplication) MidPointApplication.get(); return application.getPrismContext(); } protected TaskService getTaskService() { MidPointApplication application = (MidPointApplication) MidPointApplication.get(); return application.getTaskService(); } protected ModelInteractionService getModelInteractionService() { MidPointApplication application = (MidPointApplication) MidPointApplication.get(); return application.getModelInteractionService(); } protected WorkflowService getWorkflowService() { MidPointApplication application = (MidPointApplication) MidPointApplication.get(); return application.getWorkflowService(); } protected ModelAuditService getAuditService() { MidPointApplication application = (MidPointApplication) MidPointApplication.get(); return application.getAuditService(); } protected WorkflowManager getWorkflowManager() { MidPointApplication application = (MidPointApplication) MidPointApplication.get(); return application.getWorkflowManager(); } public List<T> getAvailableData() { if (availableData == null) { availableData = new ArrayList<T>(); } return availableData; } @Override public IModel<T> model(T object) { return new Model<T>(object); } protected PageBase getPage() { if (component instanceof PageBase) { return (PageBase) component; } if (component.getPage() instanceof PageBase) { return (PageBase) component.getPage(); } if (component.getPage() instanceof PageDialog) { return ((PageDialog) component.getPage()).getPageBase(); } throw new IllegalStateException("Component is not instance of '" + PageBase.class.getName() + "' or is not placed on page of that instance."); } public ObjectQuery getQuery() { return query; } public void setQuery(ObjectQuery query) { this.query = query; } /** * Flag method for {@link TablePanel}. If true navigation panel with paging "X to Y from Z results is shown", * otherwise only "previous and next" simple paging is used. * * @return By defaults it returns true. */ public IModel<Boolean> isSizeAvailableModel() { return new AbstractReadOnlyModel<Boolean>() { @Override public Boolean getObject() { return true; } }; } protected ObjectPaging createPaging(long offset, long pageSize) { return ObjectPaging.createPaging(safeLongToInteger(offset), safeLongToInteger(pageSize), createObjectOrderings(getSort())); } /** * Could be overridden in subclasses. */ @NotNull protected List<ObjectOrdering> createObjectOrderings(SortParam<String> sortParam) { if (sortParam != null && sortParam.getProperty() != null) { OrderDirection order = sortParam.isAscending() ? OrderDirection.ASCENDING : OrderDirection.DESCENDING; return Collections.singletonList( ObjectOrdering.createOrdering( new ItemPath(new QName(SchemaConstantsGenerated.NS_COMMON, sortParam.getProperty())), order)); } else { return Collections.emptyList(); } } public void clearCache() { cache.clear(); getAvailableData().clear(); } public int getCacheCleanupThreshold() { return cacheCleanupThreshold; } public void setCacheCleanupThreshold(int cacheCleanupThreshold) { Validate.isTrue(cacheCleanupThreshold > 0, "Cache cleanup threshold must be bigger than zero."); this.cacheCleanupThreshold = cacheCleanupThreshold; } @Override public Iterator<? extends T> iterator(long first, long count) { Iterator<? extends T> iterator = internalIterator(first, count); saveProviderPaging(getQuery(), createPaging(first, count)); return iterator; } protected void saveProviderPaging(ObjectQuery query, ObjectPaging paging) { } protected abstract Iterator<? extends T> internalIterator(long first, long count); @Override public long size() { LOGGER.trace("begin::size()"); if (!useCache) { return internalSize(); } long size; CachedSize cachedSize = getCachedSize(cache); if (cachedSize != null) { long timestamp = cachedSize.getTimestamp(); if (System.currentTimeMillis() - timestamp > cacheCleanupThreshold * 1000) { //recreate size = internalSize(); addCachedSize(cache, new CachedSize(size, System.currentTimeMillis())); } else { LOGGER.trace("Size returning from cache."); size = cachedSize.getSize(); } } else { //recreate size = internalSize(); addCachedSize(cache, new CachedSize(size, System.currentTimeMillis())); } LOGGER.trace("end::size(): {}", size); return size; } protected abstract int internalSize(); protected CachedSize getCachedSize(Map<Serializable, CachedSize> cache) { return cache.get(query); } protected void addCachedSize(Map<Serializable, CachedSize> cache, CachedSize newSize) { cache.put(query, newSize); } public static class CachedSize implements Serializable { private long timestamp; private long size; private CachedSize(long size, long timestamp) { this.size = size; this.timestamp = timestamp; } public long getSize() { return size; } public long getTimestamp() { return timestamp; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CachedSize that = (CachedSize) o; if (size != that.size) return false; if (timestamp != that.timestamp) return false; return true; } @Override public int hashCode() { int result = (int) (timestamp ^ (timestamp >>> 32)); result = 31 * result + (int) (size ^ (size >>> 32)); return result; } @Override public String toString() { return "CachedSize(size=" + size + ", timestamp=" + timestamp + ")"; } } }