Changes between Version 1 and Version 2 of AnotherPluginDevelopmentGuide


Ignore:
Timestamp:
30.06.2012 02:39:00 (8 years ago)
Author:
dgf
Comment:

enhance example

Legend:

Unmodified
Added
Removed
Modified
  • AnotherPluginDevelopmentGuide

    v1 v2  
    4040project [dm4-example https://github.com/dgf/dm4-example]. 
    4141 
     42||= '''Directories and files'''            =||= '''Explanation''' =|| 
     43|--------------------------------------------- 
    4244|| dm4-example/                             || An example plugin project directory. || 
    4345|| . [#pom pom.xml]                         || The POM of your plugin project (mandatory). || 
    4446|| . README.md                              || A project description. || 
    45 || . src/                                   |||| 
    46 || . . main/                                |||| 
     47|||| . src/                                 || 
     48|||| . . main/                              || 
    4749|| . . . [#java java]/                      || Server-side Java code goes into src/main/java. || 
    4850|| . . . . de/                              || de/deepamehta/plugins/example is the root package of your Java classes. || 
    4951|| . . . . . deepamehta/                    || The package name is under your choice. || 
    50 || . . . . . . plugins/                     |||| 
    51 || . . . . . . . example/                   |||| 
     52|||| . . . . . . plugins/                   || 
     53|||| . . . . . . . example/                 || 
    5254|| . . . . . . . . [#migration migrations]/ || Migrations go into this directory and have a fixed name. || 
    5355|| . . . . . . . . . Migration2.java        || This programmatic migration 2 is called after the declarative 1. || 
    5456|| . . . . . . . . [#provider provider/]    || REST specific provider that supports the conversion between a Java type and a stream. || 
    55 || . . . . . . . . . !ExampleProvider.java  |||| 
     57|||| . . . . . . . . . !ExampleProvider.java || 
    5658|| . . . . . . . . [#plugin ExamplePlugin.java] || The plugin's "main" class. || 
    5759|| . . resources/                           || Additional (non-Java) files. || 
     
    6567|| . . . . . [#pluginjs plugin.js]          || The JavaScipt plugin "main" file. || 
    6668|| . . . . style/                           || Another custom folder to separate the styles. || 
    67 || . . . . . screen.css                     |||| 
    68  
     69|||| . . . . . screen.css                   || 
    6970 
    7071= Setting up the development environment = 
     
    7879Inside the plugin project directory: 
    7980 
    80 {{{$ mvn package}}} 
     81{{{#!sh 
     82$ mvn package 
     83}}} 
    8184 
    8285This builds the jar file, an OSGi bundle. The jar is placed in the directory target (next to src). 
    8386 
     87||= '''Directories and files'''            =||= '''Explanation''' =|| 
     88|--------------------------------------------- 
    8489|| dm4-plugin-example/                      || Plugin project directory. || 
    85 || src/                                     |||| 
     90|||| src/                                   || 
    8691|| target/                                  || Contains artifacts build by Maven. || 
    8792|| . dm4-plugin-example-0.0.1-SNAPSHOT.jar  || The jar ready for deployment in OSGi console. || 
     
    98103take place and [BuildFromSource build] DeepaMehta from source. Then adapt the  
    99104'felix.fileinstall.dir' configuration of the DeepaMehta global POM run profile to your needs: 
    100 {{{ 
     105{{{#!xml 
    101106<!-- ... --> 
    102107    <profiles> 
     
    115120Start DeepaMehta with the Maven run profile and go on with your development 
    116121 
    117 {{{$ mvn pax:run}}} 
     122{{{#!sh 
     123$ mvn pax:run 
     124}}} 
     125 
     126 
     127== Use of an in-memory database == 
     128 
     129Developing an topic importer or some other heavy data manipulations can stress you (on a HDD) or 
     130the lifetime of your static memory (in case of a SSD). On Linux systems it is very simple to move 
     131the DeepaMehta database into a memory file system with a mount like the following: 
     132 
     133Copy your database into a in-memory file system: 
     134{{{#!sh 
     135$ mkdir offline-db 
     136$ mv deepamehta-db/* offline-db 
     137$ sudo mount -osize=100m tmpfs deepamehta-db -t tmpfs 
     138$ cp -a offline-db/* deepamehta-db 
     139}}} 
     140 
    118141 
    119142= pom.xml = #pom 
     
    127150 
    128151Plugin POM example: 
    129 {{{ 
     152{{{#!xml 
    130153<?xml version="1.0" encoding="UTF-8"?> 
    131154<project xmlns="http://maven.apache.org/POM/4.0.0" 
     
    188211Just write the declarations in the corresponding property of your migration. 
    189212 
     213'''TBD''' describe and exemplify migration*.properties file usage 
     214 
    190215Declarative plugin migration example: 
    191 {{{ 
     216{{{#!js 
    192217{ 
    193218    topic_types: [ # -------------------------------- create types 
     
    236261 
    237262Programmatic plugin migration example: 
    238 {{{ 
     263{{{#!java 
    239264package de.deepamehta.plugins.example.migrations; 
    240265 
    241 import de.deepamehta.core.model.TopicModel; 
    242266import de.deepamehta.core.service.Migration; 
    243267 
    244268/** 
    245  * update search icon back to the good old bucket 
     269 * Update search icon back to the good old bucket. 
    246270 */ 
    247271public class Migration2 extends Migration { 
     
    249273    @Override 
    250274    public void run() { 
    251         // get web client icon configuration of the search topic 
    252         TopicModel iconConfig = dms.getTopicType("dm4.webclient.search", null)// 
    253                 .getViewConfig().getConfigTopic("dm4.webclient.view_config")// 
    254                 .getCompositeValue().getTopic("dm4.webclient.icon"); 
    255  
    256         // update icon value 
    257         iconConfig.setSimpleValue("/de.deepamehta.dm4-example/images/bucket.png"); 
    258         dms.updateTopic(iconConfig, null); 
    259     } 
    260  
     275        // update web client icon configuration of the search topic 
     276        dms.getTopicType("dm4.webclient.search", null) 
     277                .getViewConfig() 
     278                .addSetting("dm4.webclient.view_config", "dm4.webclient.icon", 
     279                        "/de.deepamehta.dm4-example/images/bucket.png"); 
     280    } 
    261281} 
    262282}}} 
     
    277297 
    278298# Name of the service interface that the plugin exports and provides. 
    279 providedServiceInterface = de.deepamehta.plugins.example.ExampleService 
     299providedServiceInterface = de.deepamehta.plugins.example.service.ExampleService 
    280300 
    281301# The number of the migration the plugin requires to run. 
     
    302322 
    303323A plugin that loads some type specific renderer and adds a custom command: 
    304 {{{ 
     324{{{#!js 
    305325dm4c.add_plugin('de.deepamehta.example', function() { 
    306326 
     327    // load some renderer and a style sheet 
     328    dm4c.load_field_renderer('/de.deepamehta.dm4-example/script/example_content_field_renderer.js') 
    307329    dm4c.load_page_renderer('/de.deepamehta.dm4-example/script/example_content_page_renderer.js') 
    308     dm4c.load_field_renderer('/de.deepamehta.dm4-example/script/example_content_field_renderer.js') 
    309330    dm4c.load_stylesheet('/de.deepamehta.dm4-example/style/screen.css') 
    310331 
     332    // calls the alternative REST creation method with customized JSON format 
     333    function createAnotherExample() { 
     334        var name = prompt('Example name', 'Another Example') 
     335            topic = dm4c.restc.request('POST', '/example/create', { name: name }) 
     336        dm4c.canvas.add_topic(topic, true) 
     337        dm4c.do_select_topic(topic.id) 
     338    } 
     339 
     340    // calls the server side increase method of the selected Example topic 
    311341    function increaseExample() { 
    312         var id = dm4c.selected_object.id, 
    313             url = '/example/increase/' + id, 
    314             example = dm4c.restc.request('GET', url) 
    315         dm4c.do_select_topic(id) 
    316     } 
    317  
    318     var commands = [{ 
    319         context: ['context-menu', 'detail-panel-show'], 
    320         label: 'Hit me!', handler: increaseExample 
    321     }] 
    322  
     342        var url = '/example/increase/' + dm4c.selected_object.id, 
     343            topic = dm4c.restc.request('GET', url) 
     344        dm4c.canvas.update_topic(topic, true) 
     345        dm4c.do_select_topic(topic.id) 
     346    } 
     347 
     348    // define type specific commands and register them  
    323349    dm4c.register_listener('topic_commands', function (topic) { 
    324         return topic.type_uri === 'dm4.example.type' ? commands : [] 
     350        return topic.type_uri !== 'dm4.example.type' ? [] : [{ 
     351            context: ['context-menu', 'detail-panel-show'], 
     352            label: 'Increase me!', handler: increaseExample 
     353        }] 
     354    }) 
     355 
     356    // register an additional create command 
     357    dm4c.register_listener("post_refresh_create_menu", function(type_menu) { 
     358        type_menu.add_separator() 
     359        type_menu.add_item({ label: "New Example", handler: createAnotherExample }) 
    325360    }) 
    326361}) 
     
    331366 
    332367Declarative renderer assignment: 
    333 {{{ 
     368{{{#!js 
    334369{ 
    335370    value: "Example Content", 
     
    355390creates a fresh object for each composite part, so please use the prototype approach. 
    356391 
    357 {{{ 
     392{{{#!js 
    358393// a simple field renderer that renders an example topic name with an additional CSS class 
    359394function ExampleContentFieldRenderer(field_model) { 
     
    375410}}} 
    376411 
    377 }}} 
    378  
    379412 
    380413=== !PageRenderer === 
     
    382415The page renderer can replace the whole page content. 
    383416 
    384 {{{ 
     417{{{#!js 
    385418// a page render that simply renders the value of an example content topic instance 
    386419function ExampleContentPageRenderer() { 
     
    416449'''TBD''' use some other JVM languages like Scala and Clojure 
    417450 
     451 
     452== Model == 
     453 
     454Writing the business logic close to the domain jargon is desirable and can be supported with 
     455a simple wrap of the underlying topic model. 
     456 
     457A topic model wrapper: 
     458{{{#!java 
     459package de.deepamehta.plugins.example.model; 
     460 
     461import org.codehaus.jettison.json.JSONException; 
     462import org.codehaus.jettison.json.JSONObject; 
     463 
     464import de.deepamehta.core.model.CompositeValue; 
     465import de.deepamehta.core.model.TopicModel; 
     466 
     467public class ExampleTopic extends TopicModel { 
     468 
     469    public static final String COUNT = "dm4.example.count"; 
     470    public static final String NAME = "dm4.example.name"; 
     471    public static final String TYPE = "dm4.example.type"; 
     472 
     473    /** 
     474     * @param model { name: "an example" } 
     475     * @throws JSONException 
     476     */ 
     477    public ExampleTopic(JSONObject json) throws JSONException { 
     478        super(TYPE); 
     479        setCompositeValue(new CompositeValue().put(NAME, json.getString("name"))); 
     480    } 
     481 
     482} 
     483}}} 
     484 
     485Domain model class example: 
     486{{{#!java 
     487package de.deepamehta.plugins.example.model; 
     488 
     489import static de.deepamehta.plugins.example.model.ExampleTopic.*; 
     490 
     491import java.util.logging.Logger; 
     492 
     493import org.codehaus.jettison.json.JSONObject; 
     494 
     495import de.deepamehta.core.DeepaMehtaTransaction; 
     496import de.deepamehta.core.JSONEnabled; 
     497import de.deepamehta.core.Topic; 
     498import de.deepamehta.core.model.SimpleValue; 
     499import de.deepamehta.core.model.TopicModel; 
     500import de.deepamehta.core.service.ClientState; 
     501import de.deepamehta.core.service.DeepaMehtaService; 
     502 
     503/** 
     504 * A domain model class that wraps the underlying <code>Topic</code>. 
     505 */ 
     506public class Example implements JSONEnabled { 
     507 
     508    private final Topic topic; 
     509    private final DeepaMehtaService dms; 
     510 
     511    /** 
     512     * Loads an existing <code>Example</code> topic. 
     513     */ 
     514    public Example(long id, DeepaMehtaService dms, ClientState clientState) { 
     515        topic = dms.getTopic(id, true, clientState); 
     516        this.dms = dms; 
     517    } 
     518 
     519    private Logger log = Logger.getLogger(getClass().getName()); 
     520 
     521    /** 
     522     * Creates a new <code>Example</code> topic from <code>ExampleTopic</code> model. 
     523     */ 
     524    public Example(ExampleTopic model, DeepaMehtaService dms, ClientState clientState) { 
     525        log.warning("CREATE EXAMPLE FROM MODEL" + model); 
     526        topic = dms.createTopic(model, clientState); 
     527        log.warning("CREATED TOPIC " + topic); 
     528        this.dms = dms; 
     529    } 
     530 
     531    /** 
     532     * Increase the count by one. 
     533     *  
     534     * @return this 
     535     */ 
     536    public Example increase() { 
     537        DeepaMehtaTransaction tx = dms.beginTx(); 
     538        topic.setChildTopicValue(COUNT, new SimpleValue(getCount() + 1)); 
     539        tx.success(); 
     540        tx.finish(); 
     541        return this; 
     542    } 
     543 
     544    @Override 
     545    public JSONObject toJSON() { 
     546        return topic.toJSON(); 
     547    } 
     548 
     549    // ------------------------------ simplified composite access 
     550 
     551    public int getCount() { 
     552        return getCountTopic().getSimpleValue().intValue(); 
     553    } 
     554 
     555    public String getName() { 
     556        return getNameTopic().getSimpleValue().toString(); 
     557    } 
     558 
     559    // ------------------------------ private helper 
     560 
     561    private TopicModel getCountTopic() { 
     562        return topic.getCompositeValue().getTopic(COUNT); 
     563    } 
     564 
     565    private TopicModel getNameTopic() { 
     566        return topic.getCompositeValue().getTopic(NAME); 
     567    } 
     568 
     569} 
     570}}} 
     571 
     572 
    418573== Plugin == #plugin 
    419574 
    420 A Java plugin can interact with predefined hook overwrites and provide some REST resources. 
     575A Java plugin can interact with predefined hook overwrites and export  a OSGi service 
     576with optional REST exposure. 
     577 
     578All public methods of the plugin must be described in a service interface: 
     579{{{#!java 
     580package de.deepamehta.plugins.example.service; 
     581 
     582import de.deepamehta.core.service.ClientState; 
     583import de.deepamehta.core.service.PluginService; 
     584import de.deepamehta.plugins.example.model.Example; 
     585import de.deepamehta.plugins.example.model.ExampleTopic; 
     586 
     587public interface ExampleService extends PluginService { 
     588 
     589    Example create(ExampleTopic topic, ClientState clientState); 
     590 
     591    Example increase(long id, ClientState clientState); 
     592 
     593} 
     594}}} 
    421595 
    422596'''TBD''' rewrite hook calls in favour of listener interface per event 
    423597 
    424 {{{ 
     598The plugin itself can expose methods with  
     599[http://jersey.java.net/nonav/documentation/latest/jax-rs.html JAX-RS] annotations: 
     600{{{#!java 
    425601package de.deepamehta.plugins.example; 
    426602 
    427603import java.util.logging.Logger; 
    428  
    429 import javax.ws.rs.GET; 
    430 import javax.ws.rs.Path; 
    431 import javax.ws.rs.PathParam; 
    432 import javax.ws.rs.Produces; 
    433 import javax.ws.rs.WebApplicationException; 
     604import javax.ws.rs.*; 
    434605 
    435606import de.deepamehta.core.model.TopicModel; 
    436607import de.deepamehta.core.service.ClientState; 
    437608import de.deepamehta.core.service.Plugin; 
     609import de.deepamehta.plugins.example.model.Example; 
     610import de.deepamehta.plugins.example.model.ExampleTopic; 
     611import de.deepamehta.plugins.example.service.ExampleService; 
    438612 
    439613@Path("/example") 
     
    443617    private Logger log = Logger.getLogger(getClass().getName()); 
    444618 
     619    /** 
     620     * Initially change the count value of the unattached 
     621     * <code>TopicModel</code> to zero. 
     622     */ 
    445623    @Override 
    446624    public void preCreateHook(TopicModel model, ClientState clientState) { 
    447         log.info("init example count of " + model.getId()); 
     625        if (model.getTypeUri().equals(ExampleTopic.TYPE)) { 
     626            log.info("init Example count of " 
     627                    + model.getCompositeValue().getString(ExampleTopic.NAME)); 
     628            model.getCompositeValue().put(ExampleTopic.COUNT, 0); 
     629        } 
     630    } 
     631 
     632    /** 
     633     * Creates a new <code>Example</code> instance based on 
     634     * <code>ExampleTopic</code> model. 
     635     */ 
     636    @POST 
     637    @Path("/create") 
     638    @Override 
     639    public Example create(ExampleTopic topic, @HeaderParam("Cookie") ClientState clientState) { 
     640        log.info("create Example " + topic); 
    448641        try { 
    449             model.getCompositeValue().put("dm4.example.count", 0); 
     642            return new Example(topic, dms, clientState); 
    450643        } catch (Exception e) { 
    451             log.warning("something went wrong"); 
    452         } 
    453     } 
    454  
     644            throw new WebApplicationException(new RuntimeException("something went wrong", e)); 
     645        } 
     646    } 
     647 
     648    /** 
     649     * Increase the count of an attached <code>Example</code> topic. 
     650     */ 
    455651    @GET 
    456652    @Path("/increase/{id}") 
    457653    @Override 
    458     public Example increase(@PathParam("id") long id) { 
    459         log.info("increase " + id); 
     654    public Example increase(@PathParam("id") long id, @HeaderParam("Cookie") ClientState clientState) { 
     655        log.info("increase Example " + id); 
    460656        try { 
    461             return new Example(id, dms).increase(); 
     657            return new Example(id, dms, clientState).increase(); 
    462658        } catch (Exception e) { 
    463659            throw new WebApplicationException(new RuntimeException("something went wrong", e)); 
    464660        } 
    465661    } 
    466  
    467 } 
    468 }}} 
     662} 
     663}}} 
     664 
    469665 
    470666=== Provider === #provider 
    471667 
    472668To provide an automatically serialisation of your Java domain model classes, your can implement 
    473 a specific provider or use the JSONEnabled interface. 
    474  
    475 '''TBD''' use of the JSONEnabled interface 
    476  
    477 {{{ 
     669a specific provider or use the JSONEnabled interface. There is no need for proprietary message 
     670body writer providers as long as the object implements JSONEnabled. Every object that is about 
     671to be send over the wire is supposed to do so. A generic reader provider of course is a different 
     672story and does not yet exist. 
     673 
     674{{{#!java 
    478675package de.deepamehta.plugins.example.provider; 
    479676 
     677import java.io.InputStream; 
     678 
    480679import javax.ws.rs.ext.Provider; 
    481680 
    482681import org.codehaus.jettison.json.JSONObject; 
    483682 
    484 import de.deepamehta.plugins.example.AbstractProvider; 
    485 import de.deepamehta.plugins.example.Example; 
    486  
     683import de.deepamehta.core.util.JavaUtils; 
     684import de.deepamehta.plugins.example.AbstractJaxrsReader; 
     685import de.deepamehta.plugins.example.model.ExampleTopic; 
     686 
     687/** 
     688 * Creates a <code>ExampleTopic</code> from JSON. 
     689 *  
     690 * @see ExampleTopic#ExampleTopic(JSONObject) 
     691 */ 
    487692@Provider 
    488 public class ExampleProvider extends AbstractProvider<Example> { 
    489  
    490     public ExampleProvider() { 
    491         super(Example.class); 
     693public class ExampleTopicProvider extends AbstractJaxrsReader<ExampleTopic> { 
     694 
     695    public ExampleTopicProvider() { 
     696        super(ExampleTopic.class); 
    492697    } 
    493698 
    494699    @Override 
    495     protected JSONObject getJsonData(Example example) { 
    496         return example.toJSON(); 
    497     } 
    498 } 
    499 }}} 
     700    protected ExampleTopic createInstance(InputStream entityStream) throws Exception { 
     701        return new ExampleTopic(new JSONObject(JavaUtils.readText(entityStream))); 
     702    } 
     703 
     704} 
     705}}}