/* (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.layer; import static com.google.common.base.Preconditions.checkArgument; import static org.geoserver.gwc.GWC.tileLayerName; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.form.OnChangeAjaxBehavior; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Check; import org.apache.wicket.markup.html.form.CheckBox; import org.apache.wicket.markup.html.form.CheckGroup; import org.apache.wicket.markup.html.form.DropDownChoice; import org.apache.wicket.markup.html.form.FormComponent; import org.apache.wicket.markup.html.form.FormComponentPanel; import org.apache.wicket.markup.html.form.ChoiceRenderer; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.PropertyModel; import org.apache.wicket.model.ResourceModel; import org.apache.wicket.validation.IValidatable; import org.apache.wicket.validation.IValidator; import org.geoserver.catalog.CatalogInfo; import org.geoserver.catalog.LayerGroupInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.MetadataMap; import org.geoserver.catalog.PublishedInfo; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.impl.ModificationProxy; import org.geoserver.gwc.ConfigurableBlobStore; import org.geoserver.gwc.GWC; import org.geoserver.gwc.layer.CatalogLayerEventListener; import org.geoserver.gwc.layer.GeoServerTileLayer; import org.geoserver.gwc.layer.GeoServerTileLayerInfo; import org.geoserver.platform.GeoServerExtensions; import org.geoserver.web.wicket.GeoServerDialog; import org.geoserver.web.wicket.ParamResourceModel; import org.geowebcache.config.BlobStoreConfig; import org.geowebcache.config.XMLGridSubset; import org.geowebcache.diskquota.storage.Quota; import org.geowebcache.filter.parameters.ParameterFilter; import org.geowebcache.grid.GridSetBroker; import org.geowebcache.layer.TileLayer; import org.geowebcache.storage.blobstore.memory.CacheProvider; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; /** * Edit panel for a {@link GeoServerTileLayerInfo} (used to edit caching options for both * {@link LayerInfo} and {@link LayerGroupInfo}. * <p> * If a tile layer existed for the given layer or layer group and the "create a cached layer" * checkbox is unchecked, then the metadata properties defining the cached layers will be removed * from the layer/group {@link MetadataMap}. Once the save button is pressed on the enclosing page, * the {@link CatalogLayerEventListener} will caught the modification to the layer or layer group * and delete the cache for the layer. * </p> * * @author groldan * * @see GridSubsetsEditor * @see LayerCacheOptionsTabPanel * @see LayerGroupCacheOptionsPanel */ class GeoServerTileLayerEditor extends FormComponentPanel<GeoServerTileLayerInfo> { private static final long serialVersionUID = 7870938096047218989L; /** * Flag to indicate whether a cached layer initially existed for the given layer/group info so * that the cache for the layer is deleted at {@link #convertInput()} */ private final boolean cachedLayerExistedInitially; /** * the confirm removal of existing tile layer and its associated cache dialog */ private final GeoServerDialog confirmRemovalDialog; /** * Whether to create a {@link TileLayer} for this {@link LayerInfo} or {@link LayerGroupInfo} */ private final FormComponent<Boolean> createLayer; /** * Whether the cached layer is enabled (like in {@link TileLayer#isEnabled()} */ private final FormComponent<Boolean> enabled; /** * The blobstoreId */ private final DropDownChoice<String> blobStoreId; /** * Container for {@link #configs} */ private final WebMarkupContainer container; /** * Container for everything but {@link #createLayer} */ private final WebMarkupContainer configs; private final FormComponent<Integer> metaTilingX; private final FormComponent<Integer> metaTilingY; private final FormComponent<Integer> gutter; private final CheckGroup<String> cacheFormats; private final FormComponent<Integer> expireCache; private final FormComponent<Integer> expireClients; private final GridSubsetsEditor gridSubsets; private final ParameterFilterEditor parameterFilters; private final String originalLayerName; private IModel<? extends CatalogInfo> layerModel; private CheckBox enableInMemoryCaching; /** * @param id * @param layerModel * @param tileLayerModel must be a {@link GeoServerTileLayerInfoModel} */ public GeoServerTileLayerEditor(final String id, final IModel<? extends PublishedInfo> layerModel, final IModel<GeoServerTileLayerInfo> tileLayerModel) { super(id); checkArgument(tileLayerModel instanceof GeoServerTileLayerInfoModel); this.layerModel = layerModel; setModel(tileLayerModel); final GWC mediator = GWC.get(); final IModel<String> createTileLayerLabelModel; final PublishedInfo info = layerModel.getObject(); final GeoServerTileLayerInfo tileLayerInfo = tileLayerModel.getObject(); if (info instanceof LayerInfo) { createTileLayerLabelModel = new ResourceModel("createTileLayerForLayer"); ResourceInfo resource = ((LayerInfo) info).getResource(); // we need the _current_ name, regardless of it's name is being changed resource = ModificationProxy.unwrap(resource); originalLayerName = resource.getPrefixedName(); } else if (info instanceof LayerGroupInfo) { createTileLayerLabelModel = new ResourceModel("createTileLayerForLayerGroup"); // we need the _current_ name, regardless of if it's name is being changed LayerGroupInfo lgi = ModificationProxy.unwrap((LayerGroupInfo) info); originalLayerName = tileLayerName(lgi); } else { throw new IllegalArgumentException( "Provided model does not target a LayerInfo nor a LayerGroupInfo: " + info); } TileLayer tileLayer = null; if (originalLayerName != null) { try { tileLayer = mediator.getTileLayerByName(originalLayerName); } catch (IllegalArgumentException notFound) { // } } cachedLayerExistedInitially = tileLayer != null; // UI construction phase add(confirmRemovalDialog = new GeoServerDialog("confirmRemovalDialog")); confirmRemovalDialog.setInitialWidth(360); confirmRemovalDialog.setInitialHeight(180); add(new Label("createTileLayerLabel", createTileLayerLabelModel)); // Get the model and check if the Enabled parameter has been defined GeoServerTileLayerInfoModel model = ((GeoServerTileLayerInfoModel)tileLayerModel); boolean undefined = model.getEnabled() == null; boolean doCreateTileLayer; if (tileLayerInfo.getId() != null) { doCreateTileLayer = true; } else if (isNew() && mediator.getConfig().isCacheLayersByDefault()) { doCreateTileLayer = true; } else { doCreateTileLayer = false; } // Add the enabled/disabled parameter depending on the doCreateTileLayer variable if not already set if (undefined) { model.setEnabled(doCreateTileLayer); } add(createLayer = new CheckBox("createTileLayer", new Model<Boolean>(doCreateTileLayer))); createLayer.add(new AttributeModifier("title", new ResourceModel( "createTileLayer.title"))); container = new WebMarkupContainer("container"); container.setOutputMarkupId(true); add(container); configs = new WebMarkupContainer("configs"); configs.setOutputMarkupId(true); container.add(configs); add(enabled = new CheckBox("enabled", new PropertyModel<Boolean>(getModel(), "enabled"))); enabled.add(new AttributeModifier("title", new ResourceModel("enabled.title"))); configs.add(enabled); ChoiceRenderer<String> blobStoreRenderer = new ChoiceRenderer<String>() { private static final long serialVersionUID = 1L; final String defaultStore = getDefaultBlobStoreId(); @Override public String getIdValue(String object, int index) { return object; } @Override public Object getDisplayValue(String object) { String value = object; if(object.equals(defaultStore)){ value += " (*)"; } return value; } }; PropertyModel<String> blobStoreModel = new PropertyModel<String>(getModel(), "blobStoreId"); List<String> blobStoreChoices = getBlobStoreIds(); configs.add(blobStoreId = new DropDownChoice<String>("blobStoreId", blobStoreModel, blobStoreChoices, blobStoreRenderer)); blobStoreId.setNullValid(true); blobStoreId.add(new AttributeModifier("title", new ResourceModel("blobStoreId.title"))); add(new IValidator<GeoServerTileLayerInfo>() { private static final long serialVersionUID = 5240602030478856537L; @Override public void validate(IValidatable<GeoServerTileLayerInfo> validatable) { final Boolean createVal = createLayer.getConvertedInput(); final Boolean enabledVal = enabled.getConvertedInput(); final String blobStoreIdVal = blobStoreId.getConvertedInput(); if (createVal && enabledVal && !isBlobStoreEnabled(blobStoreIdVal)) { error(new ParamResourceModel("enabledError", GeoServerTileLayerEditor.this).getString()); } } }); // CheckBox for enabling/disabling inner caching for the layer enableInMemoryCaching = new CheckBox("inMemoryCached", new PropertyModel<Boolean>(getModel(), "inMemoryCached")); ConfigurableBlobStore store = GeoServerExtensions.bean(ConfigurableBlobStore.class); if(store != null && store.getCache() != null){ enableInMemoryCaching.setEnabled(mediator.getConfig().isInnerCachingEnabled() && !store.getCache().isImmutable()); } configs.add(enableInMemoryCaching); List<Integer> metaTilingChoices = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20); IModel<Integer> metaTilingXModel = new PropertyModel<Integer>(getModel(), "metaTilingX"); metaTilingX = new DropDownChoice<Integer>("metaTilingX", metaTilingXModel, metaTilingChoices); configs.add(metaTilingX); IModel<Integer> metaTilingYModel = new PropertyModel<Integer>(getModel(), "metaTilingY"); metaTilingY = new DropDownChoice<Integer>("metaTilingY", metaTilingYModel, metaTilingChoices); configs.add(metaTilingY); IModel<Integer> gutterModel = new PropertyModel<Integer>(getModel(), "gutter"); List<Integer> gutterChoices = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 50, 100); gutter = new DropDownChoice<Integer>("gutter", gutterModel, gutterChoices); configs.add(gutter); IModel<Set<String>> mimeFormatsModel = new PropertyModel<Set<String>>(getModel(), "mimeFormats"); cacheFormats = new CheckGroup<String>("cacheFormatsGroup", mimeFormatsModel); cacheFormats.setLabel(new ResourceModel("cacheFormats")); configs.add(cacheFormats); final List<String> formats; formats = Lists.newArrayList(GWC.getAdvertisedCachedFormats(info.getType())); ListView<String> cacheFormatsList = new ListView<String>("cacheFormats", formats) { private static final long serialVersionUID = 1L; @Override protected void populateItem(ListItem<String> item) { item.add(new Check<String>("cacheFormatsOption", item.getModel())); item.add(new Label("name", item.getModel())); } }; cacheFormatsList.setReuseItems(true);// otherwise it looses state on invalid form state // submits cacheFormats.add(cacheFormatsList); IModel<Integer> expireCacheModel = new PropertyModel<Integer>(getModel(), "expireCache"); expireCache = new TextField<Integer>("expireCache", expireCacheModel); configs.add(expireCache); IModel<Integer> expireClientsModel = new PropertyModel<Integer>(getModel(), "expireClients"); expireClients = new TextField<Integer>("expireClients", expireClientsModel); configs.add(expireClients); IModel<Set<XMLGridSubset>> gridSubsetsModel; gridSubsetsModel = new PropertyModel<Set<XMLGridSubset>>(getModel(), "gridSubsets"); gridSubsets = new GridSubsetsEditor("cachedGridsets", gridSubsetsModel); configs.add(gridSubsets); IModel<Set<ParameterFilter>> parameterFilterModel; parameterFilterModel = new PropertyModel<Set<ParameterFilter>>(getModel(), "parameterFilters"); parameterFilters = new ParameterFilterEditor("parameterFilters", parameterFilterModel, layerModel); configs.add(parameterFilters); // behavior phase configs.setVisible(createLayer.getModelObject()); setValidating(createLayer.getModelObject()); createLayer.add(new OnChangeAjaxBehavior() { private static final long serialVersionUID = 1L; @Override protected void onUpdate(AjaxRequestTarget target) { final boolean createTileLayer = createLayer.getModelObject().booleanValue(); if (!createTileLayer && cachedLayerExistedInitially) { confirmRemovalOfExistingTileLayer(target); } else { updateConfigsVisibility(target); } } }); } private List<String> getBlobStoreIds() { List<String> blobStoreIds = new ArrayList<String>(); for (BlobStoreConfig blobStore : GWC.get().getBlobStores()) { blobStoreIds.add(blobStore.getId()); } return blobStoreIds; } private boolean isBlobStoreEnabled(String blobStoreId) { if (blobStoreId == null) { return true; } for (BlobStoreConfig blobStore : GWC.get().getBlobStores()) { if(blobStore.getId().equals(blobStoreId)) { return blobStore.isEnabled(); } } return false; } private boolean isNew() { GeoServerTileLayerInfoModel model = (GeoServerTileLayerInfoModel) super.getModel(); return model.isNew(); } private String getDefaultBlobStoreId(){ BlobStoreConfig defaultBlobStore = GWC.get().getDefaultBlobStore(); return defaultBlobStore == null? null : defaultBlobStore.getId(); } public void save() { final GWC gwc = GWC.get(); final CatalogInfo layer = layerModel.getObject(); final GeoServerTileLayerInfo tileLayerInfo = getModelObject(); final boolean tileLayerExists = gwc.hasTileLayer(layer); GeoServerTileLayerInfoModel model = (GeoServerTileLayerInfoModel) getModel(); final boolean createLayer = model.getEnabled() == null ? GWC.get().getConfig() .isCacheLayersByDefault() : model.getEnabled(); if (!createLayer) { if (tileLayerExists) { String tileLayerName = tileLayerInfo.getName(); gwc.removeTileLayers(Arrays.asList(tileLayerName)); } return; } // if we're creating a new layer, at this point the layer has already been created and hence // has an id Preconditions.checkState(layer.getId() != null); tileLayerInfo.setId(layer.getId()); final String name; final GridSetBroker gridsets = gwc.getGridSetBroker(); GeoServerTileLayer tileLayer; if (layer instanceof LayerGroupInfo) { LayerGroupInfo groupInfo = (LayerGroupInfo) layer; name = tileLayerName(groupInfo); tileLayer = new GeoServerTileLayer(groupInfo, gridsets, tileLayerInfo); } else { LayerInfo layerInfo = (LayerInfo) layer; name = tileLayerName(layerInfo); tileLayer = new GeoServerTileLayer(layerInfo, gridsets, tileLayerInfo); } tileLayerInfo.setName(name); // Remove the Layer from the cache if it is present ConfigurableBlobStore store = GeoServerExtensions.bean(ConfigurableBlobStore.class); if(store != null){ CacheProvider cache = store.getCache(); if (cache != null) { if (enableInMemoryCaching.getModelObject()) { cache.removeUncachedLayer(name); } else { cache.addUncachedLayer(name); } } } if (tileLayerExists) { gwc.save(tileLayer); } else { gwc.add(tileLayer); } } private void updateConfigsVisibility(AjaxRequestTarget target) { final boolean createTileLayer = createLayer.getModelObject().booleanValue(); setValidating(createTileLayer); configs.setVisible(createTileLayer); target.add(container); } private void confirmRemovalOfExistingTileLayer(final AjaxRequestTarget origTarget) { // show confirm cache removal dialog for this layer confirmRemovalDialog.setTitle(new Model<String>("Confirm removal of cached contents?")); // if there is something to cancel, let's warn the user about what // could go wrong, and if the user accepts, let's delete what's needed confirmRemovalDialog.showOkCancel(origTarget, new GeoServerDialog.DialogDelegate() { private static final long serialVersionUID = 1L; @Override protected Component getContents(final String id) { // show a confirmation panel for all the objects we have to remove GWC gwc = GWC.get(); Quota usedQuota = gwc.getUsedQuota(originalLayerName); if (usedQuota == null) { usedQuota = new Quota(); } String usedQuotaStr = usedQuota.toNiceString(); return new Label(id, new ParamResourceModel("confirmTileLayerRemoval", GeoServerTileLayerEditor.this, usedQuotaStr)); } @Override protected boolean onSubmit(final AjaxRequestTarget target, final Component contents) { return true; } @Override public void onClose(final AjaxRequestTarget target) { target.add(createLayer); updateConfigsVisibility(target); } @Override protected boolean onCancel(final AjaxRequestTarget target) { createLayer.setModelObject(Boolean.TRUE); final boolean closeWindow = true; return closeWindow; } }); } private void setValidating(final boolean validate) { gridSubsets.setValidating(validate); cacheFormats.setRequired(validate); } /** * @see org.apache.wicket.markup.html.form.FormComponent#convertInput() */ @Override public void convertInput() { createLayer.processInput(); final boolean createTileLayer = createLayer.getModelObject().booleanValue(); GeoServerTileLayerInfoModel model = ((GeoServerTileLayerInfoModel)getModel()); model.setEnabled(createTileLayer); GeoServerTileLayerInfo tileLayerInfo = getModelObject(); if (createTileLayer) { enabled.processInput(); expireCache.processInput(); expireClients.processInput(); metaTilingX.processInput(); metaTilingY.processInput(); gutter.processInput(); cacheFormats.processInput(); parameterFilters.processInput(); gridSubsets.processInput(); // // Remove add the Layer to the cache if it is present // ConfigurableBlobStore store = GeoServerExtensions.bean(ConfigurableBlobStore.class); // if(store != null){ // CacheProvider cache = store.getCache(); // if (cache != null) { // if (enableInMemoryCaching.getModelObject()) { // cache.removeUncachedLayer(getModel().getObject().getName()); // } else { // cache.addUncachedLayer(getModel().getObject().getName()); // } // } // } tileLayerInfo.setId(layerModel.getObject().getId()); setConvertedInput(tileLayerInfo); } else { tileLayerInfo.setId(null); setConvertedInput(tileLayerInfo); } setModelObject(tileLayerInfo); } /** * @see org.apache.wicket.Component#onBeforeRender() */ @Override protected void onBeforeRender() { super.onBeforeRender(); } }