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.
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.
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.
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 method comprises the logic concerning pipes initialization. Reference to configuration section and nextPipe are passed to the pipe instance via this method. Available parameters:
Example implementation:
// local variable private ExamplePipeConfiguration _pipeConfiguration; public override void Initialize(PipeConfiguration config, IPipe nextPipe) { _pipeConfiguration = (ExamplePipeConfiguration)config; base.NextPipe = nextPipe; }
Is used to provide named static resources. The pipe can handle mapping names or generation of resources. Available parameters:
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); }
The method name stands for itself. It's a simple call used for invoking next pipe. This method uses following parameters:
When the service is initialized, its interface is built based on Attributes named SDIMethodHandlerAttributes. Those require three parameters:
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); }
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; } } }
Once the class is ready to go, you should perform these steps to make the pipe available in the service:
<sectionGroup name="pipes"> <section name="ExamplePipe" type="Intergraph.GeoMedia.Web.SDI.Pipes.ExamplePipe.ExamplePipeConfiguration, Intergraph.GeoMedia.Web.SDI.Pipes.ExamplePipe"/> </sectionGroup>
<ExamplePipe name="examplePipe" nextPipeName="metadataConfigurationPipe" tag="Example01" firstPipe="true"/>
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:
[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; }
Complete Example project can be found on BitBucket following this link.