This page last changed on Oct 21, 2011 by bencaradocdavies.

Overview

Supporting publication of AuthorityURL and Identifier elements in WMS GetCapbilities documents.

Proposed By

Gabriel Roldán

Assigned to Release

2.1.x, trunk.

State

Completed on 2011-10-20. Work committed at r16457 on trunk and r16458 on 2.1.x

Motivation

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.

Proposal

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 specific LayerInfo and LayerGroupInfo.

Backwards Compatibility

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:

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);
        }
    }

Forward Compatibility

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.

Feedback

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.

Voting

Andrea Aime: +1
Alessio Fabiani: +1
Ben Caradoc-Davies: +1
Gabriel Roldán: +1
Justin Deoliveira: +1
Jody Garnett:
Mark Leslie:
Simone Giannecchini:

Links

JIRA Task
[Email Discussion|]
[Wiki Page|]


layergroup.png (image/png)
wmsadminpage.png (image/png)
layer_publish.png (image/png)
Document generated by Confluence on May 14, 2014 23:00