Changes between Version 34 and Version 35 of PluginDevelopmentGuide


Ignore:
Timestamp:
29.10.2016 10:54:37 (5 years ago)
Author:
Malte
Comment:

Upgraded the plugin development guide from 4.1 to DeepaMehta 4.8

Legend:

Unmodified
Added
Removed
Modified
  • PluginDevelopmentGuide

    v34 v35  
    9494    <parent> 
    9595        <groupId>de.deepamehta</groupId> 
    96         <artifactId>deepamehta-plugin-parent</artifactId> 
    97         <version>4.1.1-SNAPSHOT</version> 
     96        <artifactId>deepamehta-plugin</artifactId> 
     97        <version>4.8</version> 
    9898    </parent> 
    9999 
     
    130130                { 
    131131                    type_uri: "dm4.webclient.view_config", 
    132                     composite: { 
     132                    childs: { 
    133133                        dm4.webclient.show_in_create_menu: true 
    134134                    } 
     
    144144{{{ 
    145145#!txt 
    146 requiredPluginMigrationNr=1 
    147 importModels=de.deepamehta.webclient 
     146dm4.plugin.activate_after=de.deepamehta.webclient 
     147dm4.plugin.model_version=1 
    148148}}} 
    149149 
    150150=== Setup for Hot-Deployment === 
    151151 
    152 In order to let DeepaMehta hot-deploy the plugin you must include it in DeepaMehta's hot-deployment list. 
    153  
    154 In DeepaMehta's **`pom.xml`**: add the plugin's `target` directory (here: `/home/myhome/deepamehta-dev/dm4-tagging/target`) to the `felix.fileinstall.dir` property. Important: make sure your line ends with a comma: 
     152The easiest way to let DeepaMehta hot-deploy the plugin is to develop it within the **`bundle-dev`** directory. To start doing so move the plugin directory on your hard disc into DeepaMehta's hot-deployment folder called **`bundle-dev`**. The next step is to build your plugin. 
     153 
     154Another way to let DeepaMehta hot-deploy the plugin is in DeepaMehta's **`pom.xml`**: add the plugin's `target` directory (here: `/home/myhome/deepamehta-dev/dm4-tagging/target`) to the `felix.fileinstall.dir` property. Important: make sure your line ends with a comma: 
    155155 
    156156{{{ 
     
    427427=== Plugin configuration === 
    428428 
    429 If your plugin comes with its own data model you must tell DeepaMehta the data model version it relies on. To do so, set the `requiredPluginMigrationNr` configuration property in the `plugin.properties` file, e.g.: 
    430  
    431 {{{ 
    432 #!txt 
    433 requiredPluginMigrationNr=2 
    434 }}} 
    435  
    436 DeepaMehta's migration machinery takes charge of running the plugin's migrations up to that configured number. If your plugin comes with no data model, you can specify `0` resp. omit the `requiredPluginMigrationNr` property as `0` is its default value. 
     429If your plugin comes with its own data model you must tell DeepaMehta the data model version it relies on. To do so, set the `dm4.plugin.model_version` configuration property in the `plugin.properties` file, e.g.: 
     430 
     431{{{ 
     432#!txt 
     433dm4.plugin.model_version=2 
     434}}} 
     435 
     436DeepaMehta's migration machinery takes charge of running the plugin's migrations up to that configured number. If your plugin comes with no data model, you can specify `0` resp. omit the `dm4.plugin.model_version` property as `0` is its default value. 
    437437 
    438438Usually each plugin has its own `plugin.properties` file. It allows the developer to configure certain aspects of the plugin. The name of the `plugin.properties` file and its path within the plugin directory is fixed: 
     
    489489}}} 
    490490 
    491 This example plugin would have set `requiredPluginMigrationNr` to 6 (configured in `plugin.properties`), so 6 migrations are involved. 4 are declarative and 2 are imperative here. 
    492  
    493 Important: for each number between 1 and `requiredPluginMigrationNr` exactly one migration file must exist. That is //either// a declarative migration file //or// an imperative migration file. 
     491This example plugin would have set `dm4.plugin.model_version` to 6 (configured in `plugin.properties`), so 6 migrations are involved. 4 are declarative and 2 are imperative here. 
     492 
     493Important: for each number between 1 and `dm4.plugin.model_version` exactly one migration file must exist. That is //either// a declarative migration file //or// an imperative migration file. 
    494494 
    495495It would be invalid if for a given number a) no migration file exists, or b) two migration files exist (one declarative and one imperative). In these cases the DeepaMehta migration machinery throws an error and the plugin is not activated. 
     
    556556                { 
    557557                    type_uri: "dm4.webclient.view_config", 
    558                     composite: { 
     558                    childs: { 
    559559                        dm4.webclient.icon: "/de.deepamehta.notes/images/yellow-ball.png", 
    560560                        dm4.webclient.show_in_create_menu: true 
     
    573573An imperative migration is a Java class that is derived from `de.deepamehta.core.service.Migration` and that overrides the `run()` method. The `run()` method is called by DeepaMehta to run the migration. 
    574574 
    575 Within the migration you have access to the //DeepaMehta Core Service// through the `dms` object. By the means of the DeepaMehta Core Service you can perform arbitrary database operations. Typically this involves importing further objects from the `de.deepamehta.core` API. 
     575Within the migration you have access to the DeepaMehta //Core Service// through the `dm4` object. By the means of the Core Service you can perform arbitrary database operations. Typically this involves importing further objects from the `de.deepamehta.core` API. 
    576576 
    577577As an example see a migration that comes with the //DeepaMehta 4 Topicmaps// plugin: 
     
    579579{{{ 
    580580#!java 
    581 package de.deepamehta.plugins.topicmaps.migrations; 
     581package de.deepamehta.topicmaps.migrations; 
    582582 
    583583import de.deepamehta.core.TopicType; 
    584 import de.deepamehta.core.model.AssociationDefinitionModel; 
    585584import de.deepamehta.core.service.Migration; 
    586585 
     
    589588    @Override 
    590589    public void run() { 
    591         TopicType type = dms.getTopicType("dm4.topicmaps.topicmap", null); 
    592         type.addAssocDef(new AssociationDefinitionModel("dm4.core.composition_def", 
     590        TopicType type = dm4.getTopicType("dm4.topicmaps.topicmap"); 
     591        type.addAssocDef(mf.newAssociationDefinitionModel("dm4.core.composition_def", 
    593592            "dm4.topicmaps.topicmap", "dm4.topicmaps.state", "dm4.core.one", "dm4.core.one")); 
    594593    } 
     
    608607    * **Consuming services provided by other plugins**. Example: in order to investigate a topic's workspace assignments and the current user's memberships the //DeepaMehta 4 Access Control// plugin consumes the service provided by the //DeepaMehta 4 Workspaces// plugin. 
    609608 
    610     * **Access the DeepaMehta Core Service**. The DeepaMehta Core Service provides the basic database operations (create, retrieve, update, delete) to deal with the DeepaMehta Core objects: Topics, Associations, Topic Types, Association Types. 
     609    * **Access the DeepaMehta Core Service**. The DeepaMehta //Core Service// provides the basic database operations (create, retrieve, update, delete) to deal with the DeepaMehta Core objects: Topics, Associations, Topic Types, Association Types. 
    611610 
    612611Weather a DeepaMehta plugin has a server side part at all depends on the nature of the plugin. Plugins without a server side part include those which e.g. just define a data model or just provide a custom (JavaScript) renderer. 
     
    653652 
    654653Furthermore when writing a plugin main file you must add 2 entries in the plugin's **`pom.xml`**: 
    655     1. a <dependencies> element to include the `deepamehta-core` dependency. This brings you the `PluginActivator` class. 
     654    1. a <parent> element to declare the artifactId **`deepamehta-plugin`**. This brings you necessary dependenies and the `PluginActivator` class. 
    656655    2. a <build> element to configure the Maven Bundle Plugin. It needs to know what your plugin main class is. You must specify the fully-qualified class name. 
    657656 
     
    669668    <parent> 
    670669        <groupId>de.deepamehta</groupId> 
    671         <artifactId>deepamehta-plugin-parent</artifactId> 
    672         <version>4.1.1-SNAPSHOT</version> 
     670        <artifactId>deepamehta-plugin</artifactId> 
     671        <version>4.8</version> 
    673672    </parent> 
    674  
    675     <dependencies> 
    676         <dependency> 
    677             <groupId>de.deepamehta</groupId> 
    678             <artifactId>deepamehta-core</artifactId> 
    679             <version>4.1.1-SNAPSHOT</version> 
    680         </dependency> 
    681     </dependencies> 
    682673 
    683674    <build> 
     
    724715import de.deepamehta.core.model.TopicModel; 
    725716import de.deepamehta.core.osgi.PluginActivator; 
    726 import de.deepamehta.core.service.ClientState; 
    727717import de.deepamehta.core.service.Directives; 
    728718import de.deepamehta.core.service.event.PostCreateTopicListener; 
     
    738728 
    739729    @Override 
    740     public void postCreateTopic(Topic topic, ClientState clientState, Directives directives) { 
     730    public void postCreateTopic(Topic topic) { 
    741731        log.info("### Topic created: " + topic); 
    742732    } 
    743733 
    744734    @Override 
    745     public void postUpdateTopic(Topic topic, TopicModel newModel, TopicModel oldModel, ClientState clientState, 
    746                                                                                        Directives directives) { 
     735    public void postUpdateTopic(Topic topic, TopicModel newModel, TopicModel oldModel) { 
    747736        log.info("### Topic updated: " + topic + "\nOld topic: " + oldModel); 
    748737    } 
     
    766755For a plugin to provide a service you must define a //service interface//. The service interface contains all the method signatures that make up the service. When other plugins consume your plugin's service they do so via the service interface. 
    767756 
    768 The service interface //must// be located in the plugin's `src/main/java/<your plugin package>/service/` directory. By convention the name of the service interface ends with `...Service`. The service interface must be declared `public`. 
    769  
    770 The service interface is a regular Java interface that extends `de.deepamehta.core.service.PluginService`. PluginService is a common base interface for all plugin services. It's just a marker interface. 
     757To be recogbized the service interface //must// by convention end its name on `...Service`. The service interface must be declared `public` and is a regular Java interface. 
    771758 
    772759A DeepaMehta plugin can define //one// service interface at most. More than one service interface is not supported. 
     
    782769                de/ 
    783770                    deepamehta/ 
    784                         plugins/ 
    785                             topicmaps/ 
    786                                 service/ 
    787                                     TopicmapsService.java 
    788 }}} 
    789  
    790 The service interface of the //Topicmaps// plugin is named `TopicmapsService`. The plugin package is `de.deepamehta.plugins.topicmaps`. 
     771                        topicmaps/ 
     772                            TopicmapsService.java 
     773}}} 
     774 
     775The service interface of the //Topicmaps// plugin is named `TopicmapsService`. The plugin package is `de.deepamehta.topicmaps`. 
    791776 
    792777The //Topicmaps// service interface looks like this: 
     
    794779{{{ 
    795780#!java 
    796 package de.deepamehta.plugins.topicmaps.service; 
    797  
    798 import de.deepamehta.plugins.topicmaps.TopicmapRenderer; 
    799 import de.deepamehta.plugins.topicmaps.model.ClusterCoords; 
    800 import de.deepamehta.plugins.topicmaps.model.Topicmap; 
     781package de.deepamehta.topicmaps.service; 
     782 
     783import de.deepamehta.topicmaps.TopicmapRenderer; 
     784import de.deepamehta.topicmaps.model.ClusterCoords; 
     785import de.deepamehta.topicmaps.model.Topicmap; 
    801786 
    802787import de.deepamehta.core.Topic; 
    803 import de.deepamehta.core.service.ClientState; 
    804 import de.deepamehta.core.service.PluginService; 
    805  
    806  
    807  
    808 public interface TopicmapsService extends PluginService { 
    809  
    810     Topic createTopicmap(String name,             String topicmapRendererUri, ClientState clientState); 
    811     Topic createTopicmap(String name, String uri, String topicmapRendererUri, ClientState clientState); 
     788 
     789 
     790public interface TopicmapsService { 
     791 
     792    Topic createTopicmap(String name,             String topicmapRendererUri); 
     793    Topic createTopicmap(String name, String uri, String topicmapRendererUri); 
    812794 
    813795    // --- 
    814796 
    815     Topicmap getTopicmap(long topicmapId, ClientState clientState); 
     797    Topicmap getTopicmap(long topicmapId); 
    816798 
    817799    // --- 
     
    837819}}} 
    838820 
    839 You see the Topicmaps service consist of methods to create topicmaps, retrieve topicmaps, and manipulate topicmaps. As any plugin service the Topicmaps service must be derived from `PluginService`. 
     821You see the Topicmaps service consist of methods to create topicmaps, retrieve topicmaps, and manipulate topicmaps. 
    840822 
    841823==== Implementing the service ==== 
     
    849831{{{ 
    850832#!java 
    851 package de.deepamehta.plugins.topicmaps; 
    852  
    853 import de.deepamehta.plugins.topicmaps.model.Topicmap; 
    854 import de.deepamehta.plugins.topicmaps.service.TopicmapsService; 
     833package de.deepamehta.topicmaps; 
     834 
     835import de.deepamehta.topicmaps.model.Topicmap; 
     836import de.deepamehta.topicmaps.TopicmapsService; 
    855837 
    856838import de.deepamehta.core.Topic; 
    857839import de.deepamehta.core.osgi.PluginActivator; 
    858 import de.deepamehta.core.service.ClientState; 
    859840 
    860841 
     
    865846 
    866847    @Override 
    867     public Topic createTopicmap(String name, String topicmapRendererUri, ClientState clientState) { 
    868         ... 
    869     } 
    870  
    871     @Override 
    872     public Topic createTopicmap(String name, String uri, String topicmapRendererUri, ClientState clientState) { 
     848    public Topic createTopicmap(String name, String topicmapRendererUri) { 
     849        ... 
     850    } 
     851 
     852    @Override 
     853    public Topic createTopicmap(String name, String uri, String topicmapRendererUri) { 
    873854        ... 
    874855    } 
     
    877858 
    878859    @Override 
    879     public Topicmap getTopicmap(long topicmapId, ClientState clientState) { 
     860    public Topicmap getTopicmap(long topicmapId) { 
    880861        ... 
    881862    } 
     
    897878Your plugin can consume the services provided by other plugins. To do so your plugin must get hold of the //service object// of the other plugin. Through the service object your plugin can call all the service methods declared in the other's plugin service interface. 
    898879 
     880To tell the DeepaMehta Core which plugin service your plugin wants to consume you need to declare an instance variable in your plugin like using the @Inject notation: 
     881 
     882{{{ 
     883#!java 
     884    @Inject 
     885    private AccessControlService acService; 
     886}}} 
     887 
     888Make sure to add your interest in building on the respective plugin service as dependencies to your **`pom.xml`** file. In the case of using the AccessControlService we would need to add the following: 
     889 
     890{{{ 
     891#!xml 
     892    <dependencies> 
     893        <dependency> 
     894            <groupId>de.deepamehta</groupId> 
     895            <artifactId>deepamehta-accesscontrol</artifactId> 
     896            <version>4.8</version> 
     897        </dependency> 
     898    </dependencies> 
     899}}} 
     900 
    899901Behind the scenes the DeepaMehta Core handles a plugin service as an OSGi service. Because of the dynamic nature of an OSGi environment DeepaMehta plugin services can arrive and go away at any time. Your plugin must deal with that. However, you as a plugin developer must not care about DeepaMehta's OSGi foundation. The DeepaMehta Core hides the details from you and provides an easy-to-use API for consuming plugin services. 
    900902 
    901 To consume a plugin service your plugin must override 2 hooks: `serviceArrived` and `serviceGone`. Through the former your plugin gets hold of a service object and in the latter your plugin must release that service object. These 2 hooks are called by the DeepaMehta Core as soon as a desired plugin becomes available resp. goes away. 
    902  
    903 To tell the DeepaMehta Core which plugin service your plugin wants to consume use the `@ConsumesService` annotation at the `serviceArrived` hook. State the fully qualified name (string) of the respective service interface as the annotation argument. To consume more than one service use the array notation: `{"...", "..."}`. 
     903To deal with other plugin services coming and going your plugin can override 2 hooks: `serviceArrived` and `serviceGone`. These 2 hooks are called by the DeepaMehta Core as soon as a desired plugin becomes available resp. goes away. 
    904904 
    905905The single argument of the 2 `serviceArrived` and `serviceGone` hooks is the respective service object, declared generically just as `PluginService`. (Remember, `PluginService` is the common base interface for all plugin services.) So casting is required. In `serviceArrived` you typically store the service object in a private instance variable. In `serviceGone` you typically set the instance variable to `null` in order to release the service object. 
     
    909909{{{ 
    910910#!java 
    911 package de.deepamehta.plugins.workspaces; 
    912  
    913 import de.deepamehta.plugins.facets.service.FacetsService; 
     911package de.deepamehta.workspaces; 
     912 
     913import de.deepamehta.facets.FacetsService; 
    914914 
    915915import de.deepamehta.core.osgi.PluginActivator; 
     
    921921public class WorkspacesPlugin extends PluginActivator { 
    922922 
     923    @Inject 
    923924    private FacetsService facetsService; 
    924925 
     
    926927 
    927928    @Override 
    928     @ConsumesService("de.deepamehta.plugins.facets.service.FacetsService") 
    929929    public void serviceArrived(PluginService service) { 
    930         facetsService = (FacetsService) service; 
     930        if (service instanceof FacetsService) { 
     931            // do something when the facet service comes around 
     932        } 
    931933    } 
    932934 
    933935    @Override 
    934936    public void serviceGone(PluginService service) { 
    935         facetsService = null; 
    936     } 
    937 }}} 
    938  
    939 You see the Workspaces plugin consumes just one plugin service: the //Facets// service. Its service interface is specified as the `@ConsumesService` argument. The `PluginService` object passed to the 2 hooks needs not being further investigated. 
    940  
    941 In contrast lets see how it looks like when a plugin consumes more than one service. As an example here is an extraction of the //Geomaps// plugin (part of the DeepaMehta Standard Distribution): 
     937        // do something when a service goes away 
     938    } 
     939}}} 
     940 
     941You see the Workspaces plugin consumes a plugin service: the //Facets// service.  The `PluginService` object passed to the 2 hooks needs not being further investigated. 
     942 
     943In this way your plugin could also consume more than one service. 
     944 
     945=== Providing a RESTful web service === 
     946 
     947Until here your plugin service is accessible from within the OSGi environment only. You can make the service accessible from //outside// the OSGi environment as well by promoting it to a RESTful web service. Your plugin service is then accessible from external applications via HTTP. (External application here means both, the client-side portion of a DeepaMehta plugin, or an arbitrary 3rd-party application). 
     948 
     949To provide a RESTful web service you must provide a generic plugin service first (as described above in [[#Providingaservice|Providing a service]]) and then make it RESTful by using JAX-RS annotations. With JAX-RS annotations you basically control how HTTP requests will be mapped to your service methods. 
     950 
     951To make your plugin service RESTful you must: 
     952 
     953* Annotate the plugin main class with `@Path` to anchor the plugin service in URI space. 
     954 
     955* Annotate the plugin main class with `@Consumes` and `@Produces` to declare the supported HTTP request and response media types. You can use these annotations also at a particular service method to override the class-level defaults. 
     956 
     957* Annotate each service method with one of `@GET`, `@POST`, `@PUT`, or `@DELETE` to declare the HTTP method that will invoke that service method. 
     958 
     959* Annotate each service method with `@Path` to declare the URI template that will invoke that service method. The URI template can contain parameters, notated with curly braces `{...}`. 
     960 
     961* Annotate service method parameters with `@PathParam` to map URI template parameters to service method parameters. 
     962 
     963As an example let's see how the //Topicmaps// plugin (part of the DeepaMehta Standard Distribution) annotates its main class and service methods: 
    942964 
    943965{{{ 
    944966#!java 
    945 public class GeomapsPlugin extends PluginActivator { 
    946  
    947     private TopicmapsService topicmapsService; 
    948     private FacetsService facetsService; 
    949  
    950     // *** Hook Implementations *** 
    951  
    952     @Override 
    953     @ConsumesService({ 
    954         "de.deepamehta.plugins.topicmaps.service.TopicmapsService", 
    955         "de.deepamehta.plugins.facets.service.FacetsService" 
    956     }) 
    957     public void serviceArrived(PluginService service) { 
    958         if (service instanceof TopicmapsService) { 
    959             topicmapsService = (TopicmapsService) service; 
    960         } else if (service instanceof FacetsService) { 
    961             facetsService = (FacetsService) service; 
    962         } 
    963     } 
    964  
    965     @Override 
    966     public void serviceGone(PluginService service) { 
    967         if (service == topicmapsService) { 
    968             topicmapsService = null; 
    969         } else if (service == facetsService) { 
    970             facetsService = null; 
    971         } 
    972     } 
    973 }}} 
    974  
    975 You see the Geomaps plugin consumes 2 plugin services: the //Topicmaps// service and the //Facets// service. Both service interfaces are specified as the `@ConsumesService` argument. Note the array notation with the curly braces `{...}`. Note also the `if` statements in the `serviceArrived` and `serviceGone` hooks. 
    976  
    977 === Providing a RESTful web service === 
    978  
    979 Until here your plugin service is accessible from within the OSGi environment only. You can make the service accessible from //outside// the OSGi environment as well by promoting it to a RESTful web service. Your plugin service is then accessible from external applications via HTTP. (External application here means both, the client-side portion of a DeepaMehta plugin, or an arbitrary 3rd-party application). 
    980  
    981 To provide a RESTful web service you must provide a generic plugin service first (as described above in [[#Providingaservice|Providing a service]]) and then make it RESTful by using JAX-RS annotations. With JAX-RS annotations you basically control how HTTP requests will be mapped to your service methods. 
    982  
    983 To make your plugin service RESTful you must: 
    984  
    985 * Annotate the plugin main class with `@Path` to anchor the plugin service in URI space. 
    986  
    987 * Annotate the plugin main class with `@Consumes` and `@Produces` to declare the supported HTTP request and response media types. You can use these annotations also at a particular service method to override the class-level defaults. 
    988  
    989 * Annotate each service method with one of `@GET`, `@POST`, `@PUT`, or `@DELETE` to declare the HTTP method that will invoke that service method. 
    990  
    991 * Annotate each service method with `@Path` to declare the URI template that will invoke that service method. The URI template can contain parameters, notated with curly braces `{...}`. 
    992  
    993 * Annotate service method parameters with `@PathParam` to map URI template parameters to service method parameters. 
    994  
    995 As an example let's see how the //Topicmaps// plugin (part of the DeepaMehta Standard Distribution) annotates its main class and service methods: 
    996  
    997 {{{ 
    998 #!java 
    999 package de.deepamehta.plugins.topicmaps; 
    1000  
    1001 import de.deepamehta.plugins.topicmaps.model.Topicmap; 
    1002 import de.deepamehta.plugins.topicmaps.service.TopicmapsService; 
     967package de.deepamehta.topicmaps; 
     968 
     969import de.deepamehta.topicmaps.model.Topicmap; 
     970import de.deepamehta.topicmaps.TopicmapsService; 
    1003971 
    1004972import de.deepamehta.core.Topic; 
    1005973import de.deepamehta.core.osgi.PluginActivator; 
    1006 import de.deepamehta.core.service.ClientState; 
    1007974 
    1008975import javax.ws.rs.GET; 
     
    1029996    @Override 
    1030997    public Topic createTopicmap(@PathParam("name") String name, 
    1031                                 @PathParam("topicmap_renderer_uri") String topicmapRendererUri, 
    1032                                 @HeaderParam("Cookie") ClientState clientState) { 
     998                                @PathParam("topicmap_renderer_uri") String topicmapRendererUri) { 
    1033999        ... 
    10341000    } 
     
    10371003    @Path("/{id}") 
    10381004    @Override 
    1039     public Topicmap getTopicmap(@PathParam("id") long topicmapId, @HeaderParam("Cookie") ClientState clientState) { 
     1005    public Topicmap getTopicmap(@PathParam("id") long topicmapId) { 
    10401006        ... 
    10411007    } 
     
    10851051    @Path("/{id}") 
    10861052    @Override 
    1087     public Topicmap getTopicmap(@PathParam("id") long topicmapId, @HeaderParam("Cookie") ClientState clientState) { 
    1088         ... 
    1089     } 
    1090 }}} 
    1091  
    1092 Now you know how exactly the JAX-RS implementation extracts the `topicmapId` and `clientState` parameter values from the HTTP request: 
     1053    public Topicmap getTopicmap(@PathParam("id") long topicmapId) { 
     1054        ... 
     1055    } 
     1056}}} 
     1057 
     1058Now you know how exactly the JAX-RS implementation extracts the `topicmapId` parameter value from the HTTP request: 
    10931059 
    10941060    The `topicmapId` value is extracted from the request's URI path and then converted to a `long`. Here criterion 1 is satisfied and the conversion is straight-forward. 
    10951061 
    1096     The `clientState` value is extracted from the request's `Cookie` header value. The actual `ClientState` object is created by passing the value to the `ClientState(String)` constructor. Here criterion 2 is satisfied. 
    1097  
    10981062==== Parsing the HTTP request body ==== 
    10991063