/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.gwc.web;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.ChoiceRenderer;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.IValidator;
import org.apache.wicket.validation.ValidationError;
import org.geoserver.gwc.ConfigurableBlobStore;
import org.geoserver.gwc.config.GWCConfig;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.web.util.MapModel;
import org.geoserver.web.wicket.ParamResourceModel;
import org.geowebcache.storage.blobstore.memory.CacheConfiguration;
import org.geowebcache.storage.blobstore.memory.CacheConfiguration.EvictionPolicy;
import org.geowebcache.storage.blobstore.memory.CacheProvider;
import org.geowebcache.storage.blobstore.memory.CacheStatistics;
/**
* This class is a new Panel for configuring In Memory Caching for GWC. The user can enable/disable In Memory caching, enable/disable file
* persistence. Also from this panel the user can have information about cache statistics and also change the cache configuration.
*
* @author Nicola Lagomarsini Geosolutions
*/
public class InMemoryBlobStorePanel extends Panel {
/** Key for the miss rate */
public static final String KEY_MISS_RATE = "missRate";
/** Key for the hit rate */
public static final String KEY_HIT_RATE = "hitRate";
/** Key for the evicted elements number */
public static final String KEY_EVICTED = "evicted";
/** Key for the miss count */
public static final String KEY_MISS_COUNT = "missCount";
/** Key for the hit count */
public static final String KEY_HIT_COUNT = "hitCount";
/** Key for the total elements count */
public static final String KEY_TOTAL_COUNT = "totalCount";
/** Key for the cache current memory occupation */
public static final String KEY_CURRENT_MEM = "currentMemory";
/** Key for the cache current/total size */
public static final String KEY_SIZE = "cacheSize";
/** HashMap containing the values for all the statistics values */
private HashMap<String, String> values;
public InMemoryBlobStorePanel(String id, final IModel<GWCConfig> gwcConfigModel) {
super(id, gwcConfigModel);
// Initialize the map
values = new HashMap<String, String>();
// get the CacheConfigurations Model
IModel<Map<String, CacheConfiguration>> cacheConfigurations = new PropertyModel<Map<String, CacheConfiguration>>(
gwcConfigModel, "cacheConfigurations");
// Creation of the Checbox for enabling disabling inmemory caching
IModel<Boolean> innerCachingEnabled = new PropertyModel<Boolean>(gwcConfigModel,
"innerCachingEnabled");
final CheckBox innerCachingEnabledChoice = new CheckBox("innerCachingEnabled",
innerCachingEnabled);
// Container containing all the other parameters
final WebMarkupContainer container = new WebMarkupContainer("container");
container.setOutputMarkupId(true).setEnabled(true);
// Container containing all the parameters related to cache configuration which can be seen only if In Memory caching is enabled
final CacheConfigContainerWrapper cacheConfigContainer = new CacheConfigContainerWrapper(
"cacheConfContainer", gwcConfigModel.getObject().getCacheProviderClass(),
gwcConfigModel);
cacheConfigContainer.setOutputMarkupId(true);
// Avoid Persistence checkbox
IModel<Boolean> persistenceEnabled = new PropertyModel<Boolean>(gwcConfigModel,
"persistenceEnabled");
final CheckBox persistenceEnabledChoice = new CheckBox("persistenceEnabled", persistenceEnabled);
boolean visible = innerCachingEnabledChoice.getModelObject() == null ? false
: innerCachingEnabledChoice.getModelObject();
container.setVisible(visible);
// Choice between the various Cache objects
final DropDownChoice<String> choice;
final ConfigurableBlobStore store = GeoServerExtensions.bean(ConfigurableBlobStore.class);
if (store != null) {
final Map<String, String> cacheProviders = store.getCacheProvidersNames();
final IModel<String> providerClass = new PropertyModel<String>(gwcConfigModel,
"cacheProviderClass");
ChoiceRenderer<String> renderer = new CacheProviderRenderer(cacheProviders);
choice = new DropDownChoice<String>("caches", providerClass, new ArrayList<String>(
cacheProviders.keySet()), renderer);
choice.add(new AjaxFormComponentUpdatingBehavior("change") {
@Override
protected void onUpdate(AjaxRequestTarget target) {
ConfigurableBlobStore store = GeoServerExtensions
.bean(ConfigurableBlobStore.class);
String cacheClass = providerClass.getObject();
boolean immutable = false;
if (store != null) {
immutable = store.getCacheProviders().get(cacheClass).isImmutable();
}
cacheConfigContainer.setEnabled(!immutable);
// If changing the cacheProvider, you must change also the configuration
if (!immutable) {
if (!gwcConfigModel.getObject().getCacheConfigurations()
.containsKey(cacheClass)) {
gwcConfigModel.getObject().getCacheConfigurations()
.put(cacheClass, new CacheConfiguration());
}
cacheConfigContainer.setMapKey(cacheClass, gwcConfigModel);
}
target.add(cacheConfigContainer);
}
});
cacheConfigContainer.setEnabled(!store.getCacheProviders()
.get(providerClass.getObject()).isImmutable());
} else {
choice = new DropDownChoice<String>("caches", new ArrayList<String>());
}
// Adding cache choice to the container
container.add(choice);
persistenceEnabledChoice.setOutputMarkupId(true).setEnabled(true);
// Adding cache configuration container to the global container
container.add(cacheConfigContainer);
// Definition of the behavior related to caching
innerCachingEnabledChoice.add(new AjaxFormComponentUpdatingBehavior("change") {
@Override
protected void onUpdate(AjaxRequestTarget target) {
// If In Memory caching is disabled, all the other parameters cannot be seen
boolean isVisible = innerCachingEnabledChoice.getModelObject() == null ? false
: innerCachingEnabledChoice.getModelObject();
container.setVisible(isVisible);
target.add(container.getParent());
}
});
add(innerCachingEnabledChoice);
container.add(persistenceEnabledChoice);
add(container);
// Cache Clearing Option
Button clearCache = new Button("cacheClear") {
@Override
public void onSubmit() {
final ConfigurableBlobStore store = GeoServerExtensions
.bean(ConfigurableBlobStore.class);
// Clear cache
if (store != null) {
store.clearCache();
}
}
};
container.add(clearCache);
// Cache Statistics
final WebMarkupContainer statsContainer = new WebMarkupContainer("statsContainer");
statsContainer.setOutputMarkupId(true);
// Container for the statistics
final Label totalCountLabel = new Label("totalCount", new MapModel(values, KEY_TOTAL_COUNT));
final Label hitCountLabel = new Label("hitCount", new MapModel(values, KEY_HIT_COUNT));
final Label missCountLabel = new Label("missCount", new MapModel(values, KEY_MISS_COUNT));
final Label missRateLabel = new Label("missRate", new MapModel(values, KEY_MISS_RATE));
final Label hitRateLabel = new Label("hitRate", new MapModel(values, KEY_HIT_RATE));
final Label evictedLabel = new Label("evicted", new MapModel(values, KEY_EVICTED));
final Label currentMemoryLabel = new Label("currentMemory", new MapModel(values,
KEY_CURRENT_MEM));
final Label cacheSizeLabel = new Label("cacheSize", new MapModel(values, KEY_SIZE));
statsContainer.add(new Label("title"));
statsContainer.add(totalCountLabel);
statsContainer.add(hitCountLabel);
statsContainer.add(missCountLabel);
statsContainer.add(missRateLabel);
statsContainer.add(hitRateLabel);
statsContainer.add(evictedLabel);
statsContainer.add(currentMemoryLabel);
statsContainer.add(cacheSizeLabel);
AjaxButton statistics = new AjaxButton("statistics") {
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
try {
final ConfigurableBlobStore store = GeoServerExtensions
.bean(ConfigurableBlobStore.class);
// If checked, all the statistics are reported
if (store != null) {
CacheStatistics stats = store.getCacheStatistics();
long hitCount = stats.getHitCount();
long missCount = stats.getMissCount();
long total = stats.getRequestCount();
double hitRate = stats.getHitRate();
double missRate = stats.getMissRate();
long evicted = stats.getEvictionCount();
double currentMem = stats.getCurrentMemoryOccupation();
long byteToMb = 1024 * 1024;
double actualSize = ((long) (100 * (stats.getActualSize() * 1.0d) / byteToMb)) / 100d;
double totalSize = ((long) (100 * (stats.getTotalSize() * 1.0d) / byteToMb)) / 100d;
// If a parameter is not correct, Unavailable is used
values.put(KEY_MISS_RATE, missRate >= 0 ? missRate + " %" : "Unavailable");
values.put(KEY_HIT_RATE, hitRate >= 0 ? hitRate + " %" : "Unavailable");
values.put(KEY_EVICTED, evicted >= 0 ? evicted + "" : "Unavailable");
values.put(KEY_TOTAL_COUNT, total >= 0 ? total + "" : "Unavailable");
values.put(KEY_MISS_COUNT, missCount >= 0 ? missCount + "" : "Unavailable");
values.put(KEY_HIT_COUNT, hitCount >= 0 ? hitCount + "" : "Unavailable");
values.put(KEY_CURRENT_MEM, currentMem >= 0 ? currentMem + " %"
: "Unavailable");
values.put(KEY_SIZE, currentMem >= 0 && actualSize >= 0 ? actualSize
+ " / " + totalSize + " Mb" : "Unavailable");
}
} catch (Throwable t) {
error(t);
}
target.add(statsContainer);
}
};
container.add(statsContainer);
container.add(statistics);
}
/**
* {@link IValidator} implementation for checking if the value is null, or less or equal to 0
*
* @author Nicola Lagomarsini Geosolutions
*/
static class MinimumLongValidator implements IValidator<Long> {
private String errorKey;
public MinimumLongValidator(String error) {
this.errorKey = error;
}
@Override
public void validate(IValidatable<Long> iv) {
if (iv == null || iv.getValue() <= 0) {
ValidationError error = new ValidationError();
error.setMessage(new ParamResourceModel(errorKey, null, "").getObject());
iv.error(error);
}
}
}
/**
* {@link IValidator} implementation for checking if the concurrency Level is null, or less than or equal to 0
*
* @author Nicola Lagomarsini Geosolutions
*/
static class MinimumConcurrencyValidator implements IValidator<Integer> {
@Override
public void validate(IValidatable<Integer> iv) {
if (iv == null || iv.getValue() <= 0) {
ValidationError error = new ValidationError();
error.setMessage(new ParamResourceModel("BlobStorePanel.invalidConcurrency", null, "").getObject());
iv.error(error);
}
}
}
/**
* {@link ChoiceRenderer} implementation mapping available {@link CacheProvider} names with the {@link CacheProvider} class names.
*
* @author Nicola Lagomarsini Geosolutions
*/
static class CacheProviderRenderer extends ChoiceRenderer<String> {
private Map<String, String> map;
public CacheProviderRenderer(Map<String, String> map) {
this.map = map;
}
@Override
public Object getDisplayValue(String object) {
return map.get(object);
}
@Override
public String getIdValue(String object, int index) {
return object;
}
}
/**
* {@link WebMarkupContainer} extension used for rendering a set of components based on the mapping of a key
*
* @author Nicola Lagomarsini Geosolutions
*/
static class CacheConfigContainerWrapper extends WebMarkupContainer {
public CacheConfigContainerWrapper(String id, String key, IModel<GWCConfig> gwcConfigModel) {
super(id);
setMapKey(key, gwcConfigModel);
}
/**
* This method removes all the previous mappings from the container and then adds the components again by setting as default value the one
* taken from the key mapped.
*
* @param key
* @param gwcConfigModel
*/
public void setMapKey(final String key, IModel<GWCConfig> gwcConfigModel) {
removeAll();
// get the CacheConfigurations Model
IModel<Map<String, CacheConfiguration>> cacheConfigurations = new PropertyModel<Map<String, CacheConfiguration>>(
gwcConfigModel, "cacheConfigurations");
// Get CacheConfiguration model
MapModel cacheConfiguration = new MapModel(cacheConfigurations, key);
// Cache configuration parameters
IModel<Long> hardMemoryLimit = new PropertyModel<Long>(cacheConfiguration,
"hardMemoryLimit");
IModel<Long> evictionTimeValue = new PropertyModel<Long>(cacheConfiguration,
"evictionTime");
IModel<EvictionPolicy> policy = new PropertyModel<EvictionPolicy>(cacheConfiguration,
"policy");
IModel<Integer> concurrencyLevel = new PropertyModel<Integer>(cacheConfiguration,
"concurrencyLevel");
final TextField<Long> hardMemory = new TextField<Long>("hardMemoryLimit",
hardMemoryLimit);
hardMemory.setType(Long.class).setOutputMarkupId(true).setEnabled(true);
hardMemory.add(new MinimumLongValidator("BlobStorePanel.invalidHardMemory"));
final TextField<Long> evictionTime = new TextField<Long>("evictionTime",
evictionTimeValue);
evictionTime.setType(Long.class).setOutputMarkupId(true).setEnabled(true);
// by the default all available eviction policies are available
List<EvictionPolicy> evictionPolicies = Arrays.asList(EvictionPolicy.values());
ConfigurableBlobStore store = GeoServerExtensions.bean(ConfigurableBlobStore.class);
if (store != null) {
// we use the current cache accepted eviction policies
CacheProvider cache = store.getCacheProviders().get(key);
evictionPolicies = cache.getSupportedPolicies();
}
final DropDownChoice<EvictionPolicy> policyDropDown = new DropDownChoice<EvictionPolicy>(
"policy", policy, evictionPolicies);
policyDropDown.setOutputMarkupId(true).setEnabled(true);
final TextField<Integer> textConcurrency = new TextField<Integer>("concurrencyLevel",
concurrencyLevel);
textConcurrency.setType(Integer.class).setOutputMarkupId(true).setEnabled(true);
textConcurrency.add(new MinimumConcurrencyValidator());
// Add all the parameters to the containes
add(hardMemory);
add(policyDropDown);
add(textConcurrency);
add(evictionTime);
}
}
}