-
Notifications
You must be signed in to change notification settings - Fork 4
GSIP 65 Support for WMS AuthorityURL and Identifier
Supporting publication of AuthorityURL and Identifier elements in WMS GetCapbilities documents.
Gabriel Roldan
2.1.x, trunk.
Completed on 20111020. Work committed at r16457 on trunk and
r16458 on 2.1.x
The optional AuthorityURL
and Identifier
elements have been
defined by the WMS spec at least since
WMSv1.1.1
. Now there are compelling reasons for GeoServer to support the
configuration and publication of both authority URLs and Identifiers on
a per-layer basis as well as for the root WMS Layer element, as it is a
must have for INSPIRE compliant “View Services”, although it is not
INSPIRE specific.
For the quick version see the current patch.
A WMS GetCapabilities document may contain multiple AuthorityURL
and
Identifier
elements at either the root Layer
element or a
specific Layer
.
<Layer>
<Title>GeoServer Web Map Service</Title>
...
<BoundingBox .../>
<AuthorityURL name="Root auth 1">
<OnlineResource xlink:href="http://geoserver.org"/>
</AuthorityURL>
<AuthorityURL name="Root auth 2">
<OnlineResource xlink:href="http://some.authority.org"/>
</AuthorityURL>
<Identifier authority="Root auth 1">Root ID 1</Identifier>
<Identifier authority="Root auth 2">Root ID 2</Identifier>
<Layer queryable="1">
<Name>nurc:Arc_Sample</Name>
...
<BoundingBox .../>
<AuthorityURL name="layer specific auth 1">
<OnlineResource xlink:href="http://geoserver.org/arcsample"/>
</AuthorityURL>
<AuthorityURL name="layer specific auth 2">
<OnlineResource xlink:href="http://geoserver.org/nurc"/>
</AuthorityURL>
<Identifier authority="Root auth 1">layer_id_1</Identifier>
<Identifier authority="layer specific auth 1">layer_specific_id_1</Identifier>
<Identifier authority="layer specific auth 2">layer_specific_id_2</Identifier>
<Style>
...
In order to support this, the AuthorityURLInfo and LayerIdentifierInfo interfaces are proposed as value objects:
public interface AuthorityURLInfo extends Serializable {
public String getName();
public void setName(String name);
public String getHref();
public void setHref(String href);
}
public interface LayerIdentifierInfo extends Serializable {
public String getAuthority();
public void setAuthority(String authorityName);
public String getIdentifier();
public void setIdentifier(String identifier);
}
As well as the corresponding properties in LayerInfo
and
LayerGroupInfo
:
public interface LayerInfo extends CatalogInfo {
....
/**
* @return the list of this layer's authority URLs
*/
List<AuthorityURLInfo> getAuthorityURLs();
/**
* @return the list of this layer's identifiers
*/
List<LayerIdentifierInfo> getIdentifiers();
}
public interface LayerGroupInfo extends CatalogInfo {
....
/**
* @return the list of this layer's authority URLs
*/
List<AuthorityURLInfo> getAuthorityURLs();
/**
* @return the list of this layer's identifiers
*/
List<LayerIdentifierInfo> getIdentifiers();
}
And in order to support the definition of WMS root layer’s authority
URLs and Identifiers, the addition of the following properties to the
WMSInfo
interface:
public interface WMSInfo extends ServiceInfo {
...
/**
* Defines the list of authority URLs for the root WMS layer
*
* @return the list of WMS root layer's authority URLs
*/
List<AuthorityURLInfo> getAuthorityURLs();
/**
* @return the list of identifiers for the WMS root layer
*/
List<LayerIdentifierInfo> getIdentifiers();
}
Configuration wise, the pattern followed is similar to the one already
used to administer MetadataURLInfo
objects.
Authorities and identifiers for the root layer are edited through the WMSAdminPage:
LayerInfo
authorities and identifiers are edited through the
“Publishing” tab in the layer edit page as a contribution of the
WMSLayerConfig
panel:
Configuration for LayerGroupInfo
is contributed by a
LayerGroupConfigurationPanel:
The three of them sharing the same Wicket panel
Finally, both the WMS 1.1.1
and WMS 1.3.0
GetCapabilities transformers are updated to output AuthorityURL
and Identifier
elements for the root Layer, and each specificLayerInfo
and LayerGroupInfo
.
On the stable 2.1.x series we don’t want to change the serialization
format for wms.xml
, layer.xml
and layergroups/<layer group>.xml
.
The serialized form on the 2.2.x series would be:
<authorityURLs>
<AuthorityURL><name>Root auth 1</name><href>http://geoserver.org</href></AuthorityURL>
</authorityURLs>
<identifiers>
<Identifier>
<authority>Root auth 1</authority><identifier>Root ID 1</identifier>
</Identifier>
</identifiers>
Now, common practice to keep backwards compatibility on the
configuration files is to store new properties in the MetadataMap
.
In this case we have multi-valued complex properties, so in order to
store a list of authority URLs or Identifiers as a single map entry, a
good option would be to use the serialized form of a JSON array instead
of cooking our own serialized form for a list of non primitive/String
objects.
A sample pair of entries to the metadata map looks like:
<metadata>
<entry key="authorityURLs">[{"name":"auth 1","href":"http://geoserver.org/arcsample"},{"name":"auth 1","href":"http://geoserver.org/nurc"}]</entry>
<entry key="identifiers">[{"authority":"auth 1","identifier":"id_1"},{"authority":"auth 2","identifier":"id_2"}]</entry>
</metadata>
Also, this doesn’t introduce any new dependency because the main
module already depends on the net.sf.json-lib
library.
The following two utility classes have been added in order to support marshaling and un-marshaling of these two lists in JSON array format: * AuthorityURLInfoInfoListConverter (Tests) * LayerIdentifierInfoListConverter (Tests)
And they’re used by the XStream converters so that the transient lists are populated when the config is loaded and serialized to the metadata map when the config is saved, like in:
/**
* Converter for layer groups.
*/
class LayerGroupInfoConverter extends AbstractReflectionConverter {
public LayerGroupInfoConverter() {
super( LayerGroupInfo.class );
}
@Override
public Object doUnmarshal(Object result, HierarchicalStreamReader reader,
UnmarshallingContext context) {
LayerGroupInfoImpl lgi = (LayerGroupInfoImpl) super
.doUnmarshal(result, reader, context);
MetadataMap metadata = lgi.getMetadata();
if (lgi.getAuthorityURLs() == null && metadata != null) {
String serialized = metadata.get("authorityURLs", String.class);
List<AuthorityURLInfo> authorities;
if (serialized == null) {
authorities = new ArrayList<AuthorityURLInfo>(1);
} else {
authorities = AuthorityURLInfoInfoListConverter.fromString(serialized);
}
lgi.setAuthorityURLs(authorities);
}
if (lgi.getIdentifiers() == null && metadata != null) {
String serialized = metadata.get("identifiers", String.class);
List<LayerIdentifierInfo> identifiers;
if (serialized == null) {
identifiers = new ArrayList<LayerIdentifierInfo>(1);
} else {
identifiers = LayerIdentifierInfoListConverter.fromString(serialized);
}
lgi.setIdentifiers(identifiers);
}
return lgi;
}
@Override
protected void doMarshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
LayerGroupInfo l = (LayerGroupInfo) source;
{
String authUrlsSerializedForm = AuthorityURLInfoInfoListConverter.toString(l
.getAuthorityURLs());
if (null != authUrlsSerializedForm) {
l.getMetadata().put("authorityURLs", authUrlsSerializedForm);
}
}
{
String identifiersSerializedForm = LayerIdentifierInfoListConverter.toString(l
.getIdentifiers());
if (null != identifiersSerializedForm) {
l.getMetadata().put("identifiers", identifiersSerializedForm);
}
}
super.doMarshal(source, writer, context);
}
}
An alternative for forwards compatibility (in case somewhat uses a data directory on the 2.1.x series that has previously been used on a 2.2.x+ series) would be to instead of having the actual lists of auth urls and identifiers be transient in Layer/GroupInfoImpl and WMSInfoImpl, let them be non transient (serializable), but instrument the appropriate XStream converters to save the lists in the 2.1.x serialized form, while being able of reading the 2.2.x serialized form.
For instance, some pseudocode:
/**
* Converter for layers.
*/
class LayerInfoConverter extends AbstractReflectionConverter {
public LayerInfoConverter() {
super( LayerInfo.class );
}
@Override
protected void doMarshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
...
// backup the lists of (non-transient) auth and identifiers to be
// restored after serialization
List<AuthorityURLInfo> authorityURLs = l.getAuthorityURLs();
List<LayerIdentifierInfo> identifiers = l.getIdentifiers();
try{
// set them to null to be ignored
((LayerInfoImpl)l).setAuthorityURLs(null);
((LayerInfoImpl)l).setIdentifiers(null);
// do marshaling with auth urls and identifiers in the metadata map
String authUrlsSerializedForm = ...
if (null != authUrlsSerializedForm) {
l.getMetadata().put("authorityURLs", authUrlsSerializedForm);
}
String identifiersSerializedForm = ...
if (null != identifiersSerializedForm) {
l.getMetadata().put("identifiers", identifiersSerializedForm);
}
super.doMarshal(source, writer, context);
}finally{
// restore the state of the non transient auth urls
// and identifiers after serialization
((LayerInfoImpl)l).setAuthorityURLs(authorityURLs);
((LayerInfoImpl)l).setIdentifiers(identifiers);
}
}
}
This approach to forwards compatibility has been proved and works, but I’m not sure if it goes too much against common practice, the complexity added is not worth the benefit, or we just don’t want to do that kind of forward compatibility support. Feedback required.
With regard to the “Forward compatibility” proposal above, jdeolive said “Interesting approach… Obviously being forward compatible would be great… but I wonder if we will start getting into revision hell… since various changes will only be forward compatible after a paritcular revision. Whereas our backward compatibility story is pretty clear.”, which is a fair and shared concern, so we’re not going for it as of this proposal, but will keep in mind the approach in the event it is needed in the future.
Andrea Aime: +1 Alessio Fabiani: +1 Ben Caradoc Davies: +1 Gabriel Roldan: +1 Justin Deoliveira: +1 Jody Garnett: Mark Leslie: Simone Giannecchini:
JIRA Task Email Discussion Wiki Page