The 10Duke SDK Developer Guide 10DukeSDK_developer-guide.pdf

Table of Contents

What is the 10Duke SDK?

Web application development

The 10Duke SDK has been designed to make it easier and faster to develop innovative web applications. It contains many concepts and design patterns that should make it substantially easier for you as the application developer to create your application. It can be used for different types of packaging, ranging from standard .war to custom formats with individual class paths and library management.

Inversion of Control (IoC)

The 10Duke SDK is best described as an IoC (Inversion of Control) container. Developing applications using the SDK uses a programming technique where you, as the application developer, are mostly using APIs and contracts in your development while being fully decoupled from the actual implementation fulfilling the contract of the API.

The primary pattern we have used to provide IoC is called Service Locator (the other main alternative is called Dependency Injection, see also this Wikipedia article).

In practice, inversion of control means that you as a developer may use selected default implementations provided by the SDK but that you also retain complete freedom to replace any or all of those defaults with your own implementations should you prefer to.

Content and Media

Most modern web applications involve dealing with content and media. In order to meet this need, the 10Duke SDK includes the concepts of storage, meta data, conversions (transcoding), and logic relating to serving content.

RESTful API

The 10Duke SDK has built-in options for exposing a REST API including data access and application logic as an HTTP(S) server interface.

Identity

The definition and logic around the notion of Identity varies between applications and systems. The 10Duke SDK is flexible and supports both human and machine users as a model. Using the SDK you are also free to extend the model and logic dealing with identities as you choose.

Authorization and Entitlement

The 10Duke SDK applies a versatile scoped authorization model. Authorization can be based both on a standard Role-based model as well as a License-based model. Authorization checks and decisions can be made in scopes like: internal, group, company, and related application (e.g. OAuth consumer). Authorization also includes built-in roles that allow efficient configuration using concepts like viewer, authenticated user, owner, author, etc.

Object model

An object model exists to represent the business entities an application deals with. The 10Duke SDK introduces an object model framework with dynamic relations. The benefit of this approach is that every application can model the relationships between its objects to exactly match its particular requirements. The SDK object model includes approximately 100 classes that will help you get started quickly on the most common use cases.

Object Graph

An object graph is a collection of objects related to each other. Through these relationships they form a graph. The dynamic relation model together with a meta information model in the 10Duke SDK allows the expression of object graphs. These makes implementing application logic and User Interface (UI) items easy and efficient. The concept of Graph is also reflected in the REST API provided by the 10Duke SDK.

Getting Started

Getting the 10Duke SDK

The following instructions are based on using Maven.

For other Java project types, this information can be used and applied by manually accessing the required artifacts and placing them in the alternative project structure (e.g. placing jar files in a path referenced by an ant project).

  1. Create a Maven Web Application using the wizards of your favourite IDE. Use .war as the build target type if that choice is available.
  2. Add 10Duke SDK repository to your applications pom file (or maven settings.xml, which ever you prefer):
    <repositories>
     ...
     <repository>
         <id>tenduke</id>
         <name>10Duke_Public</name>
         <url>
             https://maven.10duke.com/content/groups/public/
         </url>
     </repository>
     ...
    </repositories>
  3. Add authentication configuration for making requests to the 10Duke SDK repository. This configuration is added to the local Maven settings file in .m2/setting.xml. Note how the id element in each server entry references the corresponding id for repositories added in the previous step:
    <settings>
     ...
     <servers>
         <server>
             <id>tenduke</id>
             <username>YOUR_USER_NAME</username>
             <password>YOUR_PASSWORD</password>
         </server>
     </servers>
     ...
    </settings>
  4. Add dependencies to the libraries your application will use. e.g. a good starting point for web applications would include:

    • com.tenduke.lifecycle.webapp
    • com.tenduke.objectmodel
    • com.tenduke.jdbc.hsql2
    • com.tenduke.persistence.graph
    • com.tenduke.persistence.jdbc
    • com.tenduke.persistence.storage
    • com.tenduke.service
    • com.tenduke.storage

    Add the listed jar artifacts to the dependency section of the project's pom file. The SDK artifacts can be found at https://maven.10duke.com:

    <dependencies>
     ...
    <dependency>
         <groupId>com.tenduke.sdk</groupId>
         <artifactId>com.tenduke.lifecycle.webapp</artifactId>
         <version>2.0-SNAPSHOT</version>
     </dependency>
     <dependency>
         <groupId>com.tenduke.sdk</groupId>
         <artifactId>com.tenduke.objectmodel</artifactId>
         <version>2.0-SNAPSHOT</version>
     </dependency>
     <dependency>
         <groupId>com.tenduke.sdk</groupId>
         <artifactId>com.tenduke.persistence.graph</artifactId>
         <version>2.0-SNAPSHOT</version>
     </dependency>
     <dependency>
         <groupId>com.tenduke.sdk</groupId>
         <artifactId>com.tenduke.persistence.jdbc</artifactId>
         <version>2.0-SNAPSHOT</version>
     </dependency>
     <dependency>
         <groupId>com.tenduke.sdk</groupId>
         <artifactId>com.tenduke.persistence.storage</artifactId>
         <version>2.0-SNAPSHOT</version>
     </dependency>
     <dependency>
         <groupId>com.tenduke.sdk</groupId>
         <artifactId>com.tenduke.service</artifactId>
         <version>2.0-SNAPSHOT</version>
     </dependency>
     <dependency>
         <groupId>com.tenduke.sdk</groupId>
         <artifactId>com.tenduke.storage</artifactId>
         <version>2.0-SNAPSHOT</version>
     </dependency>
     ...
    </dependencies>
  5. Add an empty configuration file to WEB-INF/conf.xml with contents:
    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
    <properties version="1.0">
    <!-- Use XML comments to document the configuration file -->
    <entry key="example.configuration.key">example configured value</entry>
    </properties>
  6. Using Maven you can build the .war with e.g. "mvn clean install" to validate the dependency configuration.

Setting up a Web Application

Connecting SDK initialization to the web application life-cycle

Some features and functions of the SDK can be used without any special initialization while others require the application to tell the SDK that things need to start. We have used the term 'life-cycle' in relation to controlling the initialization and clean-up logic. In web applications this may be done by means of a simple configuration as follows:

Add the following fragment to the project's web.xml file:

<listener>
    <description>
        A ServletContextListener that listens to servlet
        context initialization and destruction, and notifies observers
        of lifecycle phase changes. ServletContextLifecycleManager
        should only be configured on one context.
    </description>
    <listener-class>com.tenduke.sdk2.lifecycle.webapp.ServletContextLifecycleManager</listener-class>
</listener>

This entry will automatically connect the SDK initialization and clean-up with the web application's life-cycle. Initialization of resources will be done when the web application is deployed and equally the clean-up is done when the web application is stopped / undeployed. Initializing the use of a database is a good example of something that requires explicit actions on behalf of an application.

Database and storage configuration basics

Most web applications need a database and file storage of some sort. The basics of configuring a relational database and file storage for an application is shown next. In the following example, HSQL database version 2 is used for simplicity, but any other supported JDBC or NoSql database can be used with the SDK.

  1. Add a folder called persistence under the WEB-INF folder
  2. Create a file called persistence.xml in that folder
  3. Next add content for using HSQL in embedded mode, which will enable your application to persist entities using HSQL as the database:
    <ctspg:GraphEntityManager
         xmlns:rel="http://10duke.com/schema/built-in/related"
         xmlns:ser="http://10duke.com/schema/built-in/serialization"
         xmlns:ctspg="http://10duke.com/package/com/tenduke/sdk2/persistence/graph"
         xmlns:ctsou="http://10duke.com/package/com/tenduke/sdk2/objectmodel/utils"
         xmlns:ctspj="http://10duke.com/package/com/tenduke/sdk2/persistence/jdbc"
         xmlns:ctsps="http://10duke.com/package/com/tenduke/sdk2/persistence/storage"
         xmlns:jl="http://10duke.com/package/java/lang"
         xmlns:ju="http://10duke.com/package/java/util"
         xmlns:ctsap="http://10duke.com/package/com/tenduke/sdk2/api/persistence"
         xmlns:ctsp="http://10duke.com/package/com/tenduke/sdk2/persistence"
         xmlns:ctsjh="http://10duke.com/package/com/tenduke/sdk2/jdbc/hsqldb2"
         xmlns:ctst="http://10duke.com/package/com/tenduke/sdk2/types"
         ser:version="2.0">
     <entityManagers>
         <ju:ArrayList>
             <ser:Items>
                 <ser:Item>
                     <ctspj:JdbcEntityManager>
                         <collectionResolver>
                             <ctspj:TableResolver />
                         </collectionResolver>
                         <fieldResolver>
                             <ctspj:FieldResolver />
                         </fieldResolver>
                         <id>jdbcEntityManager</id>
                         <jdbcDatabase>
                             <ctsjh:Hsql2Database>
                                 <databaseUserName>sa</databaseUserName>
                                 <databasePassword></databasePassword>
                                 <databaseUrl>jdbc:hsqldb:file:./db/com.mycompany.myapplication</databaseUrl>
                                 <serverPort>9001</serverPort>
                                 <databaseName>com.mycompany.myapplication</databaseName>
                                 <databasePath>./db/com.mycompany.myapplication</databasePath>
                                 <enableServerTrace>false</enableServerTrace>
                                 <serverIsSilent>false</serverIsSilent>
                             </ctsjh:Hsql2Database>
                         </jdbcDatabase>
                     </ctspj:JdbcEntityManager>
                 </ser:Item>
                 <ser:Item>
                     <ctsps:StorageEntityManager>
                         <id>storageEntityManager</id>
                     </ctsps:StorageEntityManager>
                 </ser:Item>
             </ser:Items>
         </ju:ArrayList>
     </entityManagers>
     <entityManagerResolver>
         <!--
             entityManagerResolver is responsible for resolving entity manager id
             for entity classes (as configured in entityManagers configuration section above)
         -->
         <ctsp:EntityManagerResolver defaultEntityManagerId="jdbcEntityManager">
             <entityManagerIds>
                 <ju:HashMap>
                     <ser:Entries>
                         <ser:Entry>
                             <!-- UriResource class must be managed by storageEntityManager -->
                             <ctst:KeyValue>
                                 <key><jl:Class ser:value="com.tenduke.sdk2.types.object.graph.UriResource"/></key>
                                 <value><jl:String ser:value="storageEntityManager"/></value>
                             </ctst:KeyValue>
                         </ser:Entry>
                     </ser:Entries>
                 </ju:HashMap>
             </entityManagerIds>
         </ctsp:EntityManagerResolver>
     </entityManagerResolver>
    </ctspg:GraphEntityManager>
  4. To activate this new persistence configuration file it must be included from the main configuration file WEB-INF/conf.xml, which will become as follows:

    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
    <properties version="1.0">
    <!-- Use XML comments to document the configuration file -->
    <entry key="example.configuration.key">example configured value</entry>
    
    <!-- The following line includes configuration for Persistence (database) in relative path ./WEB-INF/persistence/persistence.xml -->
    <entry key="data.entitymanager.graph"><![CDATA[<?value url="persistence/persistence.xml" encoding="utf-8" cache="false"?>]]></entry>
    </properties>

NOTE:

  • Database path permissions: The database configuration tells HSQL to store database files under ./db. This relative path will apply for the execution path of your servlet container. e.g. using a default Apache Tomcat that comes with Netbeans may require that you set read and write permissions under /usr/local/apache-tomcat.../bin/db/. Alternatively you may modify the path to another folder on your computer that you have read and write permissions for.
  • Storage path: the configuration for the StorageProvider relies all on defaults. The default path for files on disk is relative to the execution path of the servlet container. The path is: ./storage. You can freely change that to another path by adding the following attribute: <ctsps:StorageEntityManager storageRootPath="your path here">;

To verify the configuration added so far, lets create a simple JSP file at the root called index.jsp. In this jsp file we can quickly validate what has been configured so far (an example config key and persistence):

<%@page import="com.tenduke.sdk2.api.persistence.EntityManager"%>
<%@page import="com.tenduke.sdk2.patterns.service.ServiceLocator"%>
<%@page import="com.tenduke.sdk2.configuration.Configuration"%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <h1>10Duke SDK: Hello World!</h1>

        <h2>Checking that configuration works</h2>
        <p>
            Calling <code>Configuration.getValue("example.configuration.key") yields value:</code>

            **<%= Configuration.getValue("example.configuration.key") %>**.

            (that value came from WEB-INF/conf.xml)
        </p>

        <h2>Checking database configuration</h2>
        <p>
            Next lets check who takes care of persisting our entities. We'll do this by asking the service locator
            to find the default <code>EntityManager</code> by calling <code>ServiceLocator.locate(EntityManager.class)</code>, which
            gives the result:
            <code><%= ServiceLocator.locate(EntityManager.class).getClass().getCanonicalName() %></code>
        </p>

        <h2>Summary</h2>
        <p>
            If you have read this page instead of getting a server error 500 HTTP response then everything has been setup correctly
            so far.
        </p>
    </body>
</html>

NOTE:

  • The the Configuration class and it's getValue(...) methods will be able to fetch things from configuration in a versatile and flexible manner. It supports strong typing, deserializing and returning complex types, default values for undefined configuration keys and much more.
  • The GraphEntityManager supports a database neutral query syntax. It also supports configuration that allows routing objects by their type to different entity managers. e.g. this is commonly used to store object model data in a relational database and files on disk.

You can now build and deploy the web app. The output for index.jsp in a browser should look like this (page source view):

        <h1>10Duke SDK: Hello World!</h1>

        <h2>Checking that configuration works</h2>
        <p>
            Calling <code>Configuration.getValue("example.configuration.key") yields value:</code>

            **example configured value**.

            (that value came from WEB-INF/conf.xml)
        </p>

        <h2>Checking database configuration</h2>
        <p>
            Next lets check who takes care of persisting our entities. We'll do this by asking the service locator
            to find the default <code>EntityManager</code> by calling <code>ServiceLocator.locate(EntityManager.class)</code>, which
            gives the result:
            <code>com.tenduke.sdk2.persistence.graph.GraphEntityManager</code>
        </p>

        <h2>Summary</h2>
        <p>
            If you have read this page instead of getting a server error 500 HTTP response then everything has been setup correctly
            so far.
        </p>

Summary of setup

During the set-up process, you have now achieved the following:

  1. Created a basic Maven web application project with ability to use 10Duke SDK.
  2. Created a working configuration that can be extended as application needs grow.
  3. Configuration for persistence: Persistence services in the application will be handled by the GraphEntityManager provided by the SDK (<ctspg:GraphEntityManager> root node of the configuration). The graph entity manager supports a whole host of database independent ways of working with data. This includes issuing queries using what we call graph selectors, which are thoroughly discussed in the developer guide.
  4. GraphEntityManager can delegate storage of different types of entities to different entity managers. That is the reason why the configuration includes two different ones specified to the graph entity manager. The example configuration will pass all types except for UriResource to the SQL database and objects of type UriResource will be passed to a StorageEntityManager. The storage entity manager will store UriResource types as files on disk instead of data in HSQL database tables. Note how that was achieved using the default setting: &lt;ctsp:EntityManagerResolver defaultEntityManagerId="jdbcEntityManager"&gt; with reference to the jdbcEntityManager and a specific entry for storageEntityManager for UriResource.

This configuration demonstrates some basic aspects of IoC. The example has chosen a particular set of implementation classes to handle database and file storage requests made by the application. As the application developer, you could just as well configure your own alternative entity managers and storage providers instead of the ones provided by the SDK.

10Duke SDK Basics

Java API

Java SE and EE API version 7 are used in the 10Duke SDK. JDK 7 is used to build the product artifacts.

The 10Duke SDK API itself is mainly declared using JDK types, plain java objects (POJOs), generics and result types like OperationResult.

There is no "single" API or a statically defined envelope for what the SDK API is, which is natural to the IoC pattern ServiceLocator. The SDK includes a variety of services and signatures that classify as an API. The SDK also has an additional benefit in that you, as the developer, may extend the number of services available to your application as you prefer.

Service Locator

The Service Locator is responsible for looking up any service that an application requires while executing. The service implementations are made available using Named API Contracts.

The 10Duke SDK default services are enumerated by the default service locator implementation class: ServiceLocatorImpl. Using ServiceLocator to locate a service is easy. The most basic overload just requires the caller to specify the type of service required:

UuidGenerator uuidGenerator = ServiceLocator.locate(UuidGenerator.class);

Other overloads allow constraining the lookup by name and version, e.g.:

XmlSerializer serializer = ServiceLocator.locate(XmlSerializer.class, "RSS", "2.0");

Object Model

The 10Duke SDK provides an "out-of-the-box" object model. The foundation of this built-in object model are a couple of base classes that define a unique concept of how to model relations between objects. The concept is built around the idea that relations between objects should not be hard coded. This specifically applies to an SDK since it cannot know the specific object modelling requirements of your application in advance.
Therefore the SDK provides you with the ability to describe the relations between objects, as your application requires, using these base classes. The classes that provide the basis of this relation modelling approach are:

The benefits of using these types as base classes in your own model are:

  • definition of the relationships between objects on application level
  • flexibility to use same model classes in different types of applications, where the relation definitions vary
  • enforcing rules for data validation on application level, within persistence and in UI

The SDK also provides classes that add the concepts of author, editor, authored time stamp and edited time stamp to objects. These additional base classes that you can use in your model are:

AbstractAuthoredOwnedObject in particular enables the use of a built-in role framework that allows authorization checks on "pseudo role" level like:

  • Viewer
  • Authenticated user
  • Owner
  • Author
  • (Last) Editor

Using the out-of-the-box 10Duke SDK object model is optional. It does, however, provide you with a significant short-cut in cases where it can be applied to the specific requirements of your application. Concrete model classes can be found under object model packages:

attribute property, tag and other attribute type model classes
contact contact information related model classes
credential user name, password and base for other credential models
entitlement licensing related model classes
entitlement.licensemodel behavior and constraint model classes for support of advanced licensing logic
forum model classes that support creation of message boards, forums, social support, etc.
identity classes that model users, persons, technical actors, etc.
identityprovider classes that model OAuth consumers, SAML service providers, and other Sign-on, and authorization related services
media model classes for expressing meta data about audio, documents, images, videos and other media types
messaging email, private messaging and invitation type model classes
network IP address and range basics
preferences models for user and application preferences
security model classes for roles, permissions, actors, claims, etc. security related entities

Contract for identification of entities

Object model classes that are used with persistence must comply with providing an identifier. A class automatically gets a UUID type identifier implementation when inheriting from DynamicRelatedObject.

When implementing object model classes that don't inherit from DynamicRelatedObject you can use @ObjectId annotation to mark one single method for setting the id and one single method for getting the id of objects of that type. Such an example can look like this:

import com.tenduke.sdk2.api.io.Serializable;
import com.tenduke.sdk2.api.object.ObjectId;
import com.tenduke.sdk2.types.object.graph.RelatedObject;
import java.util.UUID;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;

/**
 * Example object model base class.
 */
@MappedSuperclass
public abstract class MyObjectModelBase extends RelatedObject {

    /**
     * Name for id field.
     */
    public static final String FIELD_NAME_ID = "id";

    /**
     * serialVersionUID used with java.io.Serializable.
     */
    private static final long serialVersionUID = 1L;

    /**
     * Unique id of the object.
     * Object id / primary key has own specific handling in persistence
     * using @Transient annotation to skip "normal" field handling.
     */
    @Serializable(name = FIELD_NAME_ID)
    @ObjectId
    @Transient // see field comment about not persisted on field basis
    private UUID _id;

    /**
     * Gets unique id of the object.
     * @return Unique id of the object.
     */
    @ObjectId
    public UUID getId() {
        //
        return _id;
    }

    /**
     * Sets unique id of the object.
     * @param id Unique id of the object.
     */
    @ObjectId
    public void setId(final UUID id) {
        //
        _id = id;
    }
}

Identifiers can also be of other types, including classes that represent JDK primitive types, URI, URL, Date and InetAddress. Using a custom Identifier type usually also requires using a custom implementation of FieldProvider when using persistence.

Creating new objects

Most SDK object model types can be instantiated either with a null id or by specifying an id for the new instance when creating it. The default id type used in the SDK is UUID.

//
// creating a new instance using the types default constructor
Entry video = new Entry();
//
// alternatively an Id can be provided at time of construction
UuidGenerator uuidGenerator = ServiceLocator.locate(UuidGenerator.class);
video = new Entry(uuidGenerator.randomUuid());

Getters and setters

Instance members (fields) are encapsulated using accessor methods starting with get and set. Field values are get and set using these accessor methods. E.g.:

//
Entry video = new Entry();
video.setTitle("Surfing Hawaii");
System.out.println("Title that was set: " + video.getTitle());

The general contract with getters and setters is a mechanical and direct value get and set approach. This is applied to primitive types as well as complex types like List. An accessor method for setting and getting a List type will provide raw and direct access to the List instance itself. This is notable if the application programmer makes multi-threaded access to an object that contains e.g. a List.

Object Graph

An object graph denotes a runtime instance of an object hierarchy having a single entity as its root node. The hierarchy is built using relations between the objects.

The relation model is fully dynamic, which means that you, as the application developer, have complete flexibility to define the relationships between objects to match your application requirements and its business entity model perfectly.

The 10Duke SDK provides some default relation definitions, which can freely be used where they are suitable for the purpose. The default relation definitions are simply described by the multiplicity they define for the relation:

  • One-to-Many
  • Many-to-One
  • Many-to-Many

You will find constants for using these default multiplicities in Multiplicity.

Additionally, this section will discuss the use of custom relation objects. The 10Duke SDK supports using any object model class as a relation type as well.

Basic use of relations

One-to-many

The following example demonstrates the basic use of a One-to-Many relation. The presented scenario models videos that may have multiple media. Multiple media in this example means that a video has a thumbnail image, a preview video clip, and the full video itself.

//
UuidGenerator uuidGenerator = ServiceLocator.locate(UuidGenerator.class);
//
Entry video = new Entry(uuidGenerator.randomUuid());
video.setTitle("Funny clip");
//
Media fullMedia = new Media(uuidGenerator.randomUuid());
fullMedia.setFileName("funny.mp4");
fullMedia.setMediaCategory(Media.MEDIA_CATEGORY_ORIGINAL);
//
Media thumbnailMedia = new Media(uuidGenerator.randomUuid());
thumbnailMedia.setFileName("funny.mp4.thumb.jpg");
fullMedia.setMediaCategory("thumbnail_image");
//
Media previewMedia = new Media(uuidGenerator.randomUuid());
previewMedia.setFileName("funny.mp4.preview.mp4");
fullMedia.setMediaCategory("preview_video");
//
// Relate the video to its media entities by a one to many relation
video.relatedObjects(Media.class, Multiplicity.ONE_TO_MANY).add(fullMedia);
video.relatedObjects(Media.class, Multiplicity.ONE_TO_MANY).add(thumbnailMedia);
video.relatedObjects(Media.class, Multiplicity.ONE_TO_MANY).add(previewMedia);
//
// Preparing the object model sample to be used with persistence we need to ensure that
// reference fields are set according to the relation model chosen above.
// Compare this to a foreign key reference in a database.
String referenceFieldName = RelatedObjectUtils.getReferenceFieldName(Entry.class);
fullMedia.referenceFields().put(referenceFieldName, video.getId());
thumbnailMedia.referenceFields().put(referenceFieldName, video.getId());
previewMedia.referenceFields().put(referenceFieldName, video.getId());
//
// access the media objects via relation from video
List<Media> media = video.relatedObjects(Media.class, Multiplicity.ONE_TO_MANY);
Many-to-one

The following example builds on a scenario that some contact information exists and additional information needs to be associated with that data. The additional information is in the example notes that are modelled using a Property type object model from the 10Duke SDK. The idea is that several notes can be related to a contact information entity.

//
UuidGenerator uuidGenerator = ServiceLocator.locate(UuidGenerator.class);
//
// Builds a fictional contact information object model sample
// using classes from the 10Duke SDK object model.
ContactInformation contactInformation = new ContactInformation(uuidGenerator.randomUuid());
//
EmailAddress emailAddress = new EmailAddress(uuidGenerator.randomUuid());
emailAddress.setValue("manyToOne@thisunittest.com");
contactInformation.relatedObjects(EmailAddress.class, Multiplicity.ONE_TO_MANY).add(emailAddress);
//
TelephoneNumber telephoneNumber = new TelephoneNumber(uuidGenerator.randomUuid());
telephoneNumber.setValue("5557777");
contactInformation.relatedObjects(TelephoneNumber.class, Multiplicity.ONE_TO_MANY).add(telephoneNumber);
//
// Imagine we need to add a notes for the contact info object. This example uses
// Property model and a many to one relation.
Property note = new Property(uuidGenerator.randomUuid());
note.setKey("note");
note.setValue("Remember to change email address after holidays");
//
// Prepare the Property object to be persisted in a way that the many to one relation is
// also written.
note.relatedObjects(ContactInformation.class, Multiplicity.MANY_TO_ONE).add(contactInformation);
String referenceFieldName = RelatedObjectUtils.getReferenceFieldName(ContactInformation.class);
note.referenceFields().put(referenceFieldName, contactInformation.getId());
Many-to-many

The 10Duke SDK includes a class called ManyToMany, which is used as the default relation object in cases where the developer uses the Multiplicity.MANY_TO_MANY multiplicity definition.

The ManyToMany class is designed to support storing references to the entities on both sides of the relation. The reference field names and values are stored and accessed using the referenceFields) member. This class can be used as a base class when implementing Many-to-Many relation types that need to include custom relation attributes.

The following example uses a common use case of tagging objects. A video is tagged with 3 tags using default Many-to-many relation.

//
UuidGenerator uuidGenerator = ServiceLocator.locate(UuidGenerator.class);
//
Entry video = new Entry(uuidGenerator.randomUuid());
video.setTitle("Funny clip");
//
Tag dog = new Tag(uuidGenerator.randomUuid());
dog.setValue("dog");
//
Tag bike = new Tag(uuidGenerator.randomUuid());
bike.setValue("bike");
//
Tag umbrella = new Tag(uuidGenerator.randomUuid());
umbrella.setValue("umbrella");
//
// Relate the video to its media entities by a one to many relation
// E.g. imagine that in our application a video would have a thumbnail media, preview media, full video, etc.
video.relatedObjects(Tag.class, Multiplicity.MANY_TO_MANY).add(dog);
video.relatedObjects(Tag.class, Multiplicity.MANY_TO_MANY).add(bike);
video.relatedObjects(Tag.class, Multiplicity.MANY_TO_MANY).add(umbrella);
//
// access the media objects via relation from video
List<Tag> videoTags = video.relatedObjects(Tag.class, Multiplicity.MANY_TO_MANY);
Custom Many-to-Many with relation attributes

Implementing relation attributes for a Many-to-Many relation can by done by e.g. inheriting from class ManyToMany or Relation.

Relation attributes are normal instance member fields. The following example shows the basics with an example of storing some additional author information in relation between a video Entry and a user Profile:

import com.tenduke.sdk2.api.io.Serializable;
import com.tenduke.sdk2.types.object.graph.ManyToMany;

/**
 * Example of custom Many-to-Many implementation that introduces an additional
 * attributes into relation.
 */
public class AuthorInfoManyToMany extends ManyToMany {

    /**
     * Field name for my custom relation attribute
     */
    public static final String FIELD_NAME_IS_MAIN_AUTHOR = "isMainAuthor";

    /**
     * serialVersionUID used with {@link java.io.Serializable}.
     */
    private static final long serialVersionUID = 1L;

    /**
     * Example custom relation attribute field to mark that a related object is the "main" author.
     */
    @Serializable(name = FIELD_NAME_IS_MAIN_AUTHOR)
    private Boolean _isMainAuthor;

    // ...
}

// ...
Entry video = new Entry();
video.setTitle("Test");
//
Profile user1 = ...
Profile user2 = ...
//
AuthorInfoManyToMany authorInfo1 = new AuthorInfoManyToMany();
authorInfo1.setMainAuthor(true);
user1.setRelationObject(authorInfo1);
//
AuthorInfoManyToMany authorInfo2 = new AuthorInfoManyToMany();
authorInfo2.setMainAuthor(false);
user2.setRelationObject(authorInfo2);
//
video.relatedObjects(Profile.class, CustomManyToMany.class).add(user1);
video.relatedObjects(Profile.class, CustomManyToMany.class).add(user2);

Entity Manager (persistence and database)

Introduction

The API contract that provides the persistence service for applications is called EntityManager. The 10Duke SDK includes several implementations of the EntityManager interface. These include: a general JDBC-based implementation, mongodb and a Graph Entity Manager. The general JDBC implementation supports using PostgreSql, MySql and HSQL v1 and v2. Other relational databases can be used by providing a slim database initialization and exception interpretation extension.

Default Configuration

The default entity manager in the SDK is: GraphEntityManager. GraphEntityManager tries to read its configuration using configuration key: data.entitymanager.graph, which shall hold a GraphEntityManager instance in XmlSerializer format. The configuration is done adding the key in main configuration and defining the value. The following example shows adding the key and defining the value in a separate file. E.g. in conf.xml add the entry with key data.entitymanager.graph, which refers to a file in relative path persistence/persistence.xml:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties version="1.0">
  <!-- Use XML comments to document the configuration file -->
  <entry key="example.configuration.key">example configured value</entry>

  <!-- The following line includes configuration for Persistence (database) in relative path ./WEB-INF/persistence/persistence.xml -->
  <entry key="data.entitymanager.graph"><![CDATA[<?value url="persistence/persistence.xml" encoding="utf-8" cache="false"?>]]></entry>
</properties>

... and an example value for the configuraton entry, which configures use of HSQL v2 database:

<ctspg:GraphEntityManager
        xmlns:rel="http://10duke.com/schema/built-in/related"
        xmlns:ser="http://10duke.com/schema/built-in/serialization"
        xmlns:ctspg="http://10duke.com/package/com/tenduke/sdk2/persistence/graph"
        xmlns:ctsou="http://10duke.com/package/com/tenduke/sdk2/objectmodel/utils"
        xmlns:ctspj="http://10duke.com/package/com/tenduke/sdk2/persistence/jdbc"
        xmlns:ctsps="http://10duke.com/package/com/tenduke/sdk2/persistence/storage"
        xmlns:jl="http://10duke.com/package/java/lang"
        xmlns:ju="http://10duke.com/package/java/util"
        xmlns:ctsap="http://10duke.com/package/com/tenduke/sdk2/api/persistence"
        xmlns:ctsp="http://10duke.com/package/com/tenduke/sdk2/persistence"
        xmlns:ctsjh="http://10duke.com/package/com/tenduke/sdk2/jdbc/hsqldb2"
        xmlns:ctst="http://10duke.com/package/com/tenduke/sdk2/types"
        ser:version="2.0">
    <entityManagers>
        <ju:ArrayList>
            <ser:Items>
                <ser:Item>
                    <ctspj:JdbcEntityManager>
                        <collectionResolver>
                            <ctspj:TableResolver />
                        </collectionResolver>
                        <fieldResolver>
                            <ctspj:FieldResolver />
                        </fieldResolver>
                        <id>jdbcEntityManager</id>
                        <jdbcDatabase>
                            <ctsjh:Hsql2Database>
                                <databaseUserName>sa</databaseUserName>
                                <databasePassword></databasePassword>
                                <databaseUrl>jdbc:hsqldb:file:./db/com.mycompany.myapplication</databaseUrl>
                                <serverPort>9001</serverPort>
                                <databaseName>com.mycompany.myapplication</databaseName>
                                <databasePath>./db/com.mycompany.myapplication</databasePath>
                                <enableServerTrace>false</enableServerTrace>
                                <serverIsSilent>false</serverIsSilent>
                            </ctsjh:Hsql2Database>
                        </jdbcDatabase>
                    </ctspj:JdbcEntityManager>
                </ser:Item>
                <ser:Item>
                    <ctsps:StorageEntityManager>
                        <id>storageEntityManager</id>
                    </ctsps:StorageEntityManager>
                </ser:Item>
            </ser:Items>
        </ju:ArrayList>
    </entityManagers>
    <entityManagerResolver>
        <!--
            entityManagerResolver is responsible for resolving entity manager id
            for entity classes (as configured in entityManagers configuration section above)
        -->
        <ctsp:EntityManagerResolver defaultEntityManagerId="jdbcEntityManager">
            <entityManagerIds>
                <ju:HashMap>
                    <ser:Entries>
                        <ser:Entry>
                            <!-- UriResource class must be managed by storageEntityManager -->
                            <ctst:KeyValue>
                                <key><jl:Class ser:value="com.tenduke.sdk2.types.object.graph.UriResource"/></key>
                                <value><jl:String ser:value="storageEntityManager"/></value>
                            </ctst:KeyValue>
                        </ser:Entry>
                    </ser:Entries>
                </ju:HashMap>
            </entityManagerIds>
        </ctsp:EntityManagerResolver>
    </entityManagerResolver>
</ctspg:GraphEntityManager>

Creating objects

In its simplest form, creating objects requires only the object and a couple authorization related arguments:

Iterable<PermissionGrant> permissionGrants = ...
UUID actorId = ...
EntityManager<EntityTransaction> entityManager = ServiceLocator.locate(EntityManager.class);
//
UuidGenerator uuidGenerator = ServiceLocator.locate(UuidGenerator.class);
//
Entry folder = new Entry(uuidGenerator.randomUuid());
folder.setType("Directory");
folder.setTitle("/TextFiles");
//
entityManager.create(folder, permissionGrants, actorId, null);

When dealing with a sequence of persistence operations it may be feasible to encapsulate things in a transaction. The example call to creating an object would then look like this:

//
try (EntityTransaction transaction = entityManager.createEntityTransaction()) {
    //
    transaction.begin();
    //
    entityManager.create(folder, permissionGrants, actorId, transaction);
    //
    transaction.commit();
}

The default create overload will make the entity manager use it's default field provider to select and provide the fields that are persisted from the object. The caller may choose to define the field provider manually with the intent to modify the default behaviour. This is relevant for an example when the object you are persisting includes some reference fields set by a call to the object's referenceFields map. Note the use of RelatedObjectFieldProvider for this purpose:

//
Entry folder = new Entry(uuidGenerator.randomUuid());
folder.setType("Directory");
folder.setTitle("/TextFiles");
//
Entry file = new Entry(uuidGenerator.randomUuid());
file.setType("RegularFile");
file.setTitle("test.txt");
//
// entryReference value = "ref_Entry_id"
String entryReference = RelatedObjectUtils.getReferenceFieldName(Entry.class);
file.referenceFields().put(entryReference, folder.getId());
//
// build the object graph between the folder and the file (optional from peristence use point of view)
folder.relatedObjects(Entry.class, Multiplicity.ONE_TO_MANY).add(file);
//
try (EntityTransaction transaction = entityManager.createEntityTransaction()) {
    //
    transaction.begin();
    //
    // persists folder object
    entityManager.create(folder, permissionGrants, actorId, transaction);
    //
    // use RelatedObjectFieldProvider to support persisting the reference field
    // that relates our file to the folder it resides in:
    RelatedObjectFieldProvider fieldProvider = new RelatedObjectFieldProvider(file);
    entityManager.create(file, fieldProvider, permissionGrants, actorId, transaction);
    //
    transaction.commit();
}

Reading objects

Reading objects is equivalent to access by identifier (primary key in database terms). The caller is required to tell the entity manager what type, the value of the identifier, and some authorization related information when reading objects:

Iterable<PermissionGrant> permissionGrants = ...
UUID actorId = ...
EntityManager<EntityTransaction> entityManager = ServiceLocator.locate(EntityManager.class);
//
UUID folderId = ...
//
Entry folder = entityManager.read(Entry.class, folderId, permissionGrants, actorId, null);

Reading also supports defining the collection name to read from (maps to database table name in relational databases). That overload may be used if certain objects have been created using a custom scheme.

Updating objects

Updating objects looks very much like creating objects. Building from the creating objects examples above, an example call to update looks like this:

//
folder.setType("Folder");
folder.setTitle("/Files-evolved");
//
entityManager.update(folder, permissionGrants, actorId, null);

Other upload overloads work just like the examples provided for create use cases.

Deleting objects

When deleting objects, the caller is required specify the object to delete and some authorization related information:

Iterable<PermissionGrant> permissionGrants = ...
UUID actorId = ...
EntityManager<EntityTransaction> entityManager = ServiceLocator.locate(EntityManager.class);
//
Entry folder = ...
//
entityManager.delete(folder, permissionGrants, actorId, null);

Query

The 10Duke SDK API supports two main alternatives for query use cases:

  • Graph queries
  • Named queries

Using graph queries requires that the application has configured an entity manager stack that supports query using 10Duke SDK graph syntax. GraphEntityManager and JdbcEntityManager that are shipped with the SDK support use of the graph query notation. A basic example of issuing a graph based query looks like this:

//
// creates a graph selector that is used to query all folder type Entry objects.
String graphSelector = "/Entry[@type='folder']";
//
// creates definition of nested selection that are used to populate the
// query result object graph with Entry objects that are related to each folder
// selected by the first level query.
// Nested selector are logically applied relative to the main graph selector.
List<String> nestedSelectors = Arrays.asList("/~OneToMany/Entry");
//
// exeute the query using PersistenceUtils
List<Entry> folders = PersistenceUtils.queryByGraphSelector(
    entityManager,
    transaction,
    Entry.class,
    graphSelector,
    nestedSelectors,
    permissionGrants,
    actorId);

The same can be achieved using a named query configured by the application. An entity manager can be requested to execute a named query by defining a special statement selector of type NamedStatementSelector:

String queryName = "AllFolders";
Object [] queryParameters = buildQueryParameters(...);
String collectionName = Entry.getSimpleName(); // this is the default if caller provides null collection name
//
List<Entry> folders;
if (queryParameters == null) {
    //
    folders = entityManager.query(
            Entry.class,
            permissionGrants,
            actorId,
            transaction,
            new NamedStatementSelector(queryName, collectionName),
            null);
} else {
    //
    folders = entityManager.query(
            Entry.class,
            permissionGrants,
            actorId,
            transaction,
            new NamedStatementSelector(queryName, collectionName),
            null,
            queryParameters);
}

All the SDK entity manager implementations are so called named statement resolvers, and allow an application to configure named statements with the entity manager. Using named statement queries requires the application to configure the used statements by correct name for the respective entity manager. This configuration can be done using the entity manager instance and API or by XML in the persistence configuration.

Using JdbcEntityManager, the named statement XML configuration that fulfils the contract in the above example looks like this:

<ctspj:JdbcEntityManager
    xmlns:rel="http://10duke.com/schema/built-in/related"
    xmlns:ser="http://10duke.com/schema/built-in/serialization"
    xmlns:ctsou="http://10duke.com/package/com/tenduke/sdk2/objectmodel/utils"
    xmlns:ctspj="http://10duke.com/package/com/tenduke/sdk2/persistence/jdbc"
    xmlns:jl="http://10duke.com/package/java/lang"
    xmlns:ju="http://10duke.com/package/java/util"
    xmlns:ctsap="http://10duke.com/package/com/tenduke/sdk2/api/persistence"
    xmlns:ctsp="http://10duke.com/package/com/tenduke/sdk2/persistence"
    xmlns:ctsjh="http://10duke.com/package/com/tenduke/sdk2/jdbc/hsqldb2"
    xmlns:ctst="http://10duke.com/package/com/tenduke/sdk2/types"
    ser:version="2.0">
    <namedStatements>
        <ju:ArrayList>
            <ser:Items>
                <!-- Example named query 1: query all folder-->
                <ser:Item>
                    <ctsap:NamedStatement name="AllFolders" entityManagerId="jdbcEntityManager">
                        <statementDefinition>
                            <ctsap:StatementDefinition>
                                <statementString>SELECT * FROM Entry WHERE "_type" = 'folder'</statementString>
                            </ctsap:StatementDefinition>
                        </statementDefinition>
                    </ctsap:NamedStatement>
                </ser:Item>
            </ser:Items>
        </ju:ArrayList>
    </namedStatements>
    <collectionResolver>
        <ctspj:TableResolver />
    </collectionResolver>
    <fieldResolver>
        <ctspj:FieldResolver />
    </fieldResolver>
    <id>jdbcEntityManager</id>
    <jdbcDatabase>
        <ctsjh:Hsql2Database>
            <databaseUserName>sa</databaseUserName>
            <databasePassword></databasePassword>
            <databaseUrl>jdbc:hsqldb:file:./db/com.mycompany.myapplication</databaseUrl>
            <serverPort>9001</serverPort>
            <databaseName>com.mycompany.myapplication</databaseName>
            <databasePath>./db/com.mycompany.myapplication</databasePath>
            <enableServerTrace>false</enableServerTrace>
            <serverIsSilent>false</serverIsSilent>
        </ctsjh:Hsql2Database>
    </jdbcDatabase>
</ctspj:JdbcEntityManager>

Note the mapping queryName = "AllFolders" and &lt;ctsap:NamedStatement name="AllFolders" entityManagerId="jdbcEntityManager"&gt; in the configuration fragment.

Persistence of One-to-Many type relations

Persisting One-to-Many (and Many-to-One) type relations is supported using a member called referenceFields in SDK object model classes. That member is designed to hold key - value type information. The naming convention for One-to-Many reference fields is: ref_ReferencedTypeName_id, where ReferencedTypeName is the name of the referenced class's simple name. The runtime definition of reference field names for this purpose can be called upon using e.g. RelatedObjectUtils.getReferenceFieldName or other overloads of the same method. The following example shows a basic scenario using a One-To-Many relation for organizing files into folders:

//
Entry folder = new Entry(uuidGenerator.randomUuid());
folder.setType("Directory");
folder.setTitle("/TextFiles");
//
Entry file = new Entry(uuidGenerator.randomUuid());
file.setType("RegularFile");
file.setTitle("test.txt");
//
// entryReference value = "ref_Entry_id"
String entryReference = RelatedObjectUtils.getReferenceFieldName(Entry.class);
file.referenceFields().put(entryReference, folder.getId());
//
// build the object graph between the folder and the file (optional from peristence use point of view)
folder.relatedObjects(Entry.class, Multiplicity.ONE_TO_MANY).add(file);
//
try (EntityTransaction transaction = entityManager.createEntityTransaction()) {
    //
    transaction.begin();
    //
    // persists folder object
    entityManager.create(folder, permissionGrants, actorId, transaction);
    //
    // use RelatedObjectFieldProvider to support persisting the reference field
    // that relates our file to the folder it resides in:
    RelatedObjectFieldProvider fieldProvider = new RelatedObjectFieldProvider(file);
    entityManager.create(file, fieldProvider, permissionGrants, actorId, transaction);
    //
    transaction.commit();
}

Persistence of Many-to-Many type relations

Many-to-Many relations function using a dedicated relation object. The next example shows a scenario of versioning files in a folder:

Iterable<PermissionGrant> permissionGrants = ...
UUID actorId = ...
EntityManager<EntityTransaction> entityManager = ServiceLocator.locate(EntityManager.class);
//
UuidGenerator uuidGenerator = ServiceLocator.locate(UuidGenerator.class);
//
Entry folder = new Entry(uuidGenerator.randomUuid());
folder.setType("Directory");
folder.setTitle("/TextFiles");
//
Entry file = new Entry(uuidGenerator.randomUuid());
file.setType("RegularFile");
file.setTitle("test.txt");
//
// This example uses a type called Versioned as the relation object between file and folder.
// Versioned supports not only version info but also an abstract concept of selection.
// The selection concept could be used to e.g. denote the "active" version.
Versioned versioned = new Versioned("v1.0");
versioned.setSelected(true);
//
// build the relation between the folder and the file
file.setRelationObject(versioned);
folder.relatedObjects(Entry.class, Versioned.class).add(file);
//
// convert object hierarchy to an object graph, which will support a simple persistence call
// that will store the entire object model sample in one call
// (assuming the entityManager supports GraphObject).
GraphObject<Entry> folderGraphObject = RelatedObjectToGraphObject.toGraphObject(folder);
//
try (EntityTransaction transaction = entityManager.createEntityTransaction()) {
    //
    transaction.begin();
    //
    // creates all objects in the graph starting with the folder instance.
    entityManager.create(folderGraphObject, permissionGrants, actorId, transaction);
    //
    transaction.commit();
}
//
// ... later on access folder and file objects and Versioned relation binding the two together.
try (EntityTransaction transaction = entityManager.createEntityTransaction()) {
    //
    transaction.begin();
    //
    ReadManyToManyFieldProvider idProvider = new ReadManyToManyFieldProvider(
        Entry.class,
        folder.getId(),
        Entry.class,
        file.getId());
    idProvider.setManyToManyType(Versioned.class);
    idProvider.setObjectName(PersistenceUtils.getDefaultManyToManyPersistenceCollectionName(
            Entry.class,
            Entry.class,
            Versioned.class));
    //
    Versioned versionInfo = entityManager.readManyToMany(
            Versioned.class,
            idProvider,
            permissionGrants,
            actorId,
            transaction);
    //
    transaction.commit();
}

Issuing queries can alternatively be done using graph selectors when using an entity manager that supports the syntax. The same query as shown in the example above can be issued using a graph selector like this:

    //
    // creates a graph selector that is used to query our folder by id.
    // select looks like this: /Entry[@id='453dc038-366f-479b-a3c2-6a691e545f66']
    String graphSelector = new StringBuilder()
            .append("/Entry[@id='")
            .append(ObjectStringFormatter.objectToString(folderId))
            .append("']")
            .toString();
    //
    // creates definition of nested selection that are used to populate the
    // query result object graph. In this case we want all files in our example
    // folder. Since files were related to folders using the Versioned many to many
    // type we can query them using the selector: "/~Versioned/Entry".
    // Nested selector are logically applied relative to the main graph selector.
    List<String> nestedSelectors = Arrays.asList("/~Versioned/Entry");
    //
    // exeute the query using PersistenceUtils
    Entry queriedEntry = PersistenceUtils.queryFirstByGraphSelector(
        entityManager,
        transaction,
        Entry.class,
        graphSelector,
        nestedSelectors,
        permissionGrants,
        actorId);
    //
    if (queriedEntry != null) {
        //
        // access the folder's files via relatedObjects(...) using type and realtion type
        List<Entry> files = queriedEntry.relatedObjects(Entry.class, Versioned.class);
        //
        for (Entry file : files) {
            //
            Versioned versionInfo = file.getRelationObject(Versioned.class);
            //
            // ...
        }
    }

Media Processing

Processing media is a common need for web applications. Requirements can include the creation of thumbnails from images and videos, or converting audio and video files to different formats. Processing media may also mean things like polygon optimization for geometry models and converting PDF documents to Flash SWF files.

The 10Duke SDK provides an API for analysing media and for processing media. The purpose of media analysis is to gain metadata about the media in order to learn more about it and then determine possible processing and further requirements.

Analysis

The API contract for analysing media is declared by MediaAnalyzer.The default implementation is provided by ConfigurableMediaAnalyzer, which works by the application providing a configuration entry in the main configuration using key: media.analysis.configuration.

ConfigurableMediaAnalyzer configuration syntax is defined by MediaProcessorConfiguration, which implements configuration value parsing and functions as the runtime provider of accessing the configured analysis instructions. The following example shows a simple configuration using an application called identify to analyse images:

<MediaProcessingToolchainConfiguration>
    <Input inputMime="/image\/.*/">
        <Output outputMime="*">
            <Exe name="/usr/bin/identify"?>">
                <Arg value="-verbose" />
                <Arg value="$INPUT_FILE" />
                <ParseValues>
                    <ParseValueEntry key="codec">/^.*Format:\s{1}([\w\d\s]*)/</ParseValueEntry>
                    <ParseValueEntry key="colorFormat">/^.*Colorspace:\s{1}([\w\d\s]*)/</ParseValueEntry>
                    <ParseValueEntry key="height">/^.*Resolution:\s*[0-9]*x([0-9]*)/</ParseValueEntry>
                    <ParseValueEntry key="width">/^.*Resolution:\s*([0-9]*)x/</ParseValueEntry>
                </ParseValues>
            </Exe>
        </Output>
    </Input>
</MediaProcessingToolchainConfiguration>

The configuration structure is like a matrix. It defines analysis commands to use by mapping input file types to output file types and then the executables to run for each combination. Input and output types are configured using a regular expression, which allows both detailed specification as well as efficiently grouping e.g. all image types to be analysed by one capable tool. See ConfigurableMediaAnalyzer for supported symbols, which can be used to parametrisize processing by runtime information from the input and output files.

The output from the analysis tool-chain is also processed using a regular expression based implementation. The application configures appropriate regular expressions for reading parts of the output that are relevant for the required meta-data.

Analysis results are delivered using Map with String keys and String values. Each key denotes a named meta-data entry that the analyser was able to produce by analysing the resource defined by the caller. In the example above the caller would receive a Map containing keys:

  • codec
  • colorformat
  • width
  • height

The value for each Map entry would have been produced based on parsing the output of the executable using the regular expression defined for each key.

Processing / conversions

Processing media has many names. Some use the term "transcoding" when converting video from one format to another and some use the term "convert" when dealing with images. This processing is, in general terms, the conversion of data from one digital form to another. Some special cases exists where the conversion is e.g. a one to one copy.

The API contract for converting (processing) media is declared by MediaProcessor. The default implementation is provided by EntityManagerMediaProcessor. That implementation extends ConfigurableMediaProcessor, which works based on configuration like the default media analyzer described earlier.

Media processors are accessed by type and name using ServiceLocator. The name is used as part of the configuration key, by which the processor is initialized. The key naming convention works by appending the processor name to the base key name media.processor.configuration. Locating a media processor without a name is interpreted as a request for the processor by name "default". Using the configuration key naming convention in the default case, the configuration key becomes: media.processor.configuration.default.

The following example shows a configuration that would attempt to process all types of images that are requested to be converted to either jpeg or png by corresponding ImageMagick convert commands:

<?include url="../nodeVariables.xml"?>
<MediaProcessingToolchainConfiguration>
    <InputFormat inputMime="/image\/.*/" containerName="">
        <OutputFormat outputMime="image/jpeg" containerName="jpeg" category="rest">
            <Exe name="/usr/bin/convert">
                <Arg value="-resize" />
                <Arg value="$WIDTHx$HEIGHT" />
                <Arg value="-quality" />
                <Arg value="100" />
                <Arg value="-colorspace" />
                <Arg value="RGB" />
                <Arg value="$INPUT_FILE" />
                <Arg value="$OUTPUT_FILE" />
            </Exe>
        </OutputFormat>
        <OutputFormat outputMime="image/png" containerName="png" category="rest">
            <Exe name="/usr/bin/convert">
                <Arg value="-resize" />
                <Arg value="$WIDTHx$HEIGHT" />
                <Arg value="-quality" />
                <Arg value="100" />
                <Arg value="-colorspace" />
                <Arg value="RGB" />
                <Arg value="-background" />
                <Arg value="none" />
                <Arg value="$INPUT_FILE" />
                <Arg value="$OUTPUT_FILE" />
            </Exe>
        </OutputFormat>
    </InputFormat>
</MediaProcessingToolchainConfiguration>

As for analysis the processing configuration structure is like a matrix. It defines analysis commands to use by mapping input file types to output file types and then the executables to run for each combination. Input and output types are configured using regexp, which allows both detailed specification as well as efficiently grouping e.g. all image types to be analyzed by one capable tool. See ConfigurableMediaProcessor for supported symbols, which can be used to parametrisize processing by runtime information from the input and output files.

The caller can subscribe to processing events and receive the processing result by registering an observer with the MediaProcessingJob that is used to submit a processing request.

Automated processing

Using the GraphServlet that comes with the SDK will activate automated media processing. This automated processing relies completely on what has been configured for API contracts: MediaProcessor and MediaAnalyzer.

This automated processing is triggered by requesting media from the GraphServlet.

A simple teaser at this stage (more about the graph servlet in the Restful API section): Assume you have created an Entry representing a Video. The original video was a .mov file and making a request: .../graph/Entry[@id='...'].mp4 to the graph servlet would trigger processing of the original .mov file to produce a .mp4 video.

Identity Provider

The IdentityProvider is responsible for providing user identity related operations. Identity provider is an API with several available implementations in the SDK. The default implementation uses the persistence framework and the entity manager provided by the setup to store user identity related information in a relational database. In addition to the default implementation the SDK provides implementations for OAuth (OAUth consumer) and SAML (SAML Service Provider) scenarios.

Identity applies both to people and software actors, which the SDK calls technical actors. The SDK object model includes classes that represent both actor types as well as an interface for Actors in general. Profile and TechnicalActor both implement the Actor interface.

The SDK identity provider implementations support use of both profile and technical actor types. Possible other identity provider implementations may document the actor types they support.

Identity model

Explanation Object model
Person
A real person is represented by an object model class named Person in this application. The Person class is in other words the core identity model for an individual.
Person
Credentials
Credentials are used to identify a person. Credentials are mainly divided into public and private types. E.g. a password is a private credential. Credentials are related to a person. The most common credential type used in this application is a user name - password credential type.
AbstractCredentialRelatedObject
User Profile
A person who uses this application does so with a certain profile. The profile model enables management of the roles that a user can be associated with. A Profile can also be related with consuming licenses.
Profile
Profile properties
The identity API supports extending Profile objects by attaching Property objects. The Property model is a key-value pair type.
Profile properties
User preferences
A user Profile may be associated with UserPreferences objects, which allows adding named sets of Property objects. The convention and contract of naming UserPreferences is defined by the application.
User preferences
Group
A group of users are modeled using the ProfileGroup class. ProfileGroup is not only used to define the group itself but also to associate a group with roles and privileges, and to enable license consumption on group basis.
ProfileGroup
Company
A company is modelled using a class called Organization. The Organization object's type field value is set to "company" in this case.

Organization
The Organization model is also capable of representing other types of Organizations if that is relevant for the application. In this case the Organization object's type field value is set some relevant value defined by the application that uses the 10Duke Entitlements.
Organization
Technical Actor
A software component or similar technical entity who uses an application. The TechnicalActor model enables management of the roles that such an actor can be associated with.
TechnicalActor

Authorization and Entitlement

The 10Duke SDK provides an authorization API with a couple of alternative implementations. Like many other features, the authorization components are used by accessing appropriate services via ServiceLocator.

The architectural idea of the authorization API is to decouple the basis of decision making from enforcing the decision. As an example, the SDK provides a Servlet (AuthzServlet) implementation, which uses internally a two-tier model of both role-based authorization and license-based authorization. When calling this Servlet endpoint with an authorization check the client will be unaware if a possible positive answer was based on permissions or licenses. This allows versatile and flexible entitlement management and client application development.

AuthorityChecker is the primary API contract used to perform an authorization check in the SDK Java API. The SDK provides a utility that wraps use of ServiceLocator and AuthorityChecker. This utility is called Authorization, and it provides a static method signature for performing authorization.

Before performing authorization, the application must have acquired the relevant information. This information consists of:

  • Who (or what) is requesting access to a protected resource. The "who" is expressed using the type Actor.
  • What is the protected resource? The protected resource can be expressed using a name (String) or an Entity (e.g. an object from the SDK's or the application's object model).
  • What are the requested actions? Actions are an array of String values. Some common actions names are defined as constants in PermissionActionNames.
  • What are the permission grants? Permission grants are resolved for the calling Actor using the PermissionGrantResolver API contract.

The default PermissionGrantResolver implementation in the SDK is: LazyPermissionGrantResolver

//
// first locate the PermissionGrantResolver using ServiceLocator
PermissionGrantResolver permissionGrantResolver = ServiceLocator.locate(PermissionGrantResolver.class);
//
// next, resolve actor's permission grants
Iterable<PermissionGrant> permissionGrants = permissionGrantResolver.resolve(entityManager, transaction, actor);
//
// ... the authorization call can be performed once the protected resource (entity),
// actions and permission grants are known.
Authorization.authorize(entity, operationScope, PermissionActionNames.CREATE, permissionGrants);

These basic building blocks and API contracts show how to use authorization. In practical scenarios the application must decide which implementation to use for the API contracts. Here the SDK helps with default choices. Many of the default implementations in the SDK rely on configuration. Configuration of implementation classes can be done using the Java API, configuration files and by means of managing data in a database. Implementation classes document what type of configuration they support.

Role-based authorization

The 10Duke SDK provides applications with an ability to perform Role-based Authorization.

The concept of Role-based Authorization (or Role-based access control) is a standard model and is also the primary one used by the 10Duke SDK to check authorization. The model is simple and is based on managing which Permissions and allowed permission actions (grants) are related to what Roles. Roles in turn are assigned to Actors (e.g. Profile and TechnicalActor).

The two Actor implementation classes provided by the SDK are Profile and TechnicalActor. Relating each type to Roles is done using the default Many-to-Many relation class. The class diagram representing this connection for profiles:

Profile roles

The exact same structure is used for TechnicalActor:

TechnicalActor roles

The Role class and instances of it exist to make management of permissions easier and more efficient. The alternative is using direct relations from actors to permissions but that would result in most cases in too many relations to keep track of. The following diagram depicts the model used to couple roles and permissions together. It also shows the Grants type, which is used to express the allowed actions.

Role permissions

This model describes the model and data, which the default PermissionGrantResolver (LazyPermissionGrantResolver) uses. Roles assigned to a Profile or TechnicalActor are resolved using EntityManager (database) at runtime. This model is also used by RolePermissionGrantResolver.

Built-in Roles

The 10Duke SDK defines a concept called Built-in Roles. This means a "meta level" authorization configuration that allows applying authorization rules by the actor's relation to an entity. These roles are

  • Last editor: can be applied if it is possible to resolve the actor who last modified an entity.
  • Author: can be applied if it is possible to resolve the actor who authored an entity.
  • Owner: can be applied if it is possible to resolve the actor who owns an entity.
  • Authenticated user: can be applied if the application has a concept of authenticating users.
  • Viewer: can be applied if the application has a concept of non-authenticated users.
  • Group member: can be applied if the application includes users, groups and models relations from users and groups to entities.
  • Organization member: can be applied if the application includes users, organizations and models relations from users and organizations to entities.

BuiltInRoles utility assists in working with Built-in Roles.

Built-in Roles work using the exact same model as described generally for default role-based authorization.

Configuring roles and permissions

Role and permission configuration works on multiple levels and scopes. A good starting point is configuration of Built-in Roles and their associated permissions.

The natural next step is modelling roles that make sense in the scope of the application. Additional scopes that roles can be modelled in are groups and organizations. Application scope roles and permissions are simply instances of types Permission. Grants and Roles that have been related to users (as explained earlier in this chapter).

Each of these configuration alternatives function using the same object model including Permissions, permission actions, and relations to Roles. The following examples depict the basics of handling these entities using the SDK Java API.

First, a simple case for configuring the permission for non-authenticated users to view profile information contained in an entity called Profile. This example uses RoleAndPermissionUtils to configure built-in role for "viewer", which is a friendly name for non-authenticated users:

RoleAndPermissionUtils.createRole(
        BuiltInRoles.BUILT_IN_ROLE_ID_VIEWER,
        BuiltInRoles.BUILT_IN_ROLE_NAME_VIEWER,
        BuiltInRoles.BUILT_IN_ROLE_DESCRIPTION_VIEWER,
        entityManager,
        transaction,
        permissionGrants, // e.g. GrantAnyPermission.asIterable()
        actorId,
        new Permission(
                UUID.randomUUID(),
                Profile.class.getSimpleName(),
                PermissionActionNames.READ));

Creating a similar set-up as an application specific role and permission configuration can be done using Role and Permission types directly:

//
// Create a role, with "read" actions granted for reading Profile entities
Role role = new Role(UUID.randomUUID());
Permission permissionProfileRead = new Permission(UUID.randomUUID(), Profile.class.getSimpleName());
Grants permissionRelation = new Grants(UUID.randomUUID(), PermissionActionNames.READ);
permissionRelation.referenceFields().put(roleReference, role.getId());
permissionRelation.referenceFields().put(permissionReference, permissionProfileRead.getId());
entityManager.create(role, allPermissionGrants, actorId, transaction);
entityManager.create(permissionProfileRead, permissionGrants, testProfile.getId(), null);
RelatedObjectFieldProvider fieldProvider =
        new RelatedObjectFieldProvider(permissionRelation, "id", null, FieldProvider.ObjectConsumeMode.DEFAULT);
fieldProvider.setTypeName(permissionRoleRelationName);
entityManager.create(permissionRelation, fieldProvider, permissionGrants, actorId, transaction);

The default Many-to-Many relation object can be used to assign roles users. The SDK Java API style to accomplish this is:

//
// ManyToMany is the default implementation for Many-To-Many
// relations between objects.
ManyToMany profileRoleRelation = new ManyToMany(UUID.randomUUID());
//
// use SDK utility RelatedObjectUtils to name reference fields in the
// Many-To-Many relation by default contract. The reference fields
// store identifiers of objects on each side of the relation:
String roleReference = RelatedObjectUtils.getReferenceFieldName(Role.class);
String profileReference = RelatedObjectUtils.getReferenceFieldName(Profile.class);
profileRoleRelation.referenceFields().put(roleReference, role.getId());
profileRoleRelation.referenceFields().put(profileReference, userProfile.getId());
//
// for persisting the relation object we need a specific field provider
// that supports persisting reference fields and controlling the name
// for the relation (table name in database)
RelatedObjectFieldProvider fieldProvider = new RelatedObjectFieldProvider(profileRoleRelation);
//
// PersistenceUtils provides a method for resolving default Many-to-Many relation name:
String profileRoleRelationName = PersistenceUtils.getDefaultManyToManyPersistenceCollectionName(
        Profile.class,
        Role.class,
        ManyToMany.class);
fieldProvider.setTypeName(profileRoleRelation);
//
// write the relation using entity manager
entityManager.create(profileRoleRelation, fieldProvider, permissionGrants, actorId, transaction);
License-based authorization

In addition to role-based authorization, a concept of entitlement management and license-based authorization model is available.

The concept is best described by "identity based licensing". An Actor can be granted rights to access protected resources based on Licenses that he / she / it is allowed to check or consume. The "allowed to consume" condition is determined by the Actor's relation to the Entitlement(s) that contain(s) the licenses. AuthzRequestHandler, PermissionRequestHandler, and LicenseConsumer provide API and constant values for license consumption and checking.

//
// Who is checking / consuming the license
Actor actor = ...;
//
// locate the PermissionGrantResolver using ServiceLocator
PermissionGrantResolver permissionGrantResolver = ServiceLocator.locate(PermissionGrantResolver.class);
//
// next, resolve actor's permission grants
Iterable<PermissionGrant> permissionGrants = permissionGrantResolver.resolve(entityManager, transaction, actor);
//
// setup license related parameters
String licensedItemName = ...; // What license is checked / consumed
LicenseAnchor anchors = ... // can be Profile, Hardware, etc. implementation class that models an entity that license is anchored to
final String consumptionMode = ...; // e.g. see LicenseConsumer.java for possible consumption mode values
Long duration = ...; // the amount of time the license is consumed for (check out)
//
// locate the LicenseConsumer registered for a named scope. See PermissionRequestHandler.java for scope definitions.
String scope = ...; // e.g. "provider" or "consumer"
List<LicenseConsumer> licenseConsumers = ServiceLocator.locateAll(LicenseConsumer.class, scope);
//
// consumer / check the license
LicenseConsumerResult result;
boolean consume = ...; // determine if consuming or checking is the case
if (consume) {
    //
    result = licenseConsumer.consume(
            licensedItemName,
            consumptionMode,
            duration,
            actor,
            permissionGrants,
            anchors.toArray(new LicenseAnchor[anchors.size()]));
} else {
    //
    result = licenseConsumer.check(
            licensedItemName,
            consumptionMode,
            duration,
            actor,
            permissionGrants,
            anchors.toArray(new LicenseAnchor[anchors.size()]));
}
Configuring licensing

The first step in applying license-based authorization is to define packaging of licensed items and the constraints and behaviours that drive checking and consumption of granted licenses.

File Storage

The 10Duke SDK API for developing features and functions related to storage is called StorageProvider. The SDK provides a couple implementations for basic local disk based storage, a local hash-based tree storage and for using Amazon S3.

Like other base services the storage provider is also accessed using ServiceLocator.

StorageProvider storageProvider = ServiceLocator.locate(StorageProvider.class);

The storage provider API is based on using JDK URI type to identify the stored entity and the 10Duke SDK's Resource type to encapsulate the data.

Constructing an URI to identify an entity with storage provider follows a couple of rules:

  1. The scheme to use is: os:// (stands for object storage).
  2. The path part of the URI should be a path that makes logical sense to the application.
  3. Parameter named container is used to denote an abstract and logical container for the entity stored with storage provider. The container should also make sense for the application.

An example URI following the rules above: os:///user_n/video//uploads/video.mp4?container=users. The same can be achieved using StorageUri:

FileIdentifierGenerator idGenerator = ServiceLocator.locate(FileIdentifierGenerator.class);
StorageUri storageUri = idGenerator.createIdentifier("users", "/user_n/video//uploads/video.mp4");

StorageUri class exists to assist with handling paths, containers and ready URIs when working with storage provider.

The SDK's default service locator implementation uses ConfigurableStorageProvider, which allows the application developer to configure the storage provider in the main configuration. The following example shows a use-case for combining a local cache as transient storage and Amazon S3 as the durable storage.

<entry key="content.storage.storageprovider.default" ><![CDATA[
    <ctss:CachingStorageProvider
        xmlns:rel="http://10duke.com/schema/built-in/related"
        xmlns:ser="http://10duke.com/schema/built-in/serialization"
        xmlns:ctssl="http://10duke.com/package/com/tenduke/sdk2/storage/local"
        xmlns:ju="http://10duke.com/package/java/util"
        xmlns:ctss="http://10duke.com/package/com/tenduke/sdk2/storage"
        xmlns:ctssa="http://10duke.com/package/com/tenduke/sdk2/storage/amazons3"
        xmlns:jl="http://10duke.com/package/java/lang"
        ser:version="2.0">
        <cacheStorage>
            <ctssl:LocalCacheStorageProvider targetFreeSize="5000000000"
                                                     maxCacheSize="10000000000"
                                                     readOnly="false">
                <storageRootPath>./cache_storage</storageRootPath>
                <fileNameEncoding>url</fileNameEncoding>
            </ctssl:LocalCacheStorageProvider>
        </cacheStorage>
        <durableStorage>
            <ctssa:S3StorageProvider
                enableEventCreate="true"
                enableEventRead="true"
                enableEventDelete="true"
                bucketAccessIsPublic="true"
                readOnly="false"
                objectExistsCacheSize="0"
                enableEventUpdate="true"
                bucketName="masherv5"
                accessKey="your-s3-access-key"
                secretKey="your-s3-secret-key">
                <storageRootPath>./storage</storageRootPath>
                <fileNameEncoding>clean-unsupported</fileNameEncoding>
            </ctssa:S3StorageProvider>
        </durableStorage>
    </ctss:CachingStorageProvider>
]]></entry>

Graph API

The 10Duke SDK includes servlets that may be used in web applications to enable a Graph-based API.

Introduction

The Graph API is an object access API that allows dealing with objects and the relations between them. In deployment terms, a web application enables the Graph API by using Graph Servlet (or derived class) as an HTTP(S) endpoint. The Graph Servlet is found in the 10Duke SDK and can be used in any web application built with the 10Duke SDK.

The Graph API works on two basic concepts:

  • An object graph
  • Operations that can operate on the object graph

The graph term aside, this describes the basic building block of any application: data and application logic that operate on the data.

The 10Duke SDK Graph concept provides a meta data layer on top of an applications object model. This layer makes handling relations easy.

Operations

Calling the Graph API means calling operations that operate on data. These operations are implemented as Object Graph Processors. The SDK includes many implementation classes including the standard CRUD cases (Create, Read, Update, Delete).

Object Graph Processors implement a method called process, which will receive arguments initialized by the caller. Operations are of course available through a Java API in an application. The main aspect in this chapter is however the Graph API, which is called using HTTP requests. In such a case, the caller that calls an operation's process method will be e.g. GraphServlet, a servlet that inherits GraphServlet or a custom servlet defined by the application itself. Such a servlet will be responsible for parsing the "graph" request and assembling the arguments for the corresponding ObjectGraphProcessor.

The operation can be chosen by defining the value for parameter named operation in the HTTP request. As mentioned earlier this parameter is optional and the HTTP method will define the operation if the operation parameter is omitted. The following example shows how to call an operation named "CustomOperation" via the Graph API:

https://myhost/graph/?operation=CustomOperation

An instance of a named ObjectGraphProcessor is accessed using ServiceLocator. The service lookup uses ObjectGraphProcessor.class as the type and the operation name defined by the caller as the name of the API contract.

In order to enable custom ObjectGraphProcessor in an application you must register those instances with the ServiceLocator before trying to call the operations. That is usually done when starting the application:

ServiceLocator.setService(ObjectGraphProcessor.class, "CustomOperation", null, new CustomOperation());

The simplest form of operation arguments are a direct mapping from HTTP request parameters. E.g.

https://myhost/graph/?operation=CustomOperation&amp;customParameter=magic

HTTP request parameters are available to the operation implementation via the GraphOperationArguments argument to the process method. GraphOperationArguments signature provides overloads for accessing parameters and their values. A single value parameter can be conveniently accessed using a "get first" semantic call:

//
// customParameter = "magic"
String customParameter = (String) arguments.getFirstParameter("customParameter");

And parameters with multiple values can be accessed using:

List<String> customParameterValues = arguments.getParameters("customParameter", String.class);

The caller can pass in a graph selector to operations. As the name suggests, a graph selector is used to select objects. The structure of a selector is nested. A selector must always have the primary selection defined while nested selection is optional. Selection is best used in use cases where operating on existing data, which is accessed e.g. from a database.

//
// GraphOperationArguments provide access to the graph selector
// defined by the caller.
GraphSelector graphSelector = arguments.getSelector();
//
// GraphOperationArguments also provides a utility to convert the GraphSelector
// to a String in a way that all nested selectors are included after ? character
String graphQueryEncoded = arguments.getGraphSelectorWithNested(graphSelector);
int indexOfNested = graphQueryEncoded.indexOf('?');
//
// The "primary selector" can be parsed from the string (sanity check omitted)
String primarySelecor = graphQueryEncoded.substring(0, indexOfNested);
//
// for cases where an operations code is interested in nested selectors
// they can be assembled into a list by simple conversion
// from argument's selector - value structure.
List<GraphSelector> nestedSelectors = new ArrayList<>();
if (arguments.getNestedSelectors() != null) {
    for (KeyValuePair<GraphSelector, Object> selectorAndValue : arguments.getNestedSelectors()) {
        //
        nestedSelectors.add(selectorAndValue.getKey());
    }
}

The graph payload is provided to the operation using GraphObjectInput.

The SDK GraphServlet implementation uses GraphRequestHandler to encapsulate parsing and processing of graph requests. That class uses GraphRequestParser and GraphObjectParameterParser internally to parse the request to form the arguments for the requested operation.

Operations access objects from the input graph using the following pattern:

//
// An operation can access object payload in form of GraphObject
final List<GraphObject<?>> parsedObjects = objectGraph.getGraphObjects();
//
// e.g.
GraphObject<Profile> userProfileGraph = ...
//
// the actual object the application is dealing with can be accessed in two
// ways depending on the need.
//
// buildObject will convert the entire graph object hierarchy to the
// corresponding object hierarchy with relations and all
// starting with the object wrapped by
// the GraphObject instance the application call buildObject for.
Profile userProfile = userProfileGraph.buildObject();
//
// alternatively if you only need access to the object wrapped
// by the graph object a simple non recursive call to getObject works fine
userProfile = userProfileGraph.getObject();

Default operations and mapping to HTTP methods

The Graph API maps HTTP methods to the default operations. This is a standard web API way of working. Operations are technically called "object graph processors" in the SDK. The default operations are self-explanatory and they are (HTTP method in ()):

Create (POST)

HTTP POST defaults to an Object graph processor called create.

Read (GET)

HTTP GET defaults to an Object graph processor called read.

Update (PUT)

HTTP PUT defaults to an Object graph processor called update.

Delete (DELETE)

HTTP DELETE defaults to an Object graph processor called delete.

Creating and updating objects

Objects can be created and updated using "raw" payload or by Html forms. In both cases the client will make a request and send an object graph as the argument for the operation. As mentioned earlier HTTP POST and PUT methods will default to create and update operations.

Form inputs

As we need to support more operations than just the basic CRUD operations and HTML Forms are always either POST or GET we've added support for an alternative method of choosing the operation to run. A simple input field with the attribute name set to "operation" will tell the graph servlet which operation to run.

<input type="hidden" name="operation" value="update" />

The actual Objects are specified as name value pairs with standard form elements. The type of the chosen form element does not effect the result. It all boils down to the submitted name value pairs. The syntax used in the name attribute is very similar as when selecting / reading objects (see next chapter). The graph servlet will use these name value pairs to build an object graph and apply the specified action to it. The Following example would create a new Person with firstName "John" and lastName "Doe"

<input type="hidden" name="operation" value="create" />;
<input type="hidden" name="/Person/@firstName" value="John" />;
<input type="hidden" name="/Person/@lastName" value="Doe" />;

As the "/Person/" part is identical the graph servlet assumes they are referring to the same Object and thus creates a Person object with both first and last name set. In reality we want the new Person Object to have a unique id. This can be easily achieved by adding "{randomUuid,profile}" as the id. The "randomUuid" part will create an id and the "profile" part is a label that allows you to re-use the same id elsewhere in the form. It will always have the same id value. If you need multiple new ids in the same form just switch the label eg. {randomUuid, otherLabel}

<input type="hidden" name="operation" value="create" />;
<input type="hidden" name="/Person[@id='{randomUuid,profile}']/@firstName" value="John" />;
<input type="hidden" name="/Person[@id='{randomUuid,profile}']/@lastName" value="Doe" />;

Updating an existing object is very similar. We simply switch the operation to update and change to an existing id to allow the Graph servlet to find a matching Object from the database. Only the id attribute will be used to find objects from the DB.

<input type="hidden" name="operation" value="update" />
<input type="hidden" name="/Person[@id='MY_PERSON_ID']/@firstName" value="John" />
<input type="hidden" name="/Person[@id='MY_PERSON_ID']/@lastName" value="Doe" />

Sometimes it's useful to have the user input a value once and use it several time in the form. In the next example we have an input field that assigns a value into a custom variable that is then used to assign a value to both /Person/@firstName and /Profile/@displayName

<input type="hidden" name="operation" value="update" />
<input type="text" name="myVariable" value="John" />
<input type="text" name="/Person[@id='MY_PERSON_ID']/@lastName" value="Doe" />
<input type="hidden" name="/Person[@id='MY_PERSON_ID']/@firstName" value="{$myVariable}" />
<input type="hidden" name="/Profile[@id='MY_PROFILE_ID']/@displayName" value="{$myVariable}" />

In addition to the custom variable the graph servlet supports the following predefined variables: {$NULL} and {$NONE}

Raw payload

The HTTP client may also POST and PUT raw payload as graph requests. The raw payload is easiest to create using the SDK's default XmlSerializer and JsonSerializer classes. In cases where the client is not Java based these classes may still be used as a utility to produce templates for the use cases the client needs.

Additional / optional request parameters
Name Value Description
setNulls true/false (false is the default value) When set to true create and update operations will write null values all the way to persistence. If set to false then null values will not over-write not null values in persistence.
setNullField {FIELD_NAME} (name of a field in one of the objects part of the object graph) This parameter may be repeated several times for all the specific field names that will be eligible for use case that null over-writes not null value.
relationWriteMode add/replaceAll/replaceAllRelations Write mode to set for all relations in the parsed input:
  • "add" means mode applicable to relation objects where the intent is adding new related objects without removing or replacing existing ones.
  • "replaceAll" means mode where existing related objects are replaced with the new specified ones.
  • "replaceAllRelations" means mode where the given relation definitions and related objects replace both relations and related objects.

Selecting / Reading objects

Selecting objects can be thought of as something like querying objects from a database, except in a database independent manner. A web application usually uses a database of some kind but equally you can implement an application that would fully rely on XML file or even memory based data.

Objects and fields are selected from an object graph using graph selectors, e.g. to get the value of a Person object's firstName field:

/Person/@firstName

The same example using a URL: https://myhost/graph/Person/@firstName

Selecting objects usually involves defining some conditionals for to narrow down the selection. Matching field values is a common case. The graph selector syntax supports matching field values and using basic boolean operators. The following example shows how to select an object using field value matching:

/Person/[@firstName='John']

This selector would select all Person objects with the first name John.

/Person/[@firstName='John' &amp;&amp; @lastName='Doe']

This selector would select all Person objects with the first name John and last name Doe.

Selection applies to media as well. The following example shows you how an image can be requested:

/Entry[@type='image' && @title=''].jpg?w=240&h=180

The same example using a URL:

https://myhost/graph/Entry[@type='image' && @title=''].jpg?w=240&h=180
Syntactical elements of Graph Selector

Explanation of characters used in Graph selectors when reading objects:

Syntax Use case Example
/ separates objects, fields and relations in selectors (path element separator) /Entry, which selects the title field from all Entry objects. The starting / denotes the start of the selector path and Entry denotes the object type to use for selection
@ denotes a field selector /Entry/@title, which selects the title field from all Entry objects.
~ denotes a relation /Entry/~OneToMany/Media, which selects all Media objects by first selecting all Entry objects, following a ONE TO MANY relation forward to the Media type of objects.

Table 1: Explanation of syntactical element in Graph Selectors

Supported boolean operators in field selectors are:

Operators Syntax Description
AND && Logical AND operator, which requires that both related conditional expression evaluates to true
OR || Logical OR operator, which requires that either related conditional expression evaluates to true
NOT ! Logical NOT operator, which requires that the related conditional expression evaluates to false

Table 2: supported boolean operators

Special short names:

Use case Syntax Example
Referencing the logged in Profile me /me.xml
Referencing the logged in Technical Actor technicalme /technicalme.json

Table 3: Supported short names in selectors

You can also access an arbitrary long chain of related objects using graph selectors. As an example, take the Profile and ContactInformation object model from the 10Duke SDK. Let us define that in our application a Profile is related to several ContactInformation objects, which each can in turn be related to several EmailAddress objects. The objects and relations between then are: Profile --> ContactInformation --> EmailAddress with a one to many relation between each. The following graph selector would select a specific profile's all contact emails:

/Profile[@id='8bec774d-c4e7-4f94-9562-42dec4418fec']/~OneToMany/ContactInformation/~OneToMany/EmailAddress

Configuration Guide

The 10Duke SDK includes a "configuration engine". This engine includes an API, utility classes, parsers, etc. It provides the means to configure how the SDK code operates. It is also good for solving application-level configuration requirements.

All the configuration keys and corresponding values that affect the SDK code are documented in the Configuration Reference in this guide (jump a couple pages forward for that).

This section will explain the basics of syntax, management, and authoring of configuration for different types of environments. The aim is to help you as an application developer to apply "configuration based solutions" to your own code (instead of hard-coding things that should be configurable).

Some example features that the 10Duke SDK configuration engine includes are:

  • Splitting configuration into multiple files - helps keep things tidy and logical
  • Variables, which help define deployment node specific values while keeping the bulk of the configuration the same for all nodes
  • Reference to environment variables for assigning values - great for environments like OpenShift, AWS, OpenStack, and Docker-based deployments.
  • Complex objects as values
  • Strong types and type checks when using the configuration

How the configuration is found

By default, the SDK configuration engine finds it's configuration by looking into a path ./conf/conf.xml, which is relative to the application's execution path.

The web application (WAR) default is to look under WEB-INF/conf/ for a file called conf.xml (WEB-INF/conf/conf.xml).

You may specify an alternative location of the "main configuration" by using one of the following means (define the value of a specific JVM or Servlet context listener parameter):

  • JVM parameter: 10duke.configuration.main
  • Servlet context listener parameter: 10duke.configuration.main

All details about these higher level configurations can be found in the 10Duke SDK Configuration Reference (see Chapter 5).

Syntax basics

The configuration syntax is XML and defined by the Java Properties XML specification. Next, an example to illustrate this XML:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties version="1.0">
  <!--
    Each key in the configuration is declared and defined using
    an element called entry:
  -->
  <entry key="example.configuration.key">example configured value</entry>

</properties>

The resulting configuration is a data structure where everything is contained in the context of a Java Properties object. In other words, the structure is a flat "key - value" space on high level. A high level requirement is to keep key names unique. The 10Duke SDK naming convention uses the dot character: "." as a separator between words / names that make up a key.

Values

Defining the value of an configuration entry can be done in the following ways:

  • a direct definition
  • reference to a variable declared and defined in the scope of the configuration
  • reference to an environment variable
  • a value definition that points the parser to read content from a file

First, examples of direct assignment of a configuration entry value include:

<entry key="messaging.send.mail.host">localhost</entry>

A configuration entry value can be read from a variable, which has been declared and defined in the scope of the configuration:

<entry key="messaging.send.mail.host"><?ref name="smtpServer"?></entry>

The "declared in the scope of the configuration" means that the referenced variable must have been introduced in the configuration prior to using it. The syntax of declaring and defining variables is:

<?var name="smtpServer" value="localost"?>

Or alternatively like this if you wish to use environment variables:

<?var name="smtpServer" env="SEND_MAIL_HOST" default="localost"?>

Environment variables can also be referenced directly from an configuration entry like this:

<entry key="messaging.send.mail.host"><?env name="SEND_MAIL_HOST"?></entry>

And finally, reading a file's content as the value of a configuration entry:

<?value url="persistence/persistence.xml" encoding="utf-8" cache="false"?>

NOTE: Remember to use a CDATA section or XML escaping if a value has reserved XML characters in it and you do not wish for them to be interpreted by the parser.

Variables

The configuration supports assigning configuration entry values from variables. Variables can be defined in a file or alternatively be read from the systems environment.

The next example explains basic use of variables. First, let's start with the use case of declaring variables and defining their values in a file.

Assume you have a web application and a file called nodeVariables.xml in the WEB-INF folder. In that nodeVariables.xml file you can add variables using the following syntax:

<!-- Host that provides email sending service -->
<?var name="smtpServer" value="localost"?>

<!-- System support email address -->
<?var name="supportEmail" value="support@10duke.com"?>

<!-- You can extend and define variables with any name -->
<?var name="myOwnVariable" value="testing"?>

<!-- Database -->
<?var name="postgresql_db_user_name" env="POSTGRESQL_DB_USERNAME" default="roadrunner"?>
<?var name="postgresql_db_password" env="POSTGRESQL_DB_PASSWORD" default="verysecret"?>
<?var name="postgresql_db_name" env="POSTGRESQL_DB_NAME" default="acmedatabase"?>
<?var name="postgresql_db_host" env="POSTGRESQL_DB_HOST" default="localhost"?>
<?var name="postgresql_db_port" env="POSTGRESQL_DB_PORT" default="5432"?>
<?var name="postgresql_db_max_connections" env="POSTGRESQL_DB_MAX_CONNECTIONS" default="50"?>

Note how you can use a direct "key - value" type definition and alternatively a "key - environment variable - default value" type structure. The default value is useful because it allows you to define a value to be used in case the environment variable has not been set.

Now that variables have been declared and defined in a file they can be used from configuration. To use the variables the configuration must "know" that the variables exist. The configuration parser supports this by using a processing instruction called include, e.g.:

<?include url="nodeVariables.xml"?>

To use a variable, an other special processing instruction entry is used. This instruction is called ref, which is short for reference. The following example shows how to reference a variable in configuration:

<entry key="messaging.send.mail.host"><?ref name="smtpServer"?></entry>

And putting this all together is shown by the following example configuration XML:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<?include url="nodeVariables.xml"?>
<properties version="1.0">

  <!-- For sending email we need to know what host runs the sending service -->
  <entry key="messaging.send.mail.host"><?ref name="smtpServer"?></entry>

</properties>

This example assumes that the nodeVariables.xml resides in the same folder as the configuration file itself.

Splitting configuration into several files

Splitting the configuration into several files to form a logical structure can be achieved using the value processing instruction. This instruction supports defining a value using a relative file path (the syntax itself is more generic and would allow an SDK user to extend the capability to load values e.g. from a web address).

The use case of defining the value by referencing to a file means that the file content as such will define the value. The control parameters in this case are:

  • url: relative location of the file
  • encoding: character set encoding of the file content, e.g. UTF-8
  • cache: flag that tells the configuration engine if the value may be cached or if it should be re-read every time a call to read to the configuration key is made
<entry key="data.entitymanager.graph"><![CDATA[<?value url="persistence/persistence.xml" encoding="utf-8" cache="false"?>]]></entry>

In the example above, the configuration author has decided to put the configuration for persistence (database) into a file of its own. That "nested" file shall be found relative to the main configuration in path ./persistence/persistence.xml.

Configuration Reference

This chapter is divided to explain JVM and Servlet Context Listener configuration separately from the configuration that applies to the application logic of 10Duke SDK code and applications created using the 10Duke SDK.

JVM and Servlet Context Listener configuration controls where the 10Duke SDK configuration is found and which "configuration engine" implementation to choose.

Configuration Keys

JVM parameters

You may use certain JVM parameters to control:

  • the location of the configuration used by the SDK and your application
  • which "configuration engine" implementation to use (including choosing one of your own)
10duke.configuration.provider.classname

Default: {@link #DEFAULT_CONFIGURATION_PROVIDER_CLASS_NAME}

The fully qualified class name of class implementing {@link ConfigurationProvider} is to be used as the {@link ConfigurationProvider} providing configuration access via this class.

10duke.configuration.variables.url

Default: N/A

The url of a separate variables file is used to define variables for reading configuration. If this configuration parameter is defined, it extends configuration by initializing another {@link ConfigurationProvider} by reading {@link Properties} xml defined by the parameter. When variable references are used in the main configuration, this {@link ConfigurationProvider} is used to return values for the referenced variables.

10duke.configuration.provider.classname

Default: {@link #DEFAULT_CONFIGURATION_PROVIDER_CLASS_NAME}

A fully qualified class name of class implementing {@link ConfigurationProvider} to be used as the {@link ConfigurationProvider} providing configuration access via this class.

10duke.configuration.main

Default: If no value for the configuration parameter is defined, {@link #DEFAULT_MAIN_CONFIG_URLS} is used for resolving the default configuration url.

Main configuration file url. If a relative url is given, it is resolved as being relative to the JVM process execution directory. If the value of this property is an empty string, no configuration is loaded. If it is not defined, the default main configuration url is used. For the default main configuration url case, initialization does not throw a ConfigurationException if no configuration is found. If the value for the parameter is defined as a non-empty value, a ConfigurationException is thrown if it failed to load configuration by the defined url.

Servlet Context Listener parameters

You may use certain Servlet Context Listener parameters to control:

  • the location of the configuration used by the SDK and your application
  • which "configuration engine" implementation to use (including choosing one of your own)
10duke.configuration.main

Default: If the value for the configuration parameter is not defined, {@link #DEFAULT_MAIN_CONFIG_URLS} is used for resolving the default configuration url. Main configuration file url. If relative url is given, it is resolved as being relative to the JVM process execution directory. If the value of this property is an empty string, no configuration is loaded. If it is not defined, the default main configuration url is used. For the default main configuration url case, initialization does not throw a ConfigurationException if no configuration is found. If the value for the parameter is defined as a non-empty value, a ConfigurationException is thrown if failed to load configuration by the defined url.

10duke.configuration.variables.url

Default: N/A

The url of a separate variables file is used to define variables for reading configuration. If this configuration parameter is defined, it extends configuration by initializing another {@link ConfigurationProvider} by reading {@link Properties} xml defined by the parameter. When variable references are used in the main configuration, this {@link ConfigurationProvider} is used to return values for the referenced variables.

SDK main configuration

lifecycle.concurrent.total.maximum.pool.size

Default: 20

Total allowed number of thread pools that this class will use. This configuration parameter is read from configuration dedicated to this class (not a parameter on main configuration root level).

lifecycle.concurrent.callername.*

Default: N/A

Configuration keys in this key space define caller names that thread pools are dedicated for. Use a continuous integer series appended to the base configuration key to define several caller names. e.g: lifecycle.concurrent.callername.1, lifecycle.concurrent.callername.2 , ..., lifecycle.concurrent.callername.N. This configuration parameter is read from configuration dedicated to this class (not a parameter on main configuration root level).

default.tmp.dir

Default: N/A

Default path where to store temporary files.

lifecycle.concurrent.execution.configuration

Default: N/A

Value must be java.util.Properties XML format configuration, which is used to control the behavior of this class. The configuration is optional and omitting it will result in only the default thread pool to be used.

Example entry in main configuration:

<entry key="lifecycle.concurrent.execution.configuration">
    <![CDATA[<?value url="concurrent.xml" encoding="utf-8" cache="true"?>]]>
</entry>

This example shows how to load configuration for this class from a file called concurrent.xml located in the same folder as the main configuration file resides in.

lifecycle.concurrent.queue.size.*

Default: 5

Configuration keys in this key space define queue size for a specific thread pool. Use a continuous integer series appended to the base configuration key to configure queue size per specific caller name. e.g: lifecycle.concurrent.queue.size.1, lifecycle.concurrent.queue.size.2 , ..., lifecycle.concurrent.queue.size.N. This configuration parameter is read from configuration dedicated to this class (not a parameter on main configuration root level).

null

Default: null

lifecycle.concurrent.keepalive.time.millis.*

Default: 2000

Configuration keys in this key space define the keep alive time for unused thread in a specific thread pool. Use a continuous integer series appended to the base configuration key to configure keep alive time per specific caller name. e.g: lifecycle.concurrent.keepalive.time.millis.1, lifecycle.concurrent.keepalive.time.millis.2 , ..., lifecycle.concurrent.keepalive.time.millis.N. This configuration parameter is read from configuration dedicated to this class (not a parameter on main configuration root level).

lifecycle.concurrent.core.pool.size.*

Default: 5

Configuration keys in this key space define core pool size for a specific thread pool. Use a continuous integer series appended to the base configuration key to configure core pool size per specific caller name. e.g: lifecycle.concurrent.core.pool.size.1, lifecycle.concurrent.core.pool.size.2 , ..., lifecycle.concurrent.core.pool.size.N. This configuration parameter is read from configuration dedicated to this class (not a parameter on main configuration root level).

lifecycle.concurrent.maximum.pool.size.*

Default: 20

Configuration keys in this key space define maximum pool size for a specific thread pool. Use a continuous integer series appended to the base configuration key to configure maximum pool size per specific caller name. e.g: lifecycle.concurrent.maximum.pool.size.1, lifecycle.concurrent.maximum.pool.size.2 , ..., lifecycle.concurrent.maximum.pool.size.N. This configuration parameter is read from configuration dedicated to this class (not a parameter on main configuration root level).

security.jwt.public.key

Default: N/A

Configures parameter for public key corresponding to the private key used for signing a JWT token, which in turn may be used in claims based authentication of an actor. The key is defined by {@link ConfigurableJwtSerializer#CONFIGURATION_PARAMETER_PUBLIC_KEY}.

lifecycle.concurrent.thread,priority.*

Default: Thread.NORM_PRIORITY

Configuration keys in this key space define the priority for threads in a specific thread pool. Use a continuous integer series appended to the base configuration key to configure priority per specific caller name. e.g: lifecycle.concurrent.thread,priority.1, lifecycle.concurrent.thread,priority.2 , ..., lifecycle.concurrent.thread,priority.N. This configuration parameter is read from configuration dedicated to this class (not a parameter on main configuration root level).

internationalization.default.locale

Default: en_US

Name of configuration parameter defining default locale to be used for serving end user requests.

data.named.statement.

Default: N/A

Configuration parameter name prefix for configuration parameters that define high level (not entity manager specific) named statements. Example of such higher level named statement is a complex graph statement mapped to a simple name. Configured named statements are intended to be accessed using {@link #getNamedStatement(java.lang.String)} and {@link #expandNamedStatement(java.lang.String, java.lang.Object[])} methods of this class.

internationalization.default.resource.locale

Default: en_US

Name of configuration parameter defining default locale for resources.

security.invitation.salt

Default: System default.

Configures parameter for defining salt for secret used in invitation acceptance process.

content.resourcelocator.file.basepath

Default: N/A

Base path, file paths resolved from requested resource uri:s are interpreted as being relative to this base path. If not specified, file paths are interpreted as being relative to current working directory. If {@link #setBasePath(java.lang.String)} is called, the value given as its argument is used instead of the configured value.

networking.jetty.autostart

Default: true

Flag controlling whether the Jetty web server should be started automatically when the web server service is initialized.

content.storage.storageprovider.default

Default: N/A

This configuration entry is used to initialize {@link NamedApiContract} of {@link StorageProvider} service using {@link ConfigurableStorageProvider#ConfigurableStorageProvider(java.lang.String)}. The value, if key is specified in configuration, must be {@link XmlSerializer} format serialized value of type instance that implements {@link StorageProvider}.

networking.jetty.configuration

Default: Default configuration starting web server in http port 8088 and displaying a welcome page in the root context.

Jetty web server configuration xml

security.role.builtin.refresh.interval.ms

Default: 5000L

Configures parameter for controlling built-in role refresh interval in milliseconds.

ecom.config.recurly.api.key

Default: N/A

API key to use when authenticating client with Recurly

ecom.config.recurly.pages.size

Default: N/A

Page size to be used when querying the Recurly API (number of items returned per call).

content.storage.storageprovider.cache.size

Default: N/A

Maximum size of the cache in bytes.

enableEventCreate

Default: false

Flag to control if a StorageProviderEvent should be fired when a file is created into storage.

fileNameEncoding

Default: clean

Name of encoding type to use for file names on disk. File name on disk is based on storage key. Allowed values are none (key used as such), clean (key is cleaned from not allowed characters), base64 (key is base64 encoded) and hex (key is hex encoded).

readOnly

Default: false

Flag to control if read only operation is required.

security.cipherutils.ciphertransformationname

Default: N/A

Cipher algorithm to use for encrypting and decrypting data. The value is an example only and there is no default value. Users of CipherUtils can specify the transformation name as an argument in construction phase as well. Example values: DES, CBC, PKCS5Padding

storageRootPath

Default: ./storage (relative path in the applications execution path)

Path to storage root where files are stored.

security.password.minlength

Default: 1

Configures parameter for required password minimum length.

objectExistsCacheSize

Default: 0

Size of cache to hold lookup results. Using a cache to hold lookup result will speed up calls to locate and get in this class. Using a size of <= 0 will disable the cache.

security.password.salt.old

Default: N/A

Configures parameter for old password salts.

security.jwt.validity.ms

Default: N/A

Configures parameter for token validity in milliseconds.

security.oauth.consumer.request.token.lifetime.ms

Default: 300000L

Time (in milliseconds) for how long time consumer should store OAuth request tokens.

bucketAccessisPublic

Default: false

Flag to enable direct HTTP access to stored objects without any authentication.

security.jwt.public.key

Default: N/A

Configures parameter for public key.

bucketName

Default: N/A

Name of S3 bucket to store files in.

security.jwt.private.key

Default: N/A

Configures parameter for private key used to sign JWT tokens.

security.session.salt

Default: If not defined, default common salt is used and a warning is logged.

Common part of salt, used for generating all session signatures.

secretKey

Default: N/A

Secret key for Amazon API client authentication.

accessKey

Default: N/A

Access key for Amazon API client authentication.

media.mimetypes.configuration

Default: Embedded default configuration is used if configuration parameter not defined.

{@link Properties} xml, key of each property is a file extension and value is the respective MIME type.

security.trusted.certificates.private.key

Default: N/A

Configuration parameter for private key of trusted certificate service. This private is used for signing certificate service responses.

security.trusted.certificates.public.key

Default: N/A

Configuration parameter for public key of trusted certificate service. This public key can be used for verifying signatures of trusted certificate service responses.

graph.relations

Default: N/A

Object relations, describing the whole graph / data model used by an application. This configuration parameter can be used for using own configuration that completely replaces default configuration defined by this class.

graph.relations.additional

Default: N/A

Additional object relations for extending the default graph / data model. This configuration parameter can be used for defining additional configuration on top default configuration defined by this class or configuration defined by {@link #CONFIGURATION_PARAMETER_GRAPH_RELATIONS}.

entity.default.set.null.fields

Default: false

Configures parameter for default value for controlling whether input object null fields should be used for overriding possible existing entity field values.

security.authorization.authorize.operations

Default: true

Configures parameter controlling whether executing graph operations must be authorized.

security.authorization.return_missing_permission

Default: false

Configures parameter controlling {@link AuthorizationException} messages are written to response {@link OperationResult}. This is not recommended in production environment due possible security hazard.

service.support.email

Default: N/A

Configures parameter for service support email address.

security.oauth.token.secret.salt

Default: If not defined, default common salt is used and a warning is logged.

Salt used for generating OAuth token secrets.

security.password.salt.old

Default: N/A

Earlier used common parts of salt, used for generating all password hashes. Array of String, as serialized by {@link com.tenduke.sdk2.io.serialization.XmlSerializer}. Example:

<![CDATA[<ser:Array elementType="jl:String" size="1" xmlns:rel="http://10duke.com/schema/built-in/related" xmlns:ser="http://10duke.com/schema/built-in/serialization" xmlns:jl="http://10duke.com/package/java/lang" ser:version="2.0">
    <ser:Items>
        <ser:Item>[OldPasswordHash]</ser:Item>
    </ser:Items>
</ser:Array>]]>
security.oauth.token.salt

Default: If not defined, default common salt is used and a warning is logged.

Salt used for generating OAuth tokens.

content.mediaprocessing.tempfiledirectorypath

Default: Value of default.tmp.dir, or ./tmp if default.tmp.dir not defined.

Temporary directory used for media processing temporary files.

enableEventUpdate

Default: false

Flag to control if a StorageProviderEvent should be fired when a file is updated in storage.

enableEventRead

Default: false

Flag to control if a StorageProviderEvent should be fired when a file is read from storage.

enableEventDelete

Default: false

Flag to control if a StorageProviderEvent should be fired when a file is deleted from storage.

security.password.salt.old

Default: N/A

Earlier used common parts of salt, used for generating all password hashes. Array of String, as serialized by {@link com.tenduke.sdk2.io.serialization.XmlSerializer}. Example:

<![CDATA[<ser:Array elementType="jl:String" size="1" xmlns:rel="http://10duke.com/schema/built-in/related" xmlns:ser="http://10duke.com/schema/built-in/serialization" xmlns:jl="http://10duke.com/package/java/lang" ser:version="2.0">
    <ser:Items>
        <ser:Item>[OldPasswordHash]</ser:Item>
    </ser:Items>
</ser:Array>]]>
security.jaas.logincontext.*.loginmodules

Default: N/A

Login module configuration for a login context. * in the key name must be substituted with respective login context name.

security.jaas.logincontext.default.loginmodules

Default: {@link #DEFAULT_LOGIN_MODULE_CONFIGURATION}

Login module configuration for default login context.

messaging.send.mail.username

Default: N/A

Configures SMTP username for authorizing SMTP server.

enableEventDelete

Default: false

Flag to control if a StorageProviderEvent should be fired when a file is deleted from storage.

messaging.send.mail.password

Default: N/A

Configures password for SMTP username.

enableEventUpdate

Default: false

Flag to control if a StorageProviderEvent should be fired when a file is updated in storage.

service.support.email

Default: N/A

Configures parameter for service support email address.

enableEventRead

Default: false

Flag to control if a StorageProviderEvent should be fired when a file is read from storage.

messaging.send.mail.host

Default: N/A

Configures parameter for SMTP host to use for sending email messages.

enableEventCreate

Default: false

Flag to control if a StorageProviderEvent should be fired when a file is created into storage.

networking.http.cross.origin.allowed.cross.origin.pattern

Default: false

Configures parameter defining URL pattern for matching cross origin request origin. Matching origins are considered as allowed requests, non-matching should be denied. Allow check is activated for request URLs that match the configured networking.http.cross.origin.is.cross.origin.pattern.

fileNameEncoding

Default: clean

Name of encoding type to use for file names on disk. File name on disk is based on storage key. Allowed values are none (key used as such), clean (key is cleaned from not allowed characters), base64 (key is base64 encoded) and hex (key is hex encoded).

readOnly

Default: false

Flag to control if read only operation is required.

networking.http.redirect.to.https

Default: false

Configures parameter for instructing to redirect http request to https.

storageRootPath

Default: ./storage (relative path in the applications execution path)

Path to storage root where files are stored.

networking.http.cross.origin.is.cross.origin.pattern

Default: *

Configures parameter defining URL pattern for matching cross origin requests. Pattern match for request URL means that a request is cross origin.

networking.https.port

Default:

Configures secure port used for HTTP server in the application.

networking.http.sessionidcookie

Default: JSESSIONID

Configures session id cookie name.

networking.http.port

Default:

Configures port used for HTTP server in the application.

messaging.send.mail.host

Default: N/A

Configures parameter for SMTP host to use for sending email messages.

messaging.send.mail.username

Default: N/A

Configures SMTP username for authorizing SMTP server.

messaging.send.mail.password

Default: N/A

Configures password for SMTP username.

reporting.persistence.storeEntries

Default: Default value is false ie. logging to file is disabled.

Boolean value to enable/disable storing report entries to database.

service.support.email

Default: N/A

Configures parameter for service support email address.

reporting.log.storeEntries

Default: Default value is false ie. logging to file is disabled.

Boolean value to enable/disable report entry logging to file.

messaging.send.mail.host

Default: N/A

Configures parameter for SMTP host to use for sending email messages.

reporting.log.entries.received.directory

Default: ./tmp/reporting/server/log

Directory where log files are created.

reporting.log.entries.received.filename

Default: Default value is reporting-{0}-data.log.

File name pattern for log files given in {@link MesssaFormat} like reporting-{0}-data.log where {0} represents date.

reporting.log.entries.received.maxfilesize

Default: 1GB.

Maximum log file size in bytes.

reporting.log.entries.received.maxfilecount

Default: 1024.

Maximum count of log files.

messaging.send.mail.username

Default: N/A

Configures SMTP username for authorizing SMTP server.

messaging.send.mail.password

Default: N/A

Configures password for SMTP username.

security.identity.delegated.user.update.selector

Default: /me?/~ManyToOne/Person

Graph selector used for querying user details from identity provider and updating the details on the consumer side.

operation.read.default.selector.parameters

Default: N/A

Configures parameter defining default graph selector parameters to apply if no values for the configured parameter names are present in parameters a {@link GraphSelector} given to this operation. Configuration value must be enclosed in curly brackets and contain comma-separated list of parameters, for example {r=10,other=foo}.

reporting.persistence.entitymanager

Default: Default is null ie. entity manager not configured.

Serialized EntityManager object.

entity.field.validation.[EntityType]@[EntityField]

Default:

Configures parameter for entity field validation regular expression. EntityType is object collection name, e.g. database table name. EntityField is name of entity field as seen in persistence.

entity.field.validation.allow.null.[EntityType]@[EntityField]

Default:

Configures parameter for entity field validation allow null. EntityType is object collection name, e.g. database table name. EntityField is name of entity field as seen in persistence.

media.objectmedia.resolver.configuration

Default: N/A

Configuration for {@link MediaResolverConfiguration} class. Value is a serialized instance of {@link MediaResolverConfiguration} class ({@link XmlSerializer} format).

depth

Default: 4

depth of the oct-cube directory hierarchy within a container.

loginmodule.profile.reference.saml_assertion_field_name

Default: email

Configures default assertion name used as reference to local profile.

containerDepth

Default: 4

depth of the oct-cube directory hierarchy for container level.

config.key.example.2

Default: example 2

Example configuration key.

persistence.logging.update

Default: false

Configures parameter controlling whether entity managers should log update operations.

config.key.example.1

Default: example 1

Example configuration key.

persistence.logging.delete

Default: false

Configures parameter controlling whether entity managers should log delete operations.

persistence.logging.change.schema

Default: false

Configures parameter controlling whether entity managers should log schema change operations.

persistence.object.set.authored

Default: true

Configures parameter controlling whether entity managers should set values for entity fields annotated with {@link com.tenduke.sdk2.api.object.Authored} and {@link com.tenduke.sdk2.api.object.AuthoredBy} annotations when creating entity.

persistence.object.set.edited

Default: true

Configures parameter controlling whether entity managers should set values for entity fields annotated with {@link com.tenduke.sdk2.api.object.Edited} and {@link com.tenduke.sdk2.api.object.EditedBy} annotations when updating entity.

security.trusted.certificate.[authority]

Default: N/A

Configuration parameter prefix for trusted certificates. Request URL authority is appended to the prefix to form configuration keys for accessing trusted certificates for that given authority.

config.key.example.4

Default: example 4

Example configuration key.

config.key.example.3

Default: example 3

Example configuration key.

security.oauth.provider.request.token.expiration.time.ms

Default: 300000

Configures parameter for OAuth request token expiration time (ms).

content.storage.multipart.tmp.dir

Default: value of configuration parameter default.tmp.dir

Temporary directory where data and files can be stored while processing a multipart form.

persistence.logging.create

Default: false

Configures parameter controlling whether entity managers should log create operations.

persistence.logging.read

Default: false

Configures parameter controlling whether entity managers should log read operations.

reporting.enabled

Default: The default value is false.

Flag for turning reporting on / off.

ecom.config.recurly.pages.subscribe

Default: N/A

Name of subscription page in Recurly service.

reporting.client.buffer.submit_full_buffer

Default: The default value is true.

Option to submit full buffers immediately instead of waiting submit interval timer to execute.

ecom.config.recurly.pages.domain

Default: N/A

Main domain name to use for accessing pages hosted in Recurly service.

reporting.client.buffer.delete_submitted

Default: The default value is false.

Boolean value to enable/disable deletion of submitted buffer files.

ecom.config.recurly.pages.subdomain

Default: N/A

Subdomain name to use for accessing pages hosted in Recurly service.

reporting.client.buffer.submit_retries

Default: The default value is 3.

Maximum submit retry count. Each buffer file has it's own counter.

ecom.config.recurly.pages.protocol

Default: N/A

Protocol to use for requests to Recurly HTTP endpoint (http / https).

reporting.client.buffer.submit_interval

Default: The default value is 3600000ms (1 hour).

Buffer submit interval in milliseconds.

security.session.salt

Default: N/A

Configures salt to use for signing sessions.

reporting.client.buffer.directory

Default: The default value is ./tmp/reporting/client/buffer.

Directory where buffer files are created.

reporting.client.buffer.count

Default: Default value is 100.

Maximum count of buffer files.

servicelocator.classname

Default: com.tenduke.sdk2.service.locator.ServiceLocatorImpl ({@link #DEFAULT_SERVICE_LOCATOR_CLASS_NAME})

Fully qualified class name of the service locator used by this class.

reporting.client.buffer.size

Default: Default value is 500kB, 1024512 bytes.

Maximum size of buffer file in bytes. Each buffer file is submitted in one "transaction" so this value affects to data transmitted in POST request.

security.session.validity.ms

Default: 31536000000L (365 days)

Configures session validity time in milliseconds.

ecom.config.recurly.pages.size

Default: N/A

Page size to be used when querying the Recurly API (number of items returned per call).

ecom.config.recurly.api.key

Default: N/A

API key to use when authenticating client with Recurly

ecom.config.recurly.pages.manage

Default: N/A

Name of subscription management page in Recurly service.

security.password.salt

Default: If not defined, default common salt is used and a warning is logged.

Common part of salt, used for generating all password hashes.

identity.provider.email.validation.sender.address

Default: N/A

Configures sender address for validation emails.

messaging.send.mail.username

Default: N/A

Configures SMTP username for authorizing SMTP server.

messaging.send.mail.host

Default: N/A

Configures SMTP host to use for sending email.

messaging.send.mail.password

Default: N/A

Configures password for SMTP username.

persistence.read.create.target.collection

Default: true

Configures parameter that is used for controlling whether reading persistence operations should create target persistence collection (database table) if it doesn't exist.

reporting.client.endpoint

Default: No default value, logging is disabled if property is not set.

Reporting endpoint where {@link ReportEntry} objects are sent. Value has to be serialized object of class derived from {@link AbstractCallEndpoint}.

ecom.config.recurly.pages.domain

Default: N/A

Main domain name to use for accessing pages hosted in Recurly service.

ecom.config.recurly.pages.subscribe

Default: N/A

Name of subscription page in Recurly service.

ecom.config.recurly.pages.manage

Default: N/A

Name of subscription management page in Recurly service.

security.password.minlength

Default: 1

Configures required minimum length of password.

ecom.config.recurly.pages.protocol

Default: N/A

Protocol to use for requests to Recurly HTTP endpoint (http / https).

ecom.config.recurly.pages.subdomain

Default: N/A

Subdomain name to use for accessing pages hosted in Recurly service.

service.defaultobjectfactory.factories

Default: {@link #DEFAULT_OBJECT_FACTORIES}

Comma-separated list of fully qualified class names of classes extending {@link ObjectFactory}.

data.integrity.allow.nonexisting.owner

Default: false

Configures parameter defining whether value of {@link OwnedBy} annotated field may or may not refer to a non-existing object.

security.forgot.password.code.length

Default: 8

Length of security code required for changing forgotten user password.

tenduke.security.auth.sp.

Default: N/A

Base parameter name for service providers used by 10Duke application platform. A provider name is appended to the base name to form configuration keys for accessing specific provider configuration entry.

security.forgot.password.sender.address

Default: Value of configuration parameter service.support.email.

Sender address for the forgotten user password message sent to the user. The configured value is used if senderAddress is not given as operation argument.

object.validation.field.[TODO]

Default: TODO

TODO.

tenduke.security.auth.idp.

Default: N/A

Base parameter name for identity providers used by 10Duke application platform. A provider name is appended to the base name to form configuration keys for accessing specific provider configuration entry.

security.password.minlength

Default: 1

Required minimum length of password.

data.entitymanager.graph

Default: N/A

GraphEntityManager configuration.

security.openid.provider.endpoint.[PROVIDER_NAME]

Default: N/A

OpenID provider endpoint configuration for the named provider (replace [PROVIDER_NAME] with the provider name used in the applicacation).

security.saml.provider.endpoint.[PROVIDER_NAME]

Default: N/A

SAML provider endpoint configuration for the named provider (replace [PROVIDER_NAME] with the provider name used in the application).

security.oauth.provider.endpoint.[PROVIDER_NAME]

Default: N/A

OAuth provider endpoint configuration for the named provider (replace [PROVIDER_NAME] with the provider name).