/* (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.web.data.workspace; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; import org.apache.wicket.Page; 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.form.AjaxCheckBox; 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.Form; import org.apache.wicket.markup.html.form.FormComponentPanel; import org.apache.wicket.markup.html.form.SubmitLink; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.html.image.Image; import org.apache.wicket.markup.html.link.BookmarkablePageLink; import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.model.CompoundPropertyModel; 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.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.request.resource.PackageResourceReference; import org.apache.wicket.validation.validator.RangeValidator; import org.apache.wicket.validation.validator.UrlValidator; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.NamespaceInfo; import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.config.ContactInfo; import org.geoserver.config.GeoServer; import org.geoserver.config.ServiceInfo; import org.geoserver.config.SettingsInfo; import org.geoserver.config.impl.ServiceInfoImpl; import org.geoserver.ows.util.OwsUtils; import org.geoserver.web.ComponentAuthorizer; import org.geoserver.web.GeoServerApplication; import org.geoserver.web.GeoServerBasePage; import org.geoserver.web.GeoServerSecuredPage; import org.geoserver.web.admin.ContactPanel; import org.geoserver.web.admin.GlobalSettingsPage; import org.geoserver.web.data.namespace.NamespaceDetachableModel; import org.geoserver.web.data.settings.SettingsPluginPanelInfo; import org.geoserver.web.services.BaseServiceAdminPage; import org.geoserver.web.services.ServiceMenuPageInfo; import org.geoserver.web.wicket.GeoServerDialog; import org.geoserver.web.wicket.HelpLink; import org.geoserver.web.wicket.ParamResourceModel; import org.geoserver.web.wicket.URIValidator; import org.geoserver.web.wicket.XMLNameValidator; import org.geotools.util.logging.Logging; /** * Allows editing a specific workspace */ public class WorkspaceEditPage extends GeoServerSecuredPage { private static final long serialVersionUID = 4341324830412716976L; private static final Logger LOGGER = Logging.getLogger("org.geoserver.web.data.workspace"); IModel<WorkspaceInfo> wsModel; IModel<NamespaceInfo> nsModel; boolean defaultWs; SettingsPanel settingsPanel; ServicesPanel servicesPanel; GeoServerDialog dialog; /** * Uses a "name" parameter to locate the workspace * @param parameters */ public WorkspaceEditPage(PageParameters parameters) { String wsName = parameters.get("name").toString(); WorkspaceInfo wsi = getCatalog().getWorkspaceByName(wsName); if(wsi == null) { getSession().error( new ParamResourceModel("WorkspaceEditPage.notFound", this, wsName).getString() ); doReturn(WorkspacePage.class); return; } init(wsi); } public WorkspaceEditPage(WorkspaceInfo ws) { init(ws); } private void init(WorkspaceInfo ws) { defaultWs = ws.getId().equals(getCatalog().getDefaultWorkspace().getId()); wsModel = new WorkspaceDetachableModel(ws); NamespaceInfo ns = getCatalog().getNamespaceByPrefix( ws.getName() ); nsModel = new NamespaceDetachableModel(ns); Form<NamespaceInfo> form = new Form<NamespaceInfo>( "form", new CompoundPropertyModel<NamespaceInfo>( nsModel ) ) { private static final long serialVersionUID = 5140757565172795453L; protected void onSubmit() { try { saveWorkspace(); } catch (RuntimeException e) { LOGGER.log(Level.WARNING, "Failed to save workspace", e); error(e.getMessage() == null ? "Failed to save workspace, no error message available, see logs for details" : e.getMessage()); } } }; add(form); //check for full admin, we don't allow workspace admins to change all settings boolean isFullAdmin = isAuthenticatedAsAdmin(); TextField<String> name = new TextField<String>("name", new PropertyModel<String>(wsModel, "name")); name.setRequired(true); name.setEnabled(isFullAdmin); name.add(new XMLNameValidator()); form.add(name); TextField<String> uri = new TextField<String>("uri", new PropertyModel<String>(nsModel, "uRI"), String.class); uri.setRequired(true); uri.add(new URIValidator()); form.add(uri); CheckBox defaultChk = new CheckBox("default", new PropertyModel<Boolean>(this, "defaultWs")); form.add(defaultChk); defaultChk.setEnabled(isFullAdmin); //stores // StorePanel storePanel = new StorePanel("storeTable", new StoreProvider(ws), false); // form.add(storePanel); add(dialog = new GeoServerDialog("dialog")); //local settings form.add(settingsPanel = new SettingsPanel("settings", wsModel)); form.add(new HelpLink("settingsHelp").setDialog(dialog)); //local services form.add(servicesPanel = new ServicesPanel("services", wsModel)); form.add(new HelpLink("servicesHelp").setDialog(dialog)); SubmitLink submit = new SubmitLink("save"); form.add(submit); form.setDefaultButton(submit); form.add(new BookmarkablePageLink<WorkspacePage>("cancel", WorkspacePage.class)); } private void saveWorkspace() { final Catalog catalog = getCatalog(); NamespaceInfo namespaceInfo = (NamespaceInfo) nsModel.getObject(); WorkspaceInfo workspaceInfo = (WorkspaceInfo) wsModel.getObject(); // sync up workspace name with namespace prefix, temp measure until the two become separate namespaceInfo.setPrefix(workspaceInfo.getName()); // this will ensure all datastore namespaces are updated when the workspace is modified catalog.save(workspaceInfo); catalog.save(namespaceInfo); if(defaultWs) { catalog.setDefaultWorkspace(workspaceInfo); } GeoServer geoServer = getGeoServer(); //persist/depersist any settings configured local to the workspace Settings set = settingsPanel.set; if (set.enabled) { if (set.model instanceof NewSettingsModel) { geoServer.add(set.model.getObject()); } else { geoServer.save(set.model.getObject()); } } else { //remove if necessary if (set.model instanceof ExistingSettingsModel) { geoServer.remove(set.model.getObject()); } } //persist/depersist any services configured local to this workspace for (Service s : servicesPanel.services) { if (s.enabled) { if (s.model instanceof ExistingServiceModel) { //nothing to do, service has already been added continue; } geoServer.add(s.model.getObject()); } else { //remove if necessary if (s.model instanceof ExistingServiceModel) { //means they are removing an existing service, look it up and remove geoServer.remove(s.model.getObject()); } } } doReturn(WorkspacePage.class); } @Override protected ComponentAuthorizer getPageAuthorizer() { return ComponentAuthorizer.WORKSPACE_ADMIN; } /* * Data object to hold onto transient settings, and maintain state of enabled for the workspace. */ static class Settings implements Serializable { private static final long serialVersionUID = -5855608735160516252L; /** track selection */ Boolean enabled; /** created settings, not yet added to configuration */ IModel<SettingsInfo> model; } static class ExistingSettingsModel extends LoadableDetachableModel<SettingsInfo> { private static final long serialVersionUID = -8203239697623788188L; IModel<WorkspaceInfo> wsModel; ExistingSettingsModel(IModel<WorkspaceInfo> wsModel) { this.wsModel = wsModel; } @Override protected SettingsInfo load() { GeoServer gs = GeoServerApplication.get().getGeoServer(); return gs.getSettings(wsModel.getObject()); } } static class NewSettingsModel extends Model<SettingsInfo> { private static final long serialVersionUID = -4365626821652771933L; IModel<WorkspaceInfo> wsModel; SettingsInfo info; NewSettingsModel(IModel<WorkspaceInfo> wsModel) { this.wsModel = wsModel; } @Override public SettingsInfo getObject() { if (info == null) { GeoServer gs = GeoServerApplication.get().getGeoServer(); info = gs.getFactory().createSettings(); //initialize from global settings SettingsInfo global = gs.getGlobal().getSettings(); //hack, we need to copy out composite objects separately to get around proxying // madness ContactInfo contact = gs.getFactory().createContact(); OwsUtils.copy(global.getContact(), contact, ContactInfo.class); OwsUtils.copy(global, info, SettingsInfo.class); info.setContact(contact); info.setWorkspace(wsModel.getObject()); } return info; } } class SettingsPanel extends FormComponentPanel<Serializable> { private static final long serialVersionUID = -1580928887379954134L; WebMarkupContainer settingsContainer; ContactPanel contactPanel; WebMarkupContainer otherSettingsPanel; Settings set; public SettingsPanel(String id, IModel<WorkspaceInfo> model) { super(id, new Model<>()); SettingsInfo settings = getGeoServer().getSettings(model.getObject()); set = new Settings(); set.enabled = settings != null; set.model = settings != null ? new ExistingSettingsModel(wsModel) : new NewSettingsModel(wsModel); add(new CheckBox("enabled", new PropertyModel<Boolean>(set, "enabled")). add(new AjaxFormComponentUpdatingBehavior("click") { private static final long serialVersionUID = -7851699665702753119L; @Override protected void onUpdate(AjaxRequestTarget target) { contactPanel.setVisible(set.enabled); otherSettingsPanel.setVisible(set.enabled); target.add(settingsContainer); } })); settingsContainer = new WebMarkupContainer("settingsContainer"); settingsContainer.setOutputMarkupId(true); add(settingsContainer); contactPanel = new ContactPanel("contact", new CompoundPropertyModel<ContactInfo>( new PropertyModel<ContactInfo>(set.model, "contact"))); contactPanel.setOutputMarkupId(true); contactPanel.setVisible(set.enabled); settingsContainer.add(contactPanel); otherSettingsPanel = new WebMarkupContainer("otherSettings", new CompoundPropertyModel<>(set.model)); otherSettingsPanel.setOutputMarkupId(true); otherSettingsPanel.setVisible(set.enabled); otherSettingsPanel.add(new CheckBox("verbose")); otherSettingsPanel.add(new CheckBox("verboseExceptions")); otherSettingsPanel.add(new CheckBox("localWorkspaceIncludesPrefix")); otherSettingsPanel.add(new TextField<Integer>("numDecimals").add(RangeValidator.minimum(0))); otherSettingsPanel.add(new DropDownChoice<String>("charset", GlobalSettingsPage.AVAILABLE_CHARSETS)); otherSettingsPanel.add(new TextField<String>("proxyBaseUrl").add(new UrlValidator())); // Addition of pluggable extension points ListView<SettingsPluginPanelInfo> extensions = SettingsPluginPanelInfo.createExtensions("extensions", set.model, getGeoServerApplication()); otherSettingsPanel.add(extensions); settingsContainer.add(otherSettingsPanel); } } /* * Data object to hold onto transient services, and maintain state of selected services for * the workspace. */ static class Service implements Serializable { private static final long serialVersionUID = 3283857206025172687L; /** track selection */ Boolean enabled; /** the admin page for the service */ ServiceMenuPageInfo adminPage; /** created service, not yet added to configuration */ IModel<ServiceInfo> model; } static class NewServiceModel extends Model<ServiceInfo> { private static final long serialVersionUID = -3467556623909292282L; IModel<WorkspaceInfo> wsModel; Class<ServiceInfo> serviceClass; ServiceInfo service; NewServiceModel(IModel<WorkspaceInfo> wsModel, Class<ServiceInfo> serviceClass) { this.wsModel = wsModel; this.serviceClass = serviceClass; } @Override public ServiceInfo getObject() { if (service == null) { service = create(); } return service; } ServiceInfo create() { //create it GeoServer gs = GeoServerApplication.get().getGeoServer(); ServiceInfo newService = gs.getFactory().create(serviceClass); //initialize from global service ServiceInfo global = gs.getService(serviceClass); OwsUtils.copy(global,newService, serviceClass); newService.setWorkspace(wsModel.getObject()); //hack, but need id to be null so its considered unattached ((ServiceInfoImpl)newService).setId(null); return newService; } } static class ExistingServiceModel extends LoadableDetachableModel<ServiceInfo> { private static final long serialVersionUID = -2170117760214309321L; IModel<WorkspaceInfo> wsModel; Class<ServiceInfo> serviceClass; ExistingServiceModel(IModel<WorkspaceInfo> wsModel, Class<ServiceInfo> serviceClass) { this.wsModel = wsModel; this.serviceClass = serviceClass; } @Override protected ServiceInfo load() { return GeoServerApplication.get().getGeoServer().getService(wsModel.getObject(), serviceClass); } } class ServicesPanel extends FormComponentPanel<Serializable> { private static final long serialVersionUID = 7375904545106343626L; List<Service> services; public ServicesPanel(String id, final IModel<WorkspaceInfo> wsModel) { super(id, new Model<>()); services = services(wsModel); ListView<Service> serviceList = new ListView<Service>("services", services) { private static final long serialVersionUID = -4142739871430618450L; @Override protected void populateItem(ListItem<Service> item) { Service service = item.getModelObject(); final Link<Service> link = new Link<Service>("link", new Model<Service>(service)) { private static final long serialVersionUID = 1111536301891090436L; @Override public void onClick() { Service s = getModelObject(); Page page = null; if (s.model instanceof ExistingServiceModel) { //service that has already been added, PageParameters pp = new PageParameters().add("workspace", wsModel.getObject().getName()); try { page = s.adminPage.getComponentClass() .getConstructor(PageParameters.class).newInstance(pp); } catch (Exception e) { throw new WicketRuntimeException(e); } } else { //service that has yet to be added try { page = s.adminPage.getComponentClass().getConstructor( s.adminPage.getServiceClass()).newInstance(s.model.getObject()); } catch (Exception e) { throw new WicketRuntimeException(e); } } ((BaseServiceAdminPage<?>)page).setReturnPage(WorkspaceEditPage.this); setResponsePage(page); } }; link.setOutputMarkupId(true); link.setEnabled(service.enabled); AjaxCheckBox enabled = new AjaxCheckBox("enabled", new PropertyModel<Boolean>(service, "enabled")) { private static final long serialVersionUID = 6369730006169869310L; @Override protected void onUpdate(AjaxRequestTarget target) { link.setEnabled(getModelObject()); target.add(link); } }; item.add(enabled); ServiceMenuPageInfo info = service.adminPage; link.add(new AttributeModifier("title", new StringResourceModel(info.getDescriptionKey(), (Component) null, null))); link.add(new Label("link.label", new StringResourceModel(info.getTitleKey(), (Component) null, null))); Image image; if(info.getIcon() != null) { image = new Image("link.icon", new PackageResourceReference(info.getComponentClass(), info.getIcon())); } else { image = new Image("link.icon", new PackageResourceReference(GeoServerBasePage.class, "img/icons/silk/wrench.png")); } image.add(new AttributeModifier("alt", new ParamResourceModel(info.getTitleKey(), null))); link.add(image); item.add(link); } }; add(serviceList); } List<Service> services(IModel<WorkspaceInfo> wsModel) { List<Service> services = new ArrayList<Service>(); for (ServiceMenuPageInfo page : getGeoServerApplication().getBeansOfType(ServiceMenuPageInfo.class)) { Service service = new Service(); service.adminPage = page; service.enabled = getGeoServer().getService(wsModel.getObject(), page.getServiceClass()) != null; //if service is disabled, create a placeholder model to hold a newly created one, // otherwise create a live model to the existing service @SuppressWarnings("unchecked") Class<ServiceInfo> serviceClass = (Class<ServiceInfo>) page.getServiceClass(); service.model = !service.enabled ? new NewServiceModel(wsModel, serviceClass) : new ExistingServiceModel(wsModel, serviceClass); services.add(service); } return services; } } }