Skip to content

Commit

Permalink
MGR-128
Browse files Browse the repository at this point in the history
  • Loading branch information
madness-inc committed Dec 9, 2021
1 parent 7abae6f commit 022ab2f
Show file tree
Hide file tree
Showing 6 changed files with 692 additions and 62 deletions.
147 changes: 85 additions & 62 deletions src/main/java/org/appng/application/manager/business/Cache.java
Expand Up @@ -18,9 +18,11 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;

import org.apache.commons.collections4.keyvalue.DefaultMapEntry;
import org.apache.commons.io.FileUtils;
Expand All @@ -33,7 +35,9 @@
import org.appng.api.Environment;
import org.appng.api.FieldProcessor;
import org.appng.api.Options;
import org.appng.api.Platform;
import org.appng.api.Request;
import org.appng.api.Scope;
import org.appng.api.model.Application;
import org.appng.api.model.Site;
import org.appng.api.support.SelectionFactory;
Expand All @@ -47,10 +51,13 @@
import org.appng.xml.platform.SelectionGroup;
import org.appng.xml.platform.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;

import com.hazelcast.cache.ICache;

/**
* Provides methods to interact with the page cache. Elements are stored in the cache by the {@link PageCacheFilter}.
*
Expand All @@ -66,81 +73,97 @@ public class Cache extends ServiceAware implements ActionProvider<Void>, DataPro
private static final String ENTRIES = "entries";
private static final String ACTION_EXPIRE_CACHE_ELEMENT = "expireCacheElement";
private static final String ACTION_CLEAR_CACHE = "clearCache";
private @Value("${" + ManagerSettings.MAX_FILTERABLE_CACHE_ENTRIES + "}") Integer maxCacheEntries;

@Autowired
private SelectionFactory selectionFactory;

public DataContainer getData(Site site, Application application, Environment environment, Options options,
Request request, FieldProcessor fp) {
public DataContainer getData(Site site, Application application, Environment env, Options options, Request request,
FieldProcessor fp) {
String mode = options.getString("mode", ID);
Integer siteId = options.getInteger("site", ID);
DataContainer dataContainer = new DataContainer(fp);
if (STATISTICS.equals(mode)) {
List<Entry<String, String>> result = new ArrayList<>();
Map<String, String> cacheStatistics = getService().getCacheStatistics(siteId);
result.add(getStatEntry(request, cacheStatistics, CacheService.STATS_NAME));
result.add(getStatEntry(request, cacheStatistics, CacheService.STATS_SIZE));
result.add(getStatEntry(request, cacheStatistics, CacheService.STATS_HITS));
result.add(getStatEntry(request, cacheStatistics, CacheService.STATS_HITS_PERCENT));
result.add(getStatEntry(request, cacheStatistics, CacheService.STATS_MISSES));
result.add(getStatEntry(request, cacheStatistics, CacheService.STATS_MISSES_PERCENT));
result.add(getStatEntry(request, cacheStatistics, CacheService.STATS_PUTS));
result.add(getStatEntry(request, cacheStatistics, CacheService.STATS_AVG_PUT_TIME));
result.add(getStatEntry(request, cacheStatistics, CacheService.STATS_GETS));
result.add(getStatEntry(request, cacheStatistics, CacheService.STATS_AVG_GET_TIME));
result.add(getStatEntry(request, cacheStatistics, CacheService.STATS_REMOVALS));
result.add(getStatEntry(request, cacheStatistics, CacheService.STATS_AVG_REMOVAL_TIME));
dataContainer.setItems(result);
} else if (ENTRIES.equals(mode)) {
Integer maxCacheEntries = application.getProperties()
.getInteger(ManagerSettings.MAX_FILTERABLE_CACHE_ENTRIES);

List<CachedResponse> allCacheEntries = getService().getCacheEntries(siteId);
List<CacheEntry> cacheEntries = new ArrayList<>();
Pageable pageable = fp.getPageable();

int cacheSize = allCacheEntries.size();
if (cacheSize > maxCacheEntries) {
fp.getFields().stream().filter(f -> !"id".equals(f.getBinding())).forEach(f -> f.setSort(null));
for (int i = pageable.getOffset(); i < pageable.getOffset() + pageable.getPageSize(); i++) {
if (i < cacheSize) {
cacheEntries.add(new CacheEntry(allCacheEntries.get(i)));

Map<String, Site> siteMap = env.getAttribute(Scope.PLATFORM, Platform.Environment.SITES);
Optional<Site> cacheSite = siteMap.values().stream().filter(s -> s.getId().equals(siteId)).findFirst();

if (cacheSite.isPresent()) {
if (STATISTICS.equals(mode)) {
List<Entry<String, String>> result = new ArrayList<>();
Map<String, String> stats = CacheService.getCacheStatistics(cacheSite.get());
result.add(getStatEntry(request, stats, CacheService.STATS_NAME));
result.add(getStatEntry(request, stats, CacheService.STATS_SIZE));
result.add(getStatEntry(request, stats, CacheService.STATS_HITS));
result.add(getStatEntry(request, stats, CacheService.STATS_HITS_PERCENT));
result.add(getStatEntry(request, stats, CacheService.STATS_MISSES));
result.add(getStatEntry(request, stats, CacheService.STATS_MISSES_PERCENT));
result.add(getStatEntry(request, stats, CacheService.STATS_PUTS));
result.add(getStatEntry(request, stats, CacheService.STATS_AVG_PUT_TIME));
result.add(getStatEntry(request, stats, CacheService.STATS_GETS));
result.add(getStatEntry(request, stats, CacheService.STATS_AVG_GET_TIME));
result.add(getStatEntry(request, stats, CacheService.STATS_REMOVALS));
result.add(getStatEntry(request, stats, CacheService.STATS_AVG_REMOVAL_TIME));
dataContainer.setItems(result);
} else if (ENTRIES.equals(mode)) {
Pageable pageable = fp.getPageable();
List<CacheEntry> cacheEntries = new ArrayList<>();

javax.cache.Cache<String, CachedResponse> cache = CacheService.getCache(cacheSite.get());
int cacheSize = cache.unwrap(ICache.class).size();

if (cacheSize > maxCacheEntries) {
Iterator<javax.cache.Cache.Entry<String, CachedResponse>> elements = cache.iterator();
int idx = 0;
int startIdx = pageable.getOffset();
int endIdx = pageable.getOffset() + pageable.getPageSize();
while (elements.hasNext()) {
javax.cache.Cache.Entry<java.lang.String, CachedResponse> entry = elements.next();
if (idx >= startIdx && idx < endIdx) {
cacheEntries.add(new CacheEntry(entry.getValue()));
}
if (idx++ >= endIdx) {
break;
}
}
}

SortOrder idOrder = fp.getField("id").getSort().getOrder();
if (null != idOrder) {
Collections.sort(cacheEntries, (e1, e2) -> StringUtils.compare(e1.getId(), e2.getId()));
if (SortOrder.DESC.equals(idOrder)) {
Collections.reverse(cacheEntries);
fp.getFields().stream().filter(f -> !"id".equals(f.getBinding())).forEach(f -> f.setSort(null));
SortOrder idOrder = fp.getField("id").getSort().getOrder();
if (null != idOrder) {
Collections.sort(cacheEntries, (e1, e2) -> StringUtils.compare(e1.getId(), e2.getId()));
if (SortOrder.DESC.equals(idOrder)) {
Collections.reverse(cacheEntries);
}
}
}

dataContainer.setPage(new PageImpl<>(cacheEntries, pageable, cacheSize));
} else {
String entryName = request.getParameter(F_ETR);
String entryType = request.getParameter(F_CTYPE);
boolean filterName = StringUtils.isNotBlank(entryName);
boolean filterType = StringUtils.isNotBlank(entryType);

for (CachedResponse entry : allCacheEntries) {
String entryId = entry.getId();
boolean nameMatches = !filterName || FilenameUtils
.wildcardMatch(entryId.substring(entryId.indexOf('/')), entryName, IOCase.INSENSITIVE);
boolean typeMatches = !filterType
|| FilenameUtils.wildcardMatch(entry.getContentType(), entryType, IOCase.INSENSITIVE);
if (nameMatches && typeMatches) {
cacheEntries.add(new CacheEntry(entry));
dataContainer.setPage(new PageImpl<>(cacheEntries, pageable, cacheSize));

} else {
String entryName = request.getParameter(F_ETR);
String entryType = request.getParameter(F_CTYPE);
boolean filterName = StringUtils.isNotBlank(entryName);
boolean filterType = StringUtils.isNotBlank(entryType);
for (javax.cache.Cache.Entry<String, CachedResponse> entry : cache) {
String entryId = entry.getKey();
CachedResponse cachedResponse = entry.getValue();
boolean nameMatches = !filterName || FilenameUtils
.wildcardMatch(entryId.substring(entryId.indexOf('/')), entryName, IOCase.INSENSITIVE);
boolean typeMatches = !filterType || FilenameUtils
.wildcardMatch(cachedResponse.getContentType(), entryType, IOCase.INSENSITIVE);
if (nameMatches && typeMatches) {
cacheEntries.add(new CacheEntry(cachedResponse));
}
}
}

Selection nameSelection = selectionFactory.getTextSelection(F_ETR, MessageConstants.NAME, entryName);
Selection typeSelection = selectionFactory.getTextSelection(F_CTYPE, MessageConstants.TYPE, entryType);
SelectionGroup selectionGroup = new SelectionGroup();
selectionGroup.getSelections().add(nameSelection);
selectionGroup.getSelections().add(typeSelection);
dataContainer.getSelectionGroups().add(selectionGroup);
dataContainer.setPage(cacheEntries, pageable);
Selection nameSelection = selectionFactory.getTextSelection(F_ETR, MessageConstants.NAME,
entryName);
Selection typeSelection = selectionFactory.getTextSelection(F_CTYPE, MessageConstants.TYPE,
entryType);
SelectionGroup selectionGroup = new SelectionGroup();
selectionGroup.getSelections().add(nameSelection);
selectionGroup.getSelections().add(typeSelection);
dataContainer.getSelectionGroups().add(selectionGroup);
dataContainer.setPage(cacheEntries, pageable);
}
}
}
return dataContainer;
Expand Down
Expand Up @@ -29,6 +29,7 @@
import org.appng.api.model.Property;
import org.appng.api.model.SimpleProperty;
import org.appng.api.support.environment.DefaultEnvironment;
import org.appng.application.manager.ManagerSettings;
import org.appng.core.repository.ApplicationRepository;
import org.appng.core.repository.GroupRepository;
import org.appng.core.repository.PermissionRepository;
Expand Down Expand Up @@ -115,6 +116,7 @@ protected Properties getProperties() {
properties.put("platform.platformRootPath", "target/ROOT");
properties.put("site.mailHost", "localHost");
properties.put("site.mailPort", "25");
properties.put(ManagerSettings.MAX_FILTERABLE_CACHE_ENTRIES, "50");
return properties;
}

Expand All @@ -132,6 +134,9 @@ protected List<Property> getSiteProperties(String prefix) {
siteProperties.add(new SimpleProperty(prefix + SiteProperties.SERVICE_PATH, "services"));
siteProperties.add(new SimpleProperty(prefix + SiteProperties.MANAGER_PATH, "ws"));
siteProperties.add(new SimpleProperty(prefix + SiteProperties.DEFAULT_PAGE_SIZE, "10"));
siteProperties.add(new SimpleProperty(prefix + SiteProperties.CACHE_TIME_TO_LIVE, "36000"));
siteProperties.add(new SimpleProperty(prefix + SiteProperties.CACHE_STATISTICS, "true"));
siteProperties.add(new SimpleProperty(prefix + SiteProperties.CACHE_ENABLED, "true"));
return siteProperties;
}

Expand Down
@@ -0,0 +1,85 @@
/*
* Copyright 2011-2021 the original author or authors.
*
* 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 org.appng.application.manager.business;

import java.util.List;

import javax.cache.Cache;

import org.appng.api.support.CallableDataSource;
import org.appng.core.controller.CachedResponse;
import org.appng.core.service.CacheService;
import org.appng.testsupport.validation.DateFieldDifferenceHandler;
import org.appng.testsupport.validation.WritingXmlValidator;
import org.appng.testsupport.validation.XPathDifferenceHandler;
import org.appng.xml.platform.Datasource;
import org.appng.xml.platform.Result;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.springframework.http.MediaType;

import com.hazelcast.config.ClasspathXmlConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class CacheTest extends AbstractTest {

static HazelcastInstance hz;

static {
WritingXmlValidator.writeXml = false;
hz = Hazelcast.getOrCreateHazelcastInstance(new ClasspathXmlConfig("hazelcast-test.xml"));
CacheService.createCacheManager(hz, false);
}

@Test
public void testCache() throws Exception {
Cache<String, CachedResponse> cache = CacheService.createCache(site);

for (int i = 0; i < 500; i++) {
String key = "/element/" + i;
cache.put(key, new CachedResponse(key, site, servletRequest, 200, MediaType.TEXT_PLAIN_VALUE, new byte[0],
null, 3600));
}

addParameter("sortCacheElements", "pageSize:10;page:4");
initParameters();
CallableDataSource ds = getDataSource("cacheElements").withParam("siteid", "1").getCallableDataSource();
ds.perform("");

Datasource datasource = ds.getDatasource();
List<Result> results = datasource.getData().getResultset().getResults();
for (int i = 0; i < 10;) {
results.get(i).getFields().get(0).setValue("/element/" + String.valueOf(++i));
}
validate(datasource, new DateFieldDifferenceHandler());
}

@Test
public void testCacheStatistics() throws Exception {
CallableDataSource ds = getDataSource("cacheStatistics").withParam("siteid", "1").getCallableDataSource();
ds.perform("");
Datasource datasource = ds.getDatasource();
XPathDifferenceHandler dh = new XPathDifferenceHandler(false);
// avg put/get times
dh.ignoreDifference("/datasource[1]/data[1]/resultset[1]/result[8]/field[2]/value[1]/text()[1]");
dh.ignoreDifference("/datasource[1]/data[1]/resultset[1]/result[10]/field[2]/value[1]/text()[1]");
validate(datasource, dh);
}

}
23 changes: 23 additions & 0 deletions src/test/resources/hazelcast-test.xml
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<hazelcast xmlns="http://www.hazelcast.com/schema/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.hazelcast.com/schema/config http://www.hazelcast.com/schema/config/hazelcast-config-5.0.xsd">

<properties>
<property name="hazelcast.logging.type">slf4j</property>
</properties>
<cluster-name>appNG</cluster-name>
<instance-name>test</instance-name>
<network>
<join>
<auto-detection enabled="false" />
<multicast enabled="false" />
<tcp-ip enabled="false" />
<aws enabled="false" />
<gcp enabled="false" />
<azure enabled="false" />
<kubernetes enabled="false" />
<eureka enabled="false" />
<discovery-strategies />
</join>
</network>
</hazelcast>

0 comments on commit 022ab2f

Please sign in to comment.