The following article assumes the developer knows Java* & Maven. Download the seed project to get started with plugins.
Here is the example plugin. You can download it from github.
-----------------------------------------------------------------------
Plugins allow the developer to write custom business logic for their API using Java. Code Execution capabilities are provided via SDK to pre-process request, post-process sql results or run a business logic.
Because there will be cases where direct sql result to JSON would not be sufficient.
Creating Plugins
The user is required to upload a compressed .zip of a Maven project folder. The zip can be uploaded by click on New Project button in the Plugins section of the console.
Do check, if the zip size is not large, usually plugin project source can be max 1-2 MB. Follow the .gitignore file guidelines for maven project and avoid unnecessary folders.
The project should contain the following
- One or more classes implementing one of the Processable interfaces explained further in this article.
- One or more classes whose object is to be returned in the HTTP response. If custom objects are being returned.
The details of uploaded projects and information of processable classes is shown in the console
Implementation
The following mtg-api dependency must be provided in the pom.xml of the Maven project
<dependency> <groupId>com.metamug</groupId> <artifactId>mtg-api</artifactId> <version>1.5</version> </dependency>
Metamug provides following two Java interfaces
- RequestProcessable
- ResultProcessable
The classes implementing either of these interfaces must be public and will have to Override the process()
method. The code inside the process method gets executed and an Object
is returned. The returned object is converted to XML or JSON as specified by the HTTP request header and is returned in the HTTP response.
Two types of plugins can be created for Metamug
- Request processors - Incoming HTTP requests can be directly processed upon.
- SQL result processors - The result set received by making SQL query to the database can be processed upon.
Request Processable
The RequestProcessable
interface can be implemented to process HTTP request. The process()
method provides access to the request parameters, the DataSource object and the request headers. The class implementing the interface should be public.
public interface RequestProcessable { public Response process(Request request, DataSource ds, Map<String, Object> args); } RequestHandler.java public class RequestHandler implements RequestProcessable { @Override public Response process(Request request, DataSource ds, Map<String, Object> args){ //retrieve request parameters String name = request.getParameter("name"); String email = request.getParameter("email"); //get database connection try(Connection conn = ds.getConnection()){ String insertSQL = "INSERT INTO users(name, email) VALUES (?,?)"; PreparedStatement preparedStatement = conn.prepareStatement(insertTableSQL); preparedStatement.setString(1, name); preparedStatement.setString(2, email); // execute insert SQL stetement preparedStatement .executeUpdate(); } String message = "Insert completed"; Response response = new Response(); response.setPayload(message); return response; } }
The DataSource
object is used to obtain the database connection. The connection is given to the database of the backend in which the Maven project is uploaded. The connection must be closed after the database operation is done.
Execute Tag
The Execute
tag is used for invoking the RequestProcessable classes. The value of classname attribute has to be the full name of a RequestProcessable class (including package name) inside an uploaded maven project. When the HTTP request is made to the resource, the process()
method inside the corresponding class will be executed. The HTTP request parameters will automatically be mapped inside the Map<String,String>
of the process()
method.
<Request method="GET"> <Execute classname="com.mycompany.plugin.RequestHandler" output="true" /> </Request>
Here
output
attribute hints the api to print the response of theExecute
tag.
Result Processable
This interface must be implemented to process SQL results. The process()
method provides resultMap
and columnNames
to iterate through records and look up column names. The rowCount
gives the number of records in the resultMap. This method is provided to override the XML/JSON output for SQL queries. This helps the user to customize the HTTP response body. The Content-Type header in this case will be text/plain and Content-Length will be the length of the output string.
public interface ResultProcessable { public Response process(Result queryResult) throws Exception; }
ResultHandler.java
import com.metamug.entity.Response; import com.metamug.entity.Result; public class ResultHandler implements ResultProcessable { @Override public Response process(Result queryResult) { Map[] records = queryResult.getRecordMap(); String id1 = records[0].get("id").toString(); String name1 = records[0].get("name").toString(); String id2 = records[1].get("id").toString(); String name2 = records[1].get("name").toString(); Response response = new Response(); //do something with the result data return response; } }
The result map obtained inside the process()
method of the ResultProcessable class can be used to obtain values from the database which can be used for further processing.
SQL Post-Processing
The result received by executing the SQL query can be processed upon by the developer using the ResultProcessable interface. The attribute "classname" is available for the <Sql>
element which is used for pointing to the ResultProcessable class. Let us consider the following resource file
... <Request method="GET"> <Sql id="shortner" classname="com.mycompany.plugin.ResultHandler" > SELECT id FROM urls WHERE url=$q </Sql> </Request> ...
In the above case, the given SQL will be executed when a GET request is made on the urls resource. The result obtained from the database on the execution of the SQL query will be handled by the com.mycompany.plugin.ResultHandler class (included in an uploaded maven project) which implements the ResultProcessable interface.
* Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners.
Execute Output Processing
The process()
method has return type as Response
. Response payload is designed to handle following 3 types.
- String
- Pojo with Annotations
- Inputstream or ByteBuffer
String
A simple Java string returned by the process()
method can be obtained directly as it is in the HTTP response.
public Response process(Result sqlResult) { String message = "Hello World"; Response response = new Response(); response.setPayload(message); return response; }
POJO with annotation
The class of the object returned by the process()
method should be annotated with JAXB annotations. At a minimum, the class has to be annotated with @XmlRootElement
to automatically produce XML/JSON response depending on the Accept Header.
@XmlRootElement public class Customer { String name; //... }
This is the minimal configuration required to configure serialization of the object to json/xml. You can find the whole list of annotations on oracle's doc page.
Take a look at this example for more on returning objects.
InputStream
InputStream object is set as the payload of the Response
. This is useful for downloading a file. Inside the process
method, file can be accessed from the desired location and converted to InputStream object.
Alternatively, in certain cases we may prefer to peform some operations on the file input stream before we send it for download. In such a case, ByteBuffer can be returned after operating on the byte array obtained from the inputstream.
Event Handlers
Event Handlers are invoked implicitly. They need not be declared from resource xml. However, plugins must include implementation of these handlers.
Upload Event
A request can be directed to the POST method of a request. As Upload event handler should be executed before the request is forwarded to the resource. So the handler can pass the Response
to the resource xml.
POST / HTTP/1.1 [[ Less interesting headers ... ]] Content-Type: multipart/form-data; boundary=---------------------------735323031399963166993862150 Content-Length: 834 -----------------------------735323031399963166993862150 Content-Disposition: form-data; name="jsonObj" Some json here in the request -----------------------------735323031399963166993862150 Content-Disposition: form-data; name="file"; filename="E-com site requirement.PNG" Content-Type: text/plain Content of E-com site requirement.PNG -----------------------------735323031399963166993862150--
When Upload is performed, the class implemented with the following interface is invoked. The uploaded file can be accessed from the event object also you will need to keep the param of the file object as file
.
public class FileUpload implements UploadListener { @Override public Response uploadPerformed(UploadEvent event, DataSource dataSource) { File uploadedFile = event.getUploadedFile(); Response response = new Response(uploadedFile); return response; } }
Here a user must return a Response
Object which can be made available to the resource. If the resource is authenticated then it is mandatory to pass the authorization token else the upload will not work.
The upload Listener is saved with implicit id _upload
. It can be accessed in the resource using the MPath $[_upload]
To access the uploaded file, we will write another execute tag as follows in the same POST request.
<Request method="POST"> <Param name="file" type="text" minlength="2" required="true" /> <Execute classname="com.mycompany.plugin.FileHandler" id="uploadProc" output="true" > <Arg name="uploadedFile" value="$[_upload]" /> </Execute> </Request>
import com.metamug.entity.Request; import com.metamug.entity.Response; import com.metamug.exec.RequestProcessable; import java.io.File; import java.util.Map; import javax.sql.DataSource; /** * * @author pc */ public class FileHandler implements RequestProcessable { @Override public Response process(Request request, DataSource ds, Map<String, Object> args) throws Exception { File file = args.get("uploadedFile"); //@TODO do some operations on the file Response response = new Response("Successfully registered."); return response; } }
UploadEvent contains Request, and the uploaded file.
By default, file upload is disabled in console. To enable file upload, the default file uploader plugin must be uploaded in plugins.
Update the plugin to the latest dependency. You can download the default File Uploader plugin from here.