Adding files to your amp

When you’re writing an Alfresco extension there’s a good chance that you’ll want to do some configuration or add some files along with your code.

One option is to, via a documented process, add everything by hand but it’s neater and more reliable if you can do it as part of your amp.

The trick here is to use acp files.

These files are created via Exporting from the Alfresco client see here – there is a good chance you’ll want to edit the acp files after you’ve created them e.g. to remove system files.

If you want to include the acp file directly in your amp then you should include them as part of the bootstrap process.

This is a two part operation.

Copy the acp file to the amp e.g. in /src/main/resources/alfresco/module/org_wrighting_module_cms/bootstrap

Add the following bean definition to /src/main/resources/alfresco/module/org_wrighting_module_cms/context/bootstrap-context.xml

  <bean id="org_wrighting_module_cms_bootstrapSpaces" class="org.alfresco.repo.module.ImporterModuleComponent" 
        parent="module.baseComponent">
        <property name="moduleId" value="org.wrighting.module.cms" />
        <property name="name" value="importScripts" />
        <property name="description" value="additional Data Dictionary scripts" />
        <property name="sinceVersion" value="1.0.0" />
        <property name="appliesFromVersion" value="1.0.0" />

        <property name="importer" ref="spacesBootstrap"/>
        <property name="bootstrapViews">
            <list>
                 <props>
                     <prop key="path">/${spaces.company_home.childname}/${spaces.dictionary.childname}/app:scripts</prop>
                     <prop key="location">alfresco/module/org_wrighting_module_cms/bootstrap/wrighting_scripts.acp</prop>
                 </props>
            </list>
        </property>
  </bean>

This will then import your scripts to the Data Dictionary ready for use.

The acp file itself is a zip file containing an XML file describing the enclosed files – it’s a good idea to use the export action to create this as there is a fair amount of meta information involved.

If you want to expand the acp and then copy it into place then the following in your pom.xml will do the job

<plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-antrun-plugin</artifactId>
      <version>1.7</version>
      <executions>
          <execution>
             <phase>prepare-package</phase>
             <configuration>
                 <target>
                     <zip basedir="${basedir}/tools/export/wrighting/wrighting_scripts.acp"
                          destfile="${project.build.directory}/${project.artifactId}-${project.version}/config/alfresco/module/org_wrighting_module_cms/bootstrap/wrighting_scripts.acp" />
                 </target>
              </configuration>
              <goals>
                 <goal>run</goal>
              </goals>
          </execution>
      </executions>
 </plugin>

CAS for Alfresco 4.2 on Ubuntu

Lots of confusion around on this subject so I’m going to attempt to distill some wisdom into this post and tweak it for Ubuntu

2 good blogs Nick with mod_auth_cas and Martin with CAS client and the Alfresco docs

I’m not going to talk about setting up CAS here as this post is complex enough already – I’ll just say be careful if using self signed certs.

I’ve used Martin’s method before with Alfresco 3.4

It’s a tricky decision as to which approach to use:

  • the mod_auth_cas approach is the approach supported by Alfresco but it introduces the Apache plug in which isn’t as well supported by CAS and you have problems with managing the mod_auth_cas cookie management, caching etc
  • the java client is a bit more involved and intrusive but seems to work quite well in the end
  • I haven’t tried container managed auth but it looks promising

Using mod_auth_cas

For a more detailed explanation look at Nick’s blog – this entry is more about how rather than why and is specific to using apt-get packages on Ubuntu.

First set up your mod_auth_cas

Next tell Tomcat to trust the Apache authentication by setting the following attribute tomcatAuthentication=”false” on the AJP Connector (port 8009)

Now you need to set up the Apache Tomcat Connectors module – mod-jk

apt-get install libapache2-mod-jk

Edit the properties file defined in /etc/apache2/mods-enables/jk.conf – /etc/libapache2-mod-jk/workers.properties – to set the following values

workers.tomcat_home=/opt/alfresco-4.2.c/tomcat
workers.java_home=/opt/alfresco-4.2.c/java

Add to your sites file e.g. /etc/apache2/sites-enabled/000-default

JkMount /alfresco ajp13_worker
JkMount /alfresco/* ajp13_worker
JkMount /share ajp13_worker
JkMount /share/* ajp13_worker

And don’t forget to tell Apache which URLs to check

<Location />
Authtype CAS
require valid-user
</Location>

A more complex example in the wiki here

Add the following to tomcat/shared/classes/alfresco-global.properties

authentication.chain=external-apache:external,alfrescoNtlm1:alfrescoNtlm
external.authentication.enabled=true
external.authentication.proxyHeader=X-Alfresco-Remote-User
external.authentication.proxyUserName=

Finally add the following section to tomcat/shared/classes/alfresco/web-extension/share-config-custom.xml

Note that if you have customizations you may need this in the share-config-custom.xml in your jar

 	<config evaluator="string-compare" condition="Remote">
		<remote>
			<endpoint>
				<id>alfresco-noauth</id>
				<name>Alfresco - unauthenticated access</name>
				<description>Access to Alfresco Repository WebScripts that do not
					require authentication
				</description>
				<connector-id>alfresco</connector-id>
				<endpoint-url>http://localhost:8080/alfresco/s</endpoint-url>
				<identity>none</identity>
			</endpoint>

			<endpoint>
				<id>alfresco</id>
				<name>Alfresco - user access</name>
				<description>Access to Alfresco Repository WebScripts that require
					user authentication
				</description>
				<connector-id>alfresco</connector-id>
				<endpoint-url>http://localhost:8080/alfresco/s</endpoint-url>
				<identity>user</identity>
			</endpoint>

			<endpoint>
				<id>alfresco-feed</id>
				<name>Alfresco Feed</name>
				<description>Alfresco Feed - supports basic HTTP authentication via
					the EndPointProxyServlet
				</description>
				<connector-id>http</connector-id>
				<endpoint-url>http://localhost:8080/alfresco/s</endpoint-url>
				<basic-auth>true</basic-auth>
				<identity>user</identity>
                                <external-auth>true</external-auth>
			</endpoint>

			<endpoint>
				<id>activiti-admin</id>
				<name>Activiti Admin UI - user access</name>
				<description>Access to Activiti Admin UI, that requires user
					authentication
				</description>
				<connector-id>activiti-admin-connector</connector-id>
				<endpoint-url>http://localhost:8080/alfresco/activiti-admin
				</endpoint-url>
				<identity>user</identity>
			</endpoint>
		</remote>
	</config>

 

This gets you logged in but you still need to logout! Share CAS logout.
One thing to be careful about with using mod_auth_cas here is that you need to be aware of the mod_auth_cas caching – if you are not careful you’ll log out but mod_auth_cas will still think that you are logged in. There are some options here – set the cache timeout to be low (inefficient), use single sign out (experimental)

Using CAS java client

Martin’s blog works for Alfresco 3.4 and here are some notes I made for 4.2.d

Note that it is not supported to make changes to the web.xml

Make the following jars available:

cas-client-core-3.2.1.jar, commons-logging-1.1.1.jar, commons-logging-api-1.1.1.jar

You can do this by including them in the wars or by copying the following jars into <<alfresco home>>/tomcat/lib
N.B. If you place them into the endorsed directory then you will get error messages like this:
SEVERE: Exception starting filter CAS java.lang.NoClassDefFoundError: javax/servlet/Filter

You need to make the same changes to tomcat/shared/classes/alfresco-global.properties and share-config-custom.xml as for the mod_auth_cas method

Now add the following to share/WEB-INF/web.xml and alfresco/WEB-INF/web.xml

There’s some fine tuning to do on the url-pattern probably the best way is to copy the filter mappings for the existing authentication filter and add /page for share and /faces for alfresco.

Using the values below works but is a little crude (shown here to be concise)

 <filter>
    <filter-name>CAS Authentication Filter</filter-name>
    <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter
    </filter-class>
    <init-param>
        <param-name>casServerLoginUrl</param-name>
        <param-value>https://www.wrighting.org/sso/login</param-value>
    </init-param>
    <init-param>
        <param-name>serverName</param-name>
        <param-value>https://alfresco.wrighting.org</param-value>
    </init-param>
</filter>
<filter>
    <filter-name>CAS Validation Filter</filter-name>
    <filter-class>org.jasig.cas.client.validation.Cas10TicketValidationFilter
    </filter-class>
    <init-param>
        <param-name>casServerUrlPrefix</param-name>
        <param-value>https://www.wrighting.org/sso</param-value>
    </init-param>
    <init-param>
        <param-name>serverName</param-name>
        <param-value>https://alfresco.wrighting.org</param-value>
    </init-param>
</filter>
<filter>
    <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
    <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>CAS Authentication Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS Validation Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Next add the following to the session-config section of the web.xml which relates to this issue which may be solved via removing the jsessionid from the url (this may cause problems with the flash uploader if you’re still using it see here)

<tracking-mode>COOKIE</tracking-mode>

There’s also a case for using web-fragments to avoid changing the main web.xml

You will need to redirect the change password link in the header (how to depends on version)

Container managed auth

This looks quite interesting CAS Tomcat container auth as it allows the use of the CAS java client within tomcat so being closer to the mod_auth_cas approach but without needing to configure Apache.

This issue referenced above gives some details of how somebody tried it – I think it should work if the session tracking mode is set to COOKIE but haven’t tried it.

More complex configurations

This is beyond what I’m trying to do but if you’ve got a load balanced configuration you may need to think about the session management – the easiest way to approach may be to use sticky sessions e.g.

ProxyRequests Off
ProxyPassReverse /share balancer://app
ProxyPass /share balancer://app stickysession=JSESSIONID|jsessionid nofailover=On

BalancerMember ajp://localhost:8019/share route=tomcat3
BalancerMember ajp://localhost:8024/share route=tomcat4

 

mod_auth_cas for CAS 3.5.2 on Ubuntu

This is not as straightforward as it should be as mod_auth_cas has not yet been brought up to date with the latest SAML 1.1 schema and the XML parsing doesn’t support the changes. In addition the pull request for the changes in github is out of date with the main branch so that’s not much help either.

That being said if you don’t use the SAML validation for attribute release you can still go ahead.


apt-get install libapache2-mod-auth-cas
a2enmod auth_cas

Configure the CAS configuration which you can do in /etc/apache2/mods-enabled/auth_cas.conf

CASCookiePath /var/cache/apache2/mod_auth_cas/
CASLoginURL https://www.wrighting.org/cas/login
#CASValidateURL https://www.wrighting.org/cas/samlValidate
CASValidateURL https://www.wrighting.org/cas/serviceValidate
CASDebug Off
CASValidateServer On
CASVersion 2
#Only if using SAML
#CASValidateSAML Off
#CASAttributeDelimiter ;
#Experimental sign out
CASSSOEnabled On

 

Configure the protected directories probably somewhere in /etc/apache2/sites-enabled
N.B. You also need to ensure that the ServerName is set otherwise the service parameter on the call to CAS will contain 127.0.1.1 as the hostname

    Authtype CAS
    CASAuthNHeader On
    require valid-user
    #Only works if you are using Attribute release which requires SAML validation
    #require cas-attribute memberOf:cn=helpDesk,ou=groups,dc=wrighting,dc=org
    Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
    Order allow,deny
    Allow from all

    Authtype CAS
    require valid-user

 

Don’t forget to restart apache service apache2 reload

Updated mod_auth_cas is now being maintained again


mkdir /var/cache/apache2/mod_auth_cas
chown www-data:www-data /var/cache/apache2/mod_auth_cas
apt-get install make apache2-prefork-dev libcurl4-gnutls-dev
git clone https://github.com/Jasig/mod_auth_cas
./configure
make
make install

 

Editing a dojox DataGrid connected to a JsonRest store

A little bit of a problem with using a dojox.grid.DataGrid connected to a dojo.store.JsonRest data store.

Note that dojox.grid is officially abandoned so it’s not really a very good idea to use it anyway – should be using dgrid or GridX instead.

If you edit a cell value then timing issues mean that although the change is held (and will be saved when you store.save()) the displayed value reverts to the original.

One way around this is to make use of onApplyCellEdit.

You can also use this function to call this.store.save() if you want to save after every change.

There also appears to be a problem with adding rows not displaying as well.

 function saveCellEdit(inValue, inRowIndex, inAttrName){
	/*
	 this.store.save();
	 */
	var data = this.getItem(inRowIndex);
	data[inAttrName] = inValue;
  };
  /*create a new grid*/
  var grid = new DataGrid({
			id : 'grid',
			store : store,
			structure : layout,
			rowSelector : '20px',
			onApplyCellEdit: saveCellEdit
		});

Some relevant links:

Using the LDAP Password Modify extended operation with Spring LDAP

If you want to change the password for a given user in an LDAP repository then you need to worry about the format in which it is being stored otherwise you will end up with the password held in plain text (although base64 encoded)

Using the password modify extended operation (rfc3062) allows OpenLDAP, in this case, to manage the hashing of the new password.

If you don’t use the extension then you have to hash the value yourself.
This code stores the new password as plaintext and treats the password as if it is any other attribute.
You can implement hashing yourself e.g. by prepending {MD5} and using the base64 encoded md5 hash of the new password – see this forum entry

Don’t use this!

	DistinguishedName dn = new DistinguishedName(dn_string);
	Attribute passwordAttribute = new BasicAttribute(passwordAttr,
			newPassword);
	ModificationItem[] modificationItems = new ModificationItem[1];
	modificationItems[0] = new ModificationItem(
			DirContext.REPLACE_ATTRIBUTE, passwordAttribute);
/*
	Attribute userPasswordChangedAttribute = new BasicAttribute(
			LDAP_PASSWORD_CHANGE_DATE, format.format(convertToUtc(null)
					.getTime()) + "Z");
	ModificationItem newPasswordChanged = new ModificationItem(
			DirContext.REPLACE_ATTRIBUTE, userPasswordChangedAttribute);
	modificationItems[1] = newPasswordChanged;
	*/
	getLdapTemplate().modifyAttributes(dn, modificationItems);

 

This example uses the extended operation which means that password will be stored according to the OpenLDAP settings i.e. SSHA by default.

The ldap template here is an instance of org.springframework.ldap.core.LdapTemplate

 ldapTemplate.executeReadOnly(new ContextExecutor() {
   public Object executeWithContext(DirContext ctx) throws NamingException {
      if (!(ctx instanceof LdapContext)) {
            throw new IllegalArgumentException(
               "Extended operations require LDAPv3 - "
               + "Context must be of type LdapContext");
      }
      LdapContext ldapContext = (LdapContext) ctx;
      ExtendedRequest er = new ModifyPasswordRequest(dn_string, new_password);
      return ldapContext.extendedOperation(er);
    }
   });

This thread gives an idea of what is required however the ModifyPasswordRequest class available from here actually has all the right details implemented.

You will find that other LDAP libraries e.g. ldapChai use the same ModifyPasswordRequest class

CAS, OpenLDAP and groups

This is actually fairly straightforward if you know what you’re doing unfortunately it takes a while, for me at least, to get to that level of understanding.

Probably the most important thing missing from the pages I’ve seen describing this is that you need to configure OpenLDAP first.

OpenLDAP

What you want is to enable the memberOf overlay

For Ubuntu 12.04 the steps are as follows:
Create the files
module.ldif

dn: cn=module,cn=config
objectClass: olcModuleList
cn: module
olcModulePath: /usr/lib/ldap
olcModuleLoad: memberof

overlay.ldif

dn: olcOverlay=memberof,olcDatabase={1}hdb,cn=config
objectClass: olcMemberOf
objectClass: olcOverlayConfig
objectClass: olcConfig
objectClass: top
olcOverlay: memberof
olcMemberOfDangling: ignore
olcMemberOfRefInt: TRUE
olcMemberOfGroupOC: groupOfNames
olcMemberOfMemberAD: member
olcMemberOfMemberOfAD: memberOf

Then configure OpenLDAP as follows:

ldapadd -Y EXTERNAL -H ldapi:/// -f module.ldif
ldapadd -Y EXTERNAL -H ldapi:/// -f overlay.ldif

You should probably read up on this a bit more – in particular note that retrospectively adding this won’t achieve what you want without extra steps to reload the groups

CAS

The CAS documentation is actually reasonably good once you understand that you are after the memberOf attribute but for example I’ll show some config here

deployerConfigContext.xml

<bean id="attributeRepository"
    class="org.jasig.services.persondir.support.ldap.LdapPersonAttributeDao">
    <property name="contextSource" ref="contextSource" />
    <property name="baseDN" value="ou=people,dc=wrighting,dc=org" />
    <property name="requireAllQueryAttributes" value="true" />

    <!-- Attribute mapping between principal (key) and LDAP (value) names used 
        to perform the LDAP search. By default, multiple search criteria are ANDed 
        together. Set the queryType property to change to OR. -->
    <property name="queryAttributeMapping">
        <map>
            <entry key="username" value="uid" />
        </map>
    </property>

    <property name="resultAttributeMapping">
        <map>
            <!-- Mapping beetween LDAP entry attributes (key) and Principal's (value) -->
            <entry value="Name" key="cn" />
            <entry value="Telephone" key="telephoneNumber" />
            <entry value="Fax" key="facsimileTelephoneNumber" />
            <entry value="memberOf" key="memberOf" />
        </map>
    </property>
</bean>

 

After that you can setup your CAS to use SAML1.1 or modify view/jsp/protocol/2.0/casServiceValidationSuccess.jsp according to your preferences.

Don’t forget to allow the attributes for the registered services as well

<bean id="serviceRegistryDao" class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl">
		<property name="registeredServices">
			<list>
				<bean class="org.jasig.cas.services.RegexRegisteredService">
					<property name="id" value="0" />
					<property name="name" value="HTTP and IMAP" />
					<property name="description" value="Allows HTTP(S) and IMAP(S) protocols" />
					<property name="serviceId" value="^(https?|imaps?)://.*" />
					<property name="evaluationOrder" value="10000001" />
					<property name="allowedAttributes">
						<list>
							<value>Name</value>
							<value>Telephone</value>
							<value>memberOf</value>
						</list>
					</property>
				</bean>
			</list>
		</property>
	</bean>

 

A first R project

To start with I’m using the ProjectTemplate library – this creates a nice project structure

I’m going to be attempting to analyze some census data so I’ll call the project ‘census’

I’m interested in Eynsham but it’s quite hard to work out which files to use – in the end I’ve stumbled across the parish of Eynsham at
this page and the ward of Eynsham from 2001 here which seem roughly comparable.

This blog entry is also quite interesting although I found it rather late in the process

install.packages("ProjectTemplate")
library('ProjectTemplate')
setwd("census")

Now we can copy some census data files into the data directory then load it all up.
(I’m not going to cover downloading the data files and creating an index of categories – it’s more painful than it should be but not that hard – I’ve used the category number as part of the file name in the downloaded files)

 

load.project()

 

This doesn’t work so some experimentation is called for…

I don’t think we can munge the data until after it’s loaded so switch off the data_loading in global.dcf

So let’s create a cache of the data in a more generic fashion

With the parish data for 2011 load up the categories

datatypes = read.csv("data/datatypes.parish.2011.csv", sep="t")
datadef = t(datatypes[,1])
colnames(datadef) <- t(datatypes[,2])

parishId = "11123312"

for (d in 1:length(datadef)){
  datasetName <- paste(parishId,datadef[d], sep = ".")
  filename <- paste("data/",datasetName,".csv", sep = "")
  input.raw = read.csv(filename,header=TRUE,sep=",", skip=2)
  input.t <- t(input.raw[2:(nrow(input.raw)-4),])
  colnames(input.t) <- input.t[1,]
  input.clipped <- input.t[5:nrow(input.t),]
  input.num <- apply(input.clipped,c(1,2),as.numeric)
  dsName <- paste("parish2011_",datadef[d], sep = "")
  assign(dsName, input.num)
  cache(dsName)

}

 

Then do the same for 2001

Now needless to say the data from 2001 and 2011 is represented in different ways so there’s a bit of data munging required to standardize and merge similar datasets – I’ve given an example here for population by age where in 2001 the value is given for each year whereas 2011 uses age ranges so it’s necessary to merge columns using sum

 

png(file="graphs/populationByAge.png",width=659,height=555)
ages <- rbind(c(sum(ward2001_91[1,2:6]),sum(ward2001_91[1,7:9]),sum(ward2001_91[1,10:11]),sum(ward2001_91[1,12:16]),
                ward2001_91[1,17],sum(ward2001_91[1,18:19]),sum(ward2001_91[1,20:21]),
                sum(ward2001_91[1,22:26]),sum(ward2001_91[1,27:31]),sum(ward2001_91[1,32:46]),
                sum(ward2001_91[1,47:61]),sum(ward2001_91[1,62:66]),sum(ward2001_91[1,67:76]),
                sum(ward2001_91[1,77:78]),sum(ward2001_91[1,79]),sum(ward2001_91[1,80:82])
                ),parish2011_2474[,c(2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17)])
ages <- ages[1:2,]
rownames(ages) <- c("Ward 2001", "Parish 2011")
colnames(ages) <- sub("Age ","",colnames(ages))
colnames(ages) <- sub(" to ","-",colnames(ages))
colnames(ages) <- sub(" and Over","+",colnames(ages))
barplot(ages, beside = TRUE, col = c("blue", "red"), legend=c("2001","2011"), main="Population", width=0.03, space=c(0,0.35), xlim=c(0,1), cex.names=0.6)
dev.off()

 

The result can be seen here

            0-4 5-7 8-9 10-14 15 16-17 18-19 20-24 25-29 30-44 45-59 60-64 65-74 75-84 85-89 90+
Ward 2001   226 160 119   335 51   112    85   202   250  1072  1003   266   427   241    61  29
Parish 2011 250 139  91   258 59   113   106   227   188   848  1005   314   584   342    80  44

Most interesting looks like a big drop in the 25-44 age range – due to house prices or lack of availability of housing as the population ages? – which is reflected in the rise in people of retirement age and numbers of children although, given the shortage of pre-school places the increase in the 0-4 range is also interesting.

Lots more analyis could be done but that’s outside the scope of this blog entry!

NoSQL – mongodb vs eXist

There are similarities between mongo and eXist in that they are both document orientated and work with collections of documents.
The main difference is that eXist uses XML documents and mongo uses JSON documents.

There’s plenty of documentation on both but from my point of view it’s quite interesting to compare them and will help my understanding!

(At this point I’m just starting out with mongo and have done more with eXist – see this project which is java war containing a REST based service that uses eXist as it’s back end)

eXist uses xquery as it’s query language
With eXist as you are running in an application server you can write a .xql which directly returns your data, or even HTML, and it’s even possible to wrap with java etc e.g. if you want to implement Spring Security or you can just use it as a database server (albeit one running in a java app server)
eXist also has a REST interface, XML-RPC, the java XML:DB api etc see here for more details.

Mongo acts more like a tradition database server with a command shell and to act as a web server requires an additional interface e.g. using python – an example of a REST service is here

There’s a nice comparison between mongodb and sql here
In mongo you can show the collections using the command show collections

To list a collection you use db.collection name.find() this being like SELECT * FROM table or collection('collection name') or xmldb:xcollection('collection name') in xquery

So for a collection ‘test’ containing atom entries in eXist you might do something like this (xquery):

 let $all-recs := xmldb:xcollection( 'test_collection' )/atom:entry (: not recursive :)
 let $test-recs := $all-recs[atom:title[. = 'Test']
 let $author := for $rec in $test-recs
                   let $auth = $rec/atom:author
                   return $auth

or in mongo

 db.test_collection.find({"title": "Test"})

so in eXist you are using XPath whereas in mongo you are writing a json document that matches the document you want to retrieve.

What’s the conclusion?

MongoDB is sexy at the moment and there’s a lot more information available and it looks quite easy to use.

eXist (and it’s big brother MarkLogic) is more niche but the XQuery/XPath language is probably more powerful than the direct mongo syntax. XForms, e.g. Orbeon, also provides a pretty simple way to edit the XML once you’ve got your head around it.

Most languages have pretty good support for either JSON or XML processing.

Overall I think it’s a case of horses for courses but there’s a strong argument for sticking with traditional SQL databases all of which can handle XML text fields with XPath queries and some can handle JSON – it’s even quite easy to serialize XML to a database using HyperJAXB3 which uses JAXB and JPA – see this project. These days it’s also really easy to knock up a simple CRUD application using something like grails.

I can certainly see a case for eXist where you are working with relatively unstructured XML documents – journal articles would be a good example here – XForms is a nice tie in too.

MongoDB is a good candidate if you are using JSON data – for example in an application which is heavily Ajax/REST based and would cope well with a lot of sparsely populated data – could be pretty good for prototyping where you want to change your data structures a lot e.g. with angular js and a python service and of course it’s "web scale" for when your app takes over the world!
(A humourous mongodb mysql comparison here which gets a bit out of control half way through)

Rookie python

When running in a virtualenv don’t forget to source bin/activate

Install packages using pip e.g. pip install beautifulsoup4

Create a REQUIREMENTS file using pip freeze and load using pip install -r REQUIREMENTS

Creating a module you need __init__.py in the directory in order to import successfully otherwise you’ll get AttributeError: 'module' object has no attribute 'xxxx'

Javascript debugging using Eclipse

Start Chrome with remote debugging enabled

google-chrome --remote-debugging-port=9222 --user-data-dir=/home/iwright/chrome

Create a new debug configuration using WebKit Protocol

Host localhost
Port 9222
Wip backend Protocol 1.0

Software update site

http://chromedevtools.googlecode.com/svn/update/dev/