Introduction

Para is a flexible backend service, created as an open-source project in the year 2013. It was born out of our need to have a robust system which would allow us to persist objects easily to anything - RDBMS, NoSQL and in-memory databases. We needed a simple solution with an API which would scale well and provide a solid foundation for our future projects.

Para is a 3-tier backend system with a REST API in front of it. The first tier is the database, the second tier is the search index and the third is the cache. Depending on how you use it, Para can either be a standalone backend service or a persistence framework that is part of your code base.

Para is multitenant, which means you can deploy it as a standalone service on one or more nodes and host one or more applications on it (“apps”). An app can be a website, mobile app, desktop app or even a command-line tool. This is made possible by the REST API which talks JSON to your apps, and with the help of the client libraries below, it’s easy to get started. If you’re building an application on the JVM, you can also add Para as Maven dependency to your project. You can still keep the REST API or turn it off completely.

Quick start

  1. Download the WAR
  2. Run it with java -jar -Dconfig.file=./application.conf para-*.war
  3. Call curl localhost:8080/v1/_setup to get the access and secret keys
  4. Open the Para Web Console or use one of the provided client libraries below to connect to the API.

The root app (the initial Para app) is automatically created. If you want to create multiple apps then you must call Para.newApp() or make an authenticated request to the API GET /v1/_setup/{app_name}.

Users are created either from Java code new User().create() or by making an API request to POST /v1/jwt_auth. See Sign in or Authentication sections for more details.

Configuration properties belong in your application.conf file. Here’s an example configuration for development purposes:

# the name of your app
para.app_name = "My App"
# or set it to 'production'
para.env = "embedded"
# allows clients to register any user with any email
para.security.allow_unverified_emails = true
# if hosting multiple apps on Para, set this to false
para.clients_can_access_root_app = true
# no need for caching in dev mode
para.cache_enabled = false
# change this to a random string
para.app_secret_key = "b8db69a24a43f2ce134909f164a45263"
# enable API request signature checking
para.security.api_security = true

The quickest way to interact with Para is through the command-line tool (CLI):

$ npm install -g para-cli
$ para-cli ping
$ echo "{\"type\":\"todo\", \"name\": \"buy milk\"}" > todo.json
$ para-cli create todo.json --id todo1 --encodeId false
$ para-cli read --id todo1
$ para-cli search "type:todo"

The Java client for Para is a separate module with these Maven coordinates:

<dependency>
  <groupId>com.erudika</groupId>
  <artifactId>para-client</artifactId>
  <version>{VERSION}</version>
</dependency>

In your own project you can create a new ParaClient instance like so:

ParaClient pc = new ParaClient(accessKey, secretKey);
// for development the default endpoint is http://localhost:8080
pc.setEndpoint(paraServerURL);
// send a test request - this should return a JSON object of type 'app'
pc.me();

We’ve built a full-blown StackOverflow clone with Para in just about 4000 lines of code - check it out at https://scoold.com

Client libraries

Building Para

Para can be compiled with JDK 6 and up, but using JDK 8+ is recommended.

To compile it you’ll need Maven. Once you have it, just clone and build:

$ git clone https://github.com/erudika/para.git && cd para
$ mvn install -DskipTests=true

To generate the executable “uber-war” run $ mvn package and it will be in ./para-war/target/para-x.y.z-SNAPSHOT.war. Two WAR files will be generated in total - the fat one is a bit bigger in size.

To run a local instance of Para for development, use:

$ mvn spring-boot:run

Standalone mode

There are two ways to run Para as a standalone server. The first one is by downloading the executable WAR file and executing it:

java -jar para-X.Y.Z.war

The WAR contains an embedded Jetty server and bundles together all the necessary libraries. This is the simplest and recommended way to run Para.

Running a standalone server allows you to build a cluster of distributed Para nodes and connect to it through the REST API. Here’s a simple diagram of that architecture:

+----------+ +----------+ +-----------+
|API Client| |API Client| |API Client |
+----+-----+ +-----+----+ +----+------+
     |             |           |
+----+-------------+-----------+------+
|        REST API over HTTPS          |
+----+-------------+-----------+------+
|       Cluster Load Balancer         |
+------+------+------+------+---------+
                   |
     +------------------ ... ----+
     |             |             |
+----+----+   +----+----+   +----+----+
| Your app|   | Your app|   | Your app|
+---------+   +---------+   +---------+
| Cache   |   | Cache   |   | Cache   |  \
+---------+   +---------+   +---------+   \
| Search  |   | Search  |   | Search  |    } Para
+---------+   +---------+   +---------+   /
| Database|   | Database|   | Database|  /
+---------+   +---------+   +---------+

  Node 1        Node 2   ...  Node N

Deploying to a servlet container

Another option is to deploy the WAR file to a servlet container like Tomcat or GlassFish, for example.

Note: We recommend deploying the Para at the root context /. You can do this by renaming the WAR file to ROOT.war before deploying. See the Config for more details about configuring your deployment.

Check the releases page for the latest WAR package.

Maven

Para is hosted on Maven Central. To add it to your project just include this into your pom.xml:

<dependency>
  <groupId>com.erudika</groupId>
  <artifactId>para-server</artifactId>
  <version>{VERSION}</version>
</dependency>

For building lightweight client-only applications connecting to Para, include only the client module:

<dependency>
  <groupId>com.erudika</groupId>
  <artifactId>para-client</artifactId>
  <version>{VERSION}</version>
</dependency>

Javadocs

para-core   para-server   para-client

The ParaObject interface

All domain classes in Para implement the ParaObject interface which gives objects basic common properties like id, timestamp, name, etc. Let’s say you have a plain old Java object like this:

class User {
    public String name;
    public int age;
}

and you want to turn it into persistable Para objects. You can do it by just adding one annotation and implementing ParaObject:

class User implements ParaObject {
    @Stored public String name;
    @Stored public int age;
}

This allows you do call create(), update(), delete() on that object and enables search indexing automatically. Now you can do:

User u = new User();
u.setName("Gordon Freeman");
u.setAge(40);
// generates a new id and persists the object
String id = u.create();

Once the objects is created and persisted we can search for it like this:

// returns a list of users found
List<User> users = Para.getSearch().findQuery(u.getType(), "freeman");

And here’s what a Para object looks like as JSON when returned from the REST API:

{
  "id" : "572040968316915712",
  "timestamp" : 1446469779546,
  "type" : "user",
  "appid" : "para",
  "updated" : 1446469780024,
  "name" : "Gordon Freeman",
  "votes" : 0,
  "identifier" : "fb:1000123456789",
  "groups" : "admins",
  "active" : true,
  "email" : "[email protected]",
  "objectURI" : "/users/572040968316915712",
  "plural" : "users"
}

Implementing your own ParaObject classes

This step is optional. There was a question about how to implement ParaObject and where to start from. First of all, there is no need to write your own custom classes if you’re going to be using them for simple stuff. So step one is to take a look at the generic class called Sysprop. Look at the source code on GitHub. It’s pretty simple and implements all of ParaObject‘s methods. Then you have to decide if you can work with that generic class or not. If you really need custom properties and methods then that class is a good starting point. Just copy the getters and setters and add your own fields and methods.

Another option is to extend Sysprop like so:

public class MyParaObject extends Sysprop implements ParaObject {
    // 'implements ParaObject' is redundant in this case

    @Stored
    public String myCustomField;
    // this field is ignored and not stored anywhere
    public transient String secretKey;

    // TODO: write getters & setters...
}

This is a quick way of having your own classes that work with Para and it spares you the writing of all the boilerplate code. You can later override the parent methods, for example if you need to execute some custom code on create():

@Override
public String create() {
    // TODO; write your own code here
    return dao.create(getAppid(), this); // this writes to DB
}

Fine-tuning backend operations

From version 1.18 Para objects have three new flags - stored, indexed and cached. These flags turn on and off the three main operations - persistence, indexing and caching. Developers can choose to switch off caching on a number of objects that change very often, for example. Also some objects my be hidden from search by setting the indexed: false flag. And finally you can turn off persistence completely with stored: false and thus have objects that live only in memory (and search index) but are never stored in the database.

The Config

Once deployed, Para will initialize itself by loading the default configuration file reference.conf. To load your own configuration you need to set one of the following system properties:

  • config.resource specifies a resource name - not a basename, i.e. ‘application.conf’ and not ‘application’
  • config.file specifies a filesystem path, again it should include the file extension
  • config.url specifies the URL from which to load the config file

You need to set these properties before calling Para.initialize() or at the startup of your servlet container. If you have a file called application.conf or application.json on the classpath, Para will pick it up automatically.

Here’s a sample application.conf file:

para.env = "embedded"
para.print_logo = true
para.security.api_security = true
para.security.csrf_protection = true

The configuration logic is to read system properties first, then environment properties and finally check the config file. So the order of precedence is as follows:

System.getProperty() > System.getenv() > application.conf

Note that environment properties containing . (dots) are invalid and will be replaced with _ (underscore). For example, the environment property $para_env is equivalent to para.env in the config file.

Important: In a production environment, set para.app_secret_key to some randomly generated secret string. This is important for securing your server because this secret key is used for signing and verifying authentication tokens.

Here’s a list of all configuration properties that can be set in the config file:

Configuration Property Default Value
para.env embedded
Enables/disables certain features, depending on the environment. Can be “production”, “development”, “embedded”.
para.print_logo true
If true, the Para ASCII logo and version will be displayed on startup.
para.aws_access_key -
AWS access key for connecting Para to AWS.
para.aws_secret_key -
AWS secret. Make sure the config file with the secret is in your .gitignore.
para.aws_region eu-west-1
The AWS region string.
para.dao -
Selects the DAO implementation at runtime. Can be AWSDynamoDAO or “dynamodb”, “MongoDBDAO”, “CassandraDAO”, etc. Each implementation has its own configuration properties. Defaults to H2DAO.
para.search -
Selects the Search implementation at runtime. Defaults to LuceneSearch.
para.cache -
Selects the Cache implementation at runtime. Use inmemory for develompment. Defaults to hazelcast.
para.fb_app_id -
Facebook app id (for OAuth authentication).
para.fb_secret -
Facebook app secret.
para.gp_app_id -
Google+ app id (for OAuth authentication).
para.gp_secret -
Google+ app secret.
para.in_app_id -
LinkedIn app id (for OAuth authentication)
para.in_secret -
LinkedIn app secret.
para.tw_app_id -
Twitter app id (for authentication).
para.tw_secret -
Twitter app secret.
para.gh_app_id -
GitHub app id (for OAuth authentication).
para.gh_secret -
GitHub app secret.
para.ms_app_id -
Microsoft app id (for OAuth authentication).
para.ms_secret -
Microsoft app secret.
para.admin_ident -
This is the identifier that will be used by the first admin user. It can be the email which the admin user will use to login or a social id in the form of prefix:social_id, e.g. fb:12345. It is used to assign administrative privileges to the user with that identifier.
para.worker_id 1
Node number, 1 to 128. Used for ID generation. Each instance of Para has to have a different worker id.
para.app_name para
The name of the root app - the main app which is used for initializing Para.
para.auth_cookie [app_name]-auth
The name of the authorization cookie. Changes depending on the app name.
para.returnto_cookie [app_name]-returnto
The name of the cookie used to remember which URL the user requested and will be redirected to after login.
para.support_email [email protected]
The email of the webmaster/support team. Para will send emails to this email.
para.app_secret_key md5(‘paraseckey’)
Para secret key. Set this to a long secure random string. Used for salting other secrets and tokens.
para.core_package_name -
The name of the Java package which contains your domain (core) classes.
para.session_timeout 24 * 60 * 60 sec.
The lifetime of the login session, in seconds.
para.jwt_expires_after 7 * 24 * 60 * 60 sec.
JWT token validity period, in seconds.
para.jwt_refresh_interval 60 * 60 sec.
JWT refresh interval, after which a new token is issued.
para.request_expires_after15 * 60 sec.
Expiration of signed API request, in seconds.
para.vote_expires_after 30 * 24 * 60 * 60 sec.
After this period has elapsed, you can vote again on the same object.
para.vote_locked_after 30 sec.
Vote locking mechanism - locks vote change after X seconds.
para.pass_reset_timeout 30 * 60 sec.
The time window in which passwords can be reset, in seconds. After that the token in the email expires.
para.cache_enabled false || para.env == production
Enable/disable object caching in Para.
para.search_enabled true
Enable/disable object indexing in Para.
para.api_enabled true
Enable/disable the REST API.
para.cors_enabled true
Enable/disable the CORS filter. It adds CORS headers to API responses.
para.gzip_enabled true
Enable/disable the GZIP filter. It compresses API responses, only.
para.access_log_enabled true
Enable/disable the access log file.
para.read_from_index true
Enable/disable reading objects from index instead of the database. This will bypass the database. Useful for local testing/development or to recover objects that were lost/deleted from the database.
para.max_items_per_page 30
The default page size - maximum number of results that will be returned in a search query.
para.max_pages 10000
The largest page number that can be specified in a search query.
para.max_datatypes_per_app256
The maximum number of custom types an app can have.
para.max_entity_size_bytes1024 * 1024
The size of the request payload. Requests with payloads larger than this will be denied.
para.min_password_length 6
The minimum length of passwords.
para.default_queue_name para-default
The default queue name (optional).
para.queue_link_enabled false
Enable/disable polling the default queue for objects to be imported into Para.
para.shared_table_name 0
The name of the table which is shared by apps with isSharingTable set to true.
para.prepend_shared_appids_with_space false
Enable/disable table sharing functionality by prepending the appid with a space. DAO implementations can detect that and switch tables based on it.
para.security.api_security true
Enable/disable the API security mechanism. If false, the API endpoint will not check request signatures and all requests will go through. For development only.
para.security.csrf_protection true
Enable/disable CSRF protection which checks for valid CSRF tokens in write requests.
para.security.csrf_cookie para-csrf-token
The name of the CSRF cookie.
para.security.protected.{name} -
Protects a named resource by requiring users to authenticated before accessing it. A protected resource has a {name} and value like this ["/{path}", "/{path}/**", ["{role}" or {http_method}]]. The value is an array of relative paths which are matche by an ANT pattern matcher. This array can contain a subarray which lists all the HTTP methods that require authentication and the user roles that are allowed to access this particular resource. No HTTP methods means that all requests to this resource require authentication.
para.security.ignored ["/{path}/**", "/{path}/**"]
These are the resource paths that will be explicitly public and not require authentication.
para.security.signin /signin
The path to the login page.
para.security.signout /signout
The path to the logout page.
para.security.access_denied /403
The path to redirect to when 403 code is returned.
para.security.returnto returnto
The path to return to when an authentication request succeeds.
para.security.signin_success /
The default page to send users to when they login.
para.security.signout_success /signin
The default page to send users to when they logout.
para.security.signin_failure /signin
The default page to send users to when login fails.
para.security.allow_unverified_emails false
Enable/disable user email verification. When false user accounts are disabled until the user checks their email and confirms it.
para.security.admins_have_full_api_access true
If set to false admin users will be subject to standard resource permissions when accessing the API. This is applicable when paraClient.signIn() is used. By default, they have unlimited access.
para.s3.bucket org.paraio.files
The S3 bucket where files will be stored by FileStore implementations.
para.s3.max_filesize_mb 10
Max filesize for files uploaded to S3, in megabytes.
para.localstorage.folder -
The local folder for file storage.
para.localstorage.max_filesize_mb 10
Max filesize for files stored locally, in megabytes.

All of the above can be overridden with System.setProperty().

Para uses the excellent Config library by TypeSafe. For more information about how it works, see the README for it.

Modules

These are the core modules defined in Para:

  • PersistenceModule - defines the core persistence class implementation
  • SearchModule - defines the core search class implementation
  • CacheModule - defines the core cache class implementation
  • EmailModule - defines an email service implementation for sending emails
  • AOPModule - manages the indexing and caching aspects of Para objects
  • I18nModule - defines a currency converter service implementation
  • QueueModule - defines a queue service implementation
  • StorageModule - defines a file storage service, e.g. Amazon S3

You can override all of the above using the ServiceLoader mechanism in Java like so:

  1. Create a file com.google.inject.Module inside the META-INF/services folder
  2. Then add the full class names of all your modules, one on each line
  3. Para will load these modules on startup use them instead of the default ones

For example, let’s say you want to implement a new PersistenceModule which connects to MySQL. You can define it like so:

class MySqlModule extends AbstractModule {
    protected void configure() {
        bind(DAO.class).to(MySqlDAO.class).asEagerSingleton();
    }
}

Then implement the DAO interface in your class MySqlDAO so that it connects to a MySQL server and stores and loads objects. Finally write the full class name com.company.MySqlModule to the file com.google.inject.Module and Para will use this module as its default persistence module.

You can also override modules programmatically like this:

Para.initialize(Modules.override(ParaServer.getCoreModules()).with(new Module() {
    public void configure(Binder binder) {
        binder.bind(DAO.class).to(MyDAO.class).asEagerSingleton();
        binder.bind(Cache.class).to(MyCache.class).asEagerSingleton();
        binder.bind(Search.class).to(MySearch.class).asEagerSingleton();
    }
}));

Para uses Google Guice as its module manager and DI system.

Environment

para.env is an important configuration property that turns features on and off. There are three main values for it - embedded, development, production, but you can define more.

When you are in embedded mode Para will use Elasticsearch as both data store and a search engine. Elasticsearch will load on each redeploy here. This is the simplest way to get started with Para as you don’t need to configure anything.

Warning: Embedded mode is not recommended for production (duh!).

When you are in development mode Para will try to connect to a DynamoDB server running on localhost. It will also try to connect to a local Elasticsearch server. This mode was created so that you can keep your DB and search servers running in the background while developing your application rather than loading them on each deploy. This will make redeploys a bit faster.

Finally when you are ready to deploy your app to a real server set para.env to production. This will enable object caching and will try to connect to the real AWS DynamoDB service. Also you have to set up a new node for Elasticsearch or run it locally.

para.env DAO Cache Search
embedded (default)H2DAOoffon
developmentAWSDynamoDAOoffon
productionAWSDynamoDAOonon

Plugins

Para will look for various plugins like IOListeners, CustomResourceHandlers, DAOs etc, on startup. The folder in which plugins JARs should be placed is ./lib/, by default, but it can be configured like so:

para.plugin_folder = "plugins/"

DAO, Search and Cache plugins

In version 1.18 we expanded the support for plugins. Para can now load third-party plugins for various DAO implementations, like MongoDB for example. The plugin para-dao-mongodb is loaded using the ServiceLoader mechanism from the classpath and replaces the default AWSDynamoDAO implementation.

To create a plugin you have to create a new project and import para-core with Maven and extend one of the three interfaces - DAO, Search, Cache. Implement one of these interfaces and name your project by following the convention:

  • para-dao-mydao for DAO plugins,
  • para-search-mysearch for Search plugins,
  • para-cache-mycache for Cache plugins.

For example, the plugin for MongoDB is called para-dao-mongodb and implements the DAO interface with the MongoDB driver for Java.

You also need to create one file inside src/main/resources/META-INF/services/ in your plugin project:

  • com.erudika.para.persistence.DAO for DAO plugins,
  • com.erudika.para.search.Search for Search plugins,
  • com.erudika.para.cache.Cache for Cache plugins.

Inside this file you put the full class name of your implementation, for example com.erudika.para.persistence.MyDAO, on one line and save the file.

To load a plugin follow these steps:

  1. Place the plugin JAR in a folder called lib (depends on configuration) or WEB-INF/lib in the same folder as the Para server or include the plugin through Maven,
  2. Set the configuration property para.dao = "MyDAO" or para.search = "MySearch" or para.cache = "MyCache" (use the simple class name here)
  3. Start the Para server and the new plugin should be loaded

Custom event listeners

You can also register your own InitializeListeners and DestroyListeners which will execute when Para is initialized and destroyed, respectively.

// this has to be registered before Para.initialize() is called
Para.addInitListener(new InitializeListener() {
    public void onInitialize() {
        // init code...
    }
});

Para.addDestroyListener(new DestroyListener() {
    public void onDestroy() {
        // shutdown code...
    }
});

Custom I/O listeners

An I/O listener is a callback function which is executed after an input/output (CRUD) operation. After a call is made to one of the DAO methods like read(), update(), etc., all registered listeners are notified and called. It is recommended that the code inside these listeners is asynchronous or less CPU intensive so it does not slow down the calls to DAO.

Para.addIOListener(new IOListener() {
    public void onPreInvoke(Method method, Object[] args) {
        // do something before the CRUD operation...
    }
    public void onPostInvoke(Method method, Object result) {
        // do something with the result...
    }
});

Custom listeners for app events

These listeners can be registered to execute code when an app is created or deleted. This is useful when we need to do additional operations like creating DB tables and/or creating indexes for the new app. Also we might want to clean up those after the app is deleted. Example:

App.addAppCreatedListener(new AppCreatedListener() {
    public void onAppCreated(App app) {
        if (app != null) {
            createTable(app.getAppIdentifier());
        }
    }
});
App.addAppDeletedListener(new AppDeletedListener() {
    public void onAppDeleted(App app) {
        if (app != null) {
            deleteTable(app.getAppIdentifier());
        }
    }
});

Custom context initializers

Para will automatically pick up your classes which extend the Para class. They should be annotated with @Configuration, @EnableAutoConfiguration and @ComponentScan. The Para class implements Spring Boot’s WebApplicationInitializer which creates the root application context.

In your custom initializers you have full access to the ServletContext and this is a good place to register your own filters and servlets. These initializer classes also act as an alternative to web.xml by providing programmatic configuration capabilities.

Custom API resource handlers

Since version 1.7, you can register custom API resources by implementing the CustomResourceHandler interface. Use the ServiceLoader mechanism to tell Para to load your handlers - add them to a file named:

com.erudika.para.rest.CustomResourceHandler

in META-INF/services where each line contains the full class name of your custom resource handler class. On startup Para will load these and register them as API resource handlers.

The CustomResourceHandler interface is simple:

public interface CustomResourceHandler {
    // the path of the resource, e.g. "my-resource"
    String getRelativePath();
    // handle GET requests
    Response handleGet(ContainerRequestContext ctx);
    // handle PUT requests
    Response handlePut(ContainerRequestContext ctx);
    // handle POST requests
    Response handlePost(ContainerRequestContext ctx);
    // handle DELETE requests
    Response handleDelete(ContainerRequestContext ctx);
}

You can use @Inject in your custom handlers to inject any object managed by Para.

Core classes

In the package com.erudika.para.core there are a number of core domain classes that are used throughout Para. These are common classes like User and App which you can use in you application directly or extend them. You can use them for your objects but you’re always free to create your own. To do that, simply POST a new object with type: mytype through the API and your new type will be automatically registered.

Note: Type definitions cannot contain the symbols / and #.

class description
User Defines a basic user with a name, email, password. Used for user registration and security.
Address Defines an address with optional geographical coordinates. Used for location based searching.
App Defines an application within Para. Usually there’s only one existing (root) app.
Tag Simple tag class which contains a tag and its frequency count. Basically any Para object can be tagged. Used for searching.
Vote Defines a user vote - negative or positive. Useful for modeling objects which can be voted on (likes, +1s, favs, etc).
Translation Holds a translated string. Can be used for collecting translations from users.
Sysprop System class used as a general-purpose data container. It’s basically a map.
Linker System class used for implementing a many-to-many relationship between objects.
Thing System class used for storing IoT devices’ state and metadata.

Voting

Para implements a simple voting mechanism for objects - each object can be voted up and down an has a votes property. Voting is useful for many application which require sorting by user votes. When a vote is cast, a new object of type Vote is created to store the vote in the database. Users have a configurable time window to amend their vote, they can no longer vote on that particular object.

User-defined classes

Let’s say you have a class Article in your application that you wish to persist. You first implement the ParaObject interface, then add a few data fields to it. Implementing the interface is trivial as the basic functionality of the required methods is already implemented in the CoreUtils.

You need to specify which fields you want to be saved by adding the @Stored annotation.

class Article implements ParaObject {
    @Stored private String title;
    @Stored private String text;
    @Stored private Map<?, ?> someCustomProperty;
}

Caution: when defining your custom properties try to stick to basic Java types like List, Map, String, boolean, etc. Complex property objects are not supported. This is due to the fact that Para uses BeanUtils to set property values and it has no clue how to deserialize your complex objects, so keep it simple.

You don’t have to define common fields like id or name because they are already defined in the parent class. Now you can create a new article like so:

Article a = new Article();
a.setTitle("Some title");
a.setText("text...");
// the article is saved and a new id is generated
String id = a.create();

Updating and deleting is easy too:

a.setTitle("A new title");
a.update();
// or
a.delete();

When you call create() Para will intercept that call and automatically index and cache the object. Calling the methods update() and delete() also get intercepted and will update or delete that object in/from the search index and cache respectively.

If you want to read an object, first you have to get access to the DAO object. You can either:

  • call Para.getDAO()
  • get it by calling CoreUtils.getInstance().getDao()
  • @Inject it with Para.injectInto()

Then you can read an object using its id:

// returns a single article or null
Article readA = dao.read(id);

You can also define custom types of objects through the REST API by changing the type property on your objects. Keep in mind that the following are reserved words and they should not be used for naming your types (plural form included): “search(es)”, “util(s)”.

Note that you can create objects with custom types and fields through the REST API without having to define them as Java classes like above. These custom objects will be based on the generic Sysprop class but can have any number of custom properties (fields). When doing search on custom fields, add the “properties” prefix to them, like properties.myfield.

For example, lets create another custom Article object through the API. We’ll add to it a custom field called author:

POST /v1/articles

{
 "appid": "myapp",
 "author": "Gordon Freeman"
}

This creates a new Article and indexes all fields including the custom field author. To search for objects through the API, containing the author field we can do a request like this:

GET /v1/articles/search?q=properties.author:Gordon*

Note that we have q=properties.author:... instead of q=author:.... This is due to the fact that custom fields are stored in Para using a nested Map called properties (see the Sysprop class).

Annotations

Para uses annotations to mark what attributes of a class need to be saved. There are several annotations that Para uses but the two main ones are @Stored and @Locked.

@Stored tell Para that an attribute needs to be persisted but that field should not be declared as transient otherwise it will be skipped.

@Locked is used as a filter for those attributes that are read-only like type and id. These are created once and never change so when calling update() they will be skipped.

@Cached is used internally by Para for caching and you don’t need to used it in your projects.

Apps

Apps allow you to have separate namespaces and data models for different purposes. Each app lives in its own separate database table and is independent from apps.

Each app has a unique identifier, like “my-custom-app”. When an object is created, Para will attach the app identifier to it automatically. Apps also have a set of data types, as set of permissions and validation constraints. Data types can be created on-the-fly, for example you can create a type called “article” and it will have be available as a new API resource at /v1/article (and in plural form /v1/articles).

Creating apps

Initially Para creates a default root app with an id equal to the value of the para.app_name configuration parameter. If you need to have only one app then you don’t need to do anything. If you want to create multiple apps then you must call Para.newApp() or make an authenticated request to the API GET /v1/_setup/{app_name}. You are responsible from creating a search index and database table for apps other than the root app (child apps).

Currently Para organizes objects in one table per app and uses a single shared search index unless that app has sharingIndex set to false. If this is the case then a separate search index is created for that app. It is possible to make Para use a single database table for all apps by prefixing id fields (e.g. app1_id1: {data}) but this is not yet implemented.

You can set custom settings to each app through the settings API /v1/_settings using GET, PUT and DELETE. These can be OAuth credentials for social apps or other configuration details that are specific for the app.

Social sign-in for apps

In v1.19 apps can have their own separate credentials for social sign-in, like an OAuth app_id and secret. These are stored within the app object and can be used to create/login users to the app that has these credentials. For example, we can send a request to /facebook_auth?appid=myapp where myapp is not the root app. This will tell Para to look for Facebook keys inside that app. This allows for each Para app to have a corresponding Facebook app (or any other app on the supported social networks, see JWT sign in).

Additionally, we’ve added two custom settings which can tell Para where to redirect users after a successful login or a login failure. These are signin_success and signin_failure (see Custom Settings). Here’s an example of all settings combined:

{
    "fb_app_id": "123U3VTNifLPqnZ1W2",
    "fb_secret": "YXBwOnBhcmE11234151667",
    "signin_success": "/dashboard",
    "signin_failure": "/signin?error"
}

Note that these settings will work for the traditional authentication flow through the browser and the standard endpoints /facebook_auth?appid=myapp, /github_auth?appid=myapp, /google_auth?appid=myapp, and the rest. The other way of authenticating users is through the JWT sign in API which requires an OAuth access token and doesn’t require stored OAuth credentials - these are not even checked because we are supplied with a ready-to-use token.

Validations

Para supports JSR-303 validation annotations and also allows you to define validation constraints in two different ways. One way is to attach annotations to fields in Java classes. The other way is by using the constraints API to add validation constraints to any object, be it core or user-defined. This method is more flexible as it allows you to validate any property of any object.

The built-in constraints are:

required, min, max, size, email, digits, pattern, false, true, future, past, url.

Note: Objects are validated on create() and update() operations only.

Annotation-based validation constraints

You can use any of the JSR 303 annotations specified by the javax.validation.constraints package, part of Java EE. Example:

@Stored @NotBlank @Email
private String email;

This will tell Para to validate that field and check that it actually contains an email. If the validation fails, the object containing that field will not be created or updated.

User-defined validation constraints

Annotations work fine for most objects but are less useful when we want to define objects through the API. For this purpose we can use the constraints API:

boolean addValidationConstraint(String type, String field, Constraint c);

boolean removeValidationConstraint(String type, String field, String constraintName);

To create a constraint you can use the static methods provided by the Constraint class. For example calling Constraint.email() will return a new constraint object for checking email addresses.

We can use these methods to define constraints on custom types and fields that are not yet defined or are created by the API.

To manually validate an object you can use:

String[] errors = ValidationUtils.validateObject(App app, ParaObject po);

Or through ParaClient:

// return a JSON object with all validation constraints
paraClient.validationConstraints();

The returned string array contains 0 elements if the po is valid or a list of errors that were encountered on validation.

Integration with the client-side

You can easily implement client-side validation by getting the JSON object containing all validation constraints for all Para classes.

String jsonValidations = ValidationUtils.getAllValidationConstraints(App app);

This returned JSON is in the following format (note that type names are all in lowercase):

'user': {
    'email': {
        'email': {
            'message': 'messages.email'
        },
        'required': {
            'message': 'messages.required'
        }
    },
    'identifier': {
        'required': {
            'message': 'messages.required'
        }
    }
    ...
},
...

This format used by the excellent JavaScript validation tool Valdr but can easily be integrated with other client-side validators.

Para uses Hibernate Validator for data validation.

DAO interface

The DAO interface declares the following methods:

method description
String create(P obj) Persists an object. A new id should be generated if absent.
P read(String key) Reads an object from the data store for a given id. Returns null if the object for this id is missing.
void update(P obj) Updates an object. Implementations of this method should set a timestamp to the field updated.
void delete(P obj) Deletes an object completely (the opposite of create).
List<P> readAll(List<String> keys, boolean all) Batch operation for read(). If all is false, only the id and type columns should be returned.
void createAll(List<P> objects) Batch operation for create().
void updateAll(List<P> objects) Batch operation for update().
void deleteAll(List<P> objects) Batch operation for delete().

A DAO object only deals with storing objects in a database. It is not concerned with relationships between objects or constraint validation.

Resource permissions

When creating new users, we usually want to specify which resources they can access. This is why we added a few methods for adding and removing resource permissions. Each app can have any number of users and each user can have a set of permissions for a given resource. Resources are identified by name, for example the _batch resource would represent requests going to /v1/_batch.

There are several methods and flags which control which requests can go through. These are:

  • GET, POST, PUT, PATCH, DELETE - use these to allow a certain method explicitly
  • ? - use this to enable public (unauthenticated) access to a resource
  • - - use this to deny all access to a resource
  • * - wildcard, allow all request to go through
  • OWN - allow subject to only access objects they created

Let’s look at a few example scenarios where we give users permission to access the _batch. We have two users - user one with id = 1 and user two with id = 2. We’ll use the following methods:

boolean grantResourcePermission(String subjectid, String resourcePath, EnumSet<AllowedMethods> permission);
// when not using the Java client - permission is an array of HTTP methods
boolean grantResourcePermission(String subjectid, String resourcePath, String[] permission);
boolean revokeResourcePermission(String subjectid, String resourcePath);

These methods allow the use of wildcards * for subjectid and resourcePath arguments.

Scenario 1: Give all users permission to READ - this allows them to make GET requests:

app.grantResourcePermission("*", "_batch", AllowedMethods.READ);

Scenario 2: Give user 1 WRITE permissions - allow HTTP methods POST, PUT, PATCH, DELETE:

app.grantResourcePermission("1", "_batch", AllowedMethods.WRITE);

Also you could grant permissions on specific objects like so:

paraClient.grantResourcePermission("user1", "posts/123", AllowedMethods.DELETE);

This will allow user1 to delete only the post object with an id of 123.

Scenario 3: Give user 2 permission ot only make POST requests:

app.grantResourcePermission("2", "_batch", AllowedMethods.POST);

Note that all users still have the READ permissions because permissions are compounded. However, when grantResourcePermission() is called again on the same subject and resource, the new permission will overwrite the old one.

Scenario 4: Revoke all permissions for user 1 except READ:

app.revokeAllResourcePermissions("1");

Scenario 5: Grant full access or deny all access to everyone:

app.grantResourcePermission("*", "*", AllowedMethods.ALL);
app.grantResourcePermission("*", "*", AllowedMethods.NONE);

Scenario 6: Grant full access to user 1 but only to the objects he/she created. In this case user 1 will be able to create, edit, delete and search todo objects but only those which he/she created, i.e. creatorid == 1.

app.grantResourcePermission("1", "todo", ["*", "OWN"]);
app.grantResourcePermission("1", "todo/*", ["*", "OWN"]);

To get all permissions for both users call:

app.getAllResourcePermissions("1", "2");

The default initial policy for all apps is “deny all” which means that new users won’t be able to access any resources, except their own object and child objects, unless given explicit permission to do so.

You can also create “anonymous” permissions to allow unauthenticated users to access certain resources:

app.grantResourcePermission("*", "public/resource", AllowedMethods.READ, true);

This resource is now public but to access it you still need to specify your access key as a parameter:

GET /v1/public/resource?accessKey=app:myapp

Alternatively, on the client-side, you can set the Authorization header to indicate that the request is anonymous:

Authorization: Anonymous app:myapp

The special permission method ? means that anyone can do a GET request on public/resource.

To check if user 1 is allowed to access a particular resource call:

// returns 'false'
app.isAllowedTo("1", "admin", "GET");

Cassandra

The Cassandra plugin adds support for Apache Cassandra. The class CassandraDAO is a DAO implementation and is responsible for connecting to a Cassandra cluster and storing/retrieving objects (items) to/from it. All operations are carried out using the latest Cassandra Java Driver by DataStax, compatible with Cassandra 3.x.

There are several configuration properties for Cassandra (these go in your application.conf file):

property description
para.cassandra.hosts Comma-separated server hostnames (contact points). Defaults to localhost.
para.cassandra.port The server port to connect to. Defaults to 9042.
para.cassandra.keyspace The keyspace name that Para will use. Default is equal to para.app_name.
para.cassandra.user The username with access to the database. Defaults to "".
para.cassandra.password The password. Defaults to "".
para.cassandra.replication_factor Replication factor for the keyspace. Defaults to 1.
para.cassandra.ssl_enabled Enables the secure SSL/TLS transport. Defaults to false.

The plugin is on Maven Central. Here’s the Maven snippet to include in your pom.xml:

<dependency>
  <groupId>com.erudika</groupId>
  <artifactId>para-dao-cassandra</artifactId>
  <version>{version}</version>
</dependency>

Alternatively you can download the JAR from the “Releases” tab above put it in a lib folder alongside the server WAR file para-x.y.z.war. Para will look for plugins inside lib and pick up the Cassandra plugin.

Finally, set the config property:

para.dao = "CassandraDAO"

This could be a Java system property or part of a application.conf file on the classpath. This tells Para to use the Cassandra Data Access Object (DAO) implementation instead of the default.

See Plugins for more information about how you can create your own plugins.

For more information about using Cassandra, see the official docs.

DynamoDB

Para can work with DynamoDB by using the AWSDynamoDAO implementation. That class is responsible for connecting to Amazon’s DynamoDB server and storing/retrieving objects (items) to/from it. All operations are carried out using the latest AWS Java SDK.

Note: DynamoDB doesn’t support batch update requests so AWSDynamoDAO does not batch update requests. It simply executes all update requests in a sequence.

The implementation adds a default prefix para- to DynamoDB tables, so if you have an app called “myapp” your table for that will be called para-myapp.

Table sharing

In v1.21, we added new functionality to AWSDynamoDAO which enables apps to share the same table. This is useful for certain deployment scenarios where you have a large number of apps (and tables) which are rarely accessed and have low throughput. This makes it expensive to run Para on many DynamoDB tables which remain underutilized for long periods of time. So with table sharing you can reduce your DynamoDB bill to a minimum by having a shared table that contains all the objects for all those apps. You can enable this feature by setting the following in your config:

para.prepend_shared_appids_with_space = true

This will prepend the appid property with a space character for those apps that have isSharingTable set to true, thus letting the DAO know that this app belongs to the shared table, instead of sending its data to a separate table. The shared table name is controlled by para.shared_table_name and defaults to “0”.

The shared table keys are a combination of appid and id (e.g. myapp_679334060962615296). Para will also automatically create a global secondary index (GSI) on the shared table, with a primary key appid and secondary key timestamp, to facilitate queries like dao.readPage().

See Modules for more information about how you can override the default implementation.

For more information about DyanamoDB see the documentation on AWS.

MongoDB

Since v1.18.0 Para supports plugins and the the first official plugin adds support for MongoDB. The class MongoDBDAO is a DAO implementation and is responsible for connecting to a MongoDB server and storing/retrieving objects (items) to/from it. All operations are carried out using the latest MongoDB Java Driver compatible with MongoDB 3.2.

There are several configuration properties for MongoDB (these go in your application.conf file):

property description
para.mongodb.host The hostname of the MongoDB server. Defaults to localhost.
para.mongodb.port The server port to connect to. Defaults to 27017.
para.mongodb.database The database name that Para will use. Default is equal to para.app_name.
para.mongodb.user The username with access to the database. Defaults to "".
para.mongodb.password The password. Defaults to "".
para.mongodb.ssl_enabled Enables the secure SSL/TLS transport. Defaults to false.
para.mongodb.ssl_allow_all Allows any hostname by skipping the certificate verification. Defaults to false.

The plugin is on Maven Central. Here’s the Maven snippet to include in your pom.xml:

<dependency>
  <groupId>com.erudika</groupId>
  <artifactId>para-dao-mongodb</artifactId>
  <version>{version}</version>
</dependency>

Alternatively you can download the JAR and put it in a lib folder alongside the server WAR file para-x.y.z.war. Para will look for plugins inside lib and pick up the Elasticsearch plugin.

Finally, set the config property:

para.dao = "MongoDBDAO"

This could be a Java system property or part of a application.conf file on the classpath. This tells Para to use the MongoDB Data Access Object (DAO) implementation instead of the default.

See Plugins for more information about how you can create your own plugins.

For more information about using MongoDB, see the official manual.

H2 DB

This is the default database for object storage since v1.25. It is lightweight and embedded, which speeds up the startup of the server. All tables are stored inside a ./data folder by default. Keep in mind that each app has its own table which is created automatically when the app is created.

There are several configuration properties for H2 (these go in your application.conf file):

property description
para.db.dir The data directory for storing DB files. Defaults to ./data.
para.db.user The username with access to the database. Defaults to para.
para.db.password The password. Defaults to secret.
para.db.tcpServer Parameters for the H2 TCP server. Defaults to -baseDir ./data.

This implementation is works well for both development and production environments.

Search interface

Para indexes objects based on their type. For example, a User objects has a type field with the value “user”. Most of the search methods below ask for a type to search for (i.e. the type field acts as a filter).

The Search interface defines the following methods used for search:

P findById(String id)
Returns the object with the given id from the index or null.
List<P> findByIds(List<String> ids)
Returns all objects for the given ids.
List<P> findNearby(String type, String query, int radius, double lat, double lng)
Location-based search query.
List<P> findPrefix(String type, String field, String prefix)
Searches for objects containing a field (property) that starts with the given prefix.
List<P> findNestedQuery(String type, String field, String query)
Searches inside a nested field nstd (a list of objects).
List<P> findQuery(String type, String query)
The main search method. Follows the Lucene query parser syntax.
List<P> findSimilar(String type, String filterKey, String[] fields, String liketext)
“More like this” search.
List<P> findTagged(String type, String[] tags)
Search for objects tagged with a set of tags.
List<P> findTags(String keyword)
Shortcut method for listing all tags.
List<P> findTermInList(String type, String field, List<?> terms)
Searches for objects containing any of the terms in the given list (matched exactly).
List<P> findTerms(String type, Map<String, ?> terms, boolean matchAll)
Searches for objects containing all of the specified terms (matched exactly)
List<P> findWildcard(String type, String field, String wildcard)
A wildcard query like “example*”.

All methods accept an optional Pager parameter for paginating and sorting the search results.

Also there are a few methods for indexing and unindexing objects but you should avoid calling them directly. These methods are:

  • void index(ParaObject obj)
  • void indexAll(List<P> objects)
  • void unindex(ParaObject obj)
  • void unindexAll(List<P> objects)

You should not index/unindex your objects manually - Para does this for you.

Elasticsearch

Now part of the para-search-elasticsearch plugin (v1.25).

Elasticsearch is the right choice as the search engine for Para in production. It supports Elasticsearch v5 and uses the TCP TransportClient by default. Support for the high-level REST HTTP client is expected when ES v6 is released.

The Search interface is implemented in the ElasticSearch class.

There are several configuration properties for Elasticsearch (these go in your application.conf file):

property description
para.cluster_name Elasticsearch cluster name. Default is para-prod when running in production.
para.es.async_enabled Asynchronous operation when indexing/unindexing. Defaults to false.
para.es.shards The number of shards per index. Used when creating an new index. Default is 5.
para.es.replicas The number of copies of an index. Default is 0.
para.es.auto_expand_replicas Automatically make a replica copy of the index to the number of nodes specified. Default is 0-1.
para.es.use_transportclient Use TransportClient to connect to a remote ES node. If false, the REST client will be used. Default is true.
para.es.transportclient_host The hostname of the Elasticsearch instance or cluster head node to connect to. Default is localhost.
para.es.transportclient_port The port of the Elasticsearch instance or cluster head node to connect to. Default is 9300.

The plugin is on Maven Central. Here’s the Maven snippet to include in your pom.xml:

<dependency>
  <groupId>com.erudika</groupId>
  <artifactId>para-search-elasticsearch</artifactId>
  <version>{version}</version>
</dependency>

Alternatively you can download the JAR from the “Releases” tab above put it in a lib folder alongside the server WAR file para-x.y.z.war. Para will look for plugins inside lib and pick up the Elasticsearch plugin.

Finally, set the config property:

para.search = "ElasticSearch"

This could be a Java system property or part of a application.conf file on the classpath. This tells Para to use the Elasticsearch implementation instead of the default (Lucene).

Read the Elasticsearch docs for more information.

Lucene

Lucene is used as the default search engine in Para. It is a lightweight alternative to Elasticsearch for self-contained deployments. It works great for local development and also in production.

The Search interface is implemented in the LuceneSearch class.

Keep in mind that each Para app has its own Lucene index, which is automatically created if missing. The path to where Lucene files are stored is controlled by para.lucene.dir which defaults to . the current directory. If you set it to para.lucene.dir = "/home/user/lucene" index files will be stored in /home/users/lucene/data.

Read the Lucene docs for more information.

Cache interface

The Cache interface defines the following methods used for object caching:

method description
void put(String id, T object) Caches an object with a key equal to its id. Should skip null objects.
void putAll(Map<String, T> objects) Caches multiple objects. Should skip null objects.
T get(String id) Retrieves an object from cache. Returns null if the object isn’t cached.
Map<String, T> getAll(List<String> ids) Retrieves multiple objects from cache.
void remove(String id) Removes an object from cache.
void removeAll() Clears the cache completely.
void removeAll(List<String> ids) Clears only the objects with the specified ids.

You should avoid calling cache related methods directly - Para does this for you.

Hazelcast

Para uses Hazelcast as the default implementation of the Cache interface. It was chosen because it provides excellent support for distributed data structures like maps and sets. It allows us to use a portion of the memory on each node for caching without having to manage a separate caching server or cluster.

Para organizes caches by application name - each application has its own separate distributed map with the same name.

There are several configuration properties for Hazelcast:

property description
para.hc.async_enabled Asynchronous operation when putting objects in cache. Defaults to false.
para.hc.eviction_policy Cache eviction policy - LRU or LFU. Defaults to LRU.
para.hc.eviction_percentage Cache eviction percentage. Defaults to 25 percent.
para.hc.ttl_seconds ‘Time To Live’ for cached objects. Defaults to 3600 seconds.
para.hc.max_size Cache size as a percentage of used heap. Defaults to 25 percent.
para.hc.jmx_enabled JMX reporting. Default is true.
para.hc.ec2_discovery_enabled Enables AWS EC2 auto discovery. Default is true.
para.hc.discovery_group Security group for cloud discovery of nodes. Default is hazelcast.
para.hc.password The Hazelcast cluster password. Default is hcpasswd.
para.hc.mancenter_enabled Enables the Hazelcast Management Center. Default is !IN_PRODUCTION.
para.hc.mancenter_url The URL for the Management Center server. Default is http://localhost:8001/mancenter.

For more information see the Hazelcast docs.

One-to-many

Object relationships are defined by the Linkable interface. All Para objects are linkable, meaning that they can be related to other Para objects.

Para supports one-to-many relationships between objects with the parentid field. It contains the id of the parent object. For example a user might be linked to their father like this:

+--------+
| Darth  |
| id: 5  |  Parent
+---+----+
    |
+---+---------+
| Luke        |
| id: 10      |  Child
| parentid: 5 |
+-------------+

This allows us to have a parent objects with many children which have the same parentid set. Now we can get all children for a given object by calling parent.getChildren(Class<P> clazz). This will return the list of objects that have a parentid equal to that object’s id. For example:

// assuming we have the parent object...
User luke = parent.getChildren(User.class).get(0);
User darth = dao.read(luke.getParentid());

Many-to-many

Many-to-many relationships are implemented in Para with Linker objects. This object contains information about a link between two objects. This is simply the id and type of both objects. Linker objects are just regular Para objects - they can be persisted, indexed and cached.

+--------+
|  tag1  |
+---+----+
    |
+---+------------+
|post:10:tag:tag1|  Linker
+---+------------+
    |
+---+------+
|  Post1   |
|  id:10   |
+----------+

Note: The following methods are only used when creating “many-to-many” links. Linking and unlinking two objects, object1 and object2, is done like this:

object1.link(object2.getType(), object2.getId());
object1.unlink(object2.getType(), object2.getId());
// delete all links to/from object1
object1.unlinkAll();

To check if two objects are linked use:

object1.isLinked(object2.getType(), object2.getId())

Also you can count the number of links by calling:

object1.countLinks(object2.getType())

Finally, to read all objects that are linked to object1, use:

object1.getLinkedObjects(object2.getType(), Pager... pager);

AWS IoT

Para is integrated with the AWS IoT cloud which means that you can create Thing objects through the Para API and have them sync their state with the AWS cloud. When you create a Thing it is also created on the AWS cloud and a certificate is generated for it which is saved within the Thing object. The certificate, private and public keys and other information about the device are only shown once the Thing is created. Here’s an example create request:

POST /v1/things
{
    "name": "MyThing",
    "serviceBroker": "AWS"
}

The response would be something like this:

{
  "id": "664870213421895680",
  "timestamp": 1468601996535,
  "type": "thing",
  "appid": "myapp",
  "name": "MyThing"
  "serviceBroker": "AWS",
  "deviceState": {},
  "deviceMetadata": {
    "thingId": "664870213421895680",
    "thingName": "MyThing",
    "thingARN": "arn:aws:iot:eu-west-1:123456789:thing/MyThing",
    "clientId": "MyThing",
    "clientCertId": "1abee5f25a5e1b1c66b66dd462dda32000621b429f6eb1ddb179ec3b",
    "clientCertARN": "arn:aws:iot:eu-west-1:123456789:cert/1abee5f25a5e1b1c66b66dd462dda32000621b429f6eb1ddb179ec3b",
    "clientCert": "-----BEGIN CERTIFICATE-----\n ... \n-----END CERTIFICATE-----\n",
    "privateKey": "-----BEGIN RSA PRIVATE KEY-----\n ... \n-----END RSA PRIVATE KEY-----\n",
    "publicKey": "-----BEGIN PUBLIC KEY-----\n ... \n-----END PUBLIC KEY-----\n",
    "region": "eu-west-1",
    "port": 8883,
    "host": "b2chw4axefhujt.iot.eu-west-1.amazonaws.com"
  }
}

Note that the field deviceDetails is only showed once and the device’s metadata is hidden on read. Now you can connect your device to the AWS cloud using the generated certificates and start sending messages with the state of the device. Para will check with AWS for shadow updates on each read and sync the Thing object’s state with the state that is stored in the cloud. If the Thing object is updated from the Para API, the AWS IoT shadow is updated accordingly so the two are in sync again. Finally, deleting the Thing object also deletes it from AWS along with the certificate and policy attached to it.

In order to use the IoT integration feature, Para needs to get your AWS credentials from the config file or from the instance it is deployed on.

Getting started with Para and AWS IoT

  1. Get an account from AWS and create a user with permissions to call the IoT API
  2. Set the para.aws_access_key and para.aws_secret_key properties in your Para config file
  3. Start the Para instance and create a Thing object through the API:
    POST /v1/things
    {
     "name": "myDevice",
     "serviceBroker": "AWS"
    }
    
  4. Take note of the deviceDetails field retured by this request as it contains certificates for your device
  5. Use the certificates to set up your device and connect it to AWS IoT
  6. The deviceState field inside the Para Thing object is synced with the device shadow on AWS on every GET request
  7. Update the deviceState from the Para API and it will be synced with the device shadow on AWS on every PATCH request.

Azure IoT

Para is integrated with the Microsoft’s Azure IoT Hub which means that you can create Thing objects through the Para API and have them sync their state with the Azure cloud. When you create a Thing it is also created as a device on the Azure cloud and primary/secondary keys are generated for it which are saved within the Thing object. These keys and other information about the device are only shown once the Thing is created. Here’s an example create request:

POST /v1/things
{
    "name": "MyThing",
    "serviceBroker": "Azure"
}

The response would be something like this:

{
  "id": "664870213421895680",
  "timestamp": 1468601996535,
  "type": "thing",
  "appid": "myapp",
  "name": "MyThing"
  "serviceBroker": "Azure",
  "deviceState": {},
  "deviceDetails": {
    "thingId": "664870213421895680",
    "thingName": "MyThing",
    "primaryKey": "1abee5f25a5e1b1c66b66dd462dda",
    "secondaryKey": "32000621b429f6eb1ddb179ec3b",
    "status": "enabled",
    "lastActivity": "{timestamp}",
    "connectionState": "disconnected",
    "connectionString": "HostName=abc.def.com;DeviceId=664870213421895680;SharedAccessKey=1abee5f25a5e1b1c66b66dd462dda",
  }
}

Note that the field deviceDetails is only showed once and the device’s metadata is hidden on read. Now you can connect your device to the Azure cloud using the generated primary/secondary keys and start sending messages with the state of the device. Para will automatically check with Azure for device messages and sync the Thing object’s state with the state that is stored in the cloud. If the Thing object is updated from the Para API, the Azure IoT device is updated accordingly so the two are in sync again. Finally, deleting the Thing object also deletes it from the Azure IoT Hub.

In order to use the IoT integration feature, Para needs to get your AWS credentials from the config file or from the instance it is deployed on.

Getting started with Para and Azure IoT

  1. Get an account from Azure and create an IoT hub
  2. Set the para.azure.iot_hostname, para.azure.iot_access_key, para.azure.iot_eventhub_name and para.azure.iot_eventhub_endpoint properties in your Para config file (these can be found in your IoT console)
  3. Start the Para instance and create a Thing object through the API:
    POST /v1/things
    {
     "name": "myDevice",
     "serviceBroker": "Azure"
    }
    
  4. Take note of the deviceDetails field retured by this request as it contains primary/secondary keys for your device
  5. Use the primary/secondary keys to set up your device and connect it to Azure IoT Hub
  6. The deviceState field inside the Para Thing object is synced with the device on Azure automatically in the background
  7. Update the deviceState from the Para API and it will be synced with the device shadow on Azure on every PATCH request.

Translations

Translations are Para objects which contain a single translation of a string. For example you can have a language file in English and for each string you can create a Translation object with the translation of that string.

Translations have a key and a value. The key is used to identify the string to be translated. Each translation also has a specific locale associated with it.

A translation can be approved which makes it possible to build a crowdsourced system for translation of language packs.

Languages

Languages are maps of keys and values. A language file might contain all strings used by an application. Also you can have the language loaded from a database.

The language map consists of language strings and each string has a short unique key. For example:

english.txt
-----------
yes = "Yes"
no  = "No"

italian.txt
-----------
yes = "Sì"
no  = "No"

The LanguageUtils class deals with the loading of languages and contains methods for working with locales. You can set default languages, write languages to a database and load them.

Filters

There are several filters you can use to enable different features like Gzip encoding, CORS, etc.

CORS

The CORS filter enables cross-origin requests for the Para API. It is enabled with the configuration property para.cors_enabled set to true. The implementation is based on the open source CORSFilter by Ebay.

Gzip

The GZipServletFilter is enabled by para.gzip_enabled and provides on-the-fly Gzip encoding for static resources, as well as, the Para API JSON responses.

Caching

The CachingHttpHeadersFilter is not used by Para but you can use it to enable Cache-Control headers for all static resources.

Misc. utilities

The Utils static class contains a variety of utility methods. These are summarized below:

Hashing

String MD5(String s)
Calculates the MD5 hash for a string.
String bcrypt(String s)
Calclates the BCrypt hash for a string. Based on Spring Security.
boolean bcryptMatches(String plain, String storedHash)
Validates a BCrypt hash.

Strings

String stripHtml(String html)
Strips all HTML tags from a string leaving only the text.
String markdownToHtml(String markdownString)
Convert a Markdown string to HTML. Based on Txtmark.
String compileMustache(Map<String, Object> scope, String template)
Compile a Mustache template string to HTML. Based on Mustache.java.
String abbreviate(String str, int max)
Abbreviates a string to a given length.
String stripAndTrim(String str)
Removes punctuation and symbols and normalizes whitespace.
String noSpaces(String str, String replaceWith)
Spaces are replaced with something or removed completely.
String formatMessage(String msg, Object... params)
Replaces placeholders like {0}, {1}, etc. with the corresponding objects (numbers, strings, booleans).

Numbers

String formatPrice(double price)
Formats a price to a decimal with two fractional digits.
double roundHalfUp(double d)
Rounds up a double using the “half up” method, scale is 2 fractional digits.
double roundHalfUp(double d, int scale)
Rounds up a double using the “half up” method.
String abbreviateInt(Number number, int decPlaces)
Rounds a number like “10000” to “10K”, “1000000” to “1M”, etc.

Dates

String formatDate(Long timestamp, String format, Locale loc)
Formats a date according to a specific locale.
String[] getMonths(Locale locale)
A list of the twelve months in the language set by the locale.

JSON

P fromJSON(String json)
Converts a JSON string to a ParaObject. Requires the type property. Based on Jackson.
String toJSON(P obj)
Converts a ParaObject to a JSON string. Based on Jackson.

Objects

boolean typesMatch(ParaObject so)
Validates that an object’s class matches its type property.
Map<String, Object> getAnnotatedFields(P bean)
Returns a map of all fields marked with the Stored annotation for a given object.
P setAnnotatedFields(Map<String, Object> data)
Reconstruct an object from a map of fields and their values.
P toObject(String type)
Constructs a new instance of the given type (must be a known ParaObject).
Class<? extends ParaObject> toClass(String type)
Returns the Class instance of a given type (must be a known ParaObject).
boolean isBasicType(Class<?> clazz)
Checks if a class is primitive, String or a primitive wrapper.
String[] validateObject(ParaObject content)
Runs the Hibernate Validator against an object. Returns a list of errors or an empty array if that object is valid.
String getNewId()
Generates a new id. Based on Twitter’s Snowflake algorithm.
String type(Class<? extends ParaObject> clazz)

URLs

boolean isValidURL(String url)
Validates a URL.
String getHostFromURL(String url)
Returns the host name of a URL - “example.com”
String getBaseURL(String url)
Returns the base URL, e.g. http://example.com

HumanTime

Para includes the class HumanTime written by Johann Burkard. This class makes it easy to convert a timestamp to an approximation in the form of “X minutes ago”.

For example you can do:

HumanTime ht = Utils.getHumanTime();
// prints 15 h 45 m
String s1 = ht.approximately(56720083L);

int timeOfEvent = 1399554448;
int timeNow = System.currentTimeMillis();
// calculate elapsed time
String s2 = ht.approximately(timeNow - timeOfEvent);
System.out.println("Event happened " + s2 + " time ago");

For more information about HumanTime see the docs.

Pager

The Pager class is used for pagination. It holds data about a page request. For example you can call a search method like this:

Pager pager = new Pager();
// limit results to 5
pager.setLimit(5);
// sort by the 'tag' field
pager.setSortby("tag");
// descending order
pager.setDesc(true);
List<Tag> tags = search.findTags("tag1", pager);
// the total number of tags for the query
int tagCount = pager.getCount();

Pager objects are used primarily in combination with search queries and allow you to limit the results of a query.

Email

The Emailer interface has a simple API for sending email messages.

public interface Emailer {
    boolean sendEmail(List<String> emails, String subject, String body);
}

Para can either use the JavaMail API or AWS SES to send emails. This is used for email verification, password recovery and notifications. Set support_email to be the email address used by the system. An example config for JavaMail:

para.emailer = "javamail"
para.support_email = "[email protected]"
para.mail.host = "smtp.example.com"
para.mail.port = 587
para.mail.username = "[email protected]"
para.mail.password = "password"
para.mail.tls = true
para.mail.ssl = false

Email templates can be loaded with Emailer.class.getClassLoader().getResourceAsStream("emails/template.html"). Para supports basic variable substitutions through Mustache with Utils.compileMustache(data, template).

Set para.emailer = "aws" to use the AWS Simple Email Service and comment out the para.mail.* properties as they are ignored. Also set para.aws_access_key and para.aws_secret_key.

Queue

The Queue interface is another simple API for pushing and pulling messages to and from a queue.

public interface Queue {
    String pull();
    void push(String task);
    String getName();
    void setName(String name);
}

Currently this interface is implemented by the AWSQueue class which relies on the AWS Simple Queue Service.

Storage

The FileStore interface allows you to have different storage services, like S3 or Google Drive, connected to your application. You can save and load files from disk or cloud storage services easily.

public interface FileStore {
    InputStream load(String path);
    String store(String path, InputStream data);
    boolean delete(String path);
}

AWSFileStore

This implementation uses AWS S3 as file storage location. The location of each file is controlled by the bucket name cofiguration property para.s3.bucket. There is also the para.s3.max_filesize_mb property which restricts the size of the upload.

Before each upload, files are prepended with a timestamp, for example myfile.txt becomes 1405632454930.myfile.txt. Currently all files are set to be stored with “Reduced Redundancy” turned on.

You can Gzip compress files before uploading uploading but you must append the .gz extension in order for the correct content encoding header to be set. The extension is removed before upload.

LocalFileStore

This implementation stores files on the local file system. The folder where files will be stored is set with the para.localstorage.folder property. The maximum file size is controlled by para.localstorage.max_filesize_mb.

Simple authentication

Para implements several authentication mechanisms which you can integrate in your application and make it easy to handle user registrations and logins.

The classic way of logging users in is with usernames and passwords. Para implements that mechanism in the PasswordAuthFilter class. It takes a username or email and a password and tries to find that user in the database. If the user exists then it validates that the hash of the given password matches the hash in the database. The hashing algorithm is BCrypt.

The default URL for this filter is /password_auth and all requests to this location will be intercepted and processed by it. The default parameters to pass to it are email and password.

Also see the configuration properties para.security.signin_success and para.security.signin_failure in section Config. These can be set for each app individually as signin_success and signin_failure in the app’s settings. For apps other than the root app use /password_auth?appid=myapp.

Here’s an example HTML form for initiating password-based authentication:

<form method="post" action="/password_auth">
    <input type="email" name="email">
    <input type="password" name="password">
    <input type="submit">
</form>

LDAP support

Users can be authenticated through an LDAP server, including Active Directory. The implementation uses the UnboundID SDK in combination with Spring Security. The user supplies a uid and password and Para connects to the LDAP server and tries to bind that user. Then, upon successful login, a new User object is created and the user is signed in. The user’s profile data (email, name, uid) is read from the LDAP directory. It is important to note that emails are not validated and are assumed valid.

Support for LDAP authentication is implemented by the LdapAuthFilter. The default URL for this filter is /ldap_auth. The filter takes two query parameters username and password and answers to any HTTP method. Example: GET /ldap_auth?username=bob&password=secret

These are the configuration options for this filter:

property description
para.security.ldap.server_urlURL of the LDAP server, including scheme and port, defaults to ldap://localhost:8389/.
para.security.ldap.base_dnThe base DN, aka domain (default is dc=springframework,dc=org).
para.security.ldap.bind_dnThe initial bind DN for a user with search privileges (default is blank).
para.security.ldap.bind_passThe password for a user with search privileges (default is blank).
para.security.ldap.user_search_baseSearch base for user searches (default is blank).
para.security.ldap.user_search_filterSearch filter for user searches (default is (cn={0})).
para.security.ldap.user_dn_patternDN pattern for finding users directly (default is uid={0},ou=people).
para.security.ldap.password_attributeThe password attribute in the directory (default is userPassword).
para.security.ldap.active_directory_domain The domain name for AD server. (default is blank, AD is disabled).
para.security.ldap.compare_passwords If set to any value, will switch to password comparison strategy instead of default “bind” method. (default - not set).

Note: Active Directory support is enabled when active_directory_domain is set.

You can also configure LDAP through the app settings API:

{
    "security.ldap.server_url": "ldap://localhost:8389/",
    "security.ldap.base_dn": "dc=springframework,dc=org",
    "security.ldap.bind_dn": "admin",
    "security.ldap.bind_pass": "secret",
    ...
    "signin_success": "http://success.url",
    "signin_failure": "http://failure.url"
}

You can also put all of the settings above in a configuration file (see the config)

OAuth 2.0 support

A generic OAuth 2.0 filter is available for situations where you need to use your own authentication server. It follows the standard OAuth 2.0 flow and requires that you redirect users to a login page first (on your server). Then, upon successful login, the user is redirected back to /oauth2_auth with the access token. Finally, the filter tries to get the user’s profile with that token, from a specified server, which could be the same server used for authentication.

Support for generic OAuth authentication is implemented by the GenericOAuth2Filter. The default URL for this filter is /oauth2_auth.

These are the configuration options for this filter:

property description
para.security.oauth.profile_urlAPI endpoint for user profile.
para.security.oauth.token_urlThe URL from which the access token will be requested (via POST).
para.security.oauth.scopeThe scope parameter of the access token request payload.
para.security.oauth.accept_headerThe Accept header - if blank, the header won’t be set. (default is blank).
para.security.oauth.parameters.idThe id parameter for requesting the user profile (default is id).
para.security.oauth.parameters.pictureThe picture parameter for requesting the user profile (default is picture).
para.security.oauth.parameters.emailThe email parameter for requesting the user profile (default is email).
para.security.oauth.parameters.nameThe name parameter for requesting the user profile (default is name).
para.security.oauth.domain This domain name is used if a valid email can’t be obtained (optional).

You can configure the URLs for authentication success and failure in the configuration file (see the config)

OpenID support

DEPRECATED

Note: OpenID 2.0 is no longer supported by Google, and we recommend using one of the other authentication providers or JWT-based authentication.

Support for OpenID authentication is implemented by the OpenIDAuthFilter. The default URL for this filter is /openid_auth.

The filter takes a request with the openid_identifier parameter and redirects the user to their OpenID provider for verification. Once the user’s identity is verified, the provider redirects the user back to our filter and the user gets either logged in or registered.

You can configure the URLs for authentication success and failure in the configuration file (see the config)

Facebook support

This describes the web authentication flow with Facebook. You could also login with an existing access token from Facebook through the API. This web flow sets a cookie, the API returns a JWT instead.

First of all you need to have your API credentials ready by creating an app in the Facebook Dev Center. Then add them to your application.conf configuration file:

para.fb_app_id = "..."
para.fb_secret = "..."

Or add these through the app settings API:

{
    "fb_app_id": "..."
    "fb_secret": "..."
    "signin_success": "http://success.url"
    "signin_failure": "http://failure.url"
}

If you want Para to generate a JWT token upon successful authentication, add the jwt=? parameter to your signin_success url. For example { "signin_success": "http://success.url?jwt=?" }. Para will redirect the user back to your host URL with the generated access token.

Support for logging in with Facebook is implemented by the FacebookAuthFilter. This filter responds to requests at /facebook_auth.

To initiate a login with Facebook just redirect the user to the Facebook OAuth endpoint:

facebook.com/dialog/oauth

Pass the parameter redirect_uri=/facebook_auth so Para can handle the response from Facebook. For apps other than the root app use redirect_uri=/facebook_auth?appid=myapp instead.

Note: You need to register a new application with Facebook in order to obtain an access and secret keys.

Below is an example Javascript code for a Facebook login button:

$("#facebookLoginBtn").click(function() {
        window.location = "https://www.facebook.com/dialog/oauth?" +
                "response_type=code&client_id={FACEBOOK_APP_ID}" +
                "&scope=email&state=" + (new Date().getTime()) +
                "&redirect_uri=" + window.location.origin + "/facebook_auth";
        return false;
});

GitHub support

This describes the web authentication flow with GitHub. You could also login with an existing access token from GitHub through the API. This web flow sets a cookie, the API returns a JWT instead.

First of all you need to have your API credentials ready by creating an app on GitHub. Then add them to your application.conf configuration file:

para.gh_app_id = "..."
para.gh_secret = "..."

Or add these through the app settings API:

{
    "gh_app_id": "..."
    "gh_secret": "..."
    "signin_success": "http://success.url"
    "signin_failure": "http://failure.url"
}

If you want Para to generate a JWT token upon successful authentication, add the jwt=? parameter to your signin_success url. For example { "signin_success": "http://success.url?jwt=?" }. Para will redirect the user back to your host URL with the generated access token.

Support for logging in with GitHub is implemented by the GitHubAuthFilter. This filter responds to requests at /github_auth.

To initiate a login with GitHub just redirect the user to the GitHub OAuth endpoint:

github.com/login/oauth/authorize

Pass the parameter redirect_uri=/github_auth so Para can handle the response from GitHub. For apps other than the root app use redirect_uri=/github_auth?appid=myapp instead.

Note: You need to register a new application with GitHub in order to obtain an access and secret keys.

Below is an example Javascript code for a GitHub login button:

$("#githubLoginBtn").click(function() {
        window.location = "https://github.com/login/oauth/authorize?" +
                "response_type=code&client_id={GITHUB_APP_ID}" +
                "&scope=user&state=" + (new Date().getTime()) +
                "&redirect_uri=" + window.location.origin + "/github_auth";
        return false;
});

Google support

This describes the web authentication flow with Google. You could also login with an existing access token from Google through the API. This web flow sets a cookie, the API returns a JWT instead.

First of all you need to have your API credentials ready by creating an app in the Google Dev Console. Then add them to your application.conf configuration file:

para.gp_app_id = "..."
para.gp_secret = "..."

Or add these through the app settings API:

{
    "gp_app_id": "..."
    "gp_secret": "..."
    "signin_success": "http://success.url"
    "signin_failure": "http://failure.url"
}

If you want Para to generate a JWT token upon successful authentication, add the jwt=? parameter to your signin_success url. For example { "signin_success": "http://success.url?jwt=?" }. Para will redirect the user back to your host URL with the generated access token.

Support for logging in with Google acoounts is implemented by the GoogleAuthFilter. This filter responds to requests at /google_auth.

To initiate a login with Google just redirect the user to the Google OAuth endpoint:

accounts.google.com/o/oauth2/v2/auth

Pass the parameter redirect_uri=/google_auth so Para can handle the response from Google. For apps other than the root app use redirect_uri=/google_auth?appid=myapp instead.

Note: You need to register a new application with Google in order to obtain an access and secret keys.

Below is an example Javascript code for a Google login button:

$("#googleLoginBtn").click(function() {
        var baseUrl = window.location.origin;
        window.location = "https://accounts.google.com/o/oauth2/v2/auth?" +
                "client_id={GOOGLE_APP_ID}&response_type=code" +
                "&scope=openid%20email&redirect_uri=" + baseUrl + "/google_auth" +
                "&state=" + (new Date().getTime()) + "&" + "openid.realm=" + baseUrl;
        return false;
});

LinkedIn support

This describes the web authentication flow with LinkedIn. You could also login with an existing access token from LinkedIn through the API. This web flow sets a cookie, the API returns a JWT instead.

First of all you need to have your API credentials ready by creating an app on LinkedIn. Then add them to your application.conf configuration file:

para.in_app_id = "..."
para.in_secret = "..."

Or add these through the app settings API:

{
    "in_app_id": "..."
    "in_secret": "..."
    "signin_success": "http://success.url"
    "signin_failure": "http://failure.url"
}

If you want Para to generate a JWT token upon successful authentication, add the jwt=? parameter to your signin_success url. For example { "signin_success": "http://success.url?jwt=?" }. Para will redirect the user back to your host URL with the generated access token.

Support for logging in with LinkedIn is implemented by the LinkedInAuthFilter. This filter responds to requests at /linkedin_auth.

To initiate a login with LinkedIn just redirect the user to the LinkedIn OAuth endpoint:

linkedin.com/uas/oauth2/authorization

Pass the parameter redirect_uri=/linkedin_auth so Para can handle the response from LinkedIn. For apps other than the root app use redirect_uri=/linkedin_auth?appid=myapp instead.

Note: You need to register a new application with LinkedIn in order to obtain an access and secret keys.

Below is an example Javascript code for a LinkedIn login button:

$("#linkedinLoginBtn").click(function() {
        window.location = "https://www.linkedin.com/uas/oauth2/authorization?" +
                "response_type=code&client_id={LINKEDIN_APP_ID}" +
                "&scope=r_emailaddress&state=" + (new Date().getTime()) +
                "&redirect_uri=" + window.location.origin + "/linkedin_auth";
        return false;
});

Microsoft support

This describes the web authentication flow with Microsoft. You could also login with an existing access token from Microsoft through the API. This web flow sets a cookie, the API returns a JWT instead.

First of all you need to have your API credentials ready by creating an app on Microsoft. Then add them to your application.conf configuration file:

para.ms_app_id = "..."
para.ms_secret = "..."
para.security.signin_success = "http://success.url"
para.security.signin_failure = "http://failure.url"

Or add these through the app settings API:

{
    "ms_app_id": "..."
    "ms_secret": "..."
    "signin_success": "http://success.url"
    "signin_failure": "http://failure.url"
}

If you want Para to generate a JWT token upon successful authentication, add the jwt=? parameter to your signin_success url. For example { "signin_success": "http://success.url?jwt=?" }. Para will redirect the user back to your host URL with the generated access token.

Support for logging in with Microsoft accounts is implemented by the MicrosoftAuthFilter. This filter responds to requests at /microsoft_auth.

To initiate a login with Microsoft just redirect the user to the Microsoft OAuth endpoint:

login.microsoftonline.com/common/oauth2/v2.0/authorize

Pass the parameter redirect_uri=/microsoft_auth so Para can handle the response from Microsoft. Note: The v2.0 endpoint for OAuth authentication with Microsoft doesn’t work well with query parameters in the redirect_uri parameter. Instead, use the state parameter to remember the appid of your app (see example below).

Note: You need to register a new application with Microsoft in order to obtain an access and secret keys.

Below is an example Javascript code for a Microsoft login button:

$("#microsoftLoginBtn").click(function() {
        window.location = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?" +
                "response_type=code&client_id={MICROSOFT_APP_ID}" +
                "&scope=https%3A%2F%2Fgraph.microsoft.com%2Fuser.read&state={YOUR_APP_ID}" +
                "&redirect_uri=" + window.location.origin + "/microsoft_auth;
        return false;
});

Twitter support

This describes the web authentication flow with Twitter. You could also login with an existing access token from Twitter through the API. This web flow sets a cookie, the API returns a JWT instead.

First of all you need to have your API credentials ready by creating an app on Twitter. Then add them to your application.conf configuration file:

para.tw_app_id = "..."
para.tw_secret = "..."

Or add these through the app settings API:

{
    "tw_app_id": "..."
    "tw_secret": "..."
    "signin_success": "http://success.url"
    "signin_failure": "http://failure.url"
}

If you want Para to generate a JWT token upon successful authentication, add the jwt=? parameter to your signin_success url. For example { "signin_success": "http://success.url?jwt=?" }. Para will redirect the user back to your host URL with the generated access token.

Support for logging in with Twitter is implemented by the TwitterAuthFilter. This filter responds to requests at /twitter_auth.

To initiate a login with Twitter just redirect the user to the /twitter_auth. This will redirect the user to Twitter for authentication. For apps other than the root app, redirect to /twitter_auth?appid=myapp.

Note: You need to register a new application with Twitter in order to obtain an access and secret keys. The Twitter API does not share users’ emails by default, you have to ask Twitter to whitelist your app first.

Below is an example Javascript code for a Twitter login button:

$("#twitterLoginBtn").click(function() {
        window.location = "/twitter_auth";
        // for apps other than the root app use:
        // window.location = "/twitter_auth?appid=myapp";
        return false;
});

Groups and roles

Para defines three security groups and roles:

  • users group with role user (normal users)
  • mods group with role mod (moderators)
  • admins group with role admin (administrators)

There is also a special role app for applications.

CSRF protection

Cross-Site Request Forgery protection is enabled by default for all POST, PUT and DELETE requests. It works by verifying that incoming requests contain a security token (CSRF token) and it matches the one stored on the server. CSRF protection is disabled for API requests.

To disable the CSRF protection set para.security.csrf_protection to false. You can also tell Para to send a cookie containing the CSRF token by setting para.security.csrf_cookie as the cookie name. Leaving this parameter blank will disable the CSRF cookie.

For example, if you wish to integrate CSRF protection with AngularJS you can do so by setting the cookie name to XSRF-TOKEN which is the default cookie used by AngularJS. Also you need to configure AngularJS to send the correct CSRF header to Para which is X-CSRF-TOKEN like so:

$httpProvider.defaults.xsrfHeaderName = "X-CSRF-TOKEN";

In order to get a CSRF token from the server you need to send a POST request to a protected resource like POST /protected/ping and the server will return the token as cookie.

Authentication

Para uses the AWS Signature Version 4 algorithm for signing API requests. We chose this algorithm instead of OAuth because it is less complicated and is already implemented inside the AWS Java SDK, which we have a direct dependency. In terms of security, both algorithms are considered very secure so there’s no compromise in that aspect.

Para offers two ways of authentication - one for apps using API keys and one for insecure clients (mobile, JS) using JWT. Apps authenticated with a secret key have full access to the API. Users authenticated with social login are issued JWT tokens and have limited access to the API, for example they can’t generate new API keys and they are authorized by specific resource permissions (see Resource permissions).

Full access for apps

In order to make a request to the API you need to have a pair of access and secret keys. Access keys are part of the HTTP request and secret keys are used for signing only and must be kept safe.

We recommend that you choose one of our API client libraries to handle the authentication for you.

First-time setup

Call GET /v1/_setup to get your first key pair. Once you do this you will get back a response like:

{
    "secretKey": "U3VTNifLPqnZ1W2S3pVVuKG4HOVbimMocdDMl8T69BB001AXGZtwZw==",
    "accessKey": "YXBwOnBhcmE=",
    "info":        "Save the secret key! It is showed only once!"
}

Make sure you save these security credentials because the API can only be accessed with them. Once you have the keys you can start making signed requests to the API. Also you can use these keys to create applications which will have their own separate keys (see Apps).

Note: when a resource has public permissions you can access it without setting the Authorization header. Simply specify your access key as a parameter:

GET /v1/public/resource?accessKey=app:myapp

Changing keys

Call POST /v1/_newkeys to generate a new secret key (the request must be signed with the old keys).

Disabling API security

To disable the API security completely, set the config parameter para.security.api_security to false.

If you wish to disable all API functions completely, set the config parameter para.api_enabled" tofalse`.

For more information see the AWS documentation for REST authentication.

JSON Web Tokens - client access based on permissions

Important: Access to the root app can be enabled or disabled for API clients.

To allow clients with access tokens to make API calls to the root app, use this configuration property:

para.clients_can_access_root_app = true

This is useful if you only have one app on the server. When hosting multiple apps on Para, this should be set to false. This does not affect clients that use an accessKey and a secretKey, only those that use access tokens (JWT).

In production, when Para is deployed as a multitenant server (hosting many apps), it is recommended that API clients are not allowed to access root app through the API with para.clients_can_access_root_app = false.

Para apps can create new users and grant them specific permissions by implementing social login (identity federation). First a user authenticates with their social identity provider such as Facebook, then comes back to Para with the access_token and is issued a new JSON Web Token that allows him to access the REST API.

JWT tokens are a new standard for authentication which is similar to cookies but is more secure, compact and stateless. An encoded token looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG
4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

When decoded the token looks like this:

// HEADER:
{
  "alg": "HS256",
  "typ": "JWT"
}
// PAYLOAD:
{
  "sub": "587408205806571520",
  "appid": "app:para",
    "refresh": 1450137214490,
  "nbf": 1450133614,
    "exp": 1450738414,
    "iat": 1450133614
}

To authenticate with users with social login use the Para client:

paraClient.signIn(String provider, String providerToken);

Supported providers are facebook, google, twitter, github, linkedin, microsoft, password, oauth2, ldap.

You will have to add the API credentials for each of these in your application.conf configuration file:

# facebook
para.fb_app_id = "..."
para.fb_secret = "..."
# google
para.gp_app_id = "..."
para.gp_secret = "..."
# linkedin
para.in_app_id = "..."
para.in_secret = "..."
# twitter
para.tw_app_id = "..."
para.tw_secret = "..."
# github
para.gh_app_id = "..."
para.gh_secret = "..."
# microsoft
para.ms_app_id = "..."
para.ms_secret = "..."
# generic oauth2
para.oa2_app_id = "..."
para.oa2_secret = "..."

For example calling paraClient.signIn("facebook", "facebook_access_token") should return a new User object and would store the JWT token in memory. To get an access token from Facebook, use their JavaScript SDK.

After you call paraClient.signIn() and the request succeeds, the client caches the access token and all subsequent requests to the API will include that token until paraClient.signOut() is called. This is different from the normal operation using access and secret keys. Tokens can only authenticate users.

To get or set access tokens use:

paraClient.getAccessToken();
paraClient.setAccessToken(String token);

To sign out and clear the JWT access token use:

paraClient.signOut();

Tokens can also be revoked by calling paraClient.revokeAllTokens() but this only works for authenticated users. The Para client takes care of refreshing the JWT tokens every hour and by default all tokens are valid for a week.

Creating “super” tokens

Since v1.18.3 we’ve added the support for “super” JSON web tokens. These are just like normal tokens for users, but instead of authenticating users we can authenticate apps with them. The give clients full access to the API, bypassing permissions. You don’t need to connect to a social identity provider like Facebook or Twitter - you simply generate the tokens on the client-side. Your code will need both the access key and secret key for this purpose.

For example, lets assume we have some JavaScript app running in the browser and we need admin access to our Para app. We could use the JavaScript client for Para but putting the secret key inside client-side code on the browser is not a smart move. So we pull in a library for generating JWT, like jsrsasign and we create the token ourselves. Here’s a snippet:

function getJWT(appid, secret) {
    var now = Math.round(new Date().getTime() / 1000);
    var sClaim = JSON.stringify({
        exp: now + (7 * 24 * 60 * 60), // expires at
        iat: now, // issued at
        nbf: now, // not valid before
        appid: appid // app id must be present
    });
    var sHeader = JSON.stringify({'alg': 'HS256', 'typ': 'JWT'});
    return KJUR.jws.JWS.sign(null, sHeader, sClaim, secret);
}

Your JS app could ask the user for the access keys, create a JWT and then discard the keys and use the newly generated “super” token. Once we have this we can attach it to every request as a header:

Authorization: Bearer eyJhbGciOiVCJ9.eyJzdWIiOi0._MKAH6SGiKSoqgwmqUaxuMyE

When calling the Para API from JavaScript in the browser, make sure you are running a web server and not as file:/// or your browser might not allow CORS requests. Also check that CORS is enabled in Para with para.cors_enabled = true.

API methods

All API methods below require authentication by default unless it’s written otherwise.

Limiting which fields are returned by the API

Field limiting is supported on all requests by using the query parameter select=xxx,yyy. This parameter takes a comma separated list of fields to include. For example:

GET /myobjects?select=id,name,my_field1,my_field2



▾▴ collapse/expand all

POST /jwt_auth

Takes an identity provider access token and fetches the user data from that provider. A new User object is created if that user doesn’t exist and is then returned. Access tokens are returned upon successful authentication using one of the SDKs from Facebook, Google, Twitter, etc.

Note: Twitter uses OAuth 1 and gives you a token and a token secret so you must concatenate them first - {oauth_token}:{oauth_token_secret}, and then use that as the provider access token. Also if you use the password provider, the token parameter must be in the format {email}:{full_name}:{password} or {email}::{password} (must be :: if name is empty). For LDAP the token looks similar - {uid}:{password} (single :).

Also keep in mind that when a new user signs in with a password and unverified email, through /jwt_auth, Para will create the user but will return an error 400 indicating that the user is not active and cannot be authenticated. Once the email is verified and the user is set to active: true, subsequent sign in attempts will be successful.

Request

  • body - a JSON object containing appid, provider and token properties (required).

Request body example for authenticating with email and password:

{
    "appid": "app:myapp",
    "provider": "password",
    "token": "[email protected]::password123"
}

Request body example for Facebook:

{
    "appid": "app:myapp",
    "provider": "facebook",
    "token": "eyJhbGciOiJIUzI1NiJ9.eWIiO..."
}

The appid is the id of your own app that you’re trying to sign in to. The provider field a string and can be one of the following values:

  • facebook - sign in with Facebook account,
  • google - sign in with Google account,
  • twitter - sign in with Twitter account,
  • github - sign in with GitHub account,
  • linkedin - sign in with LinkedIn account,
  • microsoft - sign in with Microsoft account,
  • password - sign in with email and password.
  • oauth2 - sign in with generic OAuth 2.
  • ldap - sign in with LDAP.

Response

Returns a JSON object containing JWT properties and a User object. The returned JWT properties are:

  • access_token - the JWT access token.
  • expires - a Java timestamp of when the token will expire.
  • refresh - a Java timestamp indicating when API clients should refresh their tokens, usually 1 hour after token has been issued.

  • status codes - 200, 400

Example response:

{
    "jwt": {
        "access_token": "eyJhbGciOiJIUzI1NiJ9.eyJ...",
        "expires": 1450137214490,
        "refresh": 1450137216490
    },
    "user": {
        "id":"user1",
        "timestamp": 1399721289987,
        "type":"user",
        "appid":"myapp",
        ...
    }
}

GET /jwt_auth

Refreshes the access token if and only if the provided token is valid and not expired. Tokens should be refreshed periodically in order to keep users logged in for longer periods of time.

Request

Request should include an Authorization: Bearer {JWT_TOKEN} header containing a valid access token. (required) As an alternative you could provide the token as query parameter instead of a header.

Response

Returns a JSON object containing a new JWT token and the same User object. The returned JWT properties are:

  • access_token - the JWT access token.
  • expires - a Java timestamp of when the token will expire.
  • refresh - a Java timestamp indicating when API clients should refresh their tokens.

  • status codes - 200, 400

Example response:

{
    "jwt": {
        "access_token": "eyJhbGciOiJIUzI1NiJ9.eyJ...",
        "expires": 1450137214490,
        "refresh": 1450137216490
    },
    "user": {
        "id":"user1",
        "timestamp": 1399721289987,
        "type":"user",
        "appid":"myapp",
        ...
    }
}

DELETE /jwt_auth

Revokes all user tokens for the user that is currently logged in. This would be equivalent to “logout everywhere”. Note: Generating a new API secret on the server will also invalidate all client tokens.

Request

Request should include an Authorization: Bearer {JWT_TOKEN} header containing a valid access token. (required) As an alternative you could provide the token as query parameter instead of a header.

Response

Returns a JSON object containing a new JWT token and the same User object. The returned JWT properties are:

  • access_token - the JWT access token.
  • expires - a Java timestamp of when the token will expire.
  • refresh - a Java timestamp indicating when API clients should refresh their tokens.

  • status codes - 200, 400

Example response (response is empty):

200 OK

POST /v1/{type}

Creates a new object of type {type}. You can also create objects with custom types and fields (since v1.4.0).

Request

  • body - the JSON object to create
  • {type} - the plural form of the object’s type, e.g. “users”

Example request body:

{
    "type":"tag",
    "plural":"tags",
    "tag":"tag1"
}

Notice how the type field is in singular form and the plural field is the plural form of the type’s name. These are required for mapping types to URLs.

Response

  • status codes - 201, 400

Example response for a Tag:

{
    "id":"tag:tag1",
    "timestamp":1399721289987,
    "type":"tag",
    "appid":"para",
    "name":"tag tag:tag1",
    "votes":0,
    "plural":"tags",
    "objectURI":"/tags/tag1",
    "tag":"tag1",
    "count":0
}

GET /v1/{type}/{id}

Returns an object of type {type}.

Request

  • {type} - the plural form of the object’s type, e.g. “users”
  • {id} - the id

Note: If {id} is omitted then the response will be a list of all objects of the specified type.

Response

  • status codes - 200, 404

Example response for a Tag:

{
    "id":"tag:tag1",
    "timestamp":1399721289987,
    "type":"tag",
    "appid":"para",
    "name":"tag tag:tag1",
    "votes":0,
    "plural":"tags",
    "objectURI":"/tags/tag1",
    "tag":"tag1",
    "count":0
}

PUT /v1/{type}/{id}

Overwrites an object of type {type}. If the object with this {id} doesn’t exist then a new one will be created.

Request

  • body - the JSON data to merge with the stored object
  • {type} - the plural form of the object’s type, e.g. “users”
  • {id} - the id

Response

  • status codes - 200, 400, 404, 500

Example response for a Tag with updated count:

{
    "id":"tag:tag1",
    "timestamp":1399721289987,
    "type":"tag",
    "appid":"para",
    "name":"tag tag:tag1",
    "votes":0,
    "plural":"tags",
    "objectURI":"/tags/tag1",
    "tag":"tag1",
    "count":55
}

PATCH /v1/{type}/{id}

Updates an existing object of type {type}. Partial objects are supported, meaning that only a few fields could be updated, without having to send the whole object.

Vote requests: these are a special kind of PATCH request, which has a body like {"_voteup": "user123"} or {"_votedown": "user123"}. Here user123 is the id of the voter. A successful vote request either increments or decrements the votes field by 1.

Request

  • body - the JSON object to merge with the stored object OR a vote request body like {"_voteup": "user123"}
  • {type} - the plural form of the object’s type, e.g. “users”
  • {id} - the id

Response

  • status codes - 200, 400, 404, 500, vote requests return true or false

Example response for a Tag with updated count:

{
    "id":"tag:tag1",
    "timestamp":1399721289987,
    "type":"tag",
    "appid":"para",
    "name":"tag tag:tag1",
    "votes":0,
    "plural":"tags",
    "objectURI":"/tags/tag1",
    "tag":"tag1",
    "count":55
}

DELETE /v1/{type}/{id}

Deletes an existing object of type {type}. Returns code 200 regardless of the success of the request.

Request

  • {type} - the plural form of the object’s type, e.g. “users”
  • {id} - the id

Response

  • status codes - 200, 400

No content.

POST /v1/_batch

Creates multiple objects with a single request. PUT requests to this resource are equivalent to POST.

Request

  • body - a JSON array of objects to create (required).

Maximum request size is 1 megabyte.

Response

  • status codes - 200, 400

Example response for creating 3 objects (returns a list of the created objects):

[ { "id":"id1", ... }, { "id":"id2", ... }, { "id":"id3", ... } ]

GET /v1/_batch

Returns a list of objects given their id fields.

Parameters

  • ids - a list of ids of existing objects (required).

Example: GET /v1/_batch?ids=id1&ids=id2&ids=id3

Response

  • status codes - 200, 400 (if no ids are specified)

Example response for reading 3 objects:

[ { "id":"id1", ... }, { "id":"id2", ... }, { "id":"id3", ... } ]

PATCH /v1/_batch

Updates multiple objects with a single request. Partial objects are supported. Note: These objects will not be validated as this would require us to read them first and validate them one by one.

Request

  • body - a JSON array of objects to update (required). The fields id and type are required for each object.

Maximum request size is 1 megabyte.

Response

  • status codes - 200, 400

Example response for updating 3 objects (returns a list of the updated objects):

[ {
    "id":"id1",
    "type":"type1",
    "name":"newName1", ...
  }, {
    "id":"id2",
    "type":"type2",
    "name":"newName2", ...
  }, {
     "id":"id3",
     "type":"type3",
     "name":"newName3", ...
} ]

DELETE /v1/_batch

Deletes multiple objects with a single request.

Parameters

  • ids - a list of ids of existing objects (required).

Example: DELETE /v1/_batch?ids=1&ids=2 will delete the two objects with an id of 1 and 2, respectively.

Response

  • status codes - 200, 400 (if request maximum number of ids is over the limit of ~30)

No content.

GET /v1/{type}

Searches for objects of type {type}.

Note: Requests to this path and /v1/{type}/search/{querytype} are handled identically. Also, note that custom fields must be used in search queries as properties.myfield.

For detailed syntax of the query string see Lucene’s query string syntax.

Request

  • {type} - the plural form of the object’s type, e.g. “users”
  • {querytype} - the type of query to execute (optional, see Search)

Parameters

  • q - a search query string (optional). Defaults to * (all).
  • desc - sort order - true for descending (optional). Default is true.
  • sort - the field to sort by (optional).
  • limit - the number of results to return. Default is 30.
  • page - starting page for results (optional). (note: page size is 30 items by default)

Response

  • status codes - 200

Example response for querying all tags GET /v1/tags?q=*&limit=3:

{
    "page":0,
    "totalHits":3,
    "items":[{
        "id":"tag:tag3",
        "timestamp":1400077389250,
        "type":"tag",
        "appid":"para",
        "name":"tag tag:tag3",
        "votes":0,
        "plural":"tags",
        "objectURI":"/tags/tag3",
        "tag":"tag3",
        "count":0
    }, {
        "id":"tag:tag1",
        "timestamp":1400077383588,
        "type":"tag",
        "appid":"para",
        "name":"tag tag:tag1",
        "votes":0,
        "plural":"tags",
        "objectURI":"/tags/tag1",
        "tag":"tag1",
        "count":0
    }, {
        "id":"tag:tag2",
        "timestamp":1400077386726,
        "type":"tag",
        "appid":"para",
        "name":"tag tag:tag2",
        "votes":0,
        "plural":"tags",
        "objectURI":"/tags/tag2",
        "tag":"tag2",
        "count":0
    }]
}

GET /v1/search/{querytype}

Executes a search query.

Note: custom fields must be used in search queries as properties.myfield.

Request

  • {querytype} - the type of query to execute (optional), use one of types below:

The querytype parameter switches between the different query types. If this parameter is missing then the generic findQuery() method will be executed by default.

id query

Finds the object with the given id from the index or null. This executes the method findById() with these parameters:

  • id - the id to search for

ids query

Finds all objects matchings the given ids. This executes the method findByIds() with these parameters:

  • ids - a list of ids to search for

nested query

Searches through objects in a nested field named nstd. Used internally for joining search queries on linked objects. This executes the method findNestedQuery() with these parameters:

  • q - a search query string
  • field - the name of the field to target within a nested object
  • type - a type to search for

nearby query

Location-based search query. Relies on Address objects for coordinates. This executes the method findNearby() with these parameters:

  • latlng - latitude and longitude of the center of the search perimeter
  • radius - radius of the search perimeter in kilometers.

prefix query

Searches for objects containing a field (property) that starts with the given prefix. This executes the method findPrefix() with these parameters:

  • field - the field to search on
  • prefix - the prefix

similar query

“More like this” search query. This executes the method findSimilar() with these parameters:

  • fields - a list of fields, for example:
    GET /v1/search/similar?fields=field1&fields=field2
    
  • filterid - an id filter; excludes a particular object from the results
  • like - the source text to use for comparison

tagged query

Search for objects tagged with a set of tags. This executes the method findTagged() with these parameters:

  • tags - a list of tags, for example:
    GET /v1/search/tagged?tags=tag1&tags=tag2
    

in query

Searches for objects containing any of the terms in the given list (matched exactly). This executes the method findTermInList() with these parameters:

  • field - the field to search on
  • terms - a list of terms (values)

terms query

Searches for objects containing all of the specified terms (matched exactly) This executes the method findTerms() with these parameters:

  • matchall - if true executes an AND query, otherwise an OR query
  • terms - a list of field:term pairs, for example:
    GET /v1/search/terms?terms=field1:term1&terms=field2:term2
    
  • count - if present will return 0 objects but the “totalHits” field will contain the total number of results found that match the given terms.

Since v1.9, the terms query supports ranges. For example if you have a pair like 'age':25 and you want to find objects with higher age value, you can modify the key to have a relational operator 'age >':25. You can use the >, <, >=, <= operators by appending them to the keys of the terms map.


wildcard query

A wildcard query like “example“. This executes the method findWildcard() with these *parameters:

  • field - the field to search on

count query

Returns the total number of results that would be returned by a query. This executes the method getCount() with no parameters.

Request parameters

  • q - a search query string (optional). Defaults to * (all).
  • type - the type of objects to search for (optional). prefix, tagged, in, terms, wildcard, count. (see Search)
  • desc - sort order - true for descending (optional). Default is true.
  • sort - the field to sort by (optional).
  • page - starting page for results (optional). (note: page size is 30 items by default)
  • limit - the number of results to return

Response

  • status codes - 200

Example response for counting all objects (just three for this example):

{
    "page":0,
    "totalHits":3,
    "items":[
    ]
}

GET /v1/{type}/{id}/links/{type2}/{id2}

Call this method to search for objects that linked to the object with the given {id}.

Note: When called with the parameter ?childrenonly, the request is treated as a “one-to-many” search request. It will do asearch for child objects directly connected to their parent by the parentid field. Without ?childrenonly the request is treated as a “many-to-many” search request.

Request

  • {type} - the type of the first object, e.g. “users”
  • {id} - the id of the first object
  • {type2} - the type of the second object (required)
  • {id2} - the id field of the second object (optional)

Parameters

  • childrenonly - if set and {id2} is not set, will return a list of child objects (these are the objects with parentid equal to {id} above). Also if field and term parameters are set, the results are filtered by the specified field and the value of that field (term).
  • count - if set will return no items an the total number of linked objects. If childrenonly is set, this will return only the count of child objects.
  • q - query string, if set, all linked/child objects will be searched and those that match the query are returned. To search only child objects that are linked by parentid use q in combination with childrenonly, otherwise the Linker objects will be searched. Since v1.19 Linker objects contain a copy of the two objects they connect. This enables Para to execute more complex “joined” search queries. The effectiveness of these is determined by how up-to-date the data inside a Linker is.

Response

  • If the {id2} parameter is specified, the response will be a boolean text value - true if objects are linked.
  • If the {id2} parameter is missing, the response will be a list of linked objects. (pagination parameters are applicable)
  • childrenonly - if set, the response will be a list of child objects (pagination parameters are applicable)

  • status codes - 200, 400 (if type parameter is missing)

Example response if id is missing:

{
    "page":X,
    "totalHits":Y,
    "items":[
        ...
    ]
}

Response if id is specified: true or false

POST /v1/{type}/{id}/links/{id2}

This will link the object with {id} to another object with the specified id in the id parameter. The created link represents a many-to-many relationship (see also one-to-many relationships).

Don’t use this method for “one-to-many” links. Creating one-to-many links is trivial - just set the parentid of an object (child) to be equal to the id field of another object (parent).

PUT requests to this resource are equivalent to POST.

Request

  • {type} - the type of the first object, e.g. “users”
  • {id} - the id of the first object
  • {id2} - the id field of the second object (required)

Response

Returns the id of the Linker object - the linkId - which contains the types and ids of the two objects.

  • status codes - 200, 400 (if any of the parameters are missing)

Example response:

"type1:id1:type2:id2"

DELETE /v1/{type}/{id}/links/{type2}/{id2}

Unlinks or deletes the objects linked to the object with the specified {id}.

Request

  • {type} - the type of the first object, e.g. “users”
  • {id} - the id of the first object
  • {type2} - the type of the second object (not required, if this and {id2} are missing, it will unlink everything)
  • {id2} - the id field of the second object (optional)

Parameters

  • all - setting this will delete all linked objects (be careful!)
  • childrenonly - if set, all child objects will be deleted rather than unlinked (be careful!)

Note:

  • If both {type2} and {id2} are not set, all linked objects will be unlinked from this one.
  • If id is set - the two objects are unlinked.
  • If all and id are not set, but childrenonly is set then the child objects with type type are deleted! (these are the objects with parentid equal to {id} above)

Response

  • status codes - 200

No content.

PUT /v1/_constraints/{type}/{field}/{cname}

Adds a new validation constraint to the list of constraints for the given field and type.

Request

  • body - the JSON payload of the constraint (see the table below)
  • {type} - the object type to which the constraint applies (required)
  • {field} - the name of the field to which the constraint applies (required)
  • {cname} - the constraint name (required), one of the following:

    Name Payload (example)
    requirednone
    emailnone
    falsenone
    truenone
    pastnone
    presentnone
    urlnone
    min{ "value": 123 }
    max{ "value": 123 }
    size{ "min": 123, "max": 456 }
    digits{ "integer": 4, "fraction": 2 }
    pattern{ "value": "^[a-zA-Z]+$" }

Response

Returns a JSON object containing the validation constraints for the given type.

  • status codes - 200, 400

Example response:

{
    "User" : {
        "identifier" : {
            "required" : {
                "message" : "messages.required"
            }
        },
        "groups" : {
            "required" : {
                "message" : "messages.required"
            }
        },
        "email" : {
            "required" : {
                "message" : "messages.required"
            },
            "email" : {
                "message" : "messages.email"
            }
        }
    },
    ...
}

GET /v1/_constraints/{type}

Returns an object containing all validation constraints for all defined types in the current app. This information can be used to power client-side validation libraries like valdr.

Request

  • {type} - when supplied, returns only the constraints for this type (optional). If this parameter is omitted, all constraints for all types will be returned.

Response

Returns a JSON object with all validation constraints for a given type. The message field is a key that can be used to retrieve a localized message.

  • status codes - 200

Example response:

{
    "User" : {
        "identifier" : {
            "required" : {
                "message" : "messages.required"
            }
        },
        "groups" : {
            "required" : {
                "message" : "messages.required"
            }
        },
        "email" : {
            "required" : {
                "message" : "messages.required"
            },
            "email" : {
                "message" : "messages.email"
            }
        }
    },
    ...
}

DELETE /v1/_constraints/{type}/{field}/{cname}

Removes a validation constraint from the list of constraints for the given field and type.

Request

  • {type} - the object type to which the constraint applies (required)
  • {field} - the name of the field to which the constraint applies (required)
  • {cname} - the constraint name (required, see the table above)

Response

Returns a JSON object containing the validation constraints for the given type.

  • status codes - 200, 400

Example response:

{
    "User" : {
        "identifier" : {
            "required" : {
                "message" : "messages.required"
            }
        },
        "groups" : {
            "required" : {
                "message" : "messages.required"
            }
        },
        "email" : {
            "required" : {
                "message" : "messages.required"
            },
            "email" : {
                "message" : "messages.email"
            }
        }
    },
    ...
}

GET /v1/_id/{id}

Returns the object for the given {id}.

Request

  • {id} - the id

Response

Returns a JSON object.

  • status codes - 200, 404

Example response:

{
    "id" : "417283630780387328",
  "timestamp" : 1409572755025,
  "type" : "user",
    "name" : "Gordon Freeman"
    ...
}

GET /v1/_me

Returns the currently authenticated User or App object. If the request is unauthenticated 401 error is returned.

Request

No parameters.

Response

Returns the JSON object for the authenticated User or App.

  • status codes - 200, 401

Example response:

{
    "id" : "417283630780387328",
  "timestamp" : 1409572755025,
  "type" : "user",
    "name" : "Gordon Freeman"
    ...
}

GET /v1/_types

Returns a list of all known types for this application, including core types and user-defined types. User-defined types are custom types which can be defined through the REST API and allow the users to call the standard CRUD methods on them as if they were defined as regular Para objects. See User-defined classes for more details.

Request

No parameters.

Response

Returns a list of all types that are defined for this application.

  • status codes - 200

Example response for querying all types:

[
    "addresses":"address",
    "apps":"app",
    "sysprops":"sysprop",
    "tags":"tag",
    "translations":"translation",
    "users":"user",
    "votes":"vote"
]

GET /v1/_permissions/{subjectid}/{resource}/{method}

This checks if a subject is allowed to execute a specific type of request on a resource.

There are several methods and flags which control which requests can go through. These are:

  • GET, POST, PUT, PATCH, DELETE - use these to allow a certain method explicitly
  • ? - use this to enable public (unauthenticated) access to a resource
  • - - use this to deny all access to a resource
  • * - wildcard, allow all request to go through
  • OWN - allow subject to only access objects they created

Request

  • {subjectid} - the subject/user id to grant permissions to. (required)
  • {resource} - the resource path or object type (URL encoded). (required)
  • {method} - an HTTP method or flag, listed above. (required)

Response

Returns a boolean plain text response - true or false.

  • status codes - 200, 400, 404

Example response:

true

GET /v1/_permissions/{subjectid}

Returns a permissions objects containing all permissions. If {subjectid} is provided, the returned object contains only the permissions for that subject.

Request

  • {subjectid} - the subject/user id (optional)

Response

Returns a JSON object containing the resource permissions for the given user.

  • status codes - 200, 400, 404

Example response:

{
  "*": {
        "*": ["GET"]
    },
  "user1": [],
  "user2": {
        "posts": ["GET", "POST"]
    },
  "user3": {
    "*": ["*"]
  }
}

PUT /v1/_permissions/{subjectid}/{resource}

Grants a set of permissions (allowed HTTP methods) to a subject for a given resource.

There are several methods and flags which control which requests can go through. These are:

  • GET, POST, PUT, PATCH, DELETE - use these to allow a certain method explicitly
  • ? - use this to enable public (unauthenticated) access to a resource
  • - - use this to deny all access to a resource
  • * - wildcard, allow all request to go through
  • OWN - allow subject to only access objects they created

Request

  • body - a JSON array of permitted HTTP methods/flags, listed above (required).
  • {subjectid} - the subject/user id to grant permissions to (required)
  • {resource} - the resource path or object type (URL encoded), for example posts corresponds to /v1/posts, posts%2F123 corresponds to /v1/posts/123 (required)

Response

Returns a JSON object containing the resource permissions for the given user.

  • status codes - 200, 400, 404

Example response:

{
  "user2": {
        "posts": ["GET", "POST"]
    }
}

DELETE /v1/_permissions/{subjectid}/{resource}

Revokes all permissions for a given subject and resource. If {resource} is not specified, revokes every permission that has been granted to that subject.

Request

  • {subjectid} - the subject/user id to grant permissions to (required)
  • {resource} - the resource path or object type (URL encoded). If omitted, all permissions for that subject will be revoked. (optional)

Response

Returns a JSON object containing the resource permissions for the given user.

  • status codes - 200, 400, 404

Example response:

{
  "user1": [],
}

GET /v1/_settings

Lists all custom app settings. These can be user-defined key-value pairs and are stored withing the app object.

Request

No parameters.

Response

Returns an map of keys and values.

  • status codes - 200

Example response:

{
    "fb_app_id": "123U3VTNifLPqnZ1W2",
    "fb_secret": "YXBwOnBhcmE11234151667",
    "signin_success": "/dashboard",
    "signin_failure": "/signin?error"
}

PUT /v1/_settings/{key}

Adds a new custom app setting or overwrites an existing one. To overwrite all app settings, make a PUT request without providing the key parameter, like so:

PUT /v1/_settings
{
    "fb_app_id": "123U3VTNifLPqnZ1W2",
    "fb_secret": "YXBwOnBhcmE11234151667",
    "signin_success": "/dashboard",
    "signin_failure": "/signin?error"
}

This will replace all app-specific settings with the JSON object in that request.

Request

  • body - a JSON object with a single value field: { "value": "setting_value" }, or an object containing all app-specific configuration properties.
  • {key} - a key from the settings map (optional). If {key} is missing, all app settings will be overwritten by the JSON in the body of the request.

Response

Returns an empty response.

  • status codes - 200

GET /v1/_setup/{app_name}

Requires authentication with the root app access/secret keys

Creates a new child app and generates the first pair of API keys. You are responsible for creating the database table and index for the new app. Equivalent to calling Para.newApp() from your Java code.

Request

  • {app_name} - the name of the child app

Parameters

  • sharedTable - Set it to false if the app should have its own table
  • sharedIndex - Set it to false if the app should have its own index

Response

Returns the access and secret keys for this application which will be used for request signing.

  • status codes - 200, 403

GET /v1/_setup

Creates the root application and generates the first pair of API keys. Calling this method will enable you to access the REST API.

Request

No parameters.

Response

Returns the access and secret keys for this application which will be used for request signing.

  • status codes - 200

Example response:

{
    "secretKey": "U3VTNifLPqnZ1W2S3pVVuKG4HOVbimMocdDMl8T69BB001AXGZtwZw==",
    "accessKey": "YXBwOnBhcmE=",
    "info": "Save the secret key! It is showed only once!"
}

Subsequent calls to this method return:

{
    "code": 200
    "message": "All set!"
}

POST /v1/_newkeys

This will reset your API secret key by generating a new one. Make sure you save it and use it for signing future requests.

Request

No parameters.

Response

Returns the access and secret keys for this application which will be used for request signing.

  • status codes - 200

Example response:

{
    "secretKey": "U3VTNifLPqnZ1W2S3pVVuKG4HOVbimMocdDMl8T69BB001AXGZtwZw==",
    "accessKey": "YXBwOnBhcmE=",
    "info": "Save the secret key! It is showed only once!"
}

GET /v1/utils/{method}

Utility functions which can be accessed via the REST API include (listed by the method’s short name):

  • newid - calls Utils.getNewId()
  • timestamp - calls Utils.timestamp()
  • formatdate - calls Utils.formatDate(format, locale), additional parameters: format (e.g. “dd MM yyyy”), locale.
  • formatmessage - calls Utils.formatMessage(msg, params), additional parameters: message (the message to format)
  • nospaces - calls Utils.noSpaces(str, repl), additional parameters: string (the string to process)
  • nosymbols - calls Utils.stripAndTrim(str), additional parameters: string (the string to process)
  • md2html - calls Utils.markdownToHtml(md), additional parameters: md (a Markdown string)
  • timeago - calls HumanTime.approximately(delta), additional parameters: delta (time difference between now and then)

Example: GET /v1/utils?method=nospaces&string=some string with spaces

Request

  • {method} - the name of the method to call (one of the above)

Response

Returns a JSON object.

  • status codes - 200, 400 (if no method is specified)

Example response - returns the result without envelope:

"result"