Hexagon Geospatial
MENU

Developers Knowledge Base

WebGIS enables powerful geospatial web applications and services that securely share your organization’s rich geospatial data, and provides tools to deeply examine spatial data and create value added products, on demand.
Showing results for 
Search instead for 
Do you mean 

Developing Custom Pipes

by Technical Evangelist ‎11-28-2018 07:14 AM - edited ‎11-28-2018 07:15 AM (140 Views)

Introduction

This article describes necessary steps that should be performed in order to create a custom SDI Pipe. Custom Pipe might prove useful when there's a need to alter the pipe chain processing - such as modifying web service responses, or calling internal tasks, triggers, or loggers during the service processing.

Theory

SDI Pipe Chain

Each Geospatial SDI and GeoMedia WebMap service has a common architecture which consists of SDI Service Interface and the underlying pipes. SDI Service Interface is a gateway of the service - it deserializes incoming message, builds a route of pipes through which the message flows and dispatches a request to the first pipe. After a message leaves SDI Service Interface, it is analyzed or changed by the first pipe and then it is sent to the next pipe and later on. Finally, it reaches the last pipe in the pipe chain and response is created. Then the response message travels the same way back. Each pipe has an access to incoming and outgoing messages.

fig1.png

Pipe Implementation

Basically, the project must have a reference to Intergraph.GeoMedia.Web.SDI.Common.BasePipe.dll which is available in each WebMap service instance (WMS, WFS, etc.). After adding this project reference, you can start implementation over delivered interfaces and base classes.

IPipe Interface & PipeBase class

In order to make a class a 'pipe', IPipe interface should be implemented by the class. Interface definition is following:

public interface IPipe
{
   IResponseBase GetStaticResource(string resourceName,
          AdditionalParametersCollection additionalParameters,
          IUserContext userContext);
   
   void Initialize(PipeConfiguration config, IPipe nextPipe);
}

IPipe interface is a common interface for all SDI subsystems' pipes. It describes primitives for initialization of a pipe and for resolution of static resources. Implementation of IPipe interface is sufficient, however, it is recommended to rather extend a PipeBase abstract class which comes with helper methods (sample ExamplePipe project uses this approach):

public abstract class PipeBase : IPipe
{
  protected PipeBase();

  public virtual IPipe NextPipe { get; set; }

  public abstract IResponseBase GetStaticResource(string resourceName, 
      AdditionalParametersCollection additionalParameters, 
      IUserContext userContext);
  
  public abstract void Initialize(PipeConfiguration config, IPipe nextPipe);
  
  protected IResponseBase InvokeOnNextPipe(SDIMethodParameters methodParameters, 
      AdditionalParametersCollection additionalParameters, 
      IUserContext userContext);
  // ...
}

Initialize

Initialize method comprises the logic concerning pipes initialization. Reference to configuration section and nextPipe are passed to the pipe instance via this method. Available parameters:

  • config - the pipe’s configuration object instance
  • nextPipe - next pipe in a „pipe chain” reference

Example implementation:

// local variable
private ExamplePipeConfiguration _pipeConfiguration;

public override void Initialize(PipeConfiguration config, IPipe nextPipe)
{
   _pipeConfiguration = (ExamplePipeConfiguration)config;
   base.NextPipe = nextPipe;
}

GetStaticResources

Is used to provide named static resources. The pipe can handle mapping names or generation of resources. Available parameters:

  • resourceName - Name of the resource. It can be extracted from url (.../resources/_resource_name_) or passed as a parameter
  • additionalParameters - The additional parameters not recognized by previous pipes
  • userContext - The user context (IP address, userId, etc.)

Example implementation:

public override IResponseBase GetStaticResource(
   string resourceName, 
   AdditionalParametersCollection additionalParameters, 
   IUserContext userContext)
{   
   if (base.NextPipe == null)
      throw new ConfigurationErrorsException("Next pipe is not defined");
   
   return NextPipe.GetStaticResource(resourceName, additionalParameters, userContext);
}

InvokeOnNextPipe

The method name stands for itself. It's a simple call used for invoking next pipe. This method uses following parameters:

  • SDIMethodParameter - incoming request parameters, representation reflects OGC XML structure
  • AdditionalParametersCollection - same as above
  • IUserContext - same as above

SDIMethodHandler

When the service is initialized, its interface is built based on Attributes named SDIMethodHandlerAttributes. Those require three parameters:

  • methodName - basically, a request value like e.g. GetCapabilities
  • methodVersion - requested service version, e.g. 1.1.1
  • serviceName - e.g. wfs, wms, wmts

If an operation should be the part of the service interface, each pipe shall provide a method attributed with SDIMethodHandlerAttribute having the three parameters set to the same values in each pipe.

[SDIMethodHandler("GetCapabilities", "1.1.1", "wms")]
[SDIMethodHandler("GetCapabilities", "1.3.0", "wms")]
public IResponseBase GetCapabilitiesHandler(
     SDIMethodParameters parameters,
     AdditionalParametersCollection additionalParameters,
     IUserContext userContext)
{
   //request handling
   IResponseBase response = InvokeOnNextPipe(parameters, additionalParameters, userContext);
   
   //response handling
   return response;
}

A pipe does not need to have a handler for every possible request, version and service tuple. However, not supplying a handler, and not passing the request to a next pipe will result in pipe chain breaking and total unavailability of the request type in the service's interface. To overcome this situation, create a simple pass-through handler, such as in the example below:

[SDIMethodHandler("GetFeatureInfo", "1.1.1", "wms")]
[SDIMethodHandler("GetFeatureInfo", "1.3.0", "wms")]
[SDIMethodHandler("GetMap", "1.1.1", "wms")]
[SDIMethodHandler("GetMap", "1.3.0", "wms")]
public IResponseBase OtherMethodsHandler(SDIMethodParameters parameters,
      AdditionalParametersCollection additionalParameters,
      IUserContext userContext)
{
   return InvokeOnNextPipe(parameters, additionalParameters, userContext);
}

PipeConfiguration class

Every pipe requires a configuration which is loaded from web.config XML file. For such purposes, there's available a PipeConfiguration class. In order to add a configuration attribute that is specific for the pipe, ConfigurationProperty shall be defined in the class. This is a standard .NET mechanism, so if further configuration customizations are required please refer to MSDN.

public class ExamplePipeConfiguration : PipeConfiguration
{
   [ConfigurationProperty("tag", IsRequired = true)]
   public string Tag
   {
      get { return (string)this["tag"]; }
      set { this["tag"] = value; }
   }
}

Pipe Deployment

Once the class is ready to go, you should perform these steps to make the pipe available in the service:

  1. Copy Intergraph.GeoMedia.Web.SDI.Pipes.<CustomPipeName>.dll to 'bin' directory in your service instance folder
  2. Add custom Pipe configuration to your web.config file:
    1. Add custom Pipe section to "pipes" section group, e.g.:
      <sectionGroup name="pipes">
        <section name="ExamplePipe"
           type="Intergraph.GeoMedia.Web.SDI.Pipes.ExamplePipe.ExamplePipeConfiguration, Intergraph.GeoMedia.Web.SDI.Pipes.ExamplePipe"/>
      </sectionGroup>
    2. Add custom configuration to pipe chain, e.g:
      <ExamplePipe name="examplePipe" nextPipeName="metadataConfigurationPipe" 
         tag="Example01" firstPipe="true"/>

Sample Implementation

The sample implementation creates a custom Pipe, which overwrites the WMS GetCapabilities document, changing LegendURL element to a custom layer image. Let's summarize the basic steps needed to create the custom pipe called ExamplePipe:

  1. Create a .NET Class Library project using Visual Studio (.NET Framework 4)
  2. Copy Intergraph.GeoMedia.Web.SDI.Common.BasePipe.dll and log4net.dll into \lib subfolder of the new project.
    Note: log4net library isn't actually necessary but our sample project uses it to implement additional logging for the pipe. The logging information will be appended to the common log file, usually called rol-log.txt.
  3. Add references to the DLLs copied from step (3)
  4. Create a class called ExamplePipe which will be derived from PipeBase abstract class.
    1. (optional) implement and modify Initialize method, adding some logging information.
    2. Implement GetStaticResource method, telling that NextPipe is required
    3. Implement GetCapabilities request handler that will take existing XML document, e.g.
      [SDIMethodHandler(...)]
      public IResponseBase GetCapabilitiesHandler(...)
      {
         var response = InvokeOnNextPipe(parameters, additionalParameters, userContext);
         response = ProcessResponse(response);
         return response;
      }
       
      IResponseBase ProcessResponse(IResponseBase response)
      {
         var xdoc = XDocument.Load(response.ResponseStream);
         foreach (var legendURL in xdoc.Descendants().Where(x => x.Name.LocalName == "LegendURL"))
         {
            legendURL.Descendants().Where(x => x.Name.LocalName == "OnlineResource").Attributes().First(x => x.Name.LocalName == "href").Value = "http://myserver/images/WMS-layer1.png";
         }
         var resultResponse = new XMLResponse(xdoc, response.MimeType);
         resultResponse.Headers.Add(response.Headers);
         resultResponse.HttpCode = response.HttpCode;
         return resultResponse;
      }
    4. Implement OtherMethodsHandler to allow other parameters pass through.
  5. (optional) Create a class called ExamplePipeConfiguration derived from PipeConfiguration abstract class
    1. Add a public property called "Tag" that will be required to set within web.config.
  6. Build the project.
  7. Copy the new DLL to a WMS instance and modify its web.config to grasp the new assembly.
  8. Test the service by calling GetCapabilities request.

Example Project

Complete Example project can be found on BitBucket following this link.

Overview
Contributors