Inconsistent CMIS Query Results? It’s not You, It’s your Locale.

A simple CMIS query like:

SELECT D.* FROM cmis:document AS D WHERE D.cmis:name LIKE '%Flower%' OR D.cmis:contentStreamFileName LIKE '%Flower%'

was giving me a fit lately, not working at all in RightsPro‘s CMIS plugin which uses OpenCMIS and yielding inconsistent results in CMIS Workbench where re-posting the same query ten times would give the expected result maybe half the time.

Thanks to Florian Müller and this post, it seems as though Alfresco doesn’t always behave as expected when the locale is set in the CMIS session.

Removing the locale session parameters got things working in RightsPro, but I didn’t immediately see an easy way to change the locale in a Workbench session (the log shows that it’s using a default of en_US), and I still don’t know what’s up with the inconsistent results there, perhaps a coincidental caching issue.

This was all using OpenCMIS 0.3 against Alfresco Enterprise 3.3.1 over SSL.

Render Me This: Video Thumbnails in Alfresco 3.3

Alfresco has undergone quite a few changes under the hood in version 3.3, including refactoring of the ThumbnailService to make use of the RenditionService, which I had to explore in a recent deep dive to get video working for the thumbnails extension. This is obviously a subject dear to your heart as well or you would have moved on by now.

Alfresco 3.1.

Let’s take a look at how things worked back in the old days, before 3.2 (fuzzy 2009 dream sequence begins…)

You most likely kicked things off with a thumbnail generating URL which would hit a ScriptNode.createThumbnail method which looked up the thumbnailDefinition and called the ThumbnailService with the TransformationOptions contained in the definition.

The ThumbnailService then told the ContentService to perform the transformation which looked up the ContentTransformer from the TransformerRegistry and executed transformer.transform which usually delegated to a RuntimeExec object to perform the actual command line transformation.

If we were to visualize the key elements of that stack for a synchronous video thumbnail creation it would look something like:

  • org.alfresco.repo.jscript.ScriptNode.createThumbnail
    • org.alfresco.repo.thumbnail.ThumbnailRegistry.getThumbnailDefinition (loads TransformationOptions)
    • org.alfresco.repo.thumbnail.ThumbnailServiceImpl.createThumbnail
      • org.alfresco.repo.content.ContentServiceImpl.transform
        • org.alfresco.repo.content.transform.ContentTransformerRegistry.getTransformer
        • org.alfresco.repo.content.transform.ContentTransformer.transform
          • TempFileProvider.createTempFile
          • org.alfresco.util.exec.RuntimeExec.execute
            • ffmpeg …

Well now, that’s not so bad.

Alfresco 3.3.

These Alfresco people don’t mess around. If it’s determined that refactoring should happen, it happens, real damn quick, even if it’s huge. To that end subsystems were rolled out in 3.2 and the RenditionService was introduced in 3.3.

Subsystems. Certain content transformers, like ImageMagick, have been refactored as a subsystem, which is cool. Read the wiki page to get the full story but in short it’s a much more flexible architecture.

The ffmpeg video transformer contained in the latest release of the thumbnails extension follows this precedence and has been refactored as a subsystem, so there.

RenditionService. I wouldn’t say I hate it, but this package should be wary of eating crackers in bed, or maybe I’m just not familiar enough with it yet.

The new rendition service utilizes RenditionDefinitions which require that any TransformationOptions be ‘serialized’ into a parameter map, which are then reassembled into TransformationOptions objects before being passed to the ContentTransformer. I suppose this is done to make it easier to do things like hand renditions off to another server in a cluster, but it’s a bit of a pain in the ass for developers.

So, back to our synchronous video thumbnail creation scenario (deep breath), the ThumbnailService now creates a RenditionDefinition which it passes to the RenditionService which wraps things up in a PerformRenditionActionExecuter that gets passed to the ActionService. The PerformRenditionActionExecuter calls the ActionService again which loads the RenderingEngine from the spring ApplicationContext. A RenderingEngine is itself an ActionExecuter so the ActionService calls execute which calls render which proceeds to rebuild the TransformationOptions object needed by the ContentService to get the proper ContentTransformer so transformer.transform can use a RuntimeExec object to perform the actual command line transformation.

The key elements of our pseudo stack/method trace would look like:

  • org.alfresco.repo.jscript.ScriptNode.createThumbnail
    • org.alfresco.repo.thumbnail.ThumbnailRegistry.getThumbnailDefinition (loads TransformationOptions)
    • org.alfresco.repo.thumbnail.ThumbnailServiceImpl.createThumbnail
      • org.alfresco.repo.thumbnail.ThumbnailServiceImpl.createRenditionDefinition
        • org.alfresco.repo.thumbnail.ThumbnailRegistry.getThumbnailRenditionConvertor().convert(transformationOptions, assocDetails) <- this serializes the transformationOptions into a parameter map
      • org.alfresco.repo.rendition.RenditionServiceImpl.render
        • org.alfresco.repo.rendition.RenditionServiceImpl.createAndExecuteRenditionAction
          • org.alfresco.repo.action.ActionServiceImpl.createAction(PerformRenditionActionExecuter.NAME)
          • set the renditionDefintion on performRenditionAction
          • org.alfresco.repo.action.ActionServiceImpl.executeAction
            • org.alfresco.repo.action.ActionServiceImpl.directActionExecution
              • load ActionExecuter from applicationContext
              • org.alfresco.repo.rendition.executer.ImageRenderingEngine.execute
                • org.alfresco.repo.rendition.executer.ImageRenderingEngine.render
                  • org.alfresco.repo.rendition.executer.ImageRenderingEngine.getTransformationOptions <- rebuilds parameter map into TransformationOptions objects
                  • org.alfresco.repo.content.ContentServiceImpl.transform
                    • org.alfresco.repo.content.transform.ContentTransformerRegistry.getTransformer
                    • org.alfresco.repo.content.transform.ProxyContentTransformer.transform <- subsystems
                      • org.alfresco.repo.content.transform.ContentTransformerWorker.transform
                        • TempFileProvider.createTempFile
                        • org.alfresco.util.exec.RuntimeExec.execute
                          • ffmpeg …

that’s a spicy meatball!

A while ago we submitted an issue to Alfresco suggesting TransformationOptions which contain distinct target AND source options so that one could for example specify that all ‘doclib’ thumbnails be of max-width X and max-height Y (the type of target options currently available) and furthermore, if it’s a document take page 1 (or maybe you need a different page in your deployment) or if it’s a video take 1 frame starting 0.5 seconds in, or if it’s a layered image file take the second layer, etc. (a list of source options).

Alas, this concept of SourceTargetTransformationOptions hasn’t yet been embraced by the Alfresco team but is used by the video transformer in the thumbnails extension which made refactoring for renditions even more difficult, but you’re not here to listen to me bitch and moan, so I’ll just say that it’s done and seems to be working fine.

So, there you have it, renditions and subsystems as they relate to video thumbnails. Hit me up with any questions or things I’ve missed.

JasperReports XML Datasource with Inline Images

We’re adding XML datasources for reports in RightsPro (currently JasperReports is supported) and needed to embed the image data directly in the XML so thought I’d expand on the solution I found here.

If your creating your XML dynamically using Java you can do:

import org.apache.commons.codec.binary.Base64;

// Read the byte array from DB or whatever
byte[] imageByteArray = getImageByteArray();

String base64Image = Base64.encodeBase64String(imageByteArray);


then write the Base64 encoded data in your XML as:


<image><![CDATA[ ... Base64 mess here ... ]]></image>


The rest comes from this JasperForge forum post which explains how to decode the Base64 into a ByteArrayInputStream that’s usable by the JasperReports image report element.

As the post says, in your JasperReport (we currently use iReport as a report editor) define a String field, let’s call it Image, for the image:

then define a variable, let’s call it ImageBytes, which uses that field:

with the Variable Expression:

new ByteArrayInputStream(new Base64().decodeBase64($F{Image}.getBytes("UTF-8")))

(don’t forget to add org.apache.commons.codec.binary.Base64 to the imports of the report properties)

and in the image element on your layout use the ImageBytes variable:

and you should see your image properly rendered in the final report.

However, what I found at this point is that while the images displayed fine in iReport in PDF preview most PDF readers were unable to display the images, resulting in just a black rectangle.

The solution was to change the ImageBytes variable class to java.awt.Image with a constructor of: ByteArrayInputStream(new Base64().decodeBase64($F{Image}.getBytes("UTF-8"))))

then change the expression class of the image element on the layout to java.awt.Image as well. You’ll also have to add javax.imageio.ImageIO to the imports of the report.

This technique can be useful when you need to grab the image data dynamically (not from actual files on the filesystem), you can’t get each image from a URL (perhaps due to security constraints), and don’t want to use a custom Java datasource.

Alfresco, SSO, and LDAP Expiring Date Attribute

Deciding where to handle authorization in a setup where Alfresco’s authentication is handled by CAS which itself authenticates against LDAP may not be as easy as it sounds. This post goes through some of the possibilities and one solution.

LDAP Authentication with an Expiration Date Attribute

On Alfresco version 2.x we extended the LDAPAuthenticationComponentImpl class to be able to evaluate an LDAP filter string, configurable via spring, which was used primarily for the purpose of determining whether the user’s access to Alfresco had expired, with the expiration date being stored as an LDAP attribute. So our file would contain something like:


and the extended Java class LDAPAuthenticationComponentImpl contained:

ctx = ldapInitialContextFactory.getInitialDirContext(bindDn, new String(password));

String authenticationFilter = String.format(

authenticationFilterFormat, new Object[]{userName, timestamp});

NamingEnumeration<SearchResult> answer =

authenticationSearchBase, authenticationFilter, userSearchCtls);

if (answer != null && answer.hasMoreElements()) {

setCurrentUser(escapeUserName(userName, escapeCommasInUid));


Once the expiration date was reached the user was denied access to the application.

Moving to Single Sign On

We’re now migrating to Alfresco 3.x and also have the need to move to a single sign on solution.  Jasig’s Central Authentication Service (CAS) was chosen as the implementation, requiring some changes to our expiration strategy.

In our previous configuration, authentication (is this user who they say they are) and authorization (is this user allowed to do x) were intertwined in our single custom LDAPAuthenticationComponentImpl class.  Any other applications authenticating against the same LDAP server could implement their own authentication/authorization methods.

CAS however is only concerned with authentication, not authorization, and applications utilizing it need to be able to decouple those functions, which is a better architecture in most cases anyway.

Our task then is to determine where in the authentication/authorization strategy we should handle this expiration authorization.

Choosing Where to Handle Authorization

Customize the CAS Server?

Modifying the CAS LDAP authentication handler wouldn’t be an appropriate place for our expiration authorization since all CAS services share the same authentication handler and other applications that have no notion of the Alfresco service or its expiration could be denied authentication.

We could try implementing a CAS authorizing serviceRegistryDao which would in theory allow the CAS server to determine if the user were authorized to use the service requested beyond just the Enabled and SSO Participant settings.  An authorization component could be defined that would make the authorization decision based on the service being requested and the user requesting it.  Each service could choose its authorization component as an option during setup in the CAS Service Management application.

CAS really wants the client/application to handle authorization though, so customizing the server doesn’t look like a recommended approach.

Customize Alfresco?

We could setup Alfresco’s authentication chain to talk to CAS, but in version 3.2 Alfresco has changed its authentication configuration to use the notion of Authentication Subsystems (which seems nicely implemented) and most documentation indicates that integration with something like CAS is best achieved by using mod_cas or mod_auth_cas in Apache web server, and since we’re already using Apache in front of Tomcat with mod_proxy_ajp that approach seems like a good fit.

Wait, Can a Hacker Just Spoof HTTP Headers?

It looks like AJP between Tomcat and the web server is responsible for parsing these protocol specific (non-HTTP) headers to find the remote user value and set that in Tomcat’s HttpServletRequest, so a hacker can’t just modify the headers in their browser and have that be translated into an authenticated user.

However, it is extremely important that your Tomcat connector be behind a firewall and only accept connections from known web servers.  Otherwise a hacker could set up their own web server with a bogus authentication mechanism which would add an authenticated user header in the AJP message, which in turn would translate to that user being signed in to a protected application.

mod_cas or mod_auth_cas?

With proper security in place we should be OK to use an Apache CAS module for authentication, but which one?


  • hasn’t been maintained in quite a while
  • has missing links all over the site
  • the documentation incorrectly states that “CAS stands for Common Authorization Service”
  • Jasig recommends against it

so, um, we’ll go with mod_auth_cas.

There’s a detailed wiki article on setting up Alfresco authentication through mod_auth_cas but our challenge is to determine where in the flow to inject our authorization.

Option 1: Extend Alfresco’s Request Authn to Grab CAS Attribute

The CAS server can return attributes to the client and if mod_auth_cas passes those attributes through mod_proxy_ajp to Tomcat and Alfresco then we may be able to modify the component which checks that header for the authenticated username, HTTPRequestAuthenticationFilter, to also look for the expiration date attribute and authorize or deny the user.

After modifying Tomcat’s example snoop application to show all headers and attributes:

java.util.Enumeration eH = request.getHeaderNames();
while (eH.hasMoreElements()) {
String headerName = (String) eH.nextElement();
out.print("Header <b>" + headerName + ": </b>");
out.print(request.getHeader(headerName) + "<br>");
java.util.Enumeration eA = request.getAttributeNames();
while (eA.hasMoreElements()) {
String attributeName = (String) eA.nextElement();
out.print("Attribute <b>" + attributeName + ": </b>");
out.print(request.getAttribute(attributeName) + "<br>");

it doesn’t look like the additional attributes defined to be available to the CAS service are passed through mod_auth_cas and mod_proxy_ajp to the secured application.  We might be able to pass the data through SAML attributes but it seems that would require POST requests.

Option 2: Extend Alfresco’s Request Authn to query LDAP directly

If we can’t get the expiration date attribute from the request headers we still may be able to modify the request authentication filter to make a trip directly to the LDAP server to query for the attribute and confirm or deny authorization, but maybe we can do everything at the web server.

Option 3: Handle Authorization at Apache

The ‘auth’ in mod_auth_cas appears to only include authentication but there may be hope in allowing mod_auth_cas to handle just the authentication part then using mod_authnz_ldap or the third-party mod_authz_ldap module to handle the authorization side.

mod_authnz_ldap’s Require ldap-filter looks promising, but we would need to insert a dynamic date (today’s) for comparison to the user’s expiration date. LDAP syntax doesn’t seem to have any keyword for the current date to be handled on the server side and it doesn’t look like mod_authnz_ldap has any special tags for injecting it in the filter string so the source code would have to be modified.

mod_authz_ldap however has this in its filter configuration:

The current time in the format YYYYMMDDhhmmss

that may just do it.

Unfortunately, upon further investigation mod_authz_ldap does not support secure SSL/TLS communication with the LDAP server and hasn’t been maintained in a quite a while, so that won’t work.

Dare I try modifying the mod_authnz_ldap source to allow for a dynamic replace of a date tag?  Sure, why not?

The last time I wrote C code it was probably on a machine running Mac OS 7.6, but after several hours and as many espressos I’ve taken the relevant code from mod_authz_ldap’s source, tweaked it a bit, and found the appropriate place to inject it in mod_authnz_ldap’s authn_ldap_build_filter.

You can compile and install a single module on the server with:

apxs -i -a -c mod_authnz_ldap.c

then add the appropriate config to your location directive.  So we have CAS performing authentication and a modified mod_authnz_ldap performing authorization on our Tomcat examples app:

<Location /examples>
AuthType CAS
AuthName "CAS"
CASScope /examples
AuthLDAPURL "ldaps://,dc=company,dc=com?uid?sub?(alfrescoExpirationDate>=$tZ)"
AuthLDAPBindDN "uid=reader,ou=users,dc=company,dc=com"
AuthLDAPBindPassword "*****"
require valid-user

Again, my C chops are extremely rusty but I’ve contributed these changes to mod_authnz_ldap to Apache as Bug 47838.

Other Alfresco SSO Caveats

We can secure the Alfresco app in the same manner as /examples above but we’ll have to secure Alfresco Share with SSO as well and may run into issues with ticket parameter clashes.

As stated in one of the forum posts above, the header Alfresco needs to look for is CAS-User.  Before Alfresco 3.2 the web.xml changes would look like:

<!-- Name of HTTP header containing UserID. -->

Other Spring Apps with CAS

In some of our other apps we use Spring Security and will want to integrate them with CAS.  There’s a good write up on the details of the communication between spring security and CAS and a forum post on CAS with LDAP authorization that should help.

Hopefully this account of a mad man in pursuit of expiring authorization will be of use to someone, somewhere, someday.

Web-Based Line Chart Tools Comparison


Google Visualization API

I’m in the process of evaluating various AJAX/DHTML frameworks for an enterprise application and one of the needs that deserved some hands-on experimentation was the ability create charts.

jQuery and YUI are in the lead as far as core frameworks for this application and the Google Visualization API seems pretty interesting so was included as well.

As a fairly simple and common use case I choose a target of a line chart that showed the number of users of a few services over time and defined some basic requirements:

  • The data must be fetched from a separate URL, not defined on the presentation page (this URL will be a web service in production)
  • Three series must be displayed at once
  • The data points should have tooltips
  • ‘Out-of-the-box’ components should be used when possible
  • A non-flash solution is preferred

Some of the points I found interesting from each framework follow. Note that the charts here are only screenshots as I didn’t find any immediately obvious way to get the real charts working on



At this time it doesn’t seem that jQuery or jQuery UI has any built-in charting abilities but there are a ton of community plugins available.

Flot looked the most promising (Sparklines also looks very good for tiny charts).


Flot seems to prefer its data in JSON format organized by series and we want to simulate a web service so we’ll create a file data/users-series-format.json containing something like:

{label: "Service 1 Users", data:[
{label: "Service 2 Users", data:[
{label: "Service 3 Users", data:[


Setup was simple enough.  Include the scripts:

<!--[if IE]><script language="javascript" type="text/javascript" src="../excanvas.pack.js"></script><![endif]-->

<script language="javascript" type="text/javascript" src="scripts/jquery/jquery-1.3.2.js"></script>

<script language="javascript" type="text/javascript" src="scripts/jquery/jquery.flot.js"></script>

put in the placeholder where you want the chart:

<div id="placeholder" style="width:400px; height:200px;"></div>

get the data and build the chart:

<script id="source" language="javascript" type="text/javascript">

$.getJSON("data/users-series-format.json", function(json){

$.plot($("#placeholder"), json, {

xaxis: { mode: "time" },

lines: { show: true },

points: { show: true }




Adding the tooltip was a little more complicated but not terribly so.



The Yahoo! User Interface Library (YUI) is a mature, robust library with Yahoo-supplied components for just about everything.

The YUI Charts Control (labeled as experimental at the time of this writing) creates nice looking charts, but they are flash-based.

There is plenty of documentation on YUI’s charts so I won’t go into too much detail but the concepts are similar to above.


YUI’s DataSource component can handle several data formats, for consistency’s sake I went with JSON so my file data/users-raw-format.json (named so since it most closely resembles the raw data used to build it) contains:

{"Response" : { "Results" : [

I did have trouble getting this data to load properly via URL when the HTML file containing the DataSource code was loaded in the browser via file:// rather than http:// even after adding the file location to the Flash global security settings.

The chart also displayed oddly when the data contained JavaScript timestamps and a date parser was used, so as you can see a string date was used in the data, not ideal but this is just proof of concept.


Include the scripts (YUI’s dependency configurator comes in handy here), add a place holder div for the chart, get the data, and build the chart.

Don’t forget to set YAHOO.widget.Chart.SWFURL to a location containing the swf.

Google Visualization API


There are some pretty slick examples in the Google’s Visualization Gallery like the Motion Chart, but for the purposes of our exploring we just need the Line Chart.

Once again we have a similar pattern for getting things running.


Google requires that the web service providing the data source implement a particular protocol and format.

We’ll again use JSON and with Google’s format our data/users-google-format.json file looks like:

{id:'s1',label:'Service 1 Users',type:'number'},
{id:'s2',label:'Service 2 Users',type:'number'},
{id:'s3',label:'Service 3 Users',type:'number'}],
{c:[{v:new Date(2009,4,7,0,0,0)},{v:110},{v:26},{v:22}]},
{c:[{v:new Date(2009,4,8,0,0,0)},{v:115},{v:31},{v:23}]}

At first the format seemed cumbersome but I realize that many users would prefer this close resemblance to how the same data would appear in a spreadsheet.


Loading of the scripts is a little different since you use Google’s core JavaScript API to load the charting tools needed and can call the chart initialization on successful load of that package:

<script type=“text/javascript” src=;></script>

<script type=“text/javascript”>

google.load(“visualization”, “1”, {packages:[“linechart”]});


where initialize loads the data:

function initialize() {

var query = new google.visualization.Query(‘data/users-google-format.json’);



and the query calls handleQueryResponse which draws the chart:

function handleQueryResponse(response) {

if (response.isError()) {

alert(‘Error in query: ‘ + response.getMessage() + ‘ ‘ + response.getDetailedMessage());



var data = response.getDataTable();

var chart = new google.visualization.LineChart(document.getElementById(‘chart_div’));

chart.draw(data, {width: 400, height: 200, is3D: true});




Even with the extensive documentation and examples available on YUI I’d have to say it was the most difficult to deal with, presenting some little problem at almost every turn.

It didn’t seem that it would be very easy to use a JSON format other than the one recommended by the framework for any of the tools evaluated, which is disappointing.

There are certainly a lot of factors to consider before choosing a UI framework, but if all I needed were line charts I’d probably hand it to Flot/jQuery.

Changing the Alfresco LDAP Group Identifier

We were originally using Apple OS X Server as our LDAP store for our Alfresco instance.

Apple’s OS X Server uses OpenLDAP but adds custom schema for many things including users and groups.  As a result we ended up using the description LDAP attribute for Alfresco’s ldap.synchronisation.groupIdAttributeName.

We’ve since migrated to a generic OpenLDAP server (with a bit of our own custom schema) so we’re now able to use the more common and unchanging cn attribute for the group id.

When we change ldap.synchronisation.groupIdAttributeName in Alfresco imports the new groups properly but group permissions on spaces will retain the old group name so we need to change those to use the new cn attribute.

What we did was to create a temporary table in the Alfresco database, import the mapping of the cn attribute to the description attribute, then run a query to replace the old authorities with the new.

The following assumes Alfresco version 3.x.

Create the Temp Table

CREATE TABLE alfresco.t_ldap_groups (
`dn` VARCHAR( 255 ) NULL ,
`cn` VARCHAR( 255 ) NULL ,
`description` VARCHAR( 255 ) NULL

Import the LDAP Group Data

We used phpLDAPAdmin to export our groups subtree as CSV with only the cn and description attributes, then imported that file into the t_ldap_groups table just created.

Replace the Old Authorities

I’m by no means an SQL expert but the query below does the following:

  • Strips GROUP_ from the current stored group long name
  • Searches the temporary LDAP table for that group long name and corresponding group short name
  • Updates the alf_authority.authority field with GROUP_group short name
UPDATE alf_authority
SET authority = CONCAT('GROUP_',
(SELECT cn FROM t_ldap_groups WHERE description =    SUBSTRING(alf_authority.authority, 7) LIMIT 1))
WHERE authority LIKE 'GROUP_%' AND
(SELECT cn FROM t_ldap_groups WHERE description =    SUBSTRING(alf_authority.authority, 7) LIMIT 1) IS NOT NULL;

In Alfresco 2.x the authority is stored directly in the alf_access_control_entry table as well so the update statement would be a bit more complicated.

Drop the Temp Table

DROP TABLE t_ldap_groups;

So far we haven’t had any adverse effects on our development server doing things this way but if anyone has a better method or potential issues with this one let us know. vs vs

It seems the time has come that I start making my thoughts known to whoever might be listening out there via the sometimes informative, sometimes controversial, often ridiculous medium of blogging.

After investigating several services and open source blogging web apps I’ve chosen to give a try as you can no doubt see if you’re reading this post.

I was leaning towards a hosted service from the start as I have grown very weary of maintaining servers and apps in my ‘old age’.  Google’s Blogger (formerly Blogspot) and emerged as the two best candidates for a ready-to-use, free, hosted blog platform that suited my needs.

I came across some nice comparisons with many user comments including this Pulsed post and Squidoo lens.

In the end I went with WordPress for a couple of reasons:

  • I don’t plan to ever have ads.
  • It seems to be widely used.
  • Most WordPress blogs I saw seemed to be cleaner than Blogger’s (whether that’s purely a function of the themes or the type of creators each attracts I’m not sure)
  • I can deploy it myself if the need arises.
  • Alfresco supports posting content to WordPress out of the box.

It shouldn’t be too tough to switch if I can’t stand it, but so far I do like the service/app.

I’d imagine the content here will consist primarily of brain dumps about enterprise java, open source integration, general technology thoughts, and the occasional rant.  I’ll apologize in advanced for the terrible vocabulary and grammar.

I hope it’s useful to someone.