/* (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.diskquota;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Radio;
import org.apache.wicket.markup.html.form.RadioGroup;
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.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.StringResourceModel;
import org.geoserver.gwc.GWC;
import org.geoserver.web.GeoServerApplication;
import org.geoserver.web.wicket.LocalizedChoiceRenderer;
import org.geotools.util.logging.Logging;
import org.geowebcache.diskquota.DiskQuotaConfig;
import org.geowebcache.diskquota.ExpirationPolicy;
import org.geowebcache.diskquota.QuotaStoreFactory;
import org.geowebcache.diskquota.jdbc.JDBCConfiguration;
import org.geowebcache.diskquota.jdbc.JDBCQuotaStoreFactory;
import org.geowebcache.diskquota.jdbc.SQLDialect;
import org.geowebcache.diskquota.storage.Quota;
import org.geowebcache.diskquota.storage.StorageUnit;
import org.springframework.context.ApplicationContext;
/**
* A panel to configure the global disk quota settings.
*
* @author groldan
*
*/
public class DiskQuotaConfigPanel extends Panel {
private static final long serialVersionUID = 1L;
private static final Logger LOGGER = Logging.getLogger(DiskQuotaConfigPanel.class);
private final IModel<StorageUnit> configQuotaUnitModel;
private final IModel<Double> configQuotaValueModel;
public DiskQuotaConfigPanel(final String id, final IModel<DiskQuotaConfig> diskQuotaConfigModel,
final IModel<JDBCConfiguration> jdbcQuotaConfigModel) {
super(id, diskQuotaConfigModel);
final DiskQuotaConfig diskQuotaConfig = diskQuotaConfigModel.getObject();
Quota globalQuota = diskQuotaConfig.getGlobalQuota();
if (globalQuota == null) {
LOGGER.info("There's no GWC global disk quota configured, setting a default of 100MiB");
globalQuota = new Quota(100, StorageUnit.MiB);
diskQuotaConfig.setGlobalQuota(globalQuota);
}
// use this two payload models to let the user configure the global quota as a decimal value
// plus a storage unit. Then at form sumbission we'll transform them back to a BigInteger
// representing the quota byte count
BigInteger bytes = globalQuota.getBytes();
StorageUnit bestRepresentedUnit = StorageUnit.bestFit(bytes);
BigDecimal transformedQuota = StorageUnit.B.convertTo(new BigDecimal(bytes),
bestRepresentedUnit);
configQuotaValueModel = new Model<Double>(transformedQuota.doubleValue());
configQuotaUnitModel = new Model<StorageUnit>(bestRepresentedUnit);
addDiskQuotaIntegrationEnablement(diskQuotaConfigModel);
addDiskQuotaStoreChooser(diskQuotaConfigModel, jdbcQuotaConfigModel);
addCleanUpFrequencyConfig(diskQuotaConfigModel);
addGlobalQuotaConfig(diskQuotaConfigModel, configQuotaValueModel, configQuotaUnitModel);
addGlobalExpirationPolicyConfig(diskQuotaConfigModel);
}
private void addDiskQuotaStoreChooser(IModel<DiskQuotaConfig> diskQuotaModel, IModel<JDBCConfiguration> jdbcQuotaConfigModel) {
final WebMarkupContainer quotaStoreContainer = new WebMarkupContainer("quotaStoreContainer");
quotaStoreContainer.setOutputMarkupId(true);
add(quotaStoreContainer);
// get the list of supported quota store types
ApplicationContext applicationContext = GeoServerApplication.get().getApplicationContext();
Map<String, QuotaStoreFactory> factories = applicationContext.getBeansOfType(QuotaStoreFactory.class);
List<String> storeNames = new ArrayList<String>();
for (QuotaStoreFactory sf : factories.values()) {
storeNames.addAll(sf.getSupportedStoreNames());
}
Collections.sort(storeNames);
// add the drop down chooser
PropertyModel<String> storeNameModel = new PropertyModel<String>(diskQuotaModel, "quotaStore");
if(diskQuotaModel.getObject().getQuotaStore() == null) {
storeNameModel.setObject(JDBCQuotaStoreFactory.H2_STORE);
}
final DropDownChoice<String> quotaStoreChooser = new DropDownChoice<String>("diskQuotaStore", storeNameModel, storeNames,
new LocalizedChoiceRenderer(this));
quotaStoreChooser.setOutputMarkupId(true);
quotaStoreContainer.add(quotaStoreChooser);
// add the JDBC container
final WebMarkupContainer jdbcContainer = new WebMarkupContainer("jdbcQuotaStoreContainer");
jdbcContainer.setOutputMarkupId(true);
jdbcContainer.setVisible("JDBC".equals(quotaStoreChooser.getModelObject()));
quotaStoreContainer.add(jdbcContainer);
// add a chooser for the dialect type
List<String> dialectBeanNames = new ArrayList<String>(applicationContext.getBeansOfType(SQLDialect.class).keySet());
List<String> dialectNames = new ArrayList<String>();
for (String beanName : dialectBeanNames) {
int idx = beanName.indexOf("QuotaDialect");
if(idx > 0) {
dialectNames.add(beanName.substring(0, idx));
}
}
JDBCConfiguration config = jdbcQuotaConfigModel.getObject();
IModel<String> dialectModel = new PropertyModel<String>(jdbcQuotaConfigModel, "dialect");
DropDownChoice<String> dialectChooser = new DropDownChoice<String>("dialectChooser", dialectModel, dialectNames);
dialectChooser.setRequired(true);
jdbcContainer.add(dialectChooser);
// add a chooser for the connection type
List<String> connectionTypes = Arrays.asList("JNDI", "PRIVATE_POOL");
Model<String> connectionTypeModel = new Model<String>();
if(config.getJNDISource() == null) {
connectionTypeModel.setObject("PRIVATE_POOL");
} else {
connectionTypeModel.setObject("JNDI");
}
final DropDownChoice<String> connectionTypeChooser = new DropDownChoice<String>("connectionTypeChooser",
connectionTypeModel, connectionTypes, new LocalizedChoiceRenderer(this));
connectionTypeChooser.setOutputMarkupId(true);
jdbcContainer.add(connectionTypeChooser);
// make the JDBC configuration visible only when the user chose a JDBC store
quotaStoreChooser.add(new AjaxFormComponentUpdatingBehavior("change") {
private static final long serialVersionUID = -6806581935751265393L;
@Override
protected void onUpdate(AjaxRequestTarget target) {
jdbcContainer.setVisible("JDBC".equals(quotaStoreChooser
.getModelObject()));
target.add(quotaStoreContainer);
}
});
// a container for jndi and local private pool options
final WebMarkupContainer connectionTypeContainer = new WebMarkupContainer("connectionTypeContainer");
connectionTypeContainer.setOutputMarkupId(true);
jdbcContainer.add(connectionTypeContainer);
// add a field for editing the JNDI connection parameters
final WebMarkupContainer jndiContainer = new WebMarkupContainer("jndiLocationContainer");
jndiContainer.setVisible(config.getJNDISource() != null);
connectionTypeContainer.add(jndiContainer);
IModel<String> jndiModel = new PropertyModel<String>(jdbcQuotaConfigModel, "jNDISource");
TextField<String> jndiLocation = new TextField<String>("jndiLocation", jndiModel);
jndiLocation.setRequired(true);
jndiContainer.add(jndiLocation);
// and a panel to edit the private jdbc pool
IModel<JDBCConfiguration.ConnectionPoolConfiguration> poolConfigurationModel =
new PropertyModel<JDBCConfiguration.ConnectionPoolConfiguration>(jdbcQuotaConfigModel, "connectionPool");
final JDBCConnectionPoolPanel privatePoolPanel = new JDBCConnectionPoolPanel("connectionPoolConfigurator", poolConfigurationModel);
privatePoolPanel.setVisible(config.getJNDISource() == null);
connectionTypeContainer.add(privatePoolPanel);
// make the two ways to configure the JDBC store show up as alternatives
connectionTypeChooser.add(new AjaxFormComponentUpdatingBehavior("change") {
private static final long serialVersionUID = -8286073946292214144L;
@Override
protected void onUpdate(AjaxRequestTarget target) {
boolean jndiVisible = "JNDI".equals(connectionTypeChooser
.getModelObject());
jndiContainer.setVisible(jndiVisible);
privatePoolPanel.setVisible(!jndiVisible);
target.add(connectionTypeContainer);
}
});
}
private void addGlobalQuotaConfig(final IModel<DiskQuotaConfig> diskQuotaModel,
IModel<Double> quotaValueModel, IModel<StorageUnit> unitModel) {
final IModel<Quota> globalQuotaModel = new PropertyModel<Quota>(diskQuotaModel,
"globalQuota");
final IModel<Quota> globalUsedQuotaModel = new LoadableDetachableModel<Quota>() {
private static final long serialVersionUID = 1L;
@Override
protected Quota load() {
GWC gwc = GWC.get();
if (!gwc.isDiskQuotaAvailable()) {
return new Quota();// fake
}
return gwc.getGlobalUsedQuota();
}
};
Object[] progressMessageParams = { globalUsedQuotaModel.getObject().toNiceString(),
globalQuotaModel.getObject().toNiceString() };
IModel<String> progressMessageModel =
new StringResourceModel("DiskQuotaConfigPanel.usedQuotaMessage").setParameters(progressMessageParams);
addGlobalQuotaStatusBar(globalQuotaModel, globalUsedQuotaModel, progressMessageModel);
TextField<Double> quotaValue = new TextField<Double>("globalQuota", quotaValueModel);
quotaValue.setRequired(true);
add(quotaValue);
List<? extends StorageUnit> units = Arrays.asList(StorageUnit.MiB, StorageUnit.GiB,
StorageUnit.TiB);
DropDownChoice<StorageUnit> quotaUnitChoice;
quotaUnitChoice = new DropDownChoice<StorageUnit>("globalQuotaUnits", unitModel, units);
add(quotaUnitChoice);
}
private void addGlobalExpirationPolicyConfig(final IModel<DiskQuotaConfig> diskQuotaModel) {
IModel<ExpirationPolicy> globalQuotaPolicyModel = new PropertyModel<ExpirationPolicy>(
diskQuotaModel, "globalExpirationPolicyName");
RadioGroup<ExpirationPolicy> globalQuotaPolicy;
globalQuotaPolicy = new RadioGroup<ExpirationPolicy>("globalQuotaExpirationPolicy",
globalQuotaPolicyModel);
add(globalQuotaPolicy);
IModel<ExpirationPolicy> lfuModel = new Model<ExpirationPolicy>(ExpirationPolicy.LFU);
IModel<ExpirationPolicy> lruModel = new Model<ExpirationPolicy>(ExpirationPolicy.LRU);
Radio<ExpirationPolicy> globalQuotaPolicyLFU;
Radio<ExpirationPolicy> globalQuotaPolicyLRU;
globalQuotaPolicyLFU = new Radio<ExpirationPolicy>("globalQuotaPolicyLFU", lfuModel);
globalQuotaPolicyLRU = new Radio<ExpirationPolicy>("globalQuotaPolicyLRU", lruModel);
globalQuotaPolicy.add(globalQuotaPolicyLFU);
globalQuotaPolicy.add(globalQuotaPolicyLRU);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void addCleanUpFrequencyConfig(final IModel<DiskQuotaConfig> diskQuotaModel) {
final DiskQuotaConfig diskQuotaConfig = diskQuotaModel.getObject();
int frequency = diskQuotaConfig.getCacheCleanUpFrequency();
TimeUnit unit = diskQuotaConfig.getCacheCleanUpUnits();
if (TimeUnit.SECONDS != unit) {
frequency = (int) TimeUnit.SECONDS.convert(frequency, unit);
diskQuotaConfig.setCacheCleanUpFrequency(frequency);
diskQuotaConfig.setCacheCleanUpUnits(TimeUnit.SECONDS);
}
IModel<Integer> cleanUpFreqModel;
cleanUpFreqModel = new PropertyModel<Integer>(diskQuotaModel, "cacheCleanUpFrequency");
TextField<Integer> cleanUpFreq = new TextField<Integer>("cleanUpFreq", cleanUpFreqModel);
cleanUpFreq.setRequired(true);
cleanUpFreq.add(new AttributeModifier("title", new StringResourceModel(
"DiskQuotaConfigPanel.cleanUpFreq.title", (Component) null, null)));
add(cleanUpFreq);
{
Date lastRun = diskQuotaConfig.getLastCleanUpTime();
String resourceId;
HashMap<String, String> params = new HashMap<String, String>();
if (lastRun == null) {
resourceId = "DiskQuotaConfigPanel.cleanUpLastRunNever";
} else {
resourceId = "DiskQuotaConfigPanel.cleanUpLastRun";
long timeAgo = (System.currentTimeMillis() - lastRun.getTime()) / 1000;
String timeUnits = "s";
if (timeAgo > 60 * 60 * 24) {
timeUnits = "d";
timeAgo /= 60 * 60 * 24;
} else if (timeAgo > 60 * 60) {
timeUnits = "h";
timeAgo /= 60 * 60;
} else if (timeAgo > 60) {
timeUnits = "m";
timeAgo /= 60;
}
params.put("x", String.valueOf(timeAgo));
params.put("timeUnit", timeUnits);
}
IModel<String> lastRunModel = new StringResourceModel(resourceId, this, new Model(
params));
add(new Label("cleanUpLastRun", lastRunModel));
}
}
private void addDiskQuotaIntegrationEnablement(IModel<DiskQuotaConfig> diskQuotaModel) {
IModel<Boolean> quotaEnablementModel = new PropertyModel<Boolean>(diskQuotaModel, "enabled");
CheckBox diskQuotaIntegration = checkbox("enableDiskQuota", quotaEnablementModel,
"DiskQuotaConfigPanel.enableDiskQuota.title");
add(diskQuotaIntegration);
}
private void addGlobalQuotaStatusBar(final IModel<Quota> globalQuotaModel,
final IModel<Quota> globalUsedQuotaModel, IModel<String> progressMessageModel) {
Quota limit = globalQuotaModel.getObject();
Quota used = globalUsedQuotaModel.getObject();
BigInteger limitValue = limit.getBytes();
BigInteger usedValue = used.getBytes();
StorageUnit bestUnitForLimit = StorageUnit.bestFit(limitValue);
StorageUnit bestUnitForUsed = StorageUnit.bestFit(usedValue);
StorageUnit biggerUnit = bestUnitForLimit.compareTo(bestUnitForUsed) > 0 ? bestUnitForLimit
: bestUnitForUsed;
BigDecimal showLimit = StorageUnit.B.convertTo(new BigDecimal(limitValue), biggerUnit);
BigDecimal showUsed = StorageUnit.B.convertTo(new BigDecimal(usedValue), biggerUnit);
final IModel<Number> limitModel = new Model<Number>(showLimit);
final IModel<Number> usedModel = new Model<Number>(showUsed);
StatusBar statusBar = new StatusBar("globalQuotaProgressBar", limitModel, usedModel,
progressMessageModel);
add(statusBar);
}
public StorageUnit getStorageUnit() {
return configQuotaUnitModel.getObject();
}
public Object getQuotaValue() {
// REVISIT: it seems Wicket is sending back a plain string instead of a BigDecimal
return configQuotaValueModel.getObject();
}
static CheckBox checkbox(String id, IModel<Boolean> model, String titleKey) {
CheckBox checkBox = new CheckBox(id, model);
if (null != titleKey) {
AttributeModifier attributeModifier = new AttributeModifier("title", new StringResourceModel(titleKey, (Component) null, null));
checkBox.add(attributeModifier);
}
return checkBox;
}
}