Changes between Version 1 and Version 2 of AnotherPluginDevelopmentGuide
- Timestamp:
- 30.06.2012 02:39:00 (12 years ago)
Legend:
- Unmodified
- Added
- Removed
- Modified
-
AnotherPluginDevelopmentGuide
v1 v2 40 40 project [dm4-example https://github.com/dgf/dm4-example]. 41 41 42 ||= '''Directories and files''' =||= '''Explanation''' =|| 43 |--------------------------------------------- 42 44 || dm4-example/ || An example plugin project directory. || 43 45 || . [#pom pom.xml] || The POM of your plugin project (mandatory). || 44 46 || . README.md || A project description. || 45 || . src/ ||||46 || . . main/ ||||47 |||| . src/ || 48 |||| . . main/ || 47 49 || . . . [#java java]/ || Server-side Java code goes into src/main/java. || 48 50 || . . . . de/ || de/deepamehta/plugins/example is the root package of your Java classes. || 49 51 || . . . . . deepamehta/ || The package name is under your choice. || 50 || . . . . . . plugins/ ||||51 || . . . . . . . example/ ||||52 |||| . . . . . . plugins/ || 53 |||| . . . . . . . example/ || 52 54 || . . . . . . . . [#migration migrations]/ || Migrations go into this directory and have a fixed name. || 53 55 || . . . . . . . . . Migration2.java || This programmatic migration 2 is called after the declarative 1. || 54 56 || . . . . . . . . [#provider provider/] || REST specific provider that supports the conversion between a Java type and a stream. || 55 || . . . . . . . . . !ExampleProvider.java ||||57 |||| . . . . . . . . . !ExampleProvider.java || 56 58 || . . . . . . . . [#plugin ExamplePlugin.java] || The plugin's "main" class. || 57 59 || . . resources/ || Additional (non-Java) files. || … … 65 67 || . . . . . [#pluginjs plugin.js] || The JavaScipt plugin "main" file. || 66 68 || . . . . style/ || Another custom folder to separate the styles. || 67 || . . . . . screen.css |||| 68 69 |||| . . . . . screen.css || 69 70 70 71 = Setting up the development environment = … … 78 79 Inside the plugin project directory: 79 80 80 {{{$ mvn package}}} 81 {{{#!sh 82 $ mvn package 83 }}} 81 84 82 85 This builds the jar file, an OSGi bundle. The jar is placed in the directory target (next to src). 83 86 87 ||= '''Directories and files''' =||= '''Explanation''' =|| 88 |--------------------------------------------- 84 89 || dm4-plugin-example/ || Plugin project directory. || 85 || src/ ||||90 |||| src/ || 86 91 || target/ || Contains artifacts build by Maven. || 87 92 || . dm4-plugin-example-0.0.1-SNAPSHOT.jar || The jar ready for deployment in OSGi console. || … … 98 103 take place and [BuildFromSource build] DeepaMehta from source. Then adapt the 99 104 'felix.fileinstall.dir' configuration of the DeepaMehta global POM run profile to your needs: 100 {{{ 105 {{{#!xml 101 106 <!-- ... --> 102 107 <profiles> … … 115 120 Start DeepaMehta with the Maven run profile and go on with your development 116 121 117 {{{$ mvn pax:run}}} 122 {{{#!sh 123 $ mvn pax:run 124 }}} 125 126 127 == Use of an in-memory database == 128 129 Developing an topic importer or some other heavy data manipulations can stress you (on a HDD) or 130 the lifetime of your static memory (in case of a SSD). On Linux systems it is very simple to move 131 the DeepaMehta database into a memory file system with a mount like the following: 132 133 Copy 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 118 141 119 142 = pom.xml = #pom … … 127 150 128 151 Plugin POM example: 129 {{{ 152 {{{#!xml 130 153 <?xml version="1.0" encoding="UTF-8"?> 131 154 <project xmlns="http://maven.apache.org/POM/4.0.0" … … 188 211 Just write the declarations in the corresponding property of your migration. 189 212 213 '''TBD''' describe and exemplify migration*.properties file usage 214 190 215 Declarative plugin migration example: 191 {{{ 216 {{{#!js 192 217 { 193 218 topic_types: [ # -------------------------------- create types … … 236 261 237 262 Programmatic plugin migration example: 238 {{{ 263 {{{#!java 239 264 package de.deepamehta.plugins.example.migrations; 240 265 241 import de.deepamehta.core.model.TopicModel;242 266 import de.deepamehta.core.service.Migration; 243 267 244 268 /** 245 * update search icon back to the good old bucket269 * Update search icon back to the good old bucket. 246 270 */ 247 271 public class Migration2 extends Migration { … … 249 273 @Override 250 274 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 } 261 281 } 262 282 }}} … … 277 297 278 298 # Name of the service interface that the plugin exports and provides. 279 providedServiceInterface = de.deepamehta.plugins.example. ExampleService299 providedServiceInterface = de.deepamehta.plugins.example.service.ExampleService 280 300 281 301 # The number of the migration the plugin requires to run. … … 302 322 303 323 A plugin that loads some type specific renderer and adds a custom command: 304 {{{ 324 {{{#!js 305 325 dm4c.add_plugin('de.deepamehta.example', function() { 306 326 327 // load some renderer and a style sheet 328 dm4c.load_field_renderer('/de.deepamehta.dm4-example/script/example_content_field_renderer.js') 307 329 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')309 330 dm4c.load_stylesheet('/de.deepamehta.dm4-example/style/screen.css') 310 331 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 311 341 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 323 349 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 }) 325 360 }) 326 361 }) … … 331 366 332 367 Declarative renderer assignment: 333 {{{ 368 {{{#!js 334 369 { 335 370 value: "Example Content", … … 355 390 creates a fresh object for each composite part, so please use the prototype approach. 356 391 357 {{{ 392 {{{#!js 358 393 // a simple field renderer that renders an example topic name with an additional CSS class 359 394 function ExampleContentFieldRenderer(field_model) { … … 375 410 }}} 376 411 377 }}}378 379 412 380 413 === !PageRenderer === … … 382 415 The page renderer can replace the whole page content. 383 416 384 {{{ 417 {{{#!js 385 418 // a page render that simply renders the value of an example content topic instance 386 419 function ExampleContentPageRenderer() { … … 416 449 '''TBD''' use some other JVM languages like Scala and Clojure 417 450 451 452 == Model == 453 454 Writing the business logic close to the domain jargon is desirable and can be supported with 455 a simple wrap of the underlying topic model. 456 457 A topic model wrapper: 458 {{{#!java 459 package de.deepamehta.plugins.example.model; 460 461 import org.codehaus.jettison.json.JSONException; 462 import org.codehaus.jettison.json.JSONObject; 463 464 import de.deepamehta.core.model.CompositeValue; 465 import de.deepamehta.core.model.TopicModel; 466 467 public 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 485 Domain model class example: 486 {{{#!java 487 package de.deepamehta.plugins.example.model; 488 489 import static de.deepamehta.plugins.example.model.ExampleTopic.*; 490 491 import java.util.logging.Logger; 492 493 import org.codehaus.jettison.json.JSONObject; 494 495 import de.deepamehta.core.DeepaMehtaTransaction; 496 import de.deepamehta.core.JSONEnabled; 497 import de.deepamehta.core.Topic; 498 import de.deepamehta.core.model.SimpleValue; 499 import de.deepamehta.core.model.TopicModel; 500 import de.deepamehta.core.service.ClientState; 501 import de.deepamehta.core.service.DeepaMehtaService; 502 503 /** 504 * A domain model class that wraps the underlying <code>Topic</code>. 505 */ 506 public 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 418 573 == Plugin == #plugin 419 574 420 A Java plugin can interact with predefined hook overwrites and provide some REST resources. 575 A Java plugin can interact with predefined hook overwrites and export a OSGi service 576 with optional REST exposure. 577 578 All public methods of the plugin must be described in a service interface: 579 {{{#!java 580 package de.deepamehta.plugins.example.service; 581 582 import de.deepamehta.core.service.ClientState; 583 import de.deepamehta.core.service.PluginService; 584 import de.deepamehta.plugins.example.model.Example; 585 import de.deepamehta.plugins.example.model.ExampleTopic; 586 587 public interface ExampleService extends PluginService { 588 589 Example create(ExampleTopic topic, ClientState clientState); 590 591 Example increase(long id, ClientState clientState); 592 593 } 594 }}} 421 595 422 596 '''TBD''' rewrite hook calls in favour of listener interface per event 423 597 424 {{{ 598 The plugin itself can expose methods with 599 [http://jersey.java.net/nonav/documentation/latest/jax-rs.html JAX-RS] annotations: 600 {{{#!java 425 601 package de.deepamehta.plugins.example; 426 602 427 603 import 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; 604 import javax.ws.rs.*; 434 605 435 606 import de.deepamehta.core.model.TopicModel; 436 607 import de.deepamehta.core.service.ClientState; 437 608 import de.deepamehta.core.service.Plugin; 609 import de.deepamehta.plugins.example.model.Example; 610 import de.deepamehta.plugins.example.model.ExampleTopic; 611 import de.deepamehta.plugins.example.service.ExampleService; 438 612 439 613 @Path("/example") … … 443 617 private Logger log = Logger.getLogger(getClass().getName()); 444 618 619 /** 620 * Initially change the count value of the unattached 621 * <code>TopicModel</code> to zero. 622 */ 445 623 @Override 446 624 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); 448 641 try { 449 model.getCompositeValue().put("dm4.example.count", 0);642 return new Example(topic, dms, clientState); 450 643 } 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 */ 455 651 @GET 456 652 @Path("/increase/{id}") 457 653 @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); 460 656 try { 461 return new Example(id, dms ).increase();657 return new Example(id, dms, clientState).increase(); 462 658 } catch (Exception e) { 463 659 throw new WebApplicationException(new RuntimeException("something went wrong", e)); 464 660 } 465 661 } 466 467 } 468 }}} 662 } 663 }}} 664 469 665 470 666 === Provider === #provider 471 667 472 668 To 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 {{{ 669 a specific provider or use the JSONEnabled interface. There is no need for proprietary message 670 body writer providers as long as the object implements JSONEnabled. Every object that is about 671 to be send over the wire is supposed to do so. A generic reader provider of course is a different 672 story and does not yet exist. 673 674 {{{#!java 478 675 package de.deepamehta.plugins.example.provider; 479 676 677 import java.io.InputStream; 678 480 679 import javax.ws.rs.ext.Provider; 481 680 482 681 import org.codehaus.jettison.json.JSONObject; 483 682 484 import de.deepamehta.plugins.example.AbstractProvider; 485 import de.deepamehta.plugins.example.Example; 486 683 import de.deepamehta.core.util.JavaUtils; 684 import de.deepamehta.plugins.example.AbstractJaxrsReader; 685 import de.deepamehta.plugins.example.model.ExampleTopic; 686 687 /** 688 * Creates a <code>ExampleTopic</code> from JSON. 689 * 690 * @see ExampleTopic#ExampleTopic(JSONObject) 691 */ 487 692 @Provider 488 public class Example Provider extends AbstractProvider<Example> {489 490 public Example Provider() {491 super(Example .class);693 public class ExampleTopicProvider extends AbstractJaxrsReader<ExampleTopic> { 694 695 public ExampleTopicProvider() { 696 super(ExampleTopic.class); 492 697 } 493 698 494 699 @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 }}}