Custom functionality


Summary This topic discusses how to customize security in your Web application with custom functionality by creating a class for the custom functionality, adding business logic to it, declaring the class a managed bean in faces-config.xml so that the JavaServer Faces (JSF) framework can instantiate it when required, and registering the custom functionality with a resource so that the Web Application Developer Framework (ADF) can initialize it while initializing the resource.

In this topic


Resources and functionalities

The multiresource Web ADF architecture is built around the concept of resources and functionalities. A resource is an instance of com.esri.adf.web.data.GISResource, and a functionality is an instance of com.esri.adf.web.data.GISFunctionality. Each resource contains a set of functionalities that dictate what the resource is capable of.
The Web ADF provides standard resources for ArcGIS Server, ArcIMS, a Web Map Service (WMS), and so on. The Web ADF also provides standard functionalities, such as map, query, table of contents (toc), geocode, and so on, for these resources. When a Web ADF application loads, the Web ADF initializes each resource declared in the application. As part of initializing a resource, the framework also initializes each functionality registered with that resource. You can extend an application by either writing your own custom resource or  writing your own custom functionality and registering it with a custom or standard resource.
Before proceeding, you should read the Overview of the Web ADF architecture topic.
A custom functionality is used when you want to extend the capabilities of a resource or to implement business logic that is specific to a resource. If the business logic is resource neutral or applies to multiple resources, you can implement it as a custom task, a custom command or tool, a custom context attribute, or even as a managed bean rather than a custom functionality.

Scenario

Imagine you have an ArcGIS map service called BrazilPetrochemicals serving the $ARCGISHOME/java/samples/data/mxds/Brazil.mxd map document. This map service has a layer called Pipeline_Network that contains data about the Petrochemical pipeline infrastructure in Western Brazil. You have a Web mapping application working against this map service. You have disabled anonymous access to this Web application and all users are required to log in with their username and password to view the data. Your users fall under the following two categories: 
  • Petrochemical employees who work for the petrochemical companies and should be allowed to view the pipeline installations.
  • State employees who work for the local or state government and shouldn't be allowed to view the pipeline installations.
You have defined two security roles, petroEmployee and stateEmployee, in your Web application and mapped all your users to one of these roles. (See the Securing the Web application section for information on how to secure your Web application). You want to customize the Web application so that the Pipeline_Network layer is shown only if the logged-in user belongs to the petroEmployee role.

Implementing the custom functionality

To customize the Web application, you need to implement a custom functionality that will check the logged-in user's role and remove the Pipeline_Network layer if the user does not belong to the petroEmployee role. The steps required to implement a custom functionality are as follows:
  1. Create the custom functionality Java class
  2. Declare the custom functionality
  3. Register the custom functionality with a resource

Creating the custom functionality Java class

Create a class called SecurityCheckFunctionality.java and have it implement the com.esri.adf.web.data.GISFunctionality interface. The GISFunctionality interface declares three methods to be implemented by SecurityCheckFunctionality.
The following code sample shows how this class will initially look.
[Java]
package com.mypackage;
public class SecurityCheckFunctionality implements GISFunctionality{
    public void initFunctionality(GISResource resource){
        //empty }
        public void destroyFunctionality(){
            //empty  }
            public GISResource getResource(){
                //empty  }
            }
initFunctionality() is called when the associated resource is initialized. For resources configured declaratively in faces-config.xml, initialization occurs when a user session begins. For resources configured programmatically, initialization occurs at runtime when the resource is added. destroyFunctionality() is called when the associated resource is removed, when the user session expires, or when the application shuts down.
When the Web ADF calls initFunctionality(), it passes a reference to the resource where the functionality is registered as shown in the following code sample. In this case, it is a reference to the concrete class com.esri.adf.web.ags.data.AGSMapResource, because you will later register this functionality with an ArcGIS Server Map resource. Store this reference in a member variable, since you will need to use it later.
[Java]
private AGSLocalMapResource resource;
public void initFunctionality(GISResource resource){
    this.resource = (AGSLocalMapResource)resource;
    //Enter business logic here.
}
Add business logic to the functionality so that the Pipeline_Network layer is shown only if the user belongs to the petroEmployee role. To prevent a layer from showing, you need to do the following two tasks:
  • Remove the layer from the TOC (that is, remove the layer from the MapServerInfo.MapLayerInfos property).
  • Remove the layer from the Map (that is, remove the layer from the MapServerInfo.MapDescription.LayerDescriptions property).
The following code sample shows how SecurityCheckFunctionality.java should look when complete.
[Java]
public class SecurityCheckFunctionality implements GISFunctionality{
    private AGSMapResource resource;
    public void initFunctionality(GISResource resource){
        this.resource = (AGSMapResource)resource;
        //Check if the user belongs to the petroEmployee role.
        if (!WebUtil.getExternalContext().isUserInRole("pretroEmployee")){
            //Get a handle to the resource's Map functionality.
            AGSMapFunctionality mapFunc = (AGSMapFunctionality)
                resource.getFunctionality("map");
            //Get a handle to the MapServerInfo.
            MapServerInfo serverInfo = mapFunc.getMapServerInfo();
            //Find the Pipeline_Network layer.
            MapLayerInfo pipelineLayerInfo = AGSUtil.getLayerInfo("PipeLine_Network",
                layerInfos);
            if (pipelineLayerInfo == null)
                return ;
            
                //The Pipeline_Network layer does not exist, so there's no need to remove it.
            //Remove the layer from the TOC.
            MapLayerInfo[] newLayerInfos = removeLayer(pipelineLayerInfo, layerInfos)
                ;
            serverInfo.setMapLayerInfos(newLayerInfos);
            //Remove the layer from the Map.
            LayerDescription[] layerDescriptions =
                serverInfo.getDefaultMapDescription().getLayerDescriptions();
            LayerDescription[] newLayerDescriptions = new
                LayerDescription[newLayerInfos.length];
            for (int i = 0; i < newLayerInfos.length; i++){
                newLayerDescriptions[i] = AGSUtil.getLayerDescription
                    (newLayerInfos[i].getLayerID(), layerDescriptions);
                serverInfo.getDefaultMapDescription().setLayerDescriptions
                    (newLayerDescriptions);
            }
        }
        //Enter logic to remove a layer and all its descendants.
        private MapLayerInfo[] removeLayer(MapLayerInfo unwantedLayerInfo,
            MapLayerInfo[] oldLayerInfos){
            MapLayerInfo[] newLayerInfos = new MapLayerInfo[oldLayerInfos.length -
                1];
            ArrayList descendantLayers = new ArrayList();
            for (int i = 0, j = 0; i < oldLayerInfos.length; i++){
                if (oldLayerInfos[i].getLayerID() != unwantedLayerInfo.getLayerID())
                    newLayerInfos[j++] = oldLayerInfos[i];
                if (oldLayerInfos[i].getParentLayerID() ==
                    unwantedLayerInfo.getLayerID())
                    descendantLayers.add(oldLayerInfos[i]);
            }
            for (int i = 0; i < descendantLayers.size(); i++){
                newLayerInfos = removeLayer((MapLayerInfo)descendantLayers.get(i),
                    newLayerInfos);
            }
            return newLayerInfos;
        }
        public void destroyFunctionality(){
            //Nothing special to do here. }
            public GISResource getResource(){
                return resource;
            }
        }

Declaring the custom functionality

After writing the custom functionality class, you must declare it as a managed bean in faces-config.xml as shown in the following code sample.
[Java]
 < managed - bean >  < managed - bean - name > securitycheck <  / managed - bean -
     name >  < managed - bean - class > com.mypackage.SecurityCheckFunctionality < 
     / managed - bean - class >  < managed - bean - scope > none <  / managed - bean
     - scope >  <  / managed - bean >

Registering the custom functionality with a resource

After declaring the functionality as a managed bean, you must register it with a resource so it can be initialized by the Web ADF when the resource is loaded. To register SecurityCheckFunctionality with a resource, add it as a <map-entry> to the resource's <managed-property> called functionalities as shown in the following code sample.
[Java]
 < managed - bean >  < managed - bean - name > ags1 <  / managed - bean - name >  <
     managed - bean - class > com.esri.adf.web.ags.data.AGSLocalMapResource <  /
     managed - bean - class >  < managed - bean - scope > none <  / managed - bean -
     scope > ... ... < managed - property >  < property - name > functionalities < 
     / property - name >  < map - entries >  < map - entry >  < key > map <  / key >
     < value > #{
    agsMap
}

 <  / value >  <  / map - entry >  < map - entry >  < key > query <  / key >  <
     value > #{
    agsQuery
}

 <  / value >  <  / map - entry >  < map - entry >  < key > tile <  / key >  < value
     > #{
    agsTile
}

 <  / value >  <  / map - entry >  < map - entry >  < key > overview <  / key >  <
     value > #{
    agsOverview
}

 <  / value >  <  / map - entry > 

 < map - entry >  < key > pipeline_security_check <  / key >  < value > #{
    securitycheck
}

 <  / value >  <  / map - entry > 

 < map - entry >  < key > toc <  / key >  < value > #{
    agsToc
}

 <  / value >  <  / map - entry >  <  / map - entries >  <  / managed - property > 
     <  / managed - bean >
The order in which you register functionalities is crucial. The Web ADF initializes functionalities in the exact order in which they are registered with a resource. You must register SecurityCheckFunctionality after the map functionality because it relies on the map functionality's MapServerInfo property; however, you must register it before the toc functionality because it needs to modify the TOC. If you register it after the toc functionality, the TOC will already be initialized when your functionality is called, and you will not be able remove the Pipeline_Network layer.

Securing the Web application

The following configuration in the application's web.xml enforces the security policy discussed in the scenario. Anonymous access to the entire Web application is disabled, and users must belong to either the petroEmployee or the stateEmployee role to access it.
[Java]
 < web - app > ... 
//Specify the authentication mechanism.
 < login - config >  < auth - method > DIGEST <  / auth - method >  < realm - name >
     My_WebApplication <  / realm - name >  <  / login - config > 
//Declare logical roles for the Web application.
 < security - role >  < role - name > petroEmployee <  / role - name >  <  /
     security - role >  < security - role >  < role - name > stateEmployee <  / role
     - name >  <  / security - role > 

//Declare the resources to secure and the roles that can access them.
 < security - constraint >  < web - resource - collection >  < url - pattern >  *  <
     url - pattern >  <  / web - resource - collection >  < auth - constraint >  <
     role - name > petroEmployee <  / role - name >  < role - name > stateEmployee <
     role - name >  <  / auth - constraint >  <  / security - constraint > ... <  /
     web - app >
The users of this Web application need to be mapped to logical roles declared in the application's web.xml as shown in the following code sample. In Apache Tomcat, this can be done by editing $TOMCAT_HOME/conf/tomcat-users.xml.
[Java]
 < tomcat - users > ... 
//Add users and assign them to application roles.
 < user name = "joe" password = "joe" roles = "petroEmployee" >  < user name = 
     "daisy" password = "daisy" roles = "stateEmployee" > ... <  / tomcat - users >


See Also:

Custom commands and tools
Custom context attributes
Writing a custom task