Introducing Angular-Translate Quality

For those using Angular JS 1.x, there exists a nice library to deal with internationzalition: Angular-Translate. This library provides a filter, a directive and a service to manage translations. Basically, JS code and HTML templates refer to keys. These keys point to translations located in external files (e.g. JSon files, that will be loaded by the web browser).

You can find more information on the project’s web site.
If this library is quite convenient and does the job, it is however very easy to make mistakes. One can forget a key for some language. Refactoring may miss an item. Inexisting keys might be referenced too. And so on. This is why I created Angular-Translate Quality. The idea is to have a library, used at build time, to verify the correctness of your translation files.

var qual = require('angular-translate-quality');

function cb(msg) {
  console.log(msg);
}

var valid = qual.validate({
  loc_i18n: './i18n/**/',
  loc_html: './html/**/',
  cb: cb
});

The library scans HTML files and verifies all the keys used along with the translate filter and directive are defined and are coherent across all the languages. It also provides mechanisms to verify syntax rules for languages (as regular expressions to define). This can be useful to prevent copy/paste errors. The readme provides an extensive documentation about all the options and how to use it. You can see used it in this project. We use it to verify English and French translations in our (Gulp) build process.

Mocking Nexus API for a Local Maven Repository

For the Roboconf project, the build of our Docker images relies on Sonatype OSS repository.
We use Nexus’ Core API to dynamically retrieve Maven artifacts. That’s really convenient. However, we quickly needed to be able to download local artifacts, for test purpose (without going through a remote Maven repository). Let’s call it a developer scope.

After searching for many solutions, we finally decided to mock Nexus’ API locally and specify to our build process where to download artifacts. We really wanted something light and simple. Besides, we were only interested by the redirect operation. Loading a real Nexus was too heavy. And we really wanted to use a local Maven repository, the same one that developers usually populate. The idea of volume was very pleasant.

So, we made a partial implementation of Nexus’ Core API.
We used NodeJS (efficient for I/O) and Restify. Restify allows to implement a REST API very easily. And NodeJS comes with a very small Docker image (we took the one based on Alpine).

The server class is quite simple.
We expect the local Maven repository to be loaded as a volume in the Docker container. We handle SHA1 requests specifically, as developers generally do not use the profile that generate hashes.

'user strict';
  
var restify = require('restify');
var fs = require('fs');


/**
 * Computes the hash (SHA1) of a file.
 * <p>
 * By default, local Maven repositories do not contain
 * hashes as we do not activate the profiles. So, we compute them on the fly.
 * </p>
 * 
 * @param filePath
 * @param res
 * @param next
 * @returns nothing
 */
function computeSha1(filePath, res, next) {

  var crypto = require('crypto'),
    hash = crypto.createHash('sha1'),
    stream = fs.createReadStream(filePath);

  stream.on('data', function (data) {
    hash.update(data, 'utf8')
  });

  stream.on('end', function () {
    var result = hash.digest('hex');
    res.end(result);
    next();
  });
}


/**
 * The function that handles the response for the "redirect" operation.
 * @param req
 * @param res
 * @param next
 * @returns nothing
 */
function respond(req, res, next) {

  var fileName = req.params.a +
  '-' + req.params.v +
  '.' + req.params.p;

  var filePath = '/home/maven/repository/' +
    req.params.g.replace('.','/') +
    '/' + req.params.a +
    '/' + req.params.v +
    '/' + fileName;

  fs.exists(filePath, function(exists){
    if (filePath.indexOf('.sha1', filePath.length - 5) !== -1) {
      filePath = filePath.slice(0,-5);
      computeSha1(filePath,res, next);
    }

    else if (! exists) {
      res.writeHead(400, {'Content-Type': 'text/plain'});
      res.end('ERROR File ' + filePath + ' does NOT Exists');
      next();
    }

    else {
      res.writeHead(200, {
        'Content-Type': 'application/octet-stream',
        'Content-Disposition' : 'attachment; filename=' + fileName});
      fs.createReadStream(filePath).pipe(res);
      next();
    }
  });
}


// Server setup

const server = restify.createServer({
  name: 'mock-for-nexus-api',
  version: '1.0.0'
});

server.use(restify.plugins.queryParser());
server.get('/redirect', respond);

server.listen(9090, function() {
  console.log('%s listening at %s', server.name, server.url);
});

Eventually, here is the Dockerfile, which ships NodeJS and our web application to be used with Docker.

FROM node:8-alpine

LABEL maintainer="The Roboconf Team" \
      github="https://github.com/roboconf"

EXPOSE 9090
COPY ./*.* /usr/src/app/
WORKDIR /usr/src/app/
RUN npm install
CMD [ "npm", "start" ]

We then run…

docker run -d –rm -p 9090:9090 -v /home/me/.m2:/home/maven:ro roboconf/mock-for-nexus-api

And our other build process downloads local Maven artifacts from http://localhost:9090/redirect
You can find the full project on Github. If anyone faces the same problem, I hope this article will provide some hints.

Register MBeans in Apache Karaf

Just a quick note about how your OSGi application can register MBeans in Karaf’s platform.
Apache Karaf comes with its own JMX server and a global configuration you can find under its etc directory. Configuration goes from the server’s URL and remote access security, to a fine-grained permission system (e.g. who can access this or that MBean). For more details about what this, you can refer to the official documentation of Karaf.

Now, let’s assume you want to expose custom MBeans in Karaf’s platform.
How to proceed? Well, Karaf’s management feature comes with Apache Aries. The JMX component of this project is the Reference Implementation of the OSGi JMX Management Model Specification. This specification indicates how one can easily register MBeans within OSGi thanks to the white board pattern.

The only thing you have to do is to register a service that respects JMX naming conventions and that has the jmx.objectname property. Every time such a service is registered, Aries is notified and registers a MBean from it. If you remove the service, the MBean will be unregistered too. So, you only have to register a service! And that’s great. Then, you are plugged with Karaf’s JMX configuration, meaning you can easily integrate with other tools like Apache Decanter and its JMX collector.

How to register such a service?
You can do this programmatically…

BundleContext bundleCtx = osgiHelper.findBundleContext();
if( bundleCtx != null ) {

	this.logger.fine( "Running in an OSGi environment. Trying to register a MBean for the messaging." );
	Dictionary<String,String> properties = new Hashtable<> ();
	properties.put( "jmx.objectname", "net.roboconf:type=messaging" );
	try {
		this.serviceReg = bundleCtx.registerService( MessagingApiMBean.class, this, properties );
		this.logger.fine( "A MBean was successfully registered for the messaging." );

	} catch( Exception e ) {
		this.logger.severe( "A MBean could not be registered for the messaging." );
		Utils.logException( this.logger, e );
	}
}

… or with a framework, like iPojo (here with a metadata.xml file).

<ipojo 
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xsi:schemaLocation="org.apache.felix.ipojo http://felix.apache.org/ipojo/schemas/CURRENT/core.xsd"
		xmlns="org.apache.felix.ipojo">

	<!-- Servlets registration -->
	<component classname="net.roboconf.dm.rest.services.internal.ServletRegistrationComponent" name="roboconf-dm-rest-component" immediate="true" public="false">

		<!-- Mandatory -->
		<requires field="manager" optional="false" />
		<requires field="httpService" optional="false" />

		<!-- Optional: Maven resolver -->
		<requires field="mavenResolver" optional="true">
			<callback type="bind" method="mavenResolverAppears" />
			<callback type="unbind" method="mavenResolverDisappears" />
		</requires>

		<!-- Optional: scheduler -->
		<requires field="scheduler" optional="true">
			<callback type="bind" method="schedulerAppears" />
			<callback type="unbind" method="schedulerDisappears" />
		</requires>

		<callback transition="validate" method="starting" />
		<callback transition="invalidate" method="stopping" />

		<properties pid="net.roboconf.dm.rest.services.configuration">
			<property name="enable-cors" method="setEnableCors" value="false" />
			<property name="enable-authentication" method="setEnableAuthentication" value="false" />
			<property name="authentication-realm" method="setAuthenticationRealm" value="karaf" />
			<property name="session-period" method="setSessionPeriod" value="-1" />
		</properties>

		<!-- Register OSGi services -->
		<!-- The implementation class also registers additional services... -->
		<provides specifications="net.roboconf.dm.rest.services.jmx.RestServicesMBean">
			<property name="jmx.objectname" value="net.roboconf:type=web" type="java.lang.String" />
		</provides>
	</component>

	<instance component="roboconf-dm-rest-component" name="Roboconf - DM REST services" />
</ipojo>

My example may look a little bit complex.
The interesting part is the provides section, which declares the service to register. Notice the service property. You can obviously use any framework (Blueprint, Declarative Services), no matter how you register your service, provided you actually do register it.

Here are some pointers to real implementations.
The code approach is used here to create MBeans on the fly for objects that are not managed as OSGi services. The framework approach is perfect when you want to associate MBeans with predefined OSGi services.

As usual, the interface of your service must be exported outside your bundle. This is fundamental! You must also respect JMX conventions. All my service interfaces for JMX have their name that end with MBean (and it is case sensitive). My implentation classes can use whatever names they want. You will find additional information on Apache Aries’s web site.