2016/03/21
21 Mar, 2016

[Tutorial] How To Extend Default Subscription Model in WSO2 App Manager - Part 1

  • Sajith Abeywardhana
  • Software Engineer - WSO2
Archived Content
This article is provided for historical perspective only, and may not reflect current conditions. Please refer to relevant product page for more up-to-date product information and resources.

Table of contents


Applies to

WSO2 App Manager Version 1.1.0

Introduction to WSO2 App Manager

WSO2 App Manager is a solution that provides app governance in an organization. Organizations that need to provide centralized access to multiple Web and mobile applications can use WSO2 App Manager to manage user access for all day-to-day Web and mobile apps in a single location. Developers and publishers can publish non-secured apps in WSO2 App Manager and the app manager will take care of aspects like security, throttling, statistics, etc. The end user can see the published apps in the centralized store and subscribe to them. Businesses can leverage its single-sign-on (SSO) functionality, which can reduce help desk and administrative costs with no long lists of passwords to memorize. WSO2 APP Manager comprises the following key components:

App Publisher - Enables the end-user to easily publish their apps, share documentation, and gather feedback on the quality and usage of apps.

App Store - Enables the end-user to easily access web applications and mobile application to self-register, discover apps, subscribe to apps, and evaluate them.


Introduction to user subscription model

WSO2 App Manager allows you to subscribe to an app using the app store. In addition to being able to use it, you can customize the WSO2 App Manager default subscription model by implementing the custom subscription model. In this section, we will explain how you can implement the subscription model to support the expiring subscription model by using custom implementation. WSO2 App Manager allows you to write your own business logic by extending org.wso2.carbon.appmgt.impl.workflow.SubscriptionCreationSimpleWorkflowExecutor class to achieve this objective.


Understanding the subscription workflow

  1. App publisher publishes an app to the store.
  2. The user will login to the app store and subscribe to the app using app store.
    • When the user hits the subscribe button on a particular app, the subscription workflow will be executed
    • You can configure any subscription workflow to execute by using the subscription extension point

Figure 1 shows how these components/models are connected to each other in the WSO2 App Manager.

Figure 1


Extending the subscription workflow to support subscription expiration

Overview

We are now going to support the expiring subscription model. In this first part we will discuss how you can implement a custom logic to persist the subscription details with a custom table; the second part will focus on how to retrieve subscription details by implementing an API.

First we need to have an RDBMS table to store the user subscription details. We will write our own business logic in custom executor class and then configure app manager to execute our custom class by using a user subscription extension point. When the user clicks on the app store subscription button on a particular app our custom logic will be executed and the subscription details will be saved in the RDBMS table.


Create a table to store the user subscription details

Since we are going to support the expiring subscription model, we need to create an RDBMS table with details like app-id, user-id, subscription-date, evaluation-period, etc.

Create the table in APPM_DB database by using the below script:

CREATE TABLE IF NOT EXISTS APM_SUBSCRIPTION_EXT (
    SUBSCRIPTION_ID INTEGER AUTO_INCREMENT,
    APP_ID INTEGER,
    SUBSCRIBER_ID INTEGER,
    SUBSCRIPTION_TYPE VARCHAR(50),
    SUBSCRIPTION_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    EVALUATION_PERIOD INTEGER,
    EXPIRED_ON TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    IS_PAID BOOLEAN DEFAULT FALSE,
    FOREIGN KEY(APP_ID) REFERENCES APM_APP(APP_ID) ON UPDATE CASCADE ON DELETE RESTRICT,
    FOREIGN KEY(SUBSCRIBER_ID) REFERENCES APM_SUBSCRIBER(SUBSCRIBER_ID) ON UPDATE CASCADE ON DELETE RESTRICT,
    PRIMARY KEY (SUBSCRIPTION_ID),
    UNIQUE(APP_ID, SUBSCRIBER_ID)
)ENGINE INNODB;
  • SUBSCRIPTION_ID: This is the primary key and it will be automatically incremented.
  • APP_ID: ID of the app which is the user subscribing. This should be foreign key to the APM_APP table.
  • SUBSCRIBER_ID: User id of the user. This should be foreign key to the APM_SUBSCRIBER table.
  • SUBSCRIPTION_TYPE: We can define type of the subscription as a flag.
  • SUBSCRIPTION_TIME: Date time of the subscription.
  • EVALUATION_PERIOD: Application evaluation period. This should be integer value and it should be defined as hours.
  • EXPIRED_ON: Subscription expire date and time. This value is equal to SUBSCRIPTION_TIME + EVALUATION_PERIOD.
  • IS_PAID: true/false value to update the payment details.

Create the new maven project using your favorite IDE

Enter the groupId, artifactId and version as shown below and create the maven project.

<groupId>org.wso2.carbon.appmgt</groupId>
<artifactId>org.wso2.carbon.appmgt.expiring.subscription</artifactId>
<version>1.0.0-SNAPSHOT</version>

Now you can edit the parent pom to add the required dependencies to our project. Add the blow dependencies

<dependencies>
  <dependency>
     <groupId>org.wso2.carbon.appmgt</groupId>
     <artifactId>org.wso2.carbon.appmgt.api</artifactId>
     <version>1.0.1</version>
  </dependency>
  <dependency>
     <groupId>org.wso2.carbon.appmgt</groupId>
     <artifactId>org.wso2.carbon.appmgt.impl</artifactId>
     <version>1.0.1</version>
  </dependency>
  <dependency>
     <groupId>org.wso2.carbon.appmgt</groupId>
     <artifactId>org.wso2.carbon.appmgt.hostobjects</artifactId>
     <version>1.0.1</version>
  </dependency>
</dependencies>

Finally the pom file should be like what’s illustrated in Figure 2.

Figure 2


Develop the DTO class to hold the subscription details

Now we can start developing custom logic. First we are going to develop the DTO class to hold the user subscription details. This class is POJO class and the class attributes should represent the table structure as well. Since this class is a subscription workflow custom class, we can extend this class by using SubscriptionWorkflowDTO class which defined in a org.wso2.carbon.appmgt.impl.dto package.

  • Create a new package named org.wso2.carbon.appmgt.expiring.subscription
  • Then create a new package called impl.dto under that package
  • Now create a new Java class called SubscriptionExpiryDTO in org.wso2.carbon.appmgt.expiring.subscription.impl.dto package by extending the SubscriptionWorkflowDTO class
  • Your DTO class should be something like what’s shown below and you can copy and paste the below class definition
package org.wso2.carbon.appmgt.expiring.subscription.impl.dto;

import org.wso2.carbon.appmgt.impl.dto.SubscriptionWorkflowDTO;
import java.util.Date;

public class SubscriptionExpiryDTO extends SubscriptionWorkflowDTO {

   private Date subscriptionTime;

   // Evaluation period must be provided as hours.
   private int evaluationPeriod;

   private Date expireOn;

   private int appId;

   private int subscriberId;

   public Date getSubscriptionTime() {
       return subscriptionTime;
   }

   public void setSubscriptionTime(Date subscriptionTime) {
       this.subscriptionTime = subscriptionTime;
   }

   public int getEvaluationPeriod() {
       return evaluationPeriod;
   }

   public void setEvaluationPeriod(int evaluationPeriod) {
       this.evaluationPeriod = evaluationPeriod;
   }

   public Date getExpireOn() {
       return expireOn;
   }

   public void setExpireOn(Date expireOn) {
       this.expireOn = expireOn;
   }

   public int getAppId() {
       return appId;
   }

   public void setAppId(int appId) {
       this.appId = appId;
   }

   public int getSubscriberId() {
       return subscriberId;
   }

   public void setSubscriberId(int subscriberId) {
       this.subscriberId = subscriberId;
   }
}

Develop the DAO class to manipulate the table

Now we are going to implement the database insert method in a DAO class. Since this class is a database manipulation class, we can extend the AppMDAO class that’s defined in the org.wso2.carbon.appmgt.impl.dao package.

  • Create a new package called dao under the org.wso2.carbon.appmgt.expiring.subscription.impl package.
  • Now create a new Java class called AppMSubscriptionExtensionDAO in org.wso2.carbon.appmgt.expiring.subscription.impl.dao package by extending the AppMDAO class
  • Now copy and paste the below addSubscription method to the AppMSubscriptionExtensionDAO class
  • This method will be called by custom subscription workflow extension class with the user subscription DTO as a parameter. In this method, we simply extract those user subscription details and save those user subscription details in the database table
public int addSubscription(WorkflowDTO workflowDTO) throws AppManagementException {

   SubscriptionExpiryDTO subscriptionExpiryDTO = null;
   if (workflowDTO instanceof SubscriptionExpiryDTO) {
       subscriptionExpiryDTO = (SubscriptionExpiryDTO) workflowDTO;
   } else {
       throw new AppManagementException("Error in casting....");
   }
   Connection connection = null;
   ResultSet resultSet = null;
   PreparedStatement preparedStatement = null;
   AppMDAO appMDAO = new AppMDAO();
   int subscriptionId = -1;

   try {
       connection = APIMgtDBUtil.getConnection();
       int appId = appMDAO.getAPIID(new APIIdentifier(subscriptionExpiryDTO.getApiProvider(),
                                                      subscriptionExpiryDTO.getApiName(),
                                                      subscriptionExpiryDTO.getApiVersion()), connection);
       int subscriberId = appMDAO.getSubscriber(subscriptionExpiryDTO.getSubscriber()).getId();

       String sqlQuery = "INSERT INTO APM_SUBSCRIPTION_EXT (APP_ID, SUBSCRIBER_ID, SUBSCRIPTION_TYPE, " +
               "SUBSCRIPTION_TIME, EVALUATION_PERIOD, EXPIRED_ON) VALUES (?,?,?,?,?,?)";

       // Adding data to the APM_SUBSCRIPTION_EXT table.
       preparedStatement = connection.prepareStatement(sqlQuery, new String[]{"SUBSCRIPTION_ID"});
       if (connection.getMetaData().getDriverName().contains("PostgreSQL")) {
           preparedStatement = connection.prepareStatement(sqlQuery, new String[]{"subscription_id"});
       }

       preparedStatement.setInt(1, appId);
       preparedStatement.setInt(2, subscriberId);
       preparedStatement.setString(3, subscriptionExpiryDTO.getSubscriptionType());
       preparedStatement.setTimestamp(4, new Timestamp(subscriptionExpiryDTO.getSubscriptionTime().getTime()));
       preparedStatement.setInt(5, subscriptionExpiryDTO.getEvaluationPeriod());
       preparedStatement.setTimestamp(6, new Timestamp(subscriptionExpiryDTO.getExpireOn().getTime()));

       preparedStatement.executeUpdate();
       ResultSet rs = preparedStatement.getGeneratedKeys();
       while (rs.next()) {
           subscriptionId = rs.getInt(1);
       }
       preparedStatement.close();
       connection.commit();

   } catch (SQLException e) {
       if (connection != null) {
           try {
               connection.rollback();
           } catch (SQLException e1) {
               log.error("Failed to rollback the add subscription ", e);
           }
       }
       log.error("Failed to add subscriber data ", e);
throw new AppManagementException("Failed to add subscriber data ", e);
   } finally {
       APIMgtDBUtil.closeAllConnections(preparedStatement, connection, resultSet);
   }
   return subscriptionId;
}

Import the required Java classes as shown below:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.appmgt.api.AppManagementException;
import org.wso2.carbon.appmgt.api.model.APIIdentifier;
import org.wso2.carbon.appmgt.expiring.subscription.impl.dto.SubscriptionExpiryDTO;
import org.wso2.carbon.appmgt.impl.dao.AppMDAO;
import org.wso2.carbon.appmgt.impl.dto.WorkflowDTO;
import org.wso2.carbon.appmgt.impl.utils.APIMgtDBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;

Define the class level instance variable as below. This variable is used for the logging purposes.

private static final Log log = LogFactory.getLog(AppMSubscriptionExtensionDAO.class);

Develop the expiring subscription workflow executor class

Now we are going to implement the custom subscription workflow executor class. This custom class should implement the subscription expire business logic. Since this class is a custom subscription workflow executor class, we can define it as a child class of the SubscriptionCreationSimpleWorkflowExecutor class which was defined in a org.wso2.carbon.appmgt.impl.workflow package.

  • Create a new package called workflow under the org.wso2.carbon.appmgt.expiring.subscription.impl package
  • Now create a new Java class called SubscriptionCreationExpiryWorkflowExecutor in org.wso2.carbon.appmgt.expiring.subscription.impl.workflow package by extending the SubscriptionCreationSimpleWorkflowExecutor class
  • Now copy and paste the below class definition
  • The execute method will be executed when the user clicks on the subscription button
package org.wso2.carbon.appmgt.expiring.subscription.impl.workflow;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.appmgt.api.AppManagementException;
import org.wso2.carbon.appmgt.expiring.subscription.impl.dto.SubscriptionExpiryDTO;
import org.wso2.carbon.appmgt.expiring.subscription.impl.dao.AppMSubscriptionExtensionDAO;
import org.wso2.carbon.appmgt.impl.dto.SubscriptionWorkflowDTO;
import org.wso2.carbon.appmgt.impl.dto.WorkflowDTO;
import org.wso2.carbon.appmgt.impl.workflow.SubscriptionCreationSimpleWorkflowExecutor;
import org.wso2.carbon.appmgt.impl.workflow.WorkflowConstants;
import org.wso2.carbon.appmgt.impl.workflow.WorkflowException;
import org.wso2.carbon.appmgt.impl.workflow.WorkflowStatus;

import java.util.Calendar;
import java.util.Date;
import java.util.List;

public class SubscriptionCreationExpiryWorkflowExecutor extends SubscriptionCreationSimpleWorkflowExecutor {

   private static final Log log = LogFactory.getLog(SubscriptionCreationExpiryWorkflowExecutor.class);

   @Override
   public String getWorkflowType() {
       return WorkflowConstants.WF_TYPE_AM_SUBSCRIPTION_CREATION;
   }

   @Override
   public List getWorkflowDetails(String workflowStatus) throws WorkflowException {
       return null;
   }

   @Override
   public void execute(WorkflowDTO workflowDTO) throws WorkflowException {
       workflowDTO.setStatus(WorkflowStatus.APPROVED);
       SubscriptionExpiryDTO subscriptionExpiryDTO = new SubscriptionExpiryDTO();
       SubscriptionWorkflowDTO subscriptionWorkflowDTO = null;
       if (workflowDTO instanceof SubscriptionWorkflowDTO) {
           subscriptionWorkflowDTO = (SubscriptionWorkflowDTO) workflowDTO;
       } else {
           log.error("Error in casting.....");
       }
       try {
           String subscriptionType = "";
           Date subscriptionDate = new Date();
           //In here we use 7 days as a evaluation time. According to business login you can decide the evaluation period.
           int evaluationPeriod = 24 * 7;

           Calendar cal = Calendar.getInstance();
           cal.setTime(subscriptionDate);
           cal.add(Calendar.HOUR, evaluationPeriod);
           Date expireOn = cal.getTime();

           subscriptionExpiryDTO.setApiProvider(subscriptionWorkflowDTO.getApiProvider());
           subscriptionExpiryDTO.setApiName(subscriptionWorkflowDTO.getApiName());
           subscriptionExpiryDTO.setApiVersion(subscriptionWorkflowDTO.getApiVersion());
           subscriptionExpiryDTO.setSubscriber(subscriptionWorkflowDTO.getSubscriber());
           subscriptionExpiryDTO.setSubscriptionType(subscriptionType);
           subscriptionExpiryDTO.setSubscriptionTime(subscriptionDate);
           subscriptionExpiryDTO.setEvaluationPeriod(evaluationPeriod);
           subscriptionExpiryDTO.setExpireOn(expireOn);
           subscriptionExpiryDTO.setWorkflowReference(subscriptionWorkflowDTO.getWorkflowReference());

           complete(subscriptionExpiryDTO);
       } catch (Exception e) {
           log.error("Could not complete subscription creation workflow", e);
           throw new WorkflowException("Could not complete subscription creation workflow", e);
       }
   }

   @Override
   public void complete(WorkflowDTO workflowDTO) throws WorkflowException {
       super.complete(workflowDTO);
       AppMSubscriptionExtensionDAO appMSubscriptionExtentionDAO = new AppMSubscriptionExtensionDAO();
       try {
           appMSubscriptionExtentionDAO.addSubscription(workflowDTO);
       } catch (AppManagementException e) {
           log.error("Could not complete subscription creation workflow", e);
           throw new WorkflowException("Could not complete subscription creation workflow", e);
       }
   }
}

Finally your project structure should be like what’s illustrated in Figure 3.

Figure 3


Build the project and update the configurations to execute the custom extension

If you completed all the steps listed above, you can persist the user subscription details with the RDBMS table. First we should build the project.

  • Go to the project home directory using terminal and build the project using mvn clean install command

    Figure 4

  • Download the wso2appm-1.1.0.zip from the WSO2 site and extract it.
  • Copy the <Local-Project-Dir>/target/org.wso2.carbon.appmgt.expiring.subscription-1.0.0-SNAPSHOT.jar file which you created in above step to the <AppM-Home>/repository/components/lib/ directory.
  • Now start the WSO2 App Manager and go to the carbon console via https://localhost:9443/carbon URL

    We have to change the workflow execution to hit our custom implementation when the user subscribes to the app. Browse the /_system/governance/appmgt/applicationdata/workflow-extensions.xml file and click on the edit as a text button. Edit the <SubscriptionCreation executor= section to have our custom implementation class as Figure 5. <SubscriptionCreation executor="org.wso2.carbon.appmgt.expiring.subscription.impl.workflow.SubscriptionCreationExpiryWorkflowExecutor"/>

    Figure 5

  • To test the functionality we have to publish an app using the app publisher and then the user should be logged in to the app store. When the user clicks on the subscribe button on an app our custom implementation should be executed and user subscription data should be saved in the RDBMS table
  • You can check it by using select query and there should be a user subscription record as shown in Figure 6

    Figure 6


Conclusion

This tutorial, the first in a two-part series, discussed how you can extend the default subscription model to support the expiry subscription model in WSO2 App Manager. By implementing this model, you can provide an app evaluation period (somewhat of a trial period) for each app in terms of app user; if they’re happy with the app, they can buy those by the end of the evaluation period. Otherwise, you can block the usage of the particular app when the evaluation period lapses.

In Part 2 of this tutorial, we will discuss how to implement an API by using the host object model so anyone can call that API to retrieve user subscription details.


References

 

About Author

  • Sajith Abeywardhana
  • Software Engineer
  • WSO2