OrientDB Plugins

The OrientDB Server is a customizable platform to build powerful server component and applications.

Since the OrientDB server contains an integrated Web Server what about creating server side applications without the need to have a J2EE and Servlet container? By extending the server you can benefit of the best performance because you don’t have many layers but the database and the application reside on the same JVM without the cost of the network and serialization of requests.

Furthermore you can package your application together with the OrientDB server to distribute just a ZIP file containing the entire Application, Web server and Database.

To customize the OrientDB server you have two powerful tools:

To debug the server while you develop new feature follow Debug the server.

Handlers (Server Plugins)

Handlers are plug-ins and starts when OrientDB starts.

To create a new handler create the class and register it in the OrientDB server configuration.

Create the Handler class

A Handler must implements the OServerPlugin interface or extends the OServerPluginAbstract abstract class.

Below an example of a handler that print every 5 seconds a message if the “log” parameters has been configured to be “true”:

  1. package orientdb.test;
  2. public class PrinterHandler extends OServerPluginAbstract {
  3. private boolean log = false;
  4. @Override
  5. public void config(OServer oServer, OServerParameterConfiguration[] iParams) {
  6. for (OServerParameterConfiguration p : iParams) {
  7. if (p.name.equalsIgnoreCase("log"))
  8. log = true;
  9. }
  10. Orient.getTimer().schedule( new TimerTask() {
  11. @Override
  12. public void run() {
  13. if( log )
  14. System.out.println("It's the PrinterHandler!");
  15. }
  16. }, 5000, 5000);
  17. }
  18. @Override
  19. public String getName() {
  20. return "PrinterHandler";
  21. }
  22. }

Register the handler

Once created, register it to the server configuration in orientdb-server-config.xml file:

  1. <orient-server>
  2. <handlers>
  3. <handler class="orientdb.test.PrinterHandler">
  4. <parameters>
  5. <parameter name="log" value="true"/>
  6. </parameters>
  7. </handler>
  8. </handlers>
  9. ...

Note that you can specify arbitrary parameters in form of name and value. Those parameters can be read by the config() method. In this example a parameter “log” is read. Look upon to the example of handler to know how to read parameters specified in configuration.

Steps to register a function as a Plugin in OrientDB

In this case we’ll create a plugin that only registers one function in OrientDB: pow (returns the value of the first argument raised to the power of the second argument). We’ll also support Modular exponentiation.

The syntax will be pow(<base>, <power> [, <mod>]).

  • you should have a directory structure like this
    1. .
    2. ├─ src
    3. | └─ main
    4. | ├─ assembly
    5. | | └─ assembly.xml
    6. | ├─ java
    7. | | └─ com
    8. | | └─ app
    9. | | └─ OPowPlugin.java
    10. | └─ resources
    11. | └─ plugin.json
    12. |
    13. └─ pom.xml
OPowPlugin.java
  1. package com.app;
  2. import com.orientechnologies.common.log.OLogManager;
  3. import com.orientechnologies.orient.core.command.OCommandContext;
  4. import com.orientechnologies.orient.core.db.record.OIdentifiable;
  5. import com.orientechnologies.orient.core.sql.OSQLEngine;
  6. import com.orientechnologies.orient.core.sql.functions.OSQLFunctionAbstract;
  7. import com.orientechnologies.orient.server.OServer;
  8. import com.orientechnologies.orient.server.config.OServerParameterConfiguration;
  9. import com.orientechnologies.orient.server.plugin.OServerPluginAbstract;
  10. import java.util.ArrayList;
  11. import java.util.List;
  12. public class OPowPlugin extends OServerPluginAbstract {
  13. public OPowPlugin() {
  14. }
  15. @Override
  16. public String getName() {
  17. return "pow-plugin";
  18. }
  19. @Override
  20. public void startup() {
  21. super.startup();
  22. OSQLEngine.getInstance().registerFunction("pow", new OSQLFunctionAbstract("pow", 2, 3) {
  23. @Override
  24. public String getSyntax() {
  25. return "pow(<base>, <power> [, <mod>])";
  26. }
  27. @Override
  28. public Object execute(Object iThis, OIdentifiable iCurrentRecord, Object iCurrentResult, final Object[] iParams, OCommandContext iContext) {
  29. if (iParams[0] == null || iParams[1] == null) {
  30. return null;
  31. }
  32. if (!(iParams[0] instanceof Number) || !(iParams[1] instanceof Number)) {
  33. return null;
  34. }
  35. final long base = ((Number) iParams[0]).longValue();
  36. final long power = ((Number) iParams[1]).longValue();
  37. if (iParams.length == 3) { // modular exponentiation
  38. if (iParams[2] == null) {
  39. return null;
  40. }
  41. if (!(iParams[2] instanceof Number)) {
  42. return null;
  43. }
  44. final long mod = ((Number) iParams[2]).longValue();
  45. if (power < 0) {
  46. OLogManager.instance().warn(this, "negative numbers as exponent are not supported");
  47. }
  48. return modPow(base, power, mod);
  49. }
  50. return power > 0 ? pow(base, power) : 1D / pow(base, -power);
  51. }
  52. });
  53. OLogManager.instance().info(this, "pow function registered");
  54. }
  55. private double pow(long base, long power) {
  56. double r = 1;
  57. List<Boolean> bits = bits(power);
  58. for (int i = bits.size() - 1; i >= 0; i--) {
  59. r *= r;
  60. if (bits.get(i)) {
  61. r *= base;
  62. }
  63. }
  64. return r;
  65. }
  66. private double modPow(long base, long power, long mod) {
  67. double r = 1;
  68. List<Boolean> bits = bits(power);
  69. for (int i = bits.size() - 1; i >= 0; i--) {
  70. r = (r * r) % mod;
  71. if (bits.get(i)) {
  72. r = (r * base) % mod;
  73. }
  74. }
  75. return r;
  76. }
  77. private List<Boolean> bits(long n) {
  78. List<Boolean> bits = new ArrayList();
  79. while (n > 0) {
  80. bits.add(n % 2 == 1);
  81. n /= 2;
  82. }
  83. return bits;
  84. }
  85. @Override
  86. public void config(OServer oServer, OServerParameterConfiguration[] iParams) {
  87. }
  88. @Override
  89. public void shutdown() {
  90. super.shutdown();
  91. }
  92. }
pom.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <groupId>com.app</groupId>
  6. <artifactId>pow-plugin</artifactId>
  7. <version>2.0.7</version>
  8. <packaging>jar</packaging>
  9. <name>pow-plugin</name>
  10. <properties>
  11. <orientdb.version>2.0.7</orientdb.version>
  12. </properties>
  13. <build>
  14. <plugins>
  15. <plugin>
  16. <artifactId>maven-assembly-plugin</artifactId>
  17. <version>2.4</version>
  18. <configuration>
  19. <descriptors>
  20. <descriptor>src/main/assembly/assembly.xml</descriptor>
  21. </descriptors>
  22. </configuration>
  23. <executions>
  24. <execution>
  25. <id>make-assembly</id>
  26. <!-- this is used for inheritance merges -->
  27. <phase>package</phase>
  28. <!-- bind to the packaging phase -->
  29. <goals>
  30. <goal>single</goal>
  31. </goals>
  32. <configuration></configuration>
  33. </execution>
  34. </executions>
  35. </plugin>
  36. <plugin>
  37. <groupId>org.apache.maven.plugins</groupId>
  38. <artifactId>maven-compiler-plugin</artifactId>
  39. <version>3.1</version>
  40. <configuration>
  41. </configuration>
  42. </plugin>
  43. </plugins>
  44. </build>
  45. <dependencies>
  46. <dependency>
  47. <groupId>com.orientechnologies</groupId>
  48. <artifactId>orientdb-core</artifactId>
  49. <version>${orientdb.version}</version>
  50. <scope>compile</scope>
  51. </dependency>
  52. <dependency>
  53. <groupId>com.orientechnologies</groupId>
  54. <artifactId>orientdb-server</artifactId>
  55. <version>${orientdb.version}</version>
  56. <scope>compile</scope>
  57. </dependency>
  58. </dependencies>
  59. </project>
assembly.xml
  1. <assembly>
  2. <id>dist</id>
  3. <formats>
  4. <format>jar</format>
  5. </formats>
  6. <includeBaseDirectory>false</includeBaseDirectory>
  7. <dependencySets>
  8. <dependencySet>
  9. <outputDirectory/>
  10. <unpack>true</unpack>
  11. <includes>
  12. <include>${groupId}:${artifactId}</include>
  13. </includes>
  14. </dependencySet>
  15. </dependencySets>
  16. </assembly>
plugin.json
  1. {
  2. "name" : "pow-plugin",
  3. "version" : "2.0.7",
  4. "javaClass": "com.app.OPowPlugin",
  5. "parameters" : {},
  6. "description" : "The Pow Plugin",
  7. "copyrights" : "No copyrights"
  8. }
  • Build the project and then:
  1. cp target/pow-plugin-2.0.7-dist.jar $ORIENTDB_HOME/plugins/

You should see the following in OrientDB server log:

  1. INFO Installing dynamic plugin 'pow-plugin-2.0.7-dist.jar'... [OServerPluginManager]
  2. INFO pow function registered [OPowPlugin]

And now you can:

  1. orientdb {db=Pow}> select pow(2,10)
  2. ----+------+------
  3. # |@CLASS|pow
  4. ----+------+------
  5. 0 |null |1024.0
  6. ----+------+------
  7. orientdb {db=Pow}> select pow(2,10,5)
  8. ----+------+----
  9. # |@CLASS|pow
  10. ----+------+----
  11. 0 |null |4.0
  12. ----+------+----

This small project is available here.

Creating a distributed change manager

As more complete example let’s create a distributed record manager by installing hooks to all the server’s databases and push these changes to the remote client caches.

  1. public class DistributedRecordHook extends OServerHandlerAbstract implements ORecordHook {
  2. private boolean log = false;
  3. @Override
  4. public void config(OServer oServer, OServerParameterConfiguration[] iParams) {
  5. for (OServerParameterConfiguration p : iParams) {
  6. if (p.name.equalsIgnoreCase("log"))
  7. log = true;
  8. }
  9. }
  10. @Override
  11. public void onAfterClientRequest(final OClientConnection iConnection, final byte iRequestType) {
  12. if (iRequestType == OChannelBinaryProtocol.REQUEST_DB_OPEN)
  13. iConnection.database.registerHook(this);
  14. else if (iRequestType == OChannelBinaryProtocol.REQUEST_DB_CLOSE)
  15. iConnection.database.unregisterHook(this);
  16. }
  17. @Override
  18. public boolean onTrigger(TYPE iType, ORecord<?> iRecord) {
  19. try {
  20. if (log)
  21. System.out.println("Broadcasting record: " + iRecord + "...");
  22. OClientConnectionManager.instance().broadcastRecord2Clients((ORecordInternal<?>) iRecord, null);
  23. } catch (Exception e) {
  24. e.printStackTrace();
  25. }
  26. return false;
  27. }
  28. @Override
  29. public String getName() {
  30. return "DistributedRecordHook";
  31. }
  32. }

Custom commands

Custom commands are useful when you want to add behavior or business logic at the server side.

A Server command is a class that implements the OServerCommand interface or extends one of the following abstract classes:

The Hello World Web

To learn how to create a custom command, let’s begin with a command that just returns “Hello world!”.

OrientDB follows the convention that the command name is:

OServerCommand<method><name> Where:

  • method is the HTTP method and can be: GET, POST, PUT, DELETE
  • name is the command name

In our case the class name will be “OServerCommandGetHello”. We want that the use must be authenticated against the database to execute it as any user.

Furthermore we’d like to receive via configuration if we must display the text in Italic or not, so for this purpose we’ll declare a parameter named “italic” of type boolean (true or false).

  1. package org.example;
  2. public class OServerCommandGetHello extends OServerCommandAuthenticatedDbAbstract {
  3. // DECLARE THE PARAMETERS
  4. private boolean italic = false;
  5. public OServerCommandGetHello(final OServerCommandConfiguration iConfiguration) {
  6. // PARSE PARAMETERS ON STARTUP
  7. for (OServerEntryConfiguration par : iConfiguration.parameters) {
  8. if (par.name.equals("italic")) {
  9. italic = Boolean.parseBoolean(par.value);
  10. }
  11. }
  12. }
  13. @Override
  14. public boolean execute(final OHttpRequest iRequest, OHttpResponse iResponse) throws Exception {
  15. // CHECK THE SYNTAX. 3 IS THE NUMBER OF MANDATORY PARAMETERS
  16. String[] urlParts = checkSyntax(iRequest.url, 3, "Syntax error: hello/<database>/<name>");
  17. // TELLS TO THE SERVER WHAT I'M DOING (IT'S FOR THE PROFILER)
  18. iRequest.data.commandInfo = "Salutation";
  19. iRequest.data.commandDetail = "This is just a test";
  20. // GET THE PARAMETERS
  21. String name = urlParts[2];
  22. // CREATE THE RESULT
  23. String result = "Hello " + name;
  24. if (italic) {
  25. result = "<i>" + result + "</i>";
  26. }
  27. // SEND BACK THE RESPONSE AS TEXT
  28. iResponse.send(OHttpUtils.STATUS_OK_CODE, "OK", null, OHttpUtils.CONTENT_TEXT_PLAIN, result);
  29. // RETURN ALWAYS FALSE, UNLESS YOU WANT TO EXECUTE COMMANDS IN CHAIN
  30. return false;
  31. }
  32. @Override
  33. public String[] getNames() {
  34. return new String[]{"GET|hello/* POST|hello/*"};
  35. }
  36. }

Once created the command you need to register them through the orientdb-server-config.xml file. Put a new tag <command> under the tag commands of <listener> with attribute protocol=”http”:

  1. ...
  2. <listener protocol="http" port-range="2480-2490" ip-address="0.0.0.0">
  3. <commands>
  4. <command implementation="org.example.OServerCommandGetHello" pattern="GET|hello/*">
  5. <parameters>
  6. <entry name="italic" value="true"/>
  7. </parameters>
  8. </command>
  9. </commands>
  10. </listener>

Where:

  • implementation is the full class name of the command
  • pattern is how the command is called in the format: <HTTP-method>|<name>. In this case it’s executed on HTTP GET with the URL: /<name>
  • parameters specify parameters to pass to the command on startup
  • entry is the parameter pair name/value

To test it open a browser at this address:

  1. http://localhost/hello/demo/Luca

You will see:

  1. Hello Luca

Complete example

Below a more complex example taken by official distribution. It is the command that executes queries via HTTP. Note how to get a database instance to execute operation against the database:

  1. public class OServerCommandGetQuery extends OServerCommandAuthenticatedDbAbstract {
  2. private static final String[] NAMES = { "GET|query/*" };
  3. @Override
  4. public boolean execute(OHttpRequest iRequest, OHttpResponse iResponse) throws Exception {
  5. String[] urlParts = checkSyntax(
  6. iRequest.url,
  7. 4,
  8. "Syntax error: query/<database>/sql/<query-text>[/<limit>][/<fetchPlan>].<br/>Limit is optional and is setted to 20 by default. Set expressely to 0 to have no limits.");
  9. int limit = urlParts.length > 4 ? Integer.parseInt(urlParts[4]) : 20;
  10. String fetchPlan = urlParts.length > 5 ? urlParts[5] : null;
  11. String text = urlParts[3];
  12. iRequest.data.commandInfo = "Query";
  13. iRequest.data.commandDetail = text;
  14. ODatabaseDocumentTx db = null;
  15. List<OIdentifiable> response;
  16. try {
  17. db = getProfiledDatabaseInstance(iRequest);
  18. response = (List<OIdentifiable>) db.command(new OSQLSynchQuery<OIdentifiable>(text, limit).setFetchPlan(fetchPlan)).execute();
  19. } finally {
  20. if (db != null) {
  21. db.close();
  22. }
  23. }
  24. iResponse.writeRecords(response, fetchPlan);
  25. return false;
  26. }
  27. @Override
  28. public String[] getNames() {
  29. return NAMES;
  30. }
  31. }

Include JARS in the classpath

If your extensions need additional libraries put the additional jar files under the /lib folder of the server installation.

Debug the server

To debug your plugin you can start your server in debug mode.

Parameter Value
Main class com.orientechnologies.orient.server.OServerMain
JVM parameters -server -DORIENTDB_HOME=/opt/orientdb -Dorientdb.www.path=src/site -Djava.util.logging.config.file=${ORIENTDB_HOME}/config/orientdb-server-log.properties -Dorientdb.config.file=${ORIENTDB_HOME}/config/orientdb-server-config.xml