/* (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.wms.eo.web; import java.util.List; import java.util.logging.Level; import org.apache.wicket.Component; import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow; import org.apache.wicket.markup.html.form.DropDownChoice; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.SubmitLink; import org.apache.wicket.markup.html.form.TextArea; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.util.convert.IConverter; import org.apache.wicket.validation.IValidatable; import org.apache.wicket.validation.IValidator; import org.apache.wicket.validation.ValidationError; import org.geoserver.catalog.CatalogBuilder; import org.geoserver.catalog.CoverageInfo; import org.geoserver.catalog.CoverageStoreInfo; import org.geoserver.catalog.DimensionInfo; import org.geoserver.catalog.LayerGroupInfo; import org.geoserver.catalog.LayerGroupInfo.Mode; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.MetadataMap; import org.geoserver.catalog.PublishedInfo; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.StyleInfo; import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.web.ComponentAuthorizer; import org.geoserver.web.GeoServerSecuredPage; import org.geoserver.web.data.layergroup.LayerGroupDetachableModel; import org.geoserver.web.data.layergroup.LayerInfoConverter; import org.geoserver.web.data.layergroup.RootLayerEntryPanel; import org.geoserver.web.data.layergroup.StyleInfoConverter; import org.geoserver.web.data.store.CoverageStoreNewPage; import org.geoserver.web.data.workspace.WorkspaceChoiceRenderer; import org.geoserver.web.data.workspace.WorkspacesModel; import org.geoserver.web.wicket.EnvelopePanel; import org.geoserver.web.wicket.GeoServerAjaxFormLink; import org.geoserver.web.wicket.GeoServerDialog; import org.geoserver.web.wicket.ParamResourceModel; import org.geoserver.wms.eo.EoCatalogBuilder; import org.geoserver.wms.eo.EoLayerType; import org.geotools.coverage.grid.io.StructuredGridCoverage2DReader; import org.geotools.gce.imagemosaic.ImageMosaicFormat; import org.opengis.referencing.crs.CoordinateReferenceSystem; /** * Handles layer group */ @SuppressWarnings({ "rawtypes", "unchecked", "serial" }) public abstract class EoLayerGroupAbstractPage extends GeoServerSecuredPage { public static final String GROUP = "group"; IModel<LayerGroupInfo> lgModel; EnvelopePanel envelopePanel; EoLayerGroupEntryPanel lgEntryPanel; String layerGroupId; protected RootLayerEntryPanel rootLayerPanel; TextField<String> name; ModalWindow popupWindow; String groupName; private GeoServerDialog dialog; /** * Subclasses must call this method to initialize the UI for this page * @param layerGroup */ protected void initUI(LayerGroupInfo layerGroup) { this.returnPageClass = EoLayerGroupPage.class; lgModel = new LayerGroupDetachableModel( layerGroup ); layerGroupId = layerGroup.getId(); add(popupWindow = new ModalWindow("popup")); add(dialog = new GeoServerDialog("dialog")); Form form = new Form( "form", new CompoundPropertyModel( lgModel ) ) { @Override public <C> IConverter<C> getConverter(Class<C> type) { if (LayerInfo.class.isAssignableFrom(type)) { return (IConverter<C>) new LayerInfoConverter(); } else if (StyleInfo.class.isAssignableFrom(type)) { return (IConverter<C>) new StyleInfoConverter(); } else { return super.getConverter(type); } } }; add(form); name = new TextField<String>("name"); name.setRequired(true); groupName = layerGroup.getName(); form.add(name); form.add(new TextField("title")); form.add(new TextArea("abstract")); final DropDownChoice<WorkspaceInfo> wsChoice = new DropDownChoice("workspace", new WorkspacesModel(), new WorkspaceChoiceRenderer()); wsChoice.setNullValid(true); if (!isAuthenticatedAsAdmin()) { wsChoice.setNullValid(false); wsChoice.setRequired(true); } form.add(wsChoice); //bounding box form.add(envelopePanel = new EnvelopePanel( "bounds" )/*.setReadOnly(true)*/); envelopePanel.setRequired(true); envelopePanel.setCRSFieldVisible(true); envelopePanel.setCrsRequired(true); envelopePanel.setOutputMarkupId( true ); form.add(new GeoServerAjaxFormLink( "generateBounds") { @Override public void onClick(AjaxRequestTarget target, Form form) { // build a layer group with the current contents of the group LayerGroupInfo lg = getCatalog().getFactory().createLayerGroup(); for ( EoLayerGroupEntry entry : lgEntryPanel.getEntries() ) { lg.getLayers().add(entry.getLayer()); lg.getStyles().add(entry.getStyle()); } try { // grab the eventually manually inserted CoordinateReferenceSystem crs = envelopePanel.getCoordinateReferenceSystem(); if ( crs != null ) { //ensure the bounds calculated in terms of the user specified crs new CatalogBuilder( getCatalog() ).calculateLayerGroupBounds( lg, crs ); } else { //calculate from scratch new CatalogBuilder( getCatalog() ).calculateLayerGroupBounds( lg ); } envelopePanel.setModelObject( lg.getBounds() ); target.add( envelopePanel ); } catch (Exception e) { throw new WicketRuntimeException( e ); } } }); form.add(lgEntryPanel = new EoLayerGroupEntryPanel( "layers", layerGroup, popupWindow )); lgEntryPanel.setOutputMarkupId(true); EoLayerTypeRenderer eoLayerTypeRenderer = new EoLayerTypeRenderer(); final DropDownChoice<EoLayerType> layerTypes = new DropDownChoice<EoLayerType>("layerType", EoLayerType.getRegularTypes(), eoLayerTypeRenderer); layerTypes.setModel(new Model<EoLayerType>(null)); layerTypes.setOutputMarkupId(true); form.add(layerTypes); final GeoServerAjaxFormLink createStoreLink = new GeoServerAjaxFormLink( "createStore" ) { @Override public void onClick(AjaxRequestTarget target, Form form) { final String layerGroupName = getNonNullGroupName(target); if(layerGroupName != null) { CoverageStoreNewPage coverageStoreCreator = new CoverageStoreNewPage(new ImageMosaicFormat().getName()) { protected void onSuccessfulSave(org.geoserver.catalog.CoverageStoreInfo info, org.geoserver.catalog.Catalog catalog, org.geoserver.catalog.CoverageStoreInfo savedStore) { EoCoverageSelectorPage page = new EoCoverageSelectorPage(EoLayerGroupAbstractPage.this, layerGroupName, savedStore.getId()); setResponsePage(page); }; }; setResponsePage(coverageStoreCreator); } else { dialog.showInfo(target, null, new ParamResourceModel("layerInfoTitle", EoLayerGroupAbstractPage.this), new ParamResourceModel("provideGroupName", EoLayerGroupAbstractPage.this)); } } }; createStoreLink.setOutputMarkupId(true); form.add( createStoreLink); final GeoServerAjaxFormLink addFromStoreLink = new GeoServerAjaxFormLink( "addFromStore" ) { @Override public void onClick(AjaxRequestTarget target, Form form) { final String layerGroupName = getNonNullGroupName(target); if(layerGroupName != null) { EoCoverageSelectorPage page = new EoCoverageSelectorPage(EoLayerGroupAbstractPage.this, layerGroupName); setResponsePage(page); } else { dialog.showInfo(target, null, new ParamResourceModel("layerInfoTitle", EoLayerGroupAbstractPage.this), new ParamResourceModel("provideGroupName", EoLayerGroupAbstractPage.this)); } } }; addFromStoreLink.setOutputMarkupId(true); form.add( addFromStoreLink); final GeoServerAjaxFormLink addLayerLink = new GeoServerAjaxFormLink( "addLayer" ) { @Override public void onClick(AjaxRequestTarget target, Form form) { popupWindow.setInitialHeight( 375 ); popupWindow.setInitialWidth( 525 ); popupWindow.setTitle(new ParamResourceModel("chooseLayer", this)); layerTypes.processInput(); final EoLayerType layerType = layerTypes.getModelObject(); popupWindow.setContent( new EoLayerListPanel(popupWindow.getContentId(), layerType, lgEntryPanel.entryProvider) { @Override protected void handleLayer(LayerInfo layer, AjaxRequestTarget target) { popupWindow.close( target ); layer.getMetadata().put(EoLayerType.KEY, layerType); lgEntryPanel.entryProvider.getItems().add( new EoLayerGroupEntry( layer, layer.getDefaultStyle(), groupName)); target.add(lgEntryPanel); layerTypes.setDefaultModelObject(layerTypes.getDefaultModelObject()); target.add(layerTypes); } }); popupWindow.show(target); } }; addLayerLink.setEnabled(false); form.add( addLayerLink); final DropDownChoice<EoLayerGroupEntry> outlinesEntryChooser = new DropDownChoice<EoLayerGroupEntry>("sourceLayer", new OutlineSourceModel(lgEntryPanel.items), new LayerGroupEntryRenderer()); outlinesEntryChooser.setModel(new Model<EoLayerGroupEntry>(null)); outlinesEntryChooser.setOutputMarkupId(true); outlinesEntryChooser.setEnabled(!outlinesPresent(lgEntryPanel.items)); form.add(outlinesEntryChooser); outlinesEntryChooser.add(new AjaxFormComponentUpdatingBehavior("change") { @Override protected void onUpdate(AjaxRequestTarget target) { wsChoice.processInput(); WorkspaceInfo ws = (WorkspaceInfo) wsChoice.getDefaultModelObject(); outlinesEntryChooser.processInput(); EoLayerGroupEntry entry = outlinesEntryChooser.getModelObject(); try { EoCatalogBuilder builder = new EoCatalogBuilder(getCatalog()); CoverageInfo coverage = (CoverageInfo) ((LayerInfo) entry.getLayer()).getResource(); CoverageStoreInfo store = coverage.getStore(); String url = store.getURL(); StructuredGridCoverage2DReader reader = (StructuredGridCoverage2DReader) coverage.getGridCoverageReader(null, null); LayerInfo layer = builder.createEoOutlineLayer(url, ws, groupName, coverage.getNativeCoverageName(), reader); lgEntryPanel.items.add(new EoLayerGroupEntry(layer, layer.getDefaultStyle(), groupName)); } catch(Exception e) { LOGGER.log(Level.SEVERE, "Failed to create outlines layer", e); String layerName = entry.getLayer().prefixedName(); error(new ParamResourceModel("outlinesCreationError", EoLayerGroupAbstractPage.this, layerName, e.getMessage()).getString()); } finally { outlinesEntryChooser.setDefaultModelObject(null); } target.add(lgEntryPanel); target.add(getFeedbackPanel()); target.add(outlinesEntryChooser); } }); layerTypes.add(new AjaxFormComponentUpdatingBehavior("change") { @Override protected void onUpdate(AjaxRequestTarget target) { layerTypes.processInput(); boolean input = layerTypes.getModelObject() != null; addLayerLink.setEnabled(input); target.add(addLayerLink); } }); name.add(new AjaxFormComponentUpdatingBehavior("blur") { @Override protected void onUpdate(AjaxRequestTarget target) { groupName = name.getInput(); boolean nameAvailable = groupName != null && !"".equals(groupName.trim()); if(!nameAvailable) { info(new ParamResourceModel("provideGroupName", EoLayerGroupAbstractPage.this).getString()); } else { // inform the user about possible layer renames if(isLayerRenameRequired(groupName)) { info (new ParamResourceModel("layerRenameWarning", EoLayerGroupAbstractPage.this, groupName).getString()); } } target.add(createStoreLink); target.add(addFromStoreLink); target.add(getFeedbackPanel()); } }); if(name.getDefaultModelObject() == null || "".equals(name.getDefaultModelObject())) { info(new ParamResourceModel("provideGroupName", this).getString()); } form.add(saveLink()); form.add(cancelLink()); } /** * True if we already have an outline layer, false otherwise * @param items * */ private boolean outlinesPresent(List<EoLayerGroupEntry> items) { for (EoLayerGroupEntry entry : items) { if(entry.getLayerType() == EoLayerType.COVERAGE_OUTLINE) { return true; } } return false; } private boolean isLayerRenameRequired(String layerGroupName) { String prefix = layerGroupName + "_"; for(EoLayerGroupEntry entry : lgEntryPanel.entryProvider.getItems()) { String expectedName = prefix + entry.getLayerSubName(); if(!expectedName.equals(entry.getLayer().getName())) { return true; } } return false; } protected String getNonNullGroupName(AjaxRequestTarget target) { if(groupName == null || "".equals(groupName.trim())) { error("Please given a name to the layer grup before adding layers into it"); return null; } return groupName; } private Component cancelLink() { return new AjaxLink<String>("cancel") { @Override public void onClick(AjaxRequestTarget target) { doReturn(); } }; } private SubmitLink saveLink() { return new SubmitLink("save"){ @Override public void onSubmit() { // validation if(lgEntryPanel.getEntries().size() == 0) { error((String) new ParamResourceModel("oneLayerMinimum", getPage()).getObject()); return; } LayerGroupInfo lg = (LayerGroupInfo) lgModel.getObject(); // update the layer group entries lg.getLayers().clear(); lg.getStyles().clear(); for ( EoLayerGroupEntry entry : lgEntryPanel.getEntries() ) { PublishedInfo pi = entry.getLayer(); if(pi instanceof LayerInfo) { LayerInfo li = getCatalog().getLayer(pi.getId()); String expectedName = lg.getName() + "_" + entry.getLayerSubName(); String actualName = li.getName(); if(!expectedName.equals(actualName)) { ResourceInfo resource = li.getResource(); li.setName(expectedName); resource.setName(expectedName); getCatalog().save(resource); getCatalog().save(li); } lg.getLayers().add(li); lg.getStyles().add(entry.getStyle()); } } try { EoLayerGroupAbstractPage.this.save(); } catch(Exception e) { error(e); LOGGER.log(Level.WARNING, "Error adding/modifying layer group.", e); } } }; } private final void save() { LayerGroupInfo lg = (LayerGroupInfo) lgModel.getObject(); if(validateLayerGroupContents(lg)) { onSubmit(lg); } } private boolean validateLayerGroupContents(LayerGroupInfo lg) { boolean valid = true; List<EoLayerGroupEntry> items = lgEntryPanel.items; int browseCount = 0; LayerInfo browseLayer = null; StyleInfo browseLayerStyle = null; int bandsCount = 0; for (EoLayerGroupEntry entry : items) { if(!(entry.getLayer() instanceof LayerInfo)) { error(new ParamResourceModel("nestedLayerGroupInvalid", this)); } else { EoLayerType type = entry.getLayerType(); // count band and browse layers switch(type) { case BAND_COVERAGE: bandsCount++; break; case BROWSE_IMAGE: browseCount++; browseLayer = (LayerInfo) entry.getLayer(); browseLayerStyle = entry.getStyle(); break; default: break; } if(!checkDimensions(entry)) { valid = false; } } } if(browseCount != 1) { error(new ParamResourceModel("invalidBrowseCount", this, browseCount).getString()); valid = false; } if(bandsCount > 1) { error(new ParamResourceModel("invalidBandsCount", this, bandsCount).getString()); valid = false; } if(valid) { // set the layer root lg.setRootLayer(browseLayer); lg.setRootLayerStyle(browseLayerStyle); lg.setMode(Mode.EO); } return valid; } private boolean checkDimensions(EoLayerGroupEntry entry) { LayerInfo layer = (LayerInfo) entry.getLayer(); MetadataMap metadata = layer.getResource().getMetadata(); DimensionInfo timeDimension = metadata.get(ResourceInfo.TIME, DimensionInfo.class); boolean timeAvaiable = timeDimension != null && timeDimension.isEnabled(); if(!timeAvaiable) { error(new ParamResourceModel("EoLayerGroupError.invalidLayer", null, layer.getName()).getString()); return false; } else if(entry.getLayerType() != EoLayerType.BAND_COVERAGE) { return true; } // ok, has time, and it's a band layer. Does it have any extra dimension that can be // imagined to be used as a band selector? DimensionInfo elevationDimension = metadata.get(ResourceInfo.ELEVATION, DimensionInfo.class); if(elevationDimension != null && elevationDimension.isEnabled()) { return true; } // look for custom dimensions for (String key : metadata.keySet()) { if(key != null && key.startsWith(ResourceInfo.CUSTOM_DIMENSION_PREFIX)) { DimensionInfo di = metadata.get(key, DimensionInfo.class); if(di != null && di.isEnabled()) { return true; } } } // found nothing, the layer is not valid error(new ParamResourceModel("EoLayerGroupError.invalidBandCoverage", null, layer.getName()).getString()); return false; } /** * Subclasses */ protected abstract void onSubmit(LayerGroupInfo lg); class GroupNameValidator implements IValidator<String> { @Override public void validate(IValidatable<String> iv) { String name = (String) iv.getValue(); LayerGroupInfo other = getCatalog().getLayerGroupByName(name); if(other != null && (layerGroupId == null || !other.getId().equals(layerGroupId))) { iv.error(new ValidationError("duplicateGroupNameError").setVariable("name", name)); } } } @Override protected ComponentAuthorizer getPageAuthorizer() { return ComponentAuthorizer.WORKSPACE_ADMIN; } }