Skip to content

Commit

Permalink
Merge branch 'appng-manager-1.16.x'
Browse files Browse the repository at this point in the history
  • Loading branch information
appng-buildmaster committed Sep 8, 2021
2 parents 6337ced + a2c593d commit 0d9ce39
Show file tree
Hide file tree
Showing 6 changed files with 527 additions and 30 deletions.
2 changes: 1 addition & 1 deletion application-home/application.xml
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@
<property id="databaseReportSender" description="the sender for the database report email">report@example.com</property>
<property id="databaseReportSubject" description="the subject for the database report email">appNG database report</property>
<property id="databaseReportText" description="the text for the database report email">See attached file for a report of the database related platform events.</property>
<property id="maxFilterableCacheEntries" description="The maximum number of cache entries that can be filtered/sorted">5000</property>
<property id="maxFilterableCacheEntries" description="The maximum number of cache entries that can be filtered/sorted">1000</property>
</properties>

</application>
8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<artifactId>appng-manager</artifactId>
<packaging>jar</packaging>
<name>appNG Manager</name>
<version>1.16.2</version>
<version>1.16.3</version>
<description><![CDATA[Global appNG administration]]></description>
<url>https://appng.org</url>

Expand Down Expand Up @@ -368,6 +368,12 @@
<artifactId>appng-appngizer-jaxb</artifactId>
<version>${appNG.version}</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-web</artifactId>
<version>2.0.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.appng</groupId>
<artifactId>appng-testsupport</artifactId>
Expand Down
106 changes: 78 additions & 28 deletions src/main/java/org/appng/application/manager/business/Cache.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,19 @@
*/
package org.appng.application.manager.business;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.apache.commons.collections4.keyvalue.DefaultMapEntry;
import org.apache.commons.io.FileUtils;
Expand All @@ -44,17 +52,25 @@
import org.appng.core.controller.filter.PageCacheFilter;
import org.appng.core.service.CacheService;
import org.appng.xml.platform.SelectionGroup;
import org.appng.xml.platform.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;
import net.sf.ehcache.Element;
import net.sf.ehcache.constructs.blocking.BlockingCache;
import net.sf.ehcache.constructs.web.PageInfo;

/**
* Provides methods to interact with the page cache. Elements are stored in the cache by the {@link PageCacheFilter}.
*
* @author Matthias Herlitzius
*/
@Lazy
@Slf4j
@Component
public class Cache extends ServiceAware implements ActionProvider<Void>, DataProvider {

Expand All @@ -74,12 +90,14 @@ public DataContainer getData(Site site, Application application, Environment env
Integer siteId = request.convert(options.getOptionValue("site", ID), Integer.class);
DataContainer dataContainer = new DataContainer(fp);
Pageable pageable = fp.getPageable();

Site cacheSite = getService().getSite(siteId);
BlockingCache cache = CacheService.getBlockingCache(cacheSite);

if (STATISTICS.equals(mode)) {
List<Entry<String, String>> result = new ArrayList<>();
Map<String, String> cacheStatistics = getService().getCacheStatistics(siteId);
for (Entry<String, String> e : cacheStatistics.entrySet()) {
result.add(e);
}
cacheStatistics.put("Size", String.valueOf(cache.getKeys().size()));
List<Entry<String, String>> result = new ArrayList<>(cacheStatistics.entrySet());
Collections.sort(result, new Comparator<Entry<String, String>>() {
public int compare(Entry<String, String> o1, Entry<String, String> o2) {
return o1.getKey().compareTo(o2.getKey());
Expand All @@ -89,51 +107,83 @@ public int compare(Entry<String, String> o1, Entry<String, String> o2) {
} else if (ENTRIES.equals(mode)) {
Integer maxCacheEntries = application.getProperties()
.getInteger(ManagerSettings.MAX_FILTERABLE_CACHE_ENTRIES);
List<AppngCache> allCacheEntries = getService().getCacheEntries(siteId);

@SuppressWarnings("unchecked")
List<String> keys = cache.getKeys();
List<CacheEntry> cacheEntries = new ArrayList<CacheEntry>();

int cacheSize = allCacheEntries.size();
if (cacheSize > maxCacheEntries) {
fp.getFields().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)));
SelectionGroup filter = new SelectionGroup();
String entryName = request.getParameter(F_ETR);
boolean filterName = StringUtils.isNotBlank(entryName);
Selection nameSelection = selectionFactory.getTextSelection(F_ETR, MessageConstants.NAME, entryName);

int cacheSize = keys.size();

if (cache.getSize() > maxCacheEntries) {
fp.getFields().stream().filter(f -> !"id".equals(f.getBinding())).forEach(f -> f.setSort(null));

Predicate<String> keyFilter = k -> matches(k.substring(k.indexOf('/')), entryName);
List<String> filteredKeys = filterName
? keys.stream().filter(keyFilter).map(Object::toString).collect(Collectors.toList())
: Arrays.asList(keys.toArray(new String[0]));
log.debug("Size: {}, Filtered Keys: {} (filtered? {})", cacheSize, filteredKeys.size(), filterName);

SortOrder idOrder = fp.getField("id").getSort().getOrder();
if (null != idOrder) {
Collections.sort(filteredKeys);
if (SortOrder.DESC.equals(idOrder)) {
Collections.reverse(filteredKeys);
}
}
dataContainer.setPage(new PageImpl<>(cacheEntries, pageable, cacheSize));

int toIndex = pageable.getOffset() + pageable.getPageSize();
if (toIndex > filteredKeys.size()) {
toIndex = filteredKeys.size();
}
filteredKeys.subList(pageable.getOffset(), toIndex)
.forEach(key -> getEntry(cacheSite, cache, key).ifPresent(cacheEntries::add));

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

for (AppngCache entry : allCacheEntries) {
String entryId = entry.getId();
boolean nameMatches = !filterName || FilenameUtils
.wildcardMatch(entryId.substring(entryId.indexOf('/')), entryName, IOCase.INSENSITIVE);
for (String entryId : keys) {
Optional<CacheEntry> entry = getEntry(cacheSite, cache, entryId.toString());
boolean nameMatches = !filterName || matches(entryId.substring(entryId.indexOf('/')), entryName);
boolean typeMatches = !filterType
|| FilenameUtils.wildcardMatch(entry.getContentType(), entryType, IOCase.INSENSITIVE);
|| (entry.isPresent() && matches(entry.get().getType(), entryType));
if (nameMatches && typeMatches) {
cacheEntries.add(new CacheEntry(entry));
entry.ifPresent(cacheEntries::add);
}
}

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);
filter.getSelections().add(typeSelection);
dataContainer.setPage(cacheEntries, pageable);
}
dataContainer.getSelectionGroups().add(filter);
filter.getSelections().add(nameSelection);

}
return dataContainer;
}

private Entry<String, String> getStatEntry(Request request, Map<String, String> statistics, String statKey) {
return new DefaultMapEntry<String, String>(request.getMessage("cache.statistics." + statKey),
statistics.get(statKey));
private boolean matches(String name, String matcher) {
return FilenameUtils.wildcardMatch(name, matcher, IOCase.INSENSITIVE);
}

protected Optional<CacheEntry> getEntry(Site cacheSite, BlockingCache cache, Serializable key) {
Element element = cache.getQuiet(key);
if (null != element && null != element.getObjectValue()) {
try {
PageInfo pageInfo = (PageInfo) element.getObjectValue();
return Optional.of(new CacheEntry(new AppngCache(key, cacheSite, pageInfo, element)));
} catch (IOException e) {
// ignore
}
}
return Optional.empty();
}

public class CacheEntry {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,6 @@ void createDatabaseConnection(Request request, FieldProcessor fp, DatabaseConnec

void createEvent(Type type, String message);

Site getSite(Integer siteId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2011-2019 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 org.appng.api.Platform;
import org.appng.api.support.CallableDataSource;
import org.appng.application.manager.form.PropertyForm;
import org.appng.application.manager.form.SiteForm;
import org.appng.core.domain.SiteImpl;
import org.appng.testsupport.validation.XPathDifferenceHandler;
import org.junit.Test;

import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.constructs.blocking.BlockingCache;
import net.sf.ehcache.constructs.web.PageInfo;

public class CacheTest extends AbstractTest {

@Test
public void testCacheElements() throws Exception {
// prepares using appNG >= 1.19.1
PropertyForm form = new PropertyForm();
form.getProperty().setName(Platform.Property.MESSAGING_ENABLED);
form.getProperty().setDefaultString(Boolean.FALSE.toString());
getAction("propertyEvent", "create-platform-property").withParam(FORM_ACTION, "create-platform-property")
.getCallableAction(form).perform();

SiteImpl siteToCreate = new SiteImpl();
SiteForm siteForm = new SiteForm(siteToCreate);
siteToCreate.setName("localhost");
siteToCreate.setHost("localhost");
siteToCreate.setDomain("localhost");
siteToCreate.setActive(true);

getAction("siteEvent", "create").withParam(FORM_ACTION, "create").getCallableAction(siteForm).perform();

CacheManager cacheManager = CacheManager.getInstance();
Ehcache cache = cacheManager.addCacheIfAbsent("pageCache-localhost");
BlockingCache blockingCache = new BlockingCache(cache);
String cacheKey = "GET/foo/bar.txt";
PageInfo pageInfo = new PageInfo(200, "text/plain", null, "appNG rocks!".getBytes(), false, 120, null);
for (int i = 0; i < 1001; i++) {
blockingCache.put(new Element("GET/foo/bar" + i + ".txt", pageInfo));
}
cacheManager.replaceCacheWithDecoratedCache(cache, blockingCache);

CallableDataSource callableDataSource = getDataSource("cacheElements").withParam("siteid", "1")
.getCallableDataSource();
callableDataSource.perform("");
XPathDifferenceHandler diffHandler = new XPathDifferenceHandler();
diffHandler.ignoreDifference("/datasource/data/resultset/result/field/value/text()");
validate(callableDataSource.getDatasource(), diffHandler);
}
}
Loading

0 comments on commit 0d9ce39

Please sign in to comment.