Commit 907723fa authored by Jean Pommier's avatar Jean Pommier
Browse files

[template-editor] Add documentation

+ some code auto-formatting changes
parent 3e09f8a4
.. _community_template_editor:
Template Editor
===============
The Template Editor extension offers a basic web interface to edit and manage FreeMarker templates.
.. note:: This page does not explain how to write FreeMarker templates. For this, see :ref:`tutorials_getfeatureinfo` and :ref:`tutorial_freemarkertemplate`.
.. note:: For now, it only supports header.ftl, content.ftl and footer.ftl resources (i.e. WMS GetFeatureInfo-related templates).
Installing the Template Editor extension
-----------------------------------
#. Download the Template Editor extension from the `nightly GeoServer community module builds <http://ares.boundlessgeo.com/geoserver/master/community-latest/>`_.
.. warning:: Make sure to match the version of the extension to the version of the GeoServer instance.
#. Extract the contents of the archive into the ``WEB-INF/lib`` directory of the GeoServer installation.
#. Reload GeoServer.
Accessing the Template Editor
----------------------------
Log in your GeoServer web UI. If the extension has been properly installed, a *Template Edition* entry should appear on the left menu, Data section, below *Styles*.
Clicking this menu entry opens a page very similar to the *Layers* one:
.. figure:: images/template-editor-mainpage.png
:align: center
*Template Editor resources table*
Using this table, select the resource for which you want to edit the templates. It is possible to edit templates for layers (Vector and Raster ones), but also on the datastore and workspace levels (used for templates inheritance). Click on a resource to open its template edition page.
Template edition page
---------------------
.. figure:: images/edition-page.png
:align: center
*Template edition page*
The page will be very similar for a workspace, datastore or layer, the only difference being the layers have, on the right of the page, a list of the available layer attributes.
The page is composed of 3 blocks. Each block is dedicated to a *ftl* resource and features:
Edition element
```````````````
It is a plain TextArea input element in which you can edit the template's content.
Paths
`````
Right under the edition element are 2 paths, one in bold, the other grayed. At the start, the *Source path* will be bold, indicated where the tempalte contents is taken (note: a path starting with *default* means it is the default class-based templates that is used).
When you start editing, the *Source path* will be grayed and the *Destination path* will be the bold one, postfixed with a *****, indicating the content has been changed.
.. note:: The *Destination path* will always point to the resource's path.
Buttons
```````
Three buttons for each template block allow to perform the following actions on the corresponding template resource:
* Delete the template. It actually delete the template only if the template resource is specific to the layer (or datastore or workspace) currently selected. I.e. if the *Source path* and the *Destination path* are equal.
* Reload will reload the template from the file, deleting all your changes.
* Save will persist your changes, writing the template file in the corresponding *Destination path*.
.. warning:: For now, no confirmation is asked when you click on the buttons. So, be careful with the *Delete template* button !
/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.template.editor.constants;
......
/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.template.editor.web;
import java.io.IOException;
import java.util.List;
import java.util.logging.Logger;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.geoserver.platform.GeoServerResourceLoader;
import org.geoserver.web.ComponentAuthorizer;
import org.geoserver.web.GeoServerSecuredPage;
import org.geotools.util.logging.Logging;
/**
* Base page for creating/editing templates
*/
/** Base page for creating/editing templates */
@SuppressWarnings("serial")
public abstract class AbstractTemplateEditorPage extends GeoServerSecuredPage {
protected static final Logger LOGGER = Logging.getLogger(AbstractTemplateEditorPage.class);
......@@ -36,11 +33,9 @@ public abstract class AbstractTemplateEditorPage extends GeoServerSecuredPage {
protected String resourceType = "abstract";
protected TemplateResourceObject tpl_header, tpl_content, tpl_footer;
protected AbstractTemplateFormPanel headerFormPanel, contentFormPanel, footerFormPanel;
public AbstractTemplateEditorPage() {
}
public AbstractTemplateEditorPage() {}
public AbstractTemplateEditorPage(PageParameters parameters) {
this.resourcePaths = this.getResourcePaths(parameters);
......@@ -72,17 +67,17 @@ public abstract class AbstractTemplateEditorPage extends GeoServerSecuredPage {
return;
}
GeoServerResourceLoader loader = this.getCatalog().getResourceLoader();
tpl_header = TemplateManager.readTemplate("header.ftl", resourcePaths, loader,
getCharset());
tpl_content = TemplateManager.readTemplate("content.ftl", resourcePaths, loader,
getCharset());
tpl_footer = TemplateManager.readTemplate("footer.ftl", resourcePaths, loader,
getCharset());
tpl_header =
TemplateManager.readTemplate("header.ftl", resourcePaths, loader, getCharset());
tpl_content =
TemplateManager.readTemplate("content.ftl", resourcePaths, loader, getCharset());
tpl_footer =
TemplateManager.readTemplate("footer.ftl", resourcePaths, loader, getCharset());
}
/**
* Builds a list of available paths where to look for templates
*
*
* @param parameters URL parameters used to identify the resource and build the paths
* @return List<String> List of paths (as Strings)
*/
......@@ -95,18 +90,21 @@ public abstract class AbstractTemplateEditorPage extends GeoServerSecuredPage {
add(new Label("name", Model.of(fullname)));
add(new Label("type", Model.of(resourceType)));
headerFormPanel = new AbstractTemplateFormPanel("headerTplPanel",
new CompoundPropertyModel(tpl_header));
headerFormPanel =
new AbstractTemplateFormPanel(
"headerTplPanel", new CompoundPropertyModel(tpl_header));
headerFormPanel.setParent(this);
add(headerFormPanel);
contentFormPanel = new AbstractTemplateFormPanel("contentTplPanel",
new CompoundPropertyModel(tpl_content));
contentFormPanel =
new AbstractTemplateFormPanel(
"contentTplPanel", new CompoundPropertyModel(tpl_content));
contentFormPanel.setParent(this);
add(contentFormPanel);
footerFormPanel = new AbstractTemplateFormPanel("footerTplPanel",
new CompoundPropertyModel(tpl_footer));
footerFormPanel =
new AbstractTemplateFormPanel(
"footerTplPanel", new CompoundPropertyModel(tpl_footer));
footerFormPanel.setParent(this);
add(footerFormPanel);
}
......
/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.template.editor.web;
import java.util.logging.Logger;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.markup.html.panel.Panel;
......@@ -24,7 +23,10 @@ public class AbstractTemplateFormPanel extends Panel {
/** serialVersionUID */
private static final long serialVersionUID = 1L;
private Label srcpath_label, destpath_label, destpath_label_dirty, filename_label,
private Label srcpath_label,
destpath_label,
destpath_label_dirty,
filename_label,
destfilename_label;
private TextArea<String> tpl;
......@@ -57,47 +59,50 @@ public class AbstractTemplateFormPanel extends Panel {
tpl = new TextArea<String>("content");
tpl.setOutputMarkupId(true);
add(tpl);
tpl.add(new OnChangeAjaxBehavior() {
/** serialVersionUID */
private static final long serialVersionUID = 1L;
@Override
protected void onUpdate(AjaxRequestTarget target) {
ajaxSetDirty(target);
}
});
save_btn = new AjaxLink("save_btn", Model.of("Save")) {
@Override
public void onClick(AjaxRequestTarget target) {
if (templateEditorPage != null) {
templateEditorPage.saveTemplate(model.getObject());
ajaxReload(target);
}
}
};
tpl.add(
new OnChangeAjaxBehavior() {
/** serialVersionUID */
private static final long serialVersionUID = 1L;
@Override
protected void onUpdate(AjaxRequestTarget target) {
ajaxSetDirty(target);
}
});
save_btn =
new AjaxLink("save_btn", Model.of("Save")) {
@Override
public void onClick(AjaxRequestTarget target) {
if (templateEditorPage != null) {
templateEditorPage.saveTemplate(model.getObject());
ajaxReload(target);
}
}
};
save_btn.setOutputMarkupId(true);
add(save_btn);
save_btn.setEnabled(true);
reload_btn = new AjaxLink("reload_btn", Model.of("Reload")) {
@Override
public void onClick(AjaxRequestTarget target) {
ajaxReload(target);
}
};
reload_btn =
new AjaxLink("reload_btn", Model.of("Reload")) {
@Override
public void onClick(AjaxRequestTarget target) {
ajaxReload(target);
}
};
add(reload_btn);
delete_btn = new AjaxLink("delete_btn", Model.of("Delete this template file")) {
@Override
public void onClick(AjaxRequestTarget target) {
if (templateEditorPage != null) {
templateEditorPage.deleteTemplate(model.getObject());
ajaxReload(target);
}
}
};
delete_btn =
new AjaxLink("delete_btn", Model.of("Delete this template file")) {
@Override
public void onClick(AjaxRequestTarget target) {
if (templateEditorPage != null) {
templateEditorPage.deleteTemplate(model.getObject());
ajaxReload(target);
}
}
};
add(delete_btn);
}
......@@ -126,6 +131,4 @@ public class AbstractTemplateFormPanel extends Panel {
public void setParent(AbstractTemplateEditorPage page) {
this.templateEditorPage = page;
}
}
......@@ -14,13 +14,14 @@ import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.Predicates;
import org.geoserver.catalog.util.CloseableIterator;
import org.geoserver.catalog.util.CloseableIteratorAdapter;
import org.geoserver.web.wicket.GeoServerDataProvider;
import org.geoserver.web.data.layer.LayerDetachableModel;
import org.geoserver.web.wicket.GeoServerDataProvider;
import org.opengis.filter.Filter;
import org.opengis.filter.sort.SortBy;
/**
* Provides a filtered, sorted view over the catalog layers.
*
* <p>
* <!-- Implementation detail: This class overrides the following methods in
* order to leverage the Catalog filtering and paging support:
......@@ -41,77 +42,70 @@ import org.opengis.filter.sort.SortBy;
*/
@SuppressWarnings("serial")
public class LayerProvider extends GeoServerDataProvider<LayerInfo> {
static final Property<LayerInfo> TYPE = new BeanProperty<LayerInfo>("type",
"type");
static final Property<LayerInfo> TYPE = new BeanProperty<LayerInfo>("type", "type");
static final Property<LayerInfo> WORKSPACE = new BeanProperty<LayerInfo>(
"workspace", "resource.store.workspace.name");
static final Property<LayerInfo> WORKSPACE =
new BeanProperty<LayerInfo>("workspace", "resource.store.workspace.name");
static final Property<LayerInfo> STORE = new BeanProperty<LayerInfo>(
"store", "resource.store.name");
static final Property<LayerInfo> STORE =
new BeanProperty<LayerInfo>("store", "resource.store.name");
static final Property<LayerInfo> NAME = new BeanProperty<LayerInfo>("name",
"name");
static final Property<LayerInfo> NAME = new BeanProperty<LayerInfo>("name", "name");
/**
* A custom property that uses the derived enabled() property instead of isEnabled() to account
* for disabled resource/store
*/
static final Property<LayerInfo> ENABLED = new AbstractProperty<LayerInfo>("enabled") {
static final Property<LayerInfo> ENABLED =
new AbstractProperty<LayerInfo>("enabled") {
public Boolean getPropertyValue(LayerInfo item) {
return Boolean.valueOf(item.enabled());
}
public Boolean getPropertyValue(LayerInfo item) {
return Boolean.valueOf(item.enabled());
}
};
};
static final Property<LayerInfo> SRS = new BeanProperty<LayerInfo>("SRS",
"resource.SRS") {
/**
* We roll a custom comparator that treats the numeric part of the
* code as a number
*/
public java.util.Comparator<LayerInfo> getComparator() {
return new Comparator<LayerInfo>() {
public int compare(LayerInfo o1, LayerInfo o2) {
// split out authority and code
String[] srs1 = o1.getResource().getSRS().split(":");
String[] srs2 = o2.getResource().getSRS().split(":");
// use sign to control sort order
if (srs1[0].equalsIgnoreCase(srs2[0]) && srs1.length > 1
&& srs2.length > 1) {
try {
// in case of same authority, compare numbers
return new Integer(srs1[1]).compareTo(new Integer(
srs2[1]));
} catch(NumberFormatException e) {
// a handful of codes are not numeric,
// handle the general case as well
return srs1[1].compareTo(srs2[1]);
static final Property<LayerInfo> SRS =
new BeanProperty<LayerInfo>("SRS", "resource.SRS") {
/**
* We roll a custom comparator that treats the numeric part of the code as a number
*/
public java.util.Comparator<LayerInfo> getComparator() {
return new Comparator<LayerInfo>() {
public int compare(LayerInfo o1, LayerInfo o2) {
// split out authority and code
String[] srs1 = o1.getResource().getSRS().split(":");
String[] srs2 = o2.getResource().getSRS().split(":");
// use sign to control sort order
if (srs1[0].equalsIgnoreCase(srs2[0])
&& srs1.length > 1
&& srs2.length > 1) {
try {
// in case of same authority, compare numbers
return new Integer(srs1[1]).compareTo(new Integer(srs2[1]));
} catch (NumberFormatException e) {
// a handful of codes are not numeric,
// handle the general case as well
return srs1[1].compareTo(srs2[1]);
}
} else {
// compare authorities
return srs1[0].compareToIgnoreCase(srs2[0]);
}
}
} else {
// compare authorities
return srs1[0].compareToIgnoreCase(srs2[0]);
}
};
}
};
}
};
static final List<Property<LayerInfo>> PROPERTIES = Arrays.asList(TYPE,
WORKSPACE, STORE, NAME);
static final List<Property<LayerInfo>> PROPERTIES = Arrays.asList(TYPE, WORKSPACE, STORE, NAME);
@Override
protected List<LayerInfo> getItems() {
// forced to implement this method as its abstract in the super class
throw new UnsupportedOperationException(
"This method should not be being called! "
+ "We use the catalog streaming API");
"This method should not be being called! " + "We use the catalog streaming API");
}
@Override
......
/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.template.editor.web;
......@@ -9,10 +9,9 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.geoserver.catalog.AttributeTypeInfo;
import org.geoserver.catalog.CoverageDimensionInfo;
import org.geoserver.catalog.CoverageInfo;
......@@ -20,8 +19,8 @@ import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.NamespaceInfo;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.template.editor.constants.GeoServerConstants;
import org.geoserver.platform.resource.Paths;
import org.geoserver.template.editor.constants.GeoServerConstants;
import org.geoserver.web.data.resource.ResourceConfigurationPage;
import org.geoserver.web.data.store.DataAccessEditPage;
import org.geotools.feature.NameImpl;
......
/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.template.editor.web;
import java.util.ArrayList;
import java.util.List;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.geoserver.template.editor.constants.GeoServerConstants;
import org.geoserver.platform.resource.Paths;
import org.geoserver.template.editor.constants.GeoServerConstants;
import org.geoserver.web.data.store.DataAccessEditPage;
public class StoreTemplateEditorPage extends AbstractTemplateEditorPage {
......@@ -50,5 +49,4 @@ public class StoreTemplateEditorPage extends AbstractTemplateEditorPage {
protected String buildResourceFullName() {
return workspaceName + ":" + storeName;
}
}
/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.template.editor.web;
......@@ -10,10 +10,10 @@ import org.geoserver.web.GeoServerSecuredPage;
/**
* Extension hook for template editor pages (should not show in menu)
*
*
* @author Jean Pommier <jean.pommier@pi-geosolutions.fr>
*/
@SuppressWarnings("serial")
public class TemplateEditorPageInfo extends ComponentInfo<GeoServerSecuredPage> {
// inherit everything from ComponentInfo
}
\ No newline at end of file
}
/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.template.editor.web;
import freemarker.cache.ClassTemplateLoader;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
......@@ -14,19 +15,16 @@ import java.io.Serializable;
import java.util.Iterator;
import java.util.List;