node.js, domino-db & Docker (7): The ValueHolder

I am using this for years in Java, so I thought it would be great to use this approach also in the JavaScript world: The ValueHolder. The class allows to easily define „cachable“ code and it’s result, without having to handle the memcached part and – maybe in the future – background processing stuff.

To give you an idea what it is for here is a small example:

const allDummyDocs = new ValueHolder('allDummyDocs', 60, async () => {
  // get all documents with the Form 'dummy'
  return useServer(serverConfig).then(
    async server => {
      const db = await server.useDatabase(databaseConfig);
      const response = await db.bulkReadDocuments({
        query: "Form = 'dummy'"
      });
      return JSON.stringify(response);
    }).catch(err => {
      console.log(err);
      return err;
    });
});

The first parameter is the key used to store/retreive the value from memcached. The second one is the time how long the value should be cached. And the third parameter is the code to execute:

  • Get all documents from the database with Form = ‚dummy‘
  • Convert the resulting collection object to JSON.

The JSON String is then stored in the cache for 60 seconds, and everytime the get method is called again the Domino server is not queried.

To use the definition in the application, you now have to use the get method of the value holder:

router.get('/showAllDummyDocs', (req, res) => {
  allDummyDocs.get(
      (error, result) => {
        if (error) {
          res.render('error', { title: 'Error', error });
        } else {
          res.render('index', { title: 'Express', result: `Result: ${result}` });
        }
      }
    );
  }
);

The ValueHolder checks now automatically, if the result is stored in the cache. If not, the code is executed and stored in the cache.

Here is the ValueHolder.js file (which has to be created in the /app/classes folder):

const mf = require('../classes/MemcachedFactory');

const nullHelper = '###NULL###';
/**
 * Helper class for cached values
 * 
 * @author Sven Hasselbach
 * @version 0.1
 */
class ValueHolder {

    /**
     * 
     * @param {string} key 
     *  unique identifier
     * @param {number} ttl
     *  time-to-live in seconds 
     * @param {function} code 
     *  code to execute to calculate the value
     */
    constructor(key, ttl, code) {
        this.key = key;
        this.code = code;
        this.ttl = ttl;
    }

    /**
     * loads the value from cache or 
     * computes it and stores it in the cache
     * 
     * @param {function} callback 
     * @returns Promise
     */
    async get(callback) {
        const { code, ttl, key } = this;
    
        // check if value is in cache...
        mf.getInstance().get(key, (error, value) => {
            if (error) {
                callback(error);
                return;
            }
            if (value != null) {
                console.debug(`Found '${key}' in cache.`);
                if (value === nullHelper) {
                    // result is "special", so let's return null
                    callback(error, null);
                } else {
                    console.log(value);
                    callback(error, JSON.parse(value));
                }
            } else {
                console.debug(`Computing '${key}' and adding to cache with ttl ${ttl}.`);

                // execute the computation
                code().then((result) => {
                    // check if result must be stored "special" or not
                    if (result === null) {
                        mf.getInstance().set(key, nullHelper, ttl);
                    } else {
                        mf.getInstance().set(key, JSON.stringify(result), ttl);
                    }   
                    callback(error, result);
                });
            }
        });
     }

}

module.exports = ValueHolder;
Veröffentlicht unter ES6, Java Script, node.js | Verschlagwortet mit , , , | Schreib einen Kommentar

node.js, domino-db & Docker (6): Using memcached

mem.js

I am using mem.js as client library for accessing memcached. To use it, the first thing to do is to add the requirement to your package.json:

npm install memjs --save

MemcachedFactory

Then we can create a simple helper class to have an abstraction layer between our code and the library itself.

1. Create a folder in /app named classes

2. Create a new file with the name MemcachedFactory.js

3. Add the following code:

const memjs = require('memjs');
/**
 * Helper class for using Memcache
 * 
 * @author Sven Hasselbach
 * @version 0.1
 */
class MemcachedFactory {

    constructor() {
      this.client = memjs.Client.create('127.0.0.1:11211');
    }

    /**
     * returns a single instance of the class
     */
    static getInstance() {
      if (this.instance == null) {
        this.instance = new MemcachedFactory();
      }
      return this.instance;
    }

    /**
     * stores a value in memcache
     * @param {string} key 
     *  the key used
     * @param {*} value
     *  the value to store
     * @param {number} ttl 
     *  time-to-live in seconds
     */
    set(key, value, ttl) {
      this.client.set(key, value, { expires: ttl }, err => {
        if (err) {
          console.log(err);
          throw err;
        }
      });
    }
    /**
     * gets a value from memcache
     * 
     * @param {string} key 
     *  the key used
     * @param {function} callback
     *  the callback containing the value
     */
    get(key, callback) {
      this.client.get(key, (err, value) => {
        if (err) {
          console.error(err);
          callback(err);
        }
        if (value == null) {
          callback(err, null);
        } else {
          callback(err, value.toString());
        }
      });
  }
}
module.exports = MemcachedFactory;

5. To use the class in our code, we have to add the requirement first:

const mf = require('../classes/MemcachedFactory');

6. Here is a small example how the class is used:

mf.getInstance().get(key, (error, value) => {
  if (error) {
    // handle error here
  }else{
    // we have a value
    console.log(`The value is ${value}`); 
  }
});

The getInstance method returns an instance of the class. Then the get method is used with the key to retreive, and a callback method which is called when the request to memcached is completed.

The set method allows to put a key to memcached, and with ttl we can define how long the key is valid and stored.

Veröffentlicht unter ES6, Java Script | Verschlagwortet mit , , | Schreib einen Kommentar

node.js, domino-db & Docker (5): memcached

To use memcached in our Docker container, we have to modify the existing Dockerfile a little bit. First it is required to install memcached in the container itself, and then it is required to change the CMD command to start the service and our express application.

1. Create a folder /conf in the project folder

2. Create a file memcached.conf in this folder with the following content:

# Memory: 256 MB
-m 256

# Port to use
-p 11211

# User for the service
-u memcache

# Listening IP Adress 
-l 127.0.0.1

3. Create a script startup.sh in the newly created folder:

#!/bin/bash
service memcached start
npm start

4. Modify the Dockerfile to install memcached

# install memcached
RUN apt-get update
RUN apt-get install -y memcached

5. Copy the startup.sh script and the memcached.conf

COPY ./conf/memcached.conf /etc
COPY ./conf/startup.sh .
RUN chmod +x /usr/src/app/startup.sh

6. Change the CMD to use the startup.sh script

CMD [ "./startup.sh" ]

The reason for the startup script is that in Docker containers the services are disabled by default, so we need to start the service by our own. Also, only one CMD command is allowed, so we have to use a script.

Veröffentlicht unter Docker, node.js | Verschlagwortet mit , , | Schreib einen Kommentar

node.js, domino-db & Docker (4): Error Handling

When we started our express application and accessed it in the browser, an error raised on the console and no response was sent back to the browser. The reason for this behaviour is that the database connection is not correctly configured, and the request from our application fails.

For a better understanding I have refactored the code to „old-school“ Javascript. The functions are called in a promise chain.

router.get('/', function(req, res, next) {
  useServer(serverConfig)
  .then(
      function(server){
        return server.useDatabase(databaseConfig)
      })
  .then(
      function(database){
        return database.bulkCreateDocuments(createOptions)
      })
  .then(
      function(response){
        const unids = response.documents.map(doc => doc['@unid']);
        res.render('index', { title: 'Express', result: `Documents created: ${unids}` });
      }
  );
});

1. The get method of the router calls the anonymous function (2nd parameter).

2. The chain starts: This function calls the useServer method of the domino-db package with the serverConfig.

3. If everything is OK, the next method in the chain is called with the server object (the result of the previous operation).

4. If everything is OK, the next method in the chain is called with the database object.

5. If everything is OK, the next method in the chain is called with the result object.

Promises have two callback functions: The first parameter is always the „success“ callback, and the second the „error“ callback. But we are not using a second parameter, because this allows us to use a catch function at the end of our chain:

router.get('/', function(req, res, next) {
  useServer(serverConfig)
  .then(
      ... )
  .then(
      ... )
  .then(
      ... )
  .catch(
    function(error) {
      console.log(error);
      res.render('error', { title: 'Error', error });
    }
  );
});

The catch block handles every error in our chain, and renders the view ‚error‚ with the reason of the failure.

If we now restart our application, the error is displayed to the end user with a stacktrace:

And now, we are refactoring the code using the arrow syntax:

router.get('/', (req, res) => {
  useServer(serverConfig)
  .then(server => server.useDatabase(databaseConfig))
  .then(database => database.bulkCreateDocuments(createOptions))
  .then(response => {
        const unids = response.documents.map(doc => doc['@unid']);
        res.render('index', { title: 'Express', result: `Documents created: ${unids}` });
  })
  .catch(error => {
      console.log(error);
      res.render('error', { title: 'Error', error });
    }
  );
});

A lot shorter, isn’t it?

At the end, we are using async/await syntax to shorten the promise chain too:

router.get('/', (req, res) => {
  useServer(serverConfig).then(
    async server => {
      const database = await server.useDatabase(databaseConfig);
      const response = await database.bulkCreateDocuments(createOptions);
      const unids = response.documents.map(doc => doc['@unid']);
      res.render('index', { title: 'Express', result: `Documents created: ${unids}` });
    }
  ).catch(error => {
      console.log(error);
      res.render('error', { title: 'Error', error });
  });
});
Veröffentlicht unter Java Script, JSX, node.js | Verschlagwortet mit , , , , | Schreib einen Kommentar

node.js, domino-db & Docker (3): Adding domino-db

The express application is still the boilerplate created by express generator. Now let’s look into the existing code and use the domino-db package. First we have to understand express a little bit better. I won’t go deeply into details, because there are many tutorials available, and the documentation is really awesome.

Overview

Directory structure

/domino-express
   |-app
      |-app.js
      |-bin
         |-...
      |-public
         |-...
      |-routes
         |-...
      |-views
         |-...

app.js

This is the main application. It contains the configuration of the application and global modules (like used the middleware, global route handlers, etc.). At the moment we don’t need to change anything here.

/bin

This folder contains the starting script for the application.

/public

Contains publicly accessible static resources like images, stylesheets, etc.

/routes

The handling for existing routes, which means how the application should process incoming requests.

/views

Contains the templates for the generated output. We are using Jade as our template engine.

The boilerplate

The first thing to look at is the /routes/index.js file. Just open it in Atom, and see what the express generator created for us:

The first line loads the express package and gives access to it. The next line gives us access to the router, which then is used to define a handle for all incoming requests on the application root (http://localhost:3000/).

When this route is called, the defined function renders the response using the view template ‚index‘ (which can be found in the /views folder). The variables used in the template are contained in the object handed over as second parameter.

The last line exports the router instance and gives access to it outside of our module. Just for a better understanding: Everything in this file (aka module) is private, and here it is defined what is public to the rest of our express application. For more details, have a look here: https://www.sitepoint.com/understanding-module-exports-exports-node-js/

Change to ES6

After changing everything as proposed by the editor, the code should now look like this:

const express = require('express');

const router = express.Router();

/* GET home page. */
router.get('/', (req, res) => {
  res.render('index', { title: 'Express' });
});

module.exports = router;

Now we add the requirement for the domino-db package:

const { useServer } = require('@domino/domino-db');

The curly brackets notation is used to „import“ only the useServer element from the domino-db package.

Then we add the configuration for our Domino backend (shamelessly copied from the example in the dev pack):

const serverConfig = {
  hostName: 'your.server.com', // Host name of your server
  connection: {
    port: '3002', // Proton port on your server
  },
};

const databaseConfig = {
  filePath: 'node-demo.nsf', // The database file name
};

const createOptions = {
  documents: [
    {
      Form: 'Contact',
      FirstName: 'Aaron',
      LastName: 'Aardman',
      City: 'Arlington',
      State: 'MA',
    },
    {
      Form: 'Contact',
      FirstName: 'Brian',
      LastName: 'Zelnick',
      City: 'Chelmsford',
      State: 'MA',
    },
  ],
};

And now we add the connection of the database query when our base path is accessed:

router.get('/', (req, res) => {
  useServer(serverConfig).then(
      async server => {
        const database = await server.useDatabase(databaseConfig);
        const response = await database.bulkCreateDocuments(createOptions);

        // Display the new document UNIDs
        const unids = response.documents.map(doc => doc['@unid']);
        res.render('index', { title: 'Express', result: `Documents created: ${unids}` });
  });
});

Before we get into the details of this code, let’s start our application with npm start.After accessing the URL of the application http://localhost:3000, nothing happens.

Just some console output tells us that we need some error handling when our Domino server is not reachble.

GET / - - ms - -
(node:13268) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 6): DominoDbError: gRPC client error

This topic and more details about the domino-db module will be covered in the next post.

Veröffentlicht unter ES6, Java Script, node.js | Verschlagwortet mit , , , , | Schreib einen Kommentar

node.js, domino-db & Docker (2): Dev Environment

Before we can start to create a new app we first have to setup a development environment. While there are multiple IDE’s around, I have made most of my node.js development with Atom instead of an IDE like Eclipse or Visual Studio. Maybe this will change in the future, but for a better understanding, let’s start with Atom and do the required steps manually.

By default, there is no support for ES6, so we need to make an additional installation after installing the editor:

1. Download Atom from https://atom.io/

2. Go to Atom > Preferences

3. Install linter-eslint package

4. Additional packages must be installed (This happens multiple times):

5. In a console, go to the domino-express which we have created before:

cd domino-express/

6. Install eslint-config-rallycoding module

npm install --save-dev eslint-config-rallycoding

This step has to be done for every new project. The module is required to enable the ES6 support for every project, but only during development.

7. Create a file named .eslintrc in the project folder and add the following content:

{
   "extends": "rallycoding"
}

8. Change the dependency for the domino-db package:

"@domino/domino-db": "file:./domino-domino-db-1.0.0-package"

The path has to be changed because for the Docker setup it is in the /src folder, now it is in the project root.

9. Install the required npm modules

npm install

10. Restart Atom and open the project folder

11. If everything worked correctly, you should now see an error in the index.js file (maybe you have to open the file first):

12. When opening the file, you should see an error in the first line and an explanation about the problem:

13. Click on „Fix“, then var should change to let

14. An new problem occurs, because the variable is never changed. So the advice is to change it to const.

15. Done. Now we are ready for developing.

Veröffentlicht unter ES6, Java Script, node.js | Verschlagwortet mit , , , | Schreib einen Kommentar

node.js, domino-db & Docker

Here is an example to create a express application with the new domino-db npm module and run it in a docker container. Requirements are that node.js & Docker is installed. Everything is done in the command line and a text editor.

1. Install Express application generator

npm install express-generator -g

2. Create a fresh application with stylus engine

express -c stylus domino-express

3. Go to the newly created folder

cd domino-express/

4. Create a folder for the application

md app/

5. Copy all files to the app folder, only package.json should be left

6. Copy the domino-domino-db-1.0.0-package folder into the project folder

7. Create a file .dockerignore and with the following content:

node_modules
npm-debug.log

8. Create the Dockerfile with the following content:

FROM node:8

# Create app directory
WORKDIR /usr/src/app

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./

# create a folder for the local domino-db package
RUN mkdir -p /src
COPY ./domino-domino-db-1.0.0-package /src/domino-domino-db-1.0.0-package

# install & rebuild
RUN npm install
RUN npm rebuild

# If you are building your code for production
# RUN npm install --only=production

# Bundle app source
COPY ./app .

EXPOSE 3000
CMD [ "npm", "start" ]

9. The directory structure should look like this:

/domino-express
   |-app
      |-...
   |-domino-domino-db-1.0.0-package
      |-...
   |-.dockerignore
   |-Dockerfile
   |-package.json

10. Add domino-db to package.json

{
  "name": "domino-express",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "cookie-parser": "~1.4.3",
    "debug": "~2.6.9",
    "express": "~4.16.0",
    "http-errors": "~1.6.2",
    "jade": "~1.11.0",
    "morgan": "~1.9.0",
    "stylus": "0.54.5",
    "@domino/domino-db": "file:/src/domino-domino-db-1.0.0-package"
  }
}

11. Build the docker container (replace username with your docker id)

docker build -t {username}/domino-express .

12. Start the container

docker run -p 3000:3000 -d -it {username}/domino-express

13. Open it in your browser => http://localhost:3000

Veröffentlicht unter ES6, Java Script, node.js | Verschlagwortet mit , , | 7 Kommentare

Dropping Domino’s HTTP task (3): WebSSO Integration (Part 1)

To integrate the new HTTP stack into the existing environment, we can use LTPA tokens. These tokens are cookies which store the authentication information and allow to share them betweeen different participating Domino servers. A users must log on only once, and existing applications and data/views can be accessed without a relogin.

Validating an existing LTPA token with Spring can be done with our own PreAuthentificationFilter which checks for an existing LTPA token and extracts the authentication details from the cookie and creates a new Principal instance.


import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;

public class LtpaPreAuthenticatedFilter extends AbstractPreAuthenticatedProcessingFilter {

   @Value("${ltpa.secret}")
   private String ltpaSecret;

   @Value("${ltpa.cookieName}")
   private String ltpaCookieName;

   @Override
   protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {

      Cookie[] cookies = request.getCookies();
      if( cookies == null ) {
         return null;
      }

      for( int i= 0; i<cookies.length ; i++ ){
         String name = cookies[i].getName();
         String value = cookies[i].getValue();

         if( ltpaCookieName.equalsIgnoreCase(name) ){
            DominoLtpaToken ltpaToken = new DominoLtpaToken( value, ltpaSecret );

            if( ltpaToken.isValid() ){
               return ltpaToken.getDistinguishedName();
            }
         }
      }

      return null;
   }

   @Override
   protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
      // is required to return an empty string
      return "";
   }

}

The filter implements two methods, one for extraction of the principal, and the other for the credentials (which we don’t have with LTPA tokens). In the getPreAuthenticatedPrincipal method, existinig LTPA tokens are searched, then the user extracted and the token validated.

The secret of the LTPA token and the name are stored in application.properties:

The second part is implementing a AuthenticationUserDetailsService. This service is for getting additional details for the authenticated user, for example the ACL roles or groups the user belongs to.

import java.util.Collection;
import java.util.HashSet;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;

public class LtpaUserDetailsService implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {

   @Override
   public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken token)
      throws UsernameNotFoundException {

      String userName=(String)token.getPrincipal();

      Collection<GrantedAuthority> authorities = new HashSet<GrantedAuthority>() ;
      authorities.add(new LtpaUserAuthority());

      User user = new User(userName,"",authorities);

      return user;
    }

}

In our case, we are just adding an LtpaUserAuthority to the user information. Don’t worry about the usage of the LtpaUserAuthority. We come back to this in another post.

import org.springframework.security.core.GrantedAuthority;

public class LtpaUserAuthority implements GrantedAuthority {

   private static final long serialVersionUID = 1L;

   @Override
   public String getAuthority() {
      return "ROLE_USER_LTPA";
   }

}

In the last step we have to update the SecurityConfig.java to activate the filter:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Configuration
   @Order(1)
   static class DominoLtpaSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

      @Bean
      public AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService() {
         return new LtpaUserDetailsService();
      }

      @Bean
      public PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() {
         PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();

         provider.setPreAuthenticatedUserDetailsService(authenticationUserDetailsService());
         provider.setUserDetailsChecker(new AccountStatusUserDetailsChecker());

         return provider;
      }

      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
         auth.authenticationProvider(preAuthenticatedAuthenticationProvider());
      }

      @Bean
      public AbstractPreAuthenticatedProcessingFilter preAuthenticatedProcessingFilter() throws Exception {
         LtpaPreAuthenticatedFilter filter = new LtpaPreAuthenticatedFilter();
         filter.setAuthenticationManager(authenticationManager());
         return filter;
      }

      @Override
      protected void configure(HttpSecurity http) throws Exception {
         http.addFilter(preAuthenticatedProcessingFilter())
         .authorizeRequests()
         .antMatchers("/**").permitAll() ;
      }
   }
  ...
}

This includes the filter in any request. Now, the Principal contains the user name stored in the LTPA token.

Veröffentlicht unter Java, Server, Web | Verschlagwortet mit , , , | Schreib einen Kommentar

Dropping Domino’s HTTP task (2): Running in User Context

To use the approach as an alternative to Domino’s HTTP task, we need support for the different user contexts, because using NotesFactory.createSession() just creates a session for the current Notes ID used.

This goal can be achived by using the Java NAPI and the following method:

import com.ibm.domino.napi.NException;
import com.ibm.domino.napi.c.NotesUtil;
import com.ibm.domino.napi.c.xsp.XSPNative; 

/**
* create a new Domino session for the give username
* @param userName
* String containing the canonical username
* @return
* lotus.domino.Session for the given username
* @throws NException
* @throws ServletException
*/
public static Session createUserSession(final String userName)
{
   Session session = null;
   try {
      long hList = NotesUtil.createUserNameList(userName);
      session = XSPNative.createXPageSession(userName, hList,
         false, false);

      return session;
   } catch (Exception e) {
      e.printStackTrace();
   }

   return session;
}

It is required to load the required njnotes.dll before this method can be used, otherwise the C-API references won’t work. This must be done after initiation of the NotesThread and (only once) per thread:

System.loadLibrary("njnotes");

It is also required to use the full hierarchical name for the session creation, otherwise readers / authors won’t work correctly (you can create a session for every user you want, even for not existing ones).

To get this work correctly, I have created a little helper class which memorizes if the Notes-relevant part was already initialized for the thread used:

public class Utils {

   private static final ThreadLocal<Boolean> isNotesInitialized = new ThreadLocal<Boolean>();

   public static void initNotes(){

      // check if the Thread is already initialized
      if( Boolean.TRUE.equals( isNotesInitialized.get() ) )
         return;

      // init Notes
      NotesThread.sinitThread();
      System.loadLibrary("njnotes");

      // mark as initialized
      isNotesInitialized.set( Boolean.TRUE );

   }

}

Now let’s activate Spring Boot Security on the new server by adding the required dependencies to the pom.xml:

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>

To use the authentication with the existing Domino environment, we can use our own AuthenticationProvider:

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;

import org.springframework.security.authentication.BadCredentialsException;

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

   @Override
   public Authentication authenticate(Authentication authentication) throws AuthenticationException {
      String name = authentication.getName();
      String password = authentication.getCredentials().toString();

      String realUser = validatePassword( name, password );

      if( Utils.isEmptyString( realUser ) ){
         throw new BadCredentialsException("Authentication failed for user = " + name);
      }

      return auth;
   }

   @Override
   public boolean supports(Class<?> authentication) {
      return authentication.equals(UsernamePasswordAuthenticationToken.class);
   }

   public String validatePassword( final String userName, final String password){
      // see https://github.com/hasselbach/domino-stateless-token-servlet/blob/master/src/ch/hasselba/servlet/DominoStatelessTokenServlet.java
      // and convert the username to the hierarchical one
   }
}

If the authentication was successfull, we can access the correct username from the Principal object. Just add it as a parameter to the controller method, and you get the hierarchical name.

@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message, Principal principal) throws Exception {

   Thread.sleep(1000); // simulated delay

   // init Notes
   Utils.initNotes();

   // create the session for the user
   Session session = (Session) Utils.createUserSession(principal.getName());

   return new Greeting("Hello, " + HtmlUtils.htmlEscape( session.getEffectiveUserName() ) + "!" );
}

Now let’s see this in action:

P.S.
I have ignored recycling and termination of the Notes threads for an easier understanding.

Veröffentlicht unter Java, Server, Web | Verschlagwortet mit , , , | 5 Kommentare

Dropping Domino’s HTTP task

Instead of waiting for updates of the Domino HTTP task any longer I was thinking about how to use modern HTTP technologies on top of Domino. But instead of implementing it in the Domino stack, I think I found a new way for developing and running my Spring Boot applications: Why not using the existing JVM, and run my application directly on it? This means full access to the Domino objects, and allows access to the latest available technologies: No more limitations because of the provided tech stack, Websockets, Async HTTP Request Processing, full JEE support, modern and better development tools, …

I am not talking about DIIOP or RPC, that’s something different, and more a crutch as a solution. I need full access, especially to NAPI for C-API calls for running the code in the user context I want.

First thing to do is downloading and installing Maven and Git. I am using older versions on my Winows 7 VM, because I am to lazy to upgrade them. Then I have cloned the Websockets example from Spring as a starting point for a quick testing scenario.

I have choosen Jetty as the webserver to use by adding the dependencies to the pom.xml:

<dependency>
   <groupId>org.eclipse.jetty.websocket</groupId>
   <artifactId>websocket-client</artifactId>
   <version>9.4.11.v20180605</version>
</dependency>

<dependency>
   <groupId>org.eclipse.jetty.websocket</groupId>
   <artifactId>websocket-server</artifactId>
   <version>9.4.11.v20180605</version>
</dependency>

<dependency>
   <groupId>org.eclipse.jetty</groupId>
   <artifactId>jetty-client</artifactId>
   <version>9.4.11.v20180605</version>
</dependency>

Jetty provides support for WebSockets, http/2, the latest servlet container and many more features which on the wishlist of Domino developers for years.

For an easier maintainment, I am using properties in the pom.xml for referencing the Domino environment:

<properties>
   <java.version>1.8</java.version>
   <domino.directory>T:/IBM/Domino901</domino.directory>
   <domino.osgi.shared.directory>${domino.directory}/osgi/shared/eclipse/plugins</domino.osgi.shared.directory>
   <domino.osgi.rcp.directory>${domino.directory}/osgi/rcp/eclipse/plugins</domino.osgi.rcp.directory>
   <domino.version>9.0.1</domino.version>
   <domino.jarversion>9.0.1.20180115-1058</domino.jarversion>
</properties>

My installation of Domino server is on drive T, and the JAR version is the part of the file or folder name which depends on the current installation / feature pack.

Now, we can add the dependencies to the required Domino JARs:


<dependency>
   <groupId>com.ibm</groupId>
   <artifactId>domino-api-binaries</artifactId>
   <version>${domino.version}</version>
   <scope>system</scope>
   <systemPath>${domino.directory}/jvm/lib/ext/Notes.jar</systemPath>
</dependency>

<dependency>
   <groupId>com.ibm</groupId>
   <artifactId>commons</artifactId>
   <version>${domino.version}</version>
   <scope>system</scope>
   <systemPath>${domino.osgi.shared.directory}/com.ibm.commons_${domino.jarversion}/lwpd.commons.jar</systemPath>
</dependency>

<dependency>
   <groupId>com.ibm.domino</groupId>
   <artifactId>napi</artifactId>
   <version>${domino.version}</version>
   <scope>system</scope>
   <systemPath>${domino.osgi.shared.directory}/com.ibm.domino.napi_${domino.jarversion}/lwpd.domino.napi.jar</systemPath>
</dependency>

At this point it is time to start the project for the first time. To do this, it is required to use the Domino JVM instead of the installed one. And here comes a small drawback: When using the Server JVM you cannot run the Domino server in parallel. If so, the server will crash immediatly with a „PANIC!“ message. You could start every task manually – but not the database server itself (this means you can use replication and mail and everything, but you cannot connect to the server from another client).

So here is a workaround: Install the Notes client and use his JVM! Think as it of a massive Database driver to connect to the server (there is no need to start the client, we just need the JVM and the DLLs).

Start a console, and change the environment (dependent of your installation of Maven & the Notes client):

SET JAVA_HOME=T:\IBM\Notes901\jvm
SET Path=T:\IBM\Notes901;C:\apache-maven-3.3.9\bin;T:\IBM\Notes901\jvm\bin

Go to your project folder and start the server:

mvn spring-boot:run

After opening http://localhost:8080 in your browser and clicking the „Connect“ button, you can see the WebSocket connection in the Dev Console:

At this point, nothing really spectacular. But now let’s modify the GreetingController.java and add some Domino code:

@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {

   Thread.sleep(1000); // simulated delay

   // make to Domino Thread
   NotesThread.sinitThread();
   Session session = NotesFactory.createSession();

   return new Greeting("Hello, " + HtmlUtils.htmlEscape( session.getEffectiveUserName() ) + "!" );
}

Restart the Jetty server, and enter a name. Et voilà…

In the next post, let’s use the Java NAPI to create a „real“ Webserver.

Veröffentlicht unter Java, Server, Web | Verschlagwortet mit , , , , , | 8 Kommentare