<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-37642571</id><updated>2012-01-25T14:26:55.270-08:00</updated><category term='Spring log not showing Runtime Exceptions SimpleMappingExceptionResolver'/><category term='agile portlet development liferay maven spring mvc'/><category term='On demand versus dynamic reloading log4j properties in Tomcat jboss'/><category term='LogMonitor to email log error server tomcat jboss apache'/><category term='android rss google reader notifier rnotifier'/><category term='ACL based security in JPA with jpasecurity spring'/><category term='engine'/><category term='Microsoft Exchange Online EWS API'/><category term='GAE'/><category term='context root tomcat'/><category term='talend etl tutorial'/><category term='Batch SQL  Spring JDBCTemplate Script'/><category term='switchuser impersonate doasuser spring ldap'/><category term='maven mavenize mavenizing ant java project'/><category term='Agile troubleshooting: LoggingFilter Requests logged test case recreated log traces'/><category term='application'/><category term='Rendering CSV CSVVIEW ERT BHUB Spring'/><category term='SFTP with OpenSSH User Setup made easy'/><category term='custom'/><category term='Pdf extraction: Split by bookmarks and merge back'/><category term='Log Inspector production server log tail less developer access'/><category term='couchdb tutorial install query view map reduce'/><category term='Google Reader Notifier for Android'/><category term='hot deploy eclipse netbeans java'/><category term='domain'/><category term='audit entity java envers jpa hibernate'/><category term='Practicing Business Driven Development BDD Real Time Database Documentation'/><category term='SFTP OpenSSH RSSH scponly'/><category term='Rendering Excel ExcelView ERT BHUB Spring POI'/><category term='Current page or resource in J2EE servlet application tomcat jboss glassfish java'/><category term='tomcat Memory leaks: OutOfMemoryError: PermGen space java jvisualvm'/><category term='java front end designer markup separation of concerns semantic web'/><category term='google'/><category term='Eclipse Netbeans Agile JSP development deployment eclipse'/><category term='sqlite database locked spring jdbctemplate'/><title type='text'>Thinking In Software</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default?start-index=101&amp;max-results=100'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>244</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-37642571.post-4135837461496643768</id><published>2012-01-25T14:26:00.000-08:00</published><updated>2012-01-25T14:26:55.278-08:00</updated><title type='text'>Portable PGP: Encryption with Bouncy Castle</title><content type='html'>I came across the &lt;a href="http://sourceforge.net/projects/ppgp/"&gt;PPGP&lt;/a&gt; project Today and I thought about sharing it not only because it is a nice tool that runs in any OS (Java) which allows to encrypt/decrypt files and text using PGP, sign the result, generate keys but also because being Open Source Java Project the whole source code is available so you could use proven code that actually works to perform PGP operations in your own project.&lt;br /&gt;&lt;br /&gt;Below is simple code almost entirely extracted from this project that just encrypts a file with a PGP public key.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: java"&gt;package com.nestorurquiza.utils;&lt;br /&gt;&lt;br /&gt;import java.io.FileInputStream;&lt;br /&gt;import java.io.FileOutputStream;&lt;br /&gt;import java.io.InputStream;&lt;br /&gt;import java.io.OutputStream;&lt;br /&gt;import java.security.SecureRandom;&lt;br /&gt;import java.security.Security;&lt;br /&gt;import java.util.Date;&lt;br /&gt;import java.util.Iterator;&lt;br /&gt;&lt;br /&gt;import org.bouncycastle.bcpg.ArmoredOutputStream;&lt;br /&gt;import org.bouncycastle.jce.provider.BouncyCastleProvider;&lt;br /&gt;import org.bouncycastle.openpgp.PGPCompressedData;&lt;br /&gt;import org.bouncycastle.openpgp.PGPCompressedDataGenerator;&lt;br /&gt;import org.bouncycastle.openpgp.PGPEncryptedData;&lt;br /&gt;import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;&lt;br /&gt;import org.bouncycastle.openpgp.PGPLiteralData;&lt;br /&gt;import org.bouncycastle.openpgp.PGPLiteralDataGenerator;&lt;br /&gt;import org.bouncycastle.openpgp.PGPPublicKey;&lt;br /&gt;import org.bouncycastle.openpgp.PGPPublicKeyRing;&lt;br /&gt;import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;&lt;br /&gt;import org.bouncycastle.openpgp.PGPUtil;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * &lt;br /&gt; * Code in part extracted from http://sourceforge.net/projects/ppgp/ &lt;br /&gt; * A nice GUI to generate your own keys, encrypt and sign documents using PGP&lt;br /&gt; * &lt;br /&gt; * @author nestor&lt;br /&gt; *&lt;br /&gt; */&lt;br /&gt;public class PgpUtils {&lt;br /&gt;    private final static int BUFFER_SIZE = 1 &lt;&lt; 16;&lt;br /&gt;&lt;br /&gt;    private static PGPPublicKey readPublicKeyFromCollection2(InputStream in) throws Exception {&lt;br /&gt;        in = PGPUtil.getDecoderStream(in);&lt;br /&gt;        PGPPublicKeyRing pkRing = null;&lt;br /&gt;        PGPPublicKeyRingCollection pkCol = new PGPPublicKeyRingCollection(in);&lt;br /&gt;        Iterator it = pkCol.getKeyRings();&lt;br /&gt;        while (it.hasNext()) {&lt;br /&gt;             pkRing = (PGPPublicKeyRing) it.next();&lt;br /&gt;             Iterator pkIt = pkRing.getPublicKeys();&lt;br /&gt;             while (pkIt.hasNext()) {&lt;br /&gt;                     PGPPublicKey key = (PGPPublicKey) pkIt.next();&lt;br /&gt;                     if (key.isEncryptionKey())&lt;br /&gt;                             return key;&lt;br /&gt;             }&lt;br /&gt;        }&lt;br /&gt;        return null;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Encrypts a file using a public key&lt;br /&gt;     * &lt;br /&gt;     * @param decryptedFilePath&lt;br /&gt;     * @param encryptedFilePath&lt;br /&gt;     * @param encKeyPath&lt;br /&gt;     * @param armor&lt;br /&gt;     * @param withIntegrityCheck&lt;br /&gt;     * @throws Exception&lt;br /&gt;     */&lt;br /&gt;    public static void encryptFile(String decryptedFilePath,&lt;br /&gt;            String encryptedFilePath,&lt;br /&gt;            String encKeyPath,&lt;br /&gt;            boolean armor,&lt;br /&gt;            boolean withIntegrityCheck)            &lt;br /&gt;            throws Exception{&lt;br /&gt;&lt;br /&gt;        OutputStream out = new FileOutputStream(encryptedFilePath);&lt;br /&gt;        FileInputStream pubKey = new FileInputStream(encKeyPath);&lt;br /&gt;        PGPPublicKey encKey = readPublicKeyFromCollection2(pubKey);&lt;br /&gt;        Security.addProvider(new BouncyCastleProvider());&lt;br /&gt;        &lt;br /&gt;        if (armor) &lt;br /&gt;            out = new ArmoredOutputStream(out);&lt;br /&gt;            &lt;br /&gt;        // Init encrypted data generator&lt;br /&gt;        PGPEncryptedDataGenerator encryptedDataGenerator =&lt;br /&gt;                new PGPEncryptedDataGenerator(PGPEncryptedData.CAST5, withIntegrityCheck, new SecureRandom(),"BC");&lt;br /&gt;        &lt;br /&gt;        encryptedDataGenerator.addMethod(encKey);&lt;br /&gt;        &lt;br /&gt;        &lt;br /&gt;        OutputStream encryptedOut = encryptedDataGenerator.open(out, new byte[BUFFER_SIZE]);&lt;br /&gt;&lt;br /&gt;        // Init compression  &lt;br /&gt;        PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);&lt;br /&gt;        OutputStream compressedOut = compressedDataGenerator.open(encryptedOut);  &lt;br /&gt;&lt;br /&gt;        PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator();&lt;br /&gt;        OutputStream literalOut = literalDataGenerator.open(compressedOut, PGPLiteralData.BINARY, decryptedFilePath, new Date(), new byte[BUFFER_SIZE]);&lt;br /&gt;        FileInputStream inputFileStream = new FileInputStream(decryptedFilePath);&lt;br /&gt;        byte[] buf = new byte[BUFFER_SIZE];  &lt;br /&gt;        int len;&lt;br /&gt;        while((len = inputFileStream.read(buf))&gt;0){&lt;br /&gt;            literalOut.write(buf,0,len);&lt;br /&gt;        }&lt;br /&gt;         &lt;br /&gt;        literalOut.close();&lt;br /&gt;        literalDataGenerator.close();&lt;br /&gt;        &lt;br /&gt;        compressedOut.close();&lt;br /&gt;        compressedDataGenerator.close();&lt;br /&gt;        encryptedOut.close();&lt;br /&gt;        encryptedDataGenerator.close();&lt;br /&gt;        inputFileStream.close();&lt;br /&gt;        out.close();&lt;br /&gt;        &lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The Unit test showing how to use it:&lt;br /&gt;&lt;pre class="brush: java"&gt;package com.nestorurquiza.utils;&lt;br /&gt;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;&lt;br /&gt;import org.junit.Test;&lt;br /&gt;import org.xml.sax.SAXException;&lt;br /&gt;&lt;br /&gt;public class PgpUtilsTest {&lt;br /&gt;    @Ignore&lt;br /&gt;    @Test    &lt;br /&gt;    public void encryptTest() throws IOException, Exception, SAXException {&lt;br /&gt;        String publicKeyFilePath = "/Users/nestor/Downloads/somePublicKey.pgp";&lt;br /&gt;        String decryptedFilePath = "/Users/nestor/Downloads/someFile.csv";&lt;br /&gt;        String encryptedFilePath = "/Users/nestor/Downloads/someFile.csv.enc";&lt;br /&gt;        &lt;br /&gt;        PgpUtils.encryptFile(decryptedFilePath, encryptedFilePath, publicKeyFilePath, false, true);&lt;br /&gt;        &lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-4135837461496643768?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/4135837461496643768/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=4135837461496643768' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/4135837461496643768'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/4135837461496643768'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2012/01/portable-pgp-encryption-with-bouncy.html' title='Portable PGP: Encryption with Bouncy Castle'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-8866853538183225322</id><published>2012-01-24T17:52:00.000-08:00</published><updated>2012-01-25T10:34:02.998-08:00</updated><title type='text'>Commons VFS: SFTP from Java the simple way</title><content type='html'>I have always used jsch library to SCP (or what is commonly known as SFTP) files in remote servers from Java.&lt;br /&gt;&lt;br /&gt;I was tempted to use my old code when I decide to look around for something cleaner just to came across this &lt;a href="http://www.memorylack.com/2011/06/apache-commons-vfs-for-sftp.html"&gt;great post&lt;/a&gt; about sftp using Apache VFS. I made few changes to be able to sftp any input stream as a file to a remote SFTP site which I am sharing below.&lt;br /&gt;&lt;br /&gt;Dependencies:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;dependency&gt;&lt;br /&gt;                &lt;groupid&gt;org.apache.commons&lt;/groupId&gt;&lt;br /&gt;                &lt;artifactid&gt;commons-vfs2&lt;/artifactId&gt;&lt;br /&gt;                &lt;version&gt;2.0&lt;/version&gt;&lt;br /&gt;            &lt;/dependency&gt;&lt;br /&gt;            &lt;dependency&gt;&lt;br /&gt;                &lt;groupid&gt;com.jcraft&lt;/groupId&gt;&lt;br /&gt;                &lt;artifactid&gt;jsch&lt;/artifactId&gt;&lt;br /&gt;                &lt;version&gt;0.1.45&lt;/version&gt;&lt;br /&gt;            &lt;/dependency&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The upload:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: java"&gt;package com.nestorurquiza.utils;&lt;br /&gt;&lt;br /&gt;package com.nestorurquiza.utils;&lt;br /&gt;&lt;br /&gt;import java.io.InputStream;&lt;br /&gt;import java.io.OutputStream;&lt;br /&gt;&lt;br /&gt;import org.apache.commons.io.IOUtils;&lt;br /&gt;import org.apache.commons.vfs2.FileObject;&lt;br /&gt;import org.apache.commons.vfs2.FileSystemException;&lt;br /&gt;import org.apache.commons.vfs2.FileSystemOptions;&lt;br /&gt;import org.apache.commons.vfs2.Selectors;&lt;br /&gt;import org.apache.commons.vfs2.impl.StandardFileSystemManager;&lt;br /&gt;import org.apache.commons.vfs2.provider.sftp.SftpFileSystemConfigBuilder;&lt;br /&gt;import org.slf4j.Logger;&lt;br /&gt;import org.slf4j.LoggerFactory;&lt;br /&gt;/**&lt;br /&gt; * All credits for http://www.memorylack.com/2011/06/apache-commons-vfs-for-sftp.html&lt;br /&gt; * &lt;br /&gt; * nestoru - 2012/01/24: Small changes:&lt;br /&gt; *   1. Using logging instead of System.out.println()&lt;br /&gt; *   2. The initial directory is not the user home directory &lt;br /&gt; *   3. No additional slashes when adding remoteFilePath.&lt;br /&gt; *   4. Using InputStream instead of path to a local file&lt;br /&gt; * &lt;br /&gt; * If you need extra functionality check out the url above&lt;br /&gt; * &lt;br /&gt; * &lt;br /&gt; *&lt;br /&gt; */&lt;br /&gt;public class SftpUtils {&lt;br /&gt;    private final static Logger log = LoggerFactory.getLogger(SftpUtils.class);&lt;br /&gt;    &lt;br /&gt;    public static void upload(String hostName, String username,&lt;br /&gt;            String password, InputStream localInputStream, String localInputStreamName, String remoteFilePath) {&lt;br /&gt;        &lt;br /&gt;        StandardFileSystemManager manager = new StandardFileSystemManager();&lt;br /&gt;&lt;br /&gt;        try {&lt;br /&gt;            manager.init();&lt;br /&gt;&lt;br /&gt;            FileSystemOptions fileSystemOptions = createDefaultOptions();&lt;br /&gt;            &lt;br /&gt;            // Create local file object&lt;br /&gt;            //FileObject localFile = manager.resolveFile(f.getAbsolutePath());&lt;br /&gt;            FileObject localFile = manager.resolveFile("ram://path/needed/" + localInputStreamName);&lt;br /&gt;            localFile.createFile();&lt;br /&gt;            OutputStream localOutputStream = localFile.getContent().getOutputStream();&lt;br /&gt;            IOUtils.copy(localInputStream, localOutputStream);&lt;br /&gt;            localOutputStream.flush();&lt;br /&gt;            &lt;br /&gt;            // Create remote file object&lt;br /&gt;            FileObject remoteFile = manager.resolveFile(&lt;br /&gt;                    createConnectionString(hostName, username, password,&lt;br /&gt;                            remoteFilePath), fileSystemOptions);&lt;br /&gt;&lt;br /&gt;            // Copy local file to sftp server&lt;br /&gt;            remoteFile.copyFrom(localFile, Selectors.SELECT_SELF);&lt;br /&gt;&lt;br /&gt;            log.debug("File upload success");&lt;br /&gt;        } catch (Exception e) {&lt;br /&gt;            throw new RuntimeException(e);&lt;br /&gt;        } finally {&lt;br /&gt;            manager.close();&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    public static String createConnectionString(String hostName,&lt;br /&gt;            String username, String password, String remoteFilePath) {&lt;br /&gt;        // result: "sftp://user:123456@domainname.com/resume.pdf&lt;br /&gt;        return "sftp://" + username + ":" + password + "@" + hostName&lt;br /&gt;                + remoteFilePath;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public static FileSystemOptions createDefaultOptions() throws FileSystemException {&lt;br /&gt;        // Create SFTP options&lt;br /&gt;        FileSystemOptions opts = new FileSystemOptions();&lt;br /&gt;        &lt;br /&gt;        // SSH Key checking&lt;br /&gt;        SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(&lt;br /&gt;                opts, "no");&lt;br /&gt;        &lt;br /&gt;        // Root directory set to user home&lt;br /&gt;        SftpFileSystemConfigBuilder.getInstance().setUserDirIsRoot(opts, false);&lt;br /&gt;        &lt;br /&gt;        // Timeout is count by Milliseconds&lt;br /&gt;        SftpFileSystemConfigBuilder.getInstance().setTimeout(opts, 10000);&lt;br /&gt;        &lt;br /&gt;        return opts;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Then a quick unit test that I comment out Just because I am not interested really in regression test but rather just testing if the method works as expected (I do not like main() methods inside the classes)&lt;br /&gt;&lt;pre class="brush: bash"&gt;package com.nestorurquiza.utils;&lt;br /&gt;&lt;br /&gt;import java.io.File;&lt;br /&gt;import java.io.FileInputStream;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;&lt;br /&gt;import org.junit.Test;&lt;br /&gt;import org.xml.sax.SAXException;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;public class SftpUtilsTest {&lt;br /&gt;    //@Ignore&lt;br /&gt;    @Test    &lt;br /&gt;    public void uploadTest() throws IOException, Exception, SAXException {&lt;br /&gt;        String hostName = "bhubint.nestorurquiza.com";&lt;br /&gt;        String userame = "user";&lt;br /&gt;        String password = "pass";&lt;br /&gt;        String localFilePath = "/tmp/test.txt";&lt;br /&gt;        String remoteFilePath = "/home/report/report/test.txt";&lt;br /&gt;        //SftpUtils.upload(hostName, userame, password, localFilePath, remoteFilePath);&lt;br /&gt;        File f = new File(localFilePath);&lt;br /&gt;        if (!f.exists())&lt;br /&gt;            throw new RuntimeException("Error. Local file not found");&lt;br /&gt;        FileInputStream fileInputStream = new FileInputStream(f);&lt;br /&gt;        SftpUtils.upload(hostName, userame, password, fileInputStream, "test.txt", remoteFilePath);&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-8866853538183225322?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/8866853538183225322/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=8866853538183225322' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8866853538183225322'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8866853538183225322'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2012/01/commons-vfs-sftp-from-java-simple-way.html' title='Commons VFS: SFTP from Java the simple way'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-862976821923864447</id><published>2012-01-24T10:11:00.000-08:00</published><updated>2012-01-24T10:13:10.943-08:00</updated><title type='text'>maven downloads the pom.xml but not the jar file</title><content type='html'>Today I spent sometime troubleshooting one particular library I uploaded to Artifactory and which was not downloaded by Maven.&lt;br /&gt;&lt;br /&gt;Maven was downloading just the pom file instead of downloading the jar file.&lt;br /&gt;&lt;br /&gt;I had to reupload the jar and while doing it edit the pom file to include the pacjaging=jar&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;packaging&gt;jar&lt;/packaging&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-862976821923864447?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/862976821923864447/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=862976821923864447' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/862976821923864447'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/862976821923864447'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2012/01/maven-downloads-pomxml-but-not-jar-file.html' title='maven downloads the pom.xml but not the jar file'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-3436629881995151736</id><published>2012-01-17T19:25:00.000-08:00</published><updated>2012-01-17T19:25:04.314-08:00</updated><title type='text'>Complex development environments with VirtualBox</title><content type='html'>Having a local environment independent of any other existing environments has a big advantage: Development is not stopped when other environments are down and it even stops the "need" to hit production just because it is the only environment currently available. But perhaps the biggest advantage of all is to be able to work literally anywhere even if you have no internet connection at all.&lt;br /&gt;&lt;br /&gt;Virtualization can help with this and here is how to host your whole environment locally in your machine using VirtualBox.&lt;br /&gt;&lt;br /&gt;In an Enterprise you will find Solaris, Windows, OSX, Unix and what not. I personally use a Mac Book Pro with VirtualBox. In VirtualBox I can run as many VMs as I need to recreate the surrounded environment.&lt;br /&gt;&lt;br /&gt;Ideally we should have an environment following the next features:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;The guest should have connectivity to internet taking advantage of your network configuration in the host&lt;/li&gt;&lt;li&gt;The guest should communicate to the host even if no internet connection is available&lt;/li&gt;&lt;li&gt;The guest should automatically work independent what network you are using, from work, from home or even on the road with no local network at all&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Virtual box will allow these features if we just add a first network interface as NAT (for internet) and the second as host-only (for connectivity with the box)&lt;br /&gt;&lt;br /&gt;We will show this process using a virtualized instance of an Advent Geneva Server (A Solaris Box with some proprietary Accounting Software). It is useful to have this server locally to be able to mess with RSL reports for example without affecting other members of the dev or operations team. &lt;br /&gt; &lt;br /&gt;In the host find the vboxnet0 interface to be sure you use an IP in the same network later on in the guest:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ ifconfig -a&lt;br /&gt;...&lt;br /&gt;vboxnet0: flags=8843&lt;UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST&gt; mtu 1500&lt;br /&gt; ether 0a:00:27:00:00:00 &lt;br /&gt; inet 192.168.56.1 netmask 0xffffff00 broadcast 192.168.56.255&lt;br /&gt;... &lt;br /&gt;&lt;/pre&gt; &lt;br /&gt;In the Solaris guest ...&lt;br /&gt;&lt;br /&gt;Configure first interface (NAT) to use DHCP &lt;br /&gt;&lt;pre class="brush: bash"&gt;$ touch /etc/dhcp.e1000g0&lt;br /&gt;$ rm /etc/hostname.e1000g0&lt;br /&gt;$ touch /etc/hostname.e1000g0&lt;br /&gt;$ ifconfig e1000g0 plumb&lt;br /&gt;$ ifconfig e1000g0 up&lt;br /&gt;$ ifconfig e1000g0 dhcp start&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And second interface (host-only) to use a static IP&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ vi /etc/hosts&lt;br /&gt;...&lt;br /&gt;192.168.56.2 genevadev loghost&lt;br /&gt;...&lt;br /&gt;$ rm /etc/dhcp.e1000g1&lt;br /&gt;$ vi /etc/hostname.e1000g0&lt;br /&gt;192.168.56.2&lt;br /&gt;$ ifconfig e1000g1 plumb&lt;br /&gt;$ ifconfig e1000g1 genevadev up&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Be sure to define DNS available from any network to connect to the internet (only to be used in the case you do need internet in the guest) If you really need to access local resources you might want to add local DNS as well.&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ vi /etc/resolv.conf&lt;br /&gt;nameserver 72.45.32.34&lt;br /&gt;nameserver 72.45.32.37&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Make sure you see both interfaces (the NAT and host-only). They should show up as e1000g0 and e1000g1. The first with an internal 10.0.xxx.xxx IP and the second with the static IP you picked (for this example 192.168.56.2)&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ ifconfig -a&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Clearly the static IP must be in the same subnetwork (192.168.56.xxx).&lt;br /&gt;&lt;br /&gt;You should be able to connect to the internet from the guest and at the same time ssh into it using its 192.168.56.2 IP. Do not forget to add in your host the new created server resolution:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ vi /etc/hosts&lt;br /&gt;genevadev 192.168.56.2&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The question about sharing this environment or building it from scratch is one interesting topic. An even more interesting is left as homework and it deals with &lt;a href="http://en.wikipedia.org/wiki/DevOps"&gt;devops&lt;/a&gt;. In reality regardless the path you follow you should automate your actions and there is where things like http://vagrantup.com/ and puppet/chef can help you further.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-3436629881995151736?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/3436629881995151736/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=3436629881995151736' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/3436629881995151736'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/3436629881995151736'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2012/01/complex-development-environments-with.html' title='Complex development environments with VirtualBox'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-6346713694368115991</id><published>2012-01-12T08:16:00.000-08:00</published><updated>2012-01-12T08:16:44.174-08:00</updated><title type='text'>Remove duplicate classes the agile way: Maven Duplicate Finder Plugin</title><content type='html'>Without discussing the alternatives I think a Java project cannot live without checking for dependency conflicts. The conflicts can be buried in external jar files so the check must be performed by fully qualified name on absolutely all classes included in the project.&lt;br /&gt;&lt;br /&gt;Of course such a check should stop the build until a resolution.&lt;br /&gt;&lt;br /&gt;The &lt;a href="https://github.com/ning/maven-duplicate-finder-plugin "&gt;Maven Duplicate Finder Plugin&lt;/a&gt; does exactly that.&lt;br /&gt;&lt;br /&gt;Just clone the repo, build the maven project and upload the plugin jar file to your Maven repository. Then configure your application to use the plugin:&lt;br /&gt;&lt;pre class="brush: xml"&gt;...&lt;br /&gt;   &lt;plugin&gt;&lt;br /&gt;              &lt;groupId&gt;com.ning.maven.plugins&lt;/groupId&gt;&lt;br /&gt;              &lt;artifactId&gt;maven-duplicate-finder-plugin&lt;/artifactId&gt;&lt;br /&gt;              &lt;configuration&gt;&lt;br /&gt;                &lt;failBuildInCaseOfConflict&gt;false&lt;/failBuildInCaseOfConflict&gt;&lt;br /&gt;              &lt;/configuration&gt;&lt;br /&gt;              &lt;executions&gt;&lt;br /&gt;                &lt;execution&gt;&lt;br /&gt;                  &lt;phase&gt;verify&lt;/phase&gt;&lt;br /&gt;                  &lt;goals&gt;&lt;br /&gt;                    &lt;goal&gt;check&lt;/goal&gt;&lt;br /&gt;                  &lt;/goals&gt;&lt;br /&gt;                &lt;/execution&gt;&lt;br /&gt;              &lt;/executions&gt;&lt;br /&gt;            &lt;/plugin&gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If you run it with the failBuildInCaseOfConflict flag turned off as above you can get the messages as WARNING without stopping the project from building.&lt;br /&gt;&lt;pre class="brush: bash"&gt;[INFO] [duplicate-finder:check {execution: default}]&lt;br /&gt;[INFO] Checking compile classpath&lt;br /&gt;[WARNING] Found duplicate classes in [commons-beanutils:commons-beanutils:1.8.3,commons-collections:commons-collections:3.2.1] :&lt;br /&gt;[WARNING]   org.apache.commons.collections.ArrayStack&lt;br /&gt;[WARNING]   org.apache.commons.collections.Buffer&lt;br /&gt;[WARNING]   org.apache.commons.collections.BufferUnderflowException&lt;br /&gt;[WARNING]   org.apache.commons.collections.FastHashMap&lt;br /&gt;[WARNING] Found duplicate classes in [net.objectlab.kit:datecalc-common:1.2.0,net.objectlab.kit:datecalc-jdk:1.2.0] :&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.AbstractDateCalculator&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.AbstractIMMDateCalculator&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.AbstractKitCalculatorsFactory&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.DateCalculator&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.DefaultHolidayCalendar&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.ExcelDateUtil&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.HolidayCalendar&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.HolidayHandler&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.HolidayHandlerType&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.IMMDateCalculator&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.IMMPeriod&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.ImmutableHolidayCalendar&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.KitCalculatorsFactory&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.PeriodCountBasis&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.PeriodCountCalculator&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.StandardTenor&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.Tenor&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.TenorCode&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.Utils&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.WorkingWeek&lt;br /&gt;[WARNING] Found duplicate resources in [net.sf.jasperreports:jasperreports:4.1.3,net.sf.jasperreports:jasperreports-fonts:4.1.3] :&lt;br /&gt;[WARNING]   jasperreports_extension.properties&lt;br /&gt;[WARNING] Found duplicate resources in [org.bouncycastle:bcmail-jdk14:1.38,org.bouncycastle:bcprov-jdk14:1.38,org.bouncycastle:bctsp-jdk14:1.38] :&lt;br /&gt;[WARNING]   META-INF/BCKEY.DSA&lt;br /&gt;[WARNING]   META-INF/BCKEY.SF&lt;br /&gt;[INFO] Checking runtime classpath&lt;br /&gt;[WARNING] Found duplicate classes in [commons-beanutils:commons-beanutils:1.8.3,commons-collections:commons-collections:3.2.1] :&lt;br /&gt;[WARNING]   org.apache.commons.collections.ArrayStack&lt;br /&gt;[WARNING]   org.apache.commons.collections.Buffer&lt;br /&gt;[WARNING]   org.apache.commons.collections.BufferUnderflowException&lt;br /&gt;[WARNING]   org.apache.commons.collections.FastHashMap&lt;br /&gt;[WARNING] Found duplicate classes in [net.objectlab.kit:datecalc-common:1.2.0,net.objectlab.kit:datecalc-jdk:1.2.0] :&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.AbstractDateCalculator&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.AbstractIMMDateCalculator&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.AbstractKitCalculatorsFactory&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.DateCalculator&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.DefaultHolidayCalendar&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.ExcelDateUtil&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.HolidayCalendar&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.HolidayHandler&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.HolidayHandlerType&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.IMMDateCalculator&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.IMMPeriod&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.ImmutableHolidayCalendar&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.KitCalculatorsFactory&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.PeriodCountBasis&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.PeriodCountCalculator&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.StandardTenor&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.Tenor&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.TenorCode&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.Utils&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.WorkingWeek&lt;br /&gt;[WARNING] Found duplicate resources in [net.sf.jasperreports:jasperreports:4.1.3,net.sf.jasperreports:jasperreports-fonts:4.1.3] :&lt;br /&gt;[WARNING]   jasperreports_extension.properties&lt;br /&gt;[WARNING] Found duplicate resources in [org.bouncycastle:bcmail-jdk14:1.38,org.bouncycastle:bcprov-jdk14:1.38,org.bouncycastle:bctsp-jdk14:1.38] :&lt;br /&gt;[WARNING]   META-INF/BCKEY.DSA&lt;br /&gt;[WARNING]   META-INF/BCKEY.SF&lt;br /&gt;[INFO] Checking test classpath&lt;br /&gt;[WARNING] Found duplicate classes in [commons-beanutils:commons-beanutils:1.8.3,commons-collections:commons-collections:3.2.1] :&lt;br /&gt;[WARNING]   org.apache.commons.collections.ArrayStack&lt;br /&gt;[WARNING]   org.apache.commons.collections.Buffer&lt;br /&gt;[WARNING]   org.apache.commons.collections.BufferUnderflowException&lt;br /&gt;[WARNING]   org.apache.commons.collections.FastHashMap&lt;br /&gt;[WARNING] Found duplicate classes in [net.objectlab.kit:datecalc-common:1.2.0,net.objectlab.kit:datecalc-jdk:1.2.0] :&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.AbstractDateCalculator&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.AbstractIMMDateCalculator&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.AbstractKitCalculatorsFactory&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.DateCalculator&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.DefaultHolidayCalendar&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.ExcelDateUtil&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.HolidayCalendar&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.HolidayHandler&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.HolidayHandlerType&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.IMMDateCalculator&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.IMMPeriod&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.ImmutableHolidayCalendar&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.KitCalculatorsFactory&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.PeriodCountBasis&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.PeriodCountCalculator&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.StandardTenor&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.Tenor&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.TenorCode&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.Utils&lt;br /&gt;[WARNING]   net.objectlab.kit.datecalc.common.WorkingWeek&lt;br /&gt;[WARNING] Found duplicate resources in [net.sf.jasperreports:jasperreports:4.1.3,net.sf.jasperreports:jasperreports-fonts:4.1.3] :&lt;br /&gt;[WARNING]   jasperreports_extension.properties&lt;br /&gt;[WARNING] Found duplicate resources in [org.bouncycastle:bcmail-jdk14:1.38,org.bouncycastle:bcprov-jdk14:1.38,org.bouncycastle:bctsp-jdk14:1.38] :&lt;br /&gt;[WARNING]   META-INF/BCKEY.DSA&lt;br /&gt;[WARNING]   META-INF/BCKEY.SF&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The above is the result of a project that I cleaned up so actually the errors you are seeing are the result of "accepted" conflicts. Since we are aware of them then we proceed (at our own risk) to exclude them from being checked as exceptions while setting failBuildInCaseOfConflict to true. That way no developer will be able to include a conflicting dependency without resolving it first.&lt;br /&gt;&lt;pre class="brush: xml"&gt;...&lt;br /&gt;  &lt;plugin&gt;&lt;br /&gt;              &lt;groupId&gt;com.ning.maven.plugins&lt;/groupId&gt;&lt;br /&gt;              &lt;artifactId&gt;maven-duplicate-finder-plugin&lt;/artifactId&gt;&lt;br /&gt;              &lt;configuration&gt;&lt;br /&gt;                &lt;failBuildInCaseOfConflict&gt;true&lt;/failBuildInCaseOfConflict&gt;&lt;br /&gt;                &lt;ignoredResources&gt;&lt;br /&gt;                  &lt;ignoredResource&gt;META-INF.*&lt;/ignoredResource&gt;&lt;br /&gt;                &lt;/ignoredResources&gt;&lt;br /&gt;                &lt;exceptions&gt;&lt;br /&gt;                  &lt;exception&gt;&lt;br /&gt;                    &lt;conflictingDependencies&gt;&lt;br /&gt;                      &lt;dependency&gt;&lt;br /&gt;                        &lt;groupId&gt;commons-beanutils&lt;/groupId&gt;&lt;br /&gt;                        &lt;artifactId&gt;commons-beanutils&lt;/artifactId&gt;&lt;br /&gt;                      &lt;/dependency&gt;&lt;br /&gt;                      &lt;dependency&gt;&lt;br /&gt;                        &lt;groupId&gt;commons-collections&lt;/groupId&gt;&lt;br /&gt;                        &lt;artifactId&gt;commons-collections&lt;/artifactId&gt;&lt;br /&gt;                      &lt;/dependency&gt;&lt;br /&gt;                    &lt;/conflictingDependencies&gt;&lt;br /&gt;                    &lt;packages&gt;&lt;br /&gt;                      &lt;package&gt;org.apache.commons.collections&lt;/package&gt;&lt;br /&gt;                    &lt;/packages&gt;&lt;br /&gt;                  &lt;/exception&gt;&lt;br /&gt;                  &lt;exception&gt;&lt;br /&gt;                    &lt;conflictingDependencies&gt;&lt;br /&gt;                      &lt;dependency&gt;&lt;br /&gt;                        &lt;groupId&gt;net.objectlab.kit&lt;/groupId&gt;&lt;br /&gt;                        &lt;artifactId&gt;datecalc-common&lt;/artifactId&gt;&lt;br /&gt;                      &lt;/dependency&gt;&lt;br /&gt;                      &lt;dependency&gt;&lt;br /&gt;                        &lt;groupId&gt;net.objectlab.kit&lt;/groupId&gt;&lt;br /&gt;                        &lt;artifactId&gt;datecalc-jdk&lt;/artifactId&gt;&lt;br /&gt;                      &lt;/dependency&gt;&lt;br /&gt;                    &lt;/conflictingDependencies&gt;&lt;br /&gt;                    &lt;packages&gt;&lt;br /&gt;                      &lt;package&gt;net.objectlab.kit.datecalc.common&lt;/package&gt;&lt;br /&gt;                    &lt;/packages&gt;&lt;br /&gt;                  &lt;/exception&gt;&lt;br /&gt;                  &lt;exception&gt;&lt;br /&gt;                    &lt;conflictingDependencies&gt;&lt;br /&gt;                      &lt;dependency&gt;&lt;br /&gt;                        &lt;groupId&gt;net.sf.jasperreports&lt;/groupId&gt;&lt;br /&gt;                        &lt;artifactId&gt;jasperreports&lt;/artifactId&gt;&lt;br /&gt;                      &lt;/dependency&gt;&lt;br /&gt;                      &lt;dependency&gt;&lt;br /&gt;                        &lt;groupId&gt;net.sf.jasperreports&lt;/groupId&gt;&lt;br /&gt;                        &lt;artifactId&gt;jasperreports-fonts&lt;/artifactId&gt;&lt;br /&gt;                      &lt;/dependency&gt;&lt;br /&gt;                    &lt;/conflictingDependencies&gt;&lt;br /&gt;                    &lt;resources&gt;&lt;br /&gt;                      &lt;resource&gt;jasperreports_extension.properties&lt;/resource&gt;&lt;br /&gt;                    &lt;/resources&gt;&lt;br /&gt;                  &lt;/exception&gt;&lt;br /&gt;                &lt;/exceptions&gt;&lt;br /&gt;              &lt;/configuration&gt;&lt;br /&gt;              &lt;executions&gt;&lt;br /&gt;                &lt;execution&gt;&lt;br /&gt;                  &lt;phase&gt;verify&lt;/phase&gt;&lt;br /&gt;                  &lt;goals&gt;&lt;br /&gt;                    &lt;goal&gt;check&lt;/goal&gt;&lt;br /&gt;                  &lt;/goals&gt;&lt;br /&gt;                &lt;/execution&gt;&lt;br /&gt;              &lt;/executions&gt;&lt;br /&gt;     &lt;/plugin&gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Note how I have globally ignored any resource inside META-INF and I have ignored the properties file explicitly from the artifacts where they were found.&lt;br /&gt;&lt;br /&gt;Also I have not used the classes node which would allow me to specify the individual classes that I approve to ignore. This can be bad but I feel lazy today ;-)&lt;br /&gt;&lt;br /&gt;Finally I did not specify the version number for the conflicting dependencies. This can be bad as well.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-6346713694368115991?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/6346713694368115991/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=6346713694368115991' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6346713694368115991'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6346713694368115991'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2012/01/remove-duplicate-classes-agile-way.html' title='Remove duplicate classes the agile way: Maven Duplicate Finder Plugin'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-9101584670378624802</id><published>2012-01-12T06:39:00.000-08:00</published><updated>2012-01-12T06:39:25.360-08:00</updated><title type='text'>Forgot Password with LDAP, CAS and Spring: Credentials expired</title><content type='html'>I have already documented the steps I followed to &lt;a href="http://thinkinginsoftware.blogspot.com/2012/01/sso-with-ldap-spring-and-cas.html"&gt;integrate existing Spring applications that happen to be using LDAP with CAS&lt;/a&gt; including how to support "Forgot Password" functionality. &lt;br /&gt;&lt;br /&gt;Some CAS client applications are currently using certain complicated logic to handle Terms and Conditions, Forgot Password and other features through custom filters and they were failing with a 401 ERROR "User credentials have expired". Basically the user hits the Forgot Password link in CAS which shows a CAS Client page where the email is given and after submission the temporary password is mailed. Any attempt to move to any page from that moment on results in a 401.&lt;br /&gt;&lt;br /&gt;The problem here is that the default implementation when using Spring CAS client is to test the forcePasswordChange LDAP attribute and it currently throws an Exception rather than easily allowing to hook into the current implementation let us say with a property like "IGNORE_CREDENTIALS_EXPIRATION". As the project is already handling password expiration it does not need that extra check from Spring CAS Client so after some research I had to hack into Spring code.&lt;br /&gt;&lt;br /&gt;I found that a possible solution is to use a CustomCasAuthenticationProvider to override CasAuthenticationProvider#authenticateNow() however I had to copy the whole code from the latter because of scope problems. Basically the original class is not designed to be inherited (at least for this feature). Below is the only method I changed in the class:&lt;br /&gt;&lt;pre class="brush: java"&gt;    private CasAuthenticationToken authenticateNow(final Authentication authentication) throws AuthenticationException {&lt;br /&gt;        try {&lt;br /&gt;            final Assertion assertion = this.ticketValidator.validate(authentication.getCredentials().toString(), serviceProperties.getService());&lt;br /&gt;            final UserDetails userDetails = loadUserByAssertion(assertion);&lt;br /&gt;            try {&lt;br /&gt;                userDetailsChecker.check(userDetails);&lt;br /&gt;            } catch (CredentialsExpiredException e) {&lt;br /&gt;                if (log.isDebugEnabled()) {&lt;br /&gt;                    log.debug("Credentials expired but we handle that case outside of the CAS logic.");&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            return new CasAuthenticationToken(this.key, userDetails, authentication.getCredentials(), userDetails.getAuthorities(), userDetails, assertion);&lt;br /&gt;        } catch (final TicketValidationException e) {&lt;br /&gt;            throw new BadCredentialsException(e.getMessage(), e);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-9101584670378624802?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/9101584670378624802/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=9101584670378624802' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/9101584670378624802'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/9101584670378624802'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2012/01/forgot-password-with-ldap-cas-and.html' title='Forgot Password with LDAP, CAS and Spring: Credentials expired'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-3629782297391170074</id><published>2012-01-09T18:51:00.000-08:00</published><updated>2012-01-12T06:10:27.397-08:00</updated><title type='text'>SSO with LDAP Spring and CAS</title><content type='html'>This is a step by step documentation on how to setup a CAS Server and use Single Sign On (SSO) plus Single Sign Off from Spring client applications using LDAP to store authentication and authorization information. CAS support can be enabled or disabled commenting sections of the code.&lt;br /&gt;&lt;br /&gt;Our Use Case for this exercise is to have a centralized server that handles user login and logout operations allowing the user to sign in just once and enjoy multiple applications thanks to that centralization. &lt;br /&gt;&lt;br /&gt;We then achieve Federation because thanks to SSO via CAS we obtain Authentication while we pull the attributes from LDAP to achieve Authorization. At the end the user is trusted after the first login and the attributes can be obtained from any application.&lt;br /&gt;&lt;br /&gt;Before we review Server and Client configuration be sure you understand CAS needs SSL so in order to correctly follow this post you will need to have the CAS Server and two Spring client applications serving on https. SSL needs an IP per domain and if you are running OSX by default you have only one loopback IP (127.0.0.1). This means you will need to add other two:&lt;br /&gt;&lt;pre class="brush: bash"&gt;sudo ifconfig lo0 alias 127.0.0.2 up&lt;br /&gt;sudo ifconfig lo0 alias 127.0.0.3 up&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then set in /etc/hosts a domain per loopback IP:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ vi /etc/hosts&lt;br /&gt;127.0.0.2   bhubdev.nestorurquiza.com&lt;br /&gt;127.0.0.2   portaldev.nestorurquiza.com&lt;br /&gt;127.0.0.3   casdev.nestorurquiza.com&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You should be able to get the certificate from each URL and assert they are correct, for example:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ echo | openssl s_client -connect bhubdev.nestorurquiza.com:443 2&gt;/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' &gt; ~/Downloads/bhubdev.nestorurquiza.com.cer&lt;br /&gt;$ echo | openssl s_client -connect portaldev.nestorurquiza.com:443 2&gt;/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' &gt; ~/Downloads/portaldev.nestorurquiza.com.cer&lt;br /&gt;$ echo | openssl s_client -connect casdev.nestorurquiza.com:443 2&gt;/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' &gt; ~/Downloads/casldev.nestorurquiza.com.cer&lt;br /&gt;$ openssl x509 -text -in ~/Downloads/bhubdev.nestorurquiza.com.cer&lt;br /&gt;$ openssl x509 -text -in ~/Downloads/portaldev.nestorurquiza.com.cer&lt;br /&gt;$ openssl x509 -text -in ~/Downloads/casdev.nestorurquiza.com.cer&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Once you are sure the certificates coming from the different URLs are correct (they belong to the specific domain) then you should add them to your keystore:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ sudo keytool -delete -alias bhubdev.nestorurquiza.com -keystore  /Library/Java/Home/lib/security/cacerts&lt;br /&gt;$ sudo keytool -delete -alias portaldev.nestorurquiza.com -keystore  /Library/Java/Home/lib/security/cacerts&lt;br /&gt;$ sudo keytool -delete -alias casdev.nestorurquiza.com -keystore  /Library/Java/Home/lib/security/cacerts&lt;br /&gt;$ sudo keytool -import -alias bhubdev.nestorurquiza.com -keystore  /Library/Java/Home/lib/security/cacerts -file ~/Downloads/bhubdev.nestorurquiza.com.cer&lt;br /&gt;$ sudo keytool -import -alias portaldev.nestorurquiza.com -keystore  /Library/Java/Home/lib/security/cacerts -file ~/Downloads/portaldev.nestorurquiza.com.cer&lt;br /&gt;$ sudo keytool -import -alias casdev.nestorurquiza.com -keystore  /Library/Java/Home/lib/security/cacerts -file ~/Downloads/casdev.nestorurquiza.com.cer&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Of course after you do all the above tomcat must be restarted and if you use Apache for virtual hosting like I explain below you owe to restart it as well.&lt;br /&gt;&lt;br /&gt;&lt;h1&gt;CAS Server installation and configuration&lt;/h1&gt;&lt;ol&gt;&lt;li&gt;Download CAS Server from http://www.jasig.org/cas and get cas.war deployed in your server. At this point http://localhost:8080/cas should prompt for credentials. I commonly use apache in front of tomcat so below are the steps using virtual hosts as I have documented &lt;a href="http://thinkinginsoftware.blogspot.com/2011/12/remove-context-from-url-from-tomcat.html"&gt;before&lt;/a&gt;:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ mkdir /Users/nestor/Downloads/apache-tomcat-7.0.22/cas&lt;br /&gt;$ mkdir /Users/nestor/Downloads/apache-tomcat-7.0.22/conf/Catalina/casdev.nestorurquiza.com&lt;br /&gt;$ cp /Users/nestor/Downloads/cas-server-3.4.11/modules/cas-server-webapp-3.4.11.war /Users/nestor/Downloads/apache-tomcat-7.0.22/cas/ROOT.war&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Prepare CAS to use LDAP authentication. This is about copying two files to the WEB-INF/lib directory and editing a configuration file in that same directory:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ cp /Users/nestor/Downloads/cas-server-3.4.11/modules/cas-server-support-ldap-3.4.11.jar /Users/nestor/Downloads/apache-tomcat-7.0.22/cas/ROOT/WEB-INF/lib &lt;br /&gt;$ cp /Users/nestor/Downloads/spring-ldap-1.3.0.RELEASE/dist/spring-ldap-1.3.0.RELEASE-all.jar /Users/nestor/Downloads/apache-tomcat-7.0.22/cas/ROOT/WEB-INF/lib&lt;br /&gt;$ vi /Users/nestor/Downloads/apache-tomcat-7.0.22/cas/ROOT/WEB-INF/deployerConfigContext.xml&lt;br /&gt;…&lt;br /&gt; &lt;bean id="authenticationManager"&lt;br /&gt;  class="org.jasig.cas.authentication.AuthenticationManagerImpl"&gt;&lt;br /&gt; ...&lt;br /&gt;  &lt;property name="authenticationHandlers"&gt;&lt;br /&gt;   &lt;list&gt;&lt;br /&gt;    &lt;!--&lt;br /&gt;     | This is the authentication handler that authenticates services by means of callback via SSL, thereby validating&lt;br /&gt;     | a server side SSL certificate.&lt;br /&gt;     +--&gt;&lt;br /&gt;    &lt;bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"&lt;br /&gt;     p:httpClient-ref="httpClient" /&gt;&lt;br /&gt;    &lt;!--&lt;br /&gt;     | This is the authentication handler declaration that every CAS deployer will need to change before deploying CAS &lt;br /&gt;     | into production.  The default SimpleTestUsernamePasswordAuthenticationHandler authenticates UsernamePasswordCredentials&lt;br /&gt;     | where the username equals the password.  You will need to replace this with an AuthenticationHandler that implements your&lt;br /&gt;     | local authentication strategy.  You might accomplish this by coding a new such handler and declaring&lt;br /&gt;     | edu.someschool.its.cas.MySpecialHandler here, or you might use one of the handlers provided in the adaptors modules.&lt;br /&gt;     +--&gt;&lt;br /&gt;    &lt;!--&lt;br /&gt;    &lt;bean&lt;br /&gt;     class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" /&gt;&lt;br /&gt;    --&gt;&lt;br /&gt;    &lt;br /&gt;    &lt;bean class="org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler" &gt;&lt;br /&gt;      &lt;property name="filter" value="mail=%u" /&gt;&lt;br /&gt;      &lt;property name="searchBase" value="ou=people,o=nestorurquiza" /&gt;&lt;br /&gt;      &lt;property name="contextSource" ref="contextSource" /&gt;&lt;br /&gt;    &lt;/bean&gt;&lt;br /&gt;   &lt;/list&gt;&lt;br /&gt;  &lt;/property&gt;&lt;br /&gt;&lt;li&gt;Get spring-security source code&lt;br /&gt;&lt;br /&gt;…&lt;br /&gt;&lt;br /&gt;&lt;bean id="contextSource"&lt;br /&gt;   class="org.springframework.ldap.core.support.LdapContextSource"&gt;&lt;br /&gt;&lt;br /&gt;&lt;property name="pooled" value="true"/&gt;&lt;br /&gt;&lt;br /&gt;&lt;property name="urls"&gt;&lt;br /&gt;&lt;br /&gt;&lt;list&gt;&lt;br /&gt;&lt;br /&gt;&lt;value&gt;ldaps://ldapint.nestorurquiza.com:10636&lt;/value&gt;&lt;br /&gt;&lt;br /&gt;&lt;/list&gt;&lt;br /&gt;&lt;br /&gt;&lt;/property&gt;&lt;br /&gt;&lt;br /&gt;&lt;property name="userDn" value="uid=admin,ou=system"/&gt;&lt;br /&gt;&lt;br /&gt;&lt;property name="password" value="secret"/&gt;&lt;br /&gt;&lt;br /&gt;&lt;property name="baseEnvironmentProperties"&gt;&lt;br /&gt;&lt;br /&gt;&lt;map&gt;&lt;br /&gt;&lt;br /&gt;&lt;entry key="java.naming.security.authentication" value="simple" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/map&gt;&lt;br /&gt;&lt;br /&gt;&lt;/property&gt;&lt;br /&gt;&lt;br /&gt;&lt;/bean&gt;&lt;br /&gt;&lt;br /&gt;…&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;While at this point you should be able to login from the CAS landing page using your LDAP credentials CAS needs to run from an SSL URL in order to be used as Single Sign On (SSO). I use Apache and mod-jk with Tomcat so my SSL configuration is achieved on the Apache side. Here are the steps to generate the self signed certificate and configure Apache virtual host in OSX to serve the CAS service:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ cd ~/Downloads/&lt;br /&gt;$ sudo mkdir /private/etc/apache2/certs/&lt;br /&gt;$ export DOMAIN=casdev.nestorurquiza.com&lt;br /&gt;$ openssl genrsa -des3 -out ${DOMAIN}.key 1024&lt;br /&gt;$ openssl req -new -key ${DOMAIN}.key -out  ${DOMAIN}.csr&lt;br /&gt;$ openssl x509 -req -days 365 -in ${DOMAIN}.csr -signkey ${DOMAIN}.key -out ${DOMAIN}.crt&lt;br /&gt;$ openssl rsa -in ${DOMAIN}.key -out ${DOMAIN}.key&lt;br /&gt;$ sudo cp ${DOMAIN}.key /private/etc/apache2/certs/&lt;br /&gt;$ sudo cp ${DOMAIN}.crt /private/etc/apache2/certs/&lt;br /&gt;$ sudo vi /private/etc/apache2/extra/httpd-vhosts-ssl.conf&lt;br /&gt;Listen 443&lt;br /&gt;AddType application/x-x509-ca-cert .crt&lt;br /&gt;AddType application/x-pkcs7-crl    .crl&lt;br /&gt;SSLPassPhraseDialog  builtin&lt;br /&gt;SSLSessionCache        "shmcb:/private/var/run/ssl_scache(512000)"&lt;br /&gt;SSLSessionCacheTimeout  300&lt;br /&gt;SSLMutex  "file:/private/var/run/ssl_mutex"&lt;br /&gt;&lt;br /&gt;&lt;virtualhost casdev.nestorurquiza.com:443&gt;&lt;br /&gt;DocumentRoot "/usr/docs/casdev.nestorurquiza.com"&lt;br /&gt;ServerName casdev.nestorurquiza.com:443&lt;br /&gt;ServerAdmin nurquiza@nestorurquiza.com&lt;br /&gt;ErrorLog "/private/var/log/apache2/casdev.nestorurquiza.com.log"&lt;br /&gt;TransferLog "/private/var/log/apache2/casdev.nestorurquiza.com.log"&lt;br /&gt;SSLEngine on&lt;br /&gt;SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL&lt;br /&gt;SSLCertificateFile "/private/etc/apache2/certs/casdev.nestorurquiza.com.crt"&lt;br /&gt;SSLCertificateKeyFile "/private/etc/apache2/certs/casdev.nestorurquiza.com.key"&lt;br /&gt;&lt;directory "/usr/docs/casdev.nestorurquiza.com"&gt;&lt;br /&gt;    Options +FollowSymLinks&lt;br /&gt;    AllowOverride All&lt;br /&gt;    Order deny,allow&lt;br /&gt;    Allow from all&lt;br /&gt;&lt;/Directory&gt;&lt;br /&gt;JkMount "/*" nestorurquiza-app1&lt;br /&gt;&lt;/VirtualHost&gt;&lt;br /&gt;$ sudo vi /etc/apache2/httpd.conf&lt;br /&gt;...&lt;br /&gt;LoadModule ssl_module libexec/apache2/mod_ssl.so&lt;br /&gt;...&lt;br /&gt;Include /private/etc/apache2/extra/httpd-vhosts-ssl.conf&lt;br /&gt;…&lt;br /&gt;$ sudo apachectl configtest&lt;br /&gt;$ sudo apachectl graceful&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Logout and login from SSL. Note that you do not get any alerts stating SSO won't work:&lt;br /&gt;&lt;pre class="brush: bash"&gt;https://casdev.nestorurquiza.com/logout&lt;br /&gt;https://casdev.nestorurquiza.com&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Change the look and feel for the CAS login page editing the file /WEB-INF/view/jsp/default/ui/casLogoutView.jsp and dependencies like the logo from /css/cas.css #header background url and #header h1#app-name background color&lt;/li&gt;&lt;li&gt;Further customize the login page for example as I needed to provide a forgot password functionality I came up with the below implementation in casLogoutView.jsp:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;c:if test="${ !empty param.service }"&gt;  &lt;br /&gt;  &lt;div class="row"&gt;&lt;c:set var="schema" value="${fn:substringBefore(param.service, '://')}"/&gt; &lt;br /&gt;   &lt;c:set var="remainingUrl" value="${fn:substringAfter(param.service, '://')}"/&gt; &lt;br /&gt;   &lt;c:set var="domain" value="${fn:substringBefore(remainingUrl, '/')}"/&gt;&lt;br /&gt;                 &amp;lt;a href=&amp;quot;&amp;lt;c:out value=&amp;quot;${schema}://${domain}/forgotPassword&amp;quot;/&amp;gt;&amp;quot;&amp;gt;Forgot Password&amp;lt;/a&amp;gt;&lt;br /&gt;         &lt;/div&gt;&lt;/c:if&gt;&lt;br /&gt;&lt;/pre&gt;&lt;li&gt;Import the client website certificate in the CAS server for example:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ echo | openssl s_client -connect bhubdev.nestorurquiza.com:443 2&gt;/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' &gt; ~/Downloads/bhubdev.nestorurquiza.com.cer&lt;br /&gt;$ sudo keytool -import -alias bhubdev.nestorurquiza.com -keystore  /Library/Java/Home/lib/security/cacerts -file ~/Downloads/bhubdev.nestorurquiza.com.cer&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/ol&gt;&lt;h1&gt;Spring CAS Client&lt;/h1&gt;&lt;ol&gt;&lt;li&gt;Import the CAS certificate in the client&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ echo | openssl s_client -connect casdev.nestorurquiza.com:443 2&gt;/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' &gt; ~/Downloads/casdev.nestorurquiza.com.cer&lt;br /&gt;$ sudo keytool -import -alias casdev.nestorurquiza.com -keystore  /Library/Java/Home/lib/security/cacerts -file ~/Downloads/casdev.nestorurquiza.com.cer&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Declare some properties in the classpath of the client application:&lt;br /&gt;&lt;pre class="brush: bash"&gt;...&lt;br /&gt;#&lt;br /&gt;# CAS&lt;br /&gt;#&lt;br /&gt;cas.loginURL=https://casdev.nestorurquiza.com/login&lt;br /&gt;cas.homeURL=https://casdev.nestorurquiza.com&lt;br /&gt;cas.serviceURL=https://bhubdev.nestorurquiza.com/j_spring_cas_security_check&lt;br /&gt;cas.logoutURL=https://casdev.nestorurquiza.com/logout?service=https%3A%2F%2Fbhubdev.nestorurquiza.com%2Flogout&lt;br /&gt;…&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Then configure the client to perform the Single Sign On and Single Sign Off through CAS. Here is the Spring security configuration for such a use case:&lt;br /&gt;&lt;pre class="brush: xml"&gt;...&lt;br /&gt;    &lt;!-- Uncomment this if CAS --&gt;&lt;br /&gt;    &lt;http auto-config="true" use-expressions="true" access-decision-manager-ref="accessDecisionManager" disable-url-rewriting="true" entry-point-ref="casEntryPoint"&gt;&lt;br /&gt;    &lt;!--  Comment this out if CAS --&gt;&lt;br /&gt;    &lt;!-- &lt;br /&gt;    &lt;http auto-config="true" use-expressions="true" access-decision-manager-ref="accessDecisionManager" disable-url-rewriting="true"&gt;&lt;br /&gt;    --&gt;&lt;br /&gt;    &lt;br /&gt;        &lt;!--  Comment this out if CAS --&gt;&lt;br /&gt;        &lt;!--&lt;br /&gt;        &lt;form-login login-page="/login"&lt;br /&gt;            authentication-success-handler-ref="customAuthenticationHandler"&lt;br /&gt;            authentication-failure-url="/login?error=authorizationFailed" /&gt;&lt;br /&gt;            &lt;logout invalidate-session="true" logout-url="/logout"&lt;br /&gt;            logout-success-url="/login?error=loggedOut" /&gt;&lt;br /&gt;        --&gt;&lt;br /&gt;        &lt;br /&gt;…&lt;br /&gt; &lt;!-- Uncomment this if CAS --&gt;&lt;br /&gt;        &lt;custom-filter position="CAS_FILTER" ref="casFilter" /&gt;&lt;br /&gt;        &lt;!-- Uncomment this if CAS --&gt;&lt;br /&gt; &lt;custom-filter  before="CAS_FILTER" ref="casSingleSignOutFilter" /&gt;&lt;br /&gt;        &lt;br /&gt;…&lt;br /&gt;    &lt;authentication-manager alias="authenticationManager"&gt;&lt;br /&gt;        &lt;!-- Uncomment this if CAS --&gt;&lt;br /&gt;        &lt;authentication-provider ref="casAuthenticationProvider" /&gt;&lt;br /&gt;        &lt;br /&gt;        &lt;!-- Comment this out if CAS --&gt;&lt;br /&gt;        &lt;!-- &lt;br /&gt;        &lt;ldap-authentication-provider&lt;br /&gt;            user-search-filter="mail={0}" user-search-base="ou=people,o=nestorurquiza"&lt;br /&gt;            user-context-mapper-ref="customUserDetailsContextMapper" group-search-base="ou=groups,o=nestorurquiza" /&gt; &lt;br /&gt;         --&gt;           &lt;br /&gt;    &lt;/authentication-manager&gt;  &lt;br /&gt;…&lt;br /&gt;    &lt;!-- CAS --&gt;&lt;br /&gt;    &lt;beans:bean id="casServiceProperties" class="org.springframework.security.cas.ServiceProperties"&gt;&lt;br /&gt;        &lt;beans:property name="service" value="${cas.serviceURL}"/&gt;&lt;br /&gt;        &lt;beans:property name="sendRenew" value="false"/&gt;&lt;br /&gt;    &lt;/beans:bean&gt;&lt;br /&gt;    &lt;br /&gt;    &lt;beans:bean id="casFilter"&lt;br /&gt;          class="org.springframework.security.cas.web.CasAuthenticationFilter"&gt;&lt;br /&gt;      &lt;beans:property name="authenticationManager" ref="authenticationManager"/&gt;&lt;br /&gt;    &lt;/beans:bean&gt;&lt;br /&gt;    &lt;br /&gt;    &lt;beans:bean id="exceptionTranslationFilter"&lt;br /&gt;     class="org.springframework.security.web.access.ExceptionTranslationFilter"&gt;&lt;br /&gt;      &lt;beans:property name="authenticationEntryPoint" ref="casEntryPoint"/&gt;&lt;br /&gt;      &lt;beans:property name="accessDeniedHandler" ref="accessDeniedHandler"/&gt;&lt;br /&gt;    &lt;/beans:bean&gt;&lt;br /&gt;    &lt;br /&gt;    &lt;beans:bean id="accessDeniedHandler"&lt;br /&gt;         class="org.springframework.security.web.access.AccessDeniedHandlerImpl"&gt;&lt;br /&gt;      &lt;beans:property name="errorPage" value="/error?id=accessDenied"/&gt;&lt;br /&gt;    &lt;/beans:bean&gt;&lt;br /&gt;    &lt;br /&gt;    &lt;beans:bean id="casEntryPoint"&lt;br /&gt;        class="org.springframework.security.cas.web.CasAuthenticationEntryPoint"&gt;&lt;br /&gt;      &lt;beans:property name="loginUrl" value="${cas.loginURL}"/&gt;&lt;br /&gt;      &lt;beans:property name="serviceProperties" ref="casServiceProperties"/&gt;&lt;br /&gt;    &lt;/beans:bean&gt;&lt;br /&gt;    &lt;br /&gt;    &lt;beans:bean id="casAuthenticationProvider"&lt;br /&gt;      class="org.springframework.security.cas.authentication.CasAuthenticationProvider"&gt;&lt;br /&gt;        &lt;beans:property name="userDetailsService" ref="ldapUserDetailsService"/&gt;&lt;br /&gt;        &lt;beans:property name="serviceProperties" ref="casServiceProperties" /&gt;&lt;br /&gt;        &lt;beans:property name="ticketValidator"&gt;&lt;br /&gt;          &lt;beans:bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator"&gt;&lt;br /&gt;            &lt;beans:constructor-arg index="0" value="${cas.homeURL}" /&gt;&lt;br /&gt;            &lt;/beans:bean&gt;&lt;br /&gt;        &lt;/beans:property&gt;&lt;br /&gt;        &lt;beans:property name="key" value="nestorurquiza_auth_provider_id"/&gt;&lt;br /&gt;    &lt;/beans:bean&gt;&lt;br /&gt;&lt;br /&gt;    &lt;beans:bean id="casSingleSignOutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter"/&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;A simple JSP View for the client application Logout Controller&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;%@ include file="/WEB-INF/jsp/includes.jsp" %&gt;&lt;br /&gt;&lt;%@ include file="/WEB-INF/jsp/header.jsp" %&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="info"&gt;&lt;spring:message code="welcome.loggedOut" text="You are logged out"/&gt;&lt;/div&gt;&lt;p&gt;&lt;%@ include file="/WEB-INF/jsp/footer.jsp" %&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;The LogoutController&lt;br /&gt;&lt;pre class="brush: java"&gt;package com.nestorurquiza.web;&lt;br /&gt;&lt;br /&gt;import javax.servlet.http.HttpServletRequest;&lt;br /&gt;import javax.servlet.http.HttpServletResponse;&lt;br /&gt;&lt;br /&gt;import org.springframework.security.core.context.SecurityContextHolder;&lt;br /&gt;import org.springframework.stereotype.Controller;&lt;br /&gt;import org.springframework.web.bind.annotation.RequestMapping;&lt;br /&gt;import org.springframework.web.servlet.ModelAndView;&lt;br /&gt;&lt;br /&gt;@Controller&lt;br /&gt;public class LogoutController extends RootController {&lt;br /&gt;&lt;br /&gt;    @RequestMapping("/logout")&lt;br /&gt;    public ModelAndView welcomeHandler(HttpServletRequest request,&lt;br /&gt;            HttpServletResponse response) {&lt;br /&gt;        SecurityContextHolder.getContext().setAuthentication(null);&lt;br /&gt;        &lt;br /&gt;        ...&lt;br /&gt;        &lt;br /&gt;        return getModelAndView(ctx, "logout", null);&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;li&gt;The logout link in JSP:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;!-- Comment it out if using CAS --&gt;&lt;br /&gt;                        &lt;!--  &lt;br /&gt;                        &lt;a href="&lt;spring:url value="/logout" htmlEscape="true" /&gt;"&gt;&lt;br /&gt;                            &lt;spring:message code="logout"/&gt;&lt;/a&gt;&lt;br /&gt;                        --&gt;&lt;br /&gt;                            &lt;br /&gt;                        &lt;!-- Uncomment if using CAS --&gt;&lt;br /&gt;                       &lt;a href="${applicationProperties['cas.logoutURL']}"&gt;&lt;br /&gt;                            &lt;spring:message code="logout"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;At least one dependency is needed in the client application:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;dependency&gt;&lt;br /&gt;            &lt;groupid&gt;org.springframework.security&lt;/groupId&gt;&lt;br /&gt;            &lt;artifactid&gt;spring-security-cas-client&lt;/artifactId&gt;&lt;br /&gt;            &lt;version&gt;${spring-security.version}&lt;/version&gt;&lt;br /&gt;            &lt;scope&gt;compile&lt;/scope&gt;&lt;br /&gt;        &lt;/dependency&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;At this point hitting any client application URL (for example https://bhubdev.nestorurquiza.com/client/home) will redirect the user to the CAS login URL passing the service parameter which contains the encoded servciceURL (https://casdev.nestorurquiza.com/login?service=https%3A%2F%2Fbhubdev.nestorurquiza.com%2Fj_spring_cas_security_check). When the user provides valid credentials CAS will redirect her back to the j_spring_cas_security_check client resource and Spring will take care to pull from the session the originally requested URL. When the user clicks on the logout URL (https://casdev.nestorurquiza.com/logout?service=https%3A%2F%2Fbhubdev.nestorurquiza.com%2Flogout) the CAS server will invalidate the security token and the user will return to a URL where a custom Logout Controller invalidates the Spring Security Context. At this point any attempt to access a protected resource will result in a redirect to the CAS login page as explained.&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;Probably you will need in your custom code to do extra logic with the saved request (the original requested URL). In Spring 3.0.3 at least you use the below snippet to gain access to it:&lt;br /&gt;&lt;pre class="brush: java"&gt;SavedRequest savedRequest = new DefaultSavedRequest(request, new PortResolverImpl());&lt;br /&gt;if( savedRequest != null ) {&lt;br /&gt;  String redirectUrl = savedRequest.getRedirectUrl();&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;Clearly you can proceed with the final step from the Server and the steps from the Client to configure extra applications (CAS Services). When you logout from one application or directly from CAS interface you will be logged out from all applications that are integrated with CAS (Single Sign Out or Single Sign Off functionality)&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;/ol&gt;Other than a little issue I reported &lt;a href="https://lists.wisc.edu/read/messages?id=17872011"&gt;here&lt;/a&gt; the whole integration went smooth. CAS is well supported in Spring and both Single Sign On and Single Sign Off will work out of the box.&lt;br /&gt;&lt;br /&gt;By default CAS is shipped with a 4 hours expiration ticket expiration policy. This can be configured as explained in the documentation https://wiki.jasig.org/display/CASUM/Ticket+Expiration+Policy&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-3629782297391170074?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/3629782297391170074/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=3629782297391170074' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/3629782297391170074'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/3629782297391170074'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2012/01/sso-with-ldap-spring-and-cas.html' title='SSO with LDAP Spring and CAS'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-5497413397027948133</id><published>2012-01-05T09:37:00.000-08:00</published><updated>2012-01-05T09:37:32.204-08:00</updated><title type='text'>Access a properties file from JSP/JSTL EL</title><content type='html'>Sometimes you need to show information in a JSP from a properties file. Typical scenario is configuration data.&lt;br /&gt;&lt;br /&gt;With spring you could do:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;util:properties id="applicationProperties" location="classpath:app.properties"/&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then from a Listener or even from a Controller you could Autowire and inject the variable as a context or request attribute respectively:&lt;br /&gt;&lt;pre class="brush: java"&gt;...&lt;br /&gt;@Autowired&lt;br /&gt;public Properties applicationProperties;&lt;br /&gt;...&lt;br /&gt;request.setAttribute("applicationProperties", applicationProperties);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Suppose app.properties has this content:&lt;br /&gt;&lt;pre class="brush: bash"&gt;...&lt;br /&gt;bhub.environment=LOCAL&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then from JSP/JSTL you can use it as:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;c:out value = "${applicationProperties['bhub.environment']}"/&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-5497413397027948133?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/5497413397027948133/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=5497413397027948133' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5497413397027948133'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5497413397027948133'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2012/01/access-properties-file-from-jspjstl-el.html' title='Access a properties file from JSP/JSTL EL'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-7678224987076917696</id><published>2012-01-05T09:05:00.000-08:00</published><updated>2012-01-05T09:05:35.620-08:00</updated><title type='text'>Subversive Authentication error svn OPTIONS 403 Forbidden</title><content type='html'>Suddenly Eclipse started to complaint with an error like the below when I tried to update certain local svn copies:&lt;br /&gt;&lt;pre class="brush: bash"&gt;Authentication error.&lt;br /&gt;svn: OPTIONS of '/repos/reporting': 403 Forbidden (http://subversion.nestorurquiza.com)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;While someone could be tempted to grant more permisions on the SVN server the reality is that from command line I did not get the same experience. Everything was working just fine from there.&lt;br /&gt;&lt;br /&gt;To correct this I used the Subversive Update site from Eclipse Help/Install Software Menu. After an Eclipse restart I had to pick the latest 1.6 svn connector (not native) for it to work:&lt;br /&gt;&lt;pre class="brush: bash"&gt;http://download.eclipse.org/technology/subversive/0.7/update-site/ - [required] Subversive plug-in&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-7678224987076917696?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/7678224987076917696/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=7678224987076917696' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7678224987076917696'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7678224987076917696'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2012/01/subversive-authentication-error-svn.html' title='Subversive Authentication error svn OPTIONS 403 Forbidden'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-7900947650423915263</id><published>2011-12-15T18:21:00.000-08:00</published><updated>2011-12-15T18:21:30.071-08:00</updated><title type='text'>Two hours with Rasmus Lerdorf, the creator of PHP</title><content type='html'>I had the pleasure to meet Rasmus Lerdorf, the creator of PHP in a two hour session organized by South Florida PHP Users Group in Nova South Eastern University.&lt;br /&gt;&lt;br /&gt;As one of the earliest PHP adopters I share a lot of feelings for the language and it was great to see so many young people in the room together with not so young like me ;-) and some older than me. It is great to see a language going on for so long. I would say it was a celebration of more than 15 years of PHP coding.&lt;br /&gt;&lt;br /&gt;Even though I have been working more with python, ruby and Java for the last 5 years PHP always comes back: I need to patch a built in PHP software, configure it or even get a quick snippet and modify it to get my &lt;b&gt;work done&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;So with a lot of quotations here is an extract of the introductory ideas this MIT awarded "top 100 innovators in the world" shared with us.&lt;br /&gt;&lt;br /&gt;The idea behind PHP was always "the need for speed and performance". Rasmus "did not worry about correctness but about resolving the problem". He is a guy that "does not love to program" and that is precisely why you want to use PHP because instead he actually "loves to solve problems".&lt;br /&gt;&lt;br /&gt;"You have to add non scalable features in order to make php not scalable". The "documentation was always done before the function was implemented". He constantly "insisted in having lot of examples as part of the documentation"&lt;br /&gt;&lt;br /&gt;PHP was born while "thinking in the ecosystem". Basically shared hosting demanded control on memory and CPU (QoS) to adequate existing servers to the needs of multiple applications.&lt;br /&gt;&lt;br /&gt;PHP "runs crappy code extremely fast" so it is better for startups than other languages like Java. It simply scales well.&lt;br /&gt;&lt;br /&gt;I asked the question about why he considered Java not scalable and he clarified:&lt;br /&gt;-&lt;i&gt;It does scale but it comes out of the box with features that allow the requests to be sticked in memory, a thing php by default does not allow. Also when it comes to go beyond one server it is then more difficult in java just because of this.&lt;/i&gt; &lt;br /&gt;&lt;br /&gt;Of course one can argue sticky sessions are enough for most projects. Yes the user session will expire and then you will need to re login but things like remember-me will help (not that I like it really). Not big deal considering how many times this actually happens, I would say nobody complained to me on the clusters I have setup for which in really weird circumstances a shared clustered session was used.&lt;br /&gt;&lt;br /&gt;He described some features that make PHP even faster today:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;libevent allows event driven programming ala nodejs.&lt;/li&gt;&lt;li&gt;zeromq for better and simplest socket programming. I have to add that messaging is in fact something I would say will gain more and more momentum within the software community. The world is asynchronous as the creator of Erlang language said said.&lt;/li&gt;&lt;li&gt;FastCGI Process Manager (FPM) allows dividing the workload while sending jobs to workers. This is the PHP way to say no to threads.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Rasmus talked about performance and brought some examples providing some guidance to improve it:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Use wisely your datastores. Do not try to replace sql with nosql, it simply does not work that way.&lt;/li&gt;&lt;li&gt;Turn off logging in production (error_reporting(-1) is expensive).&lt;/li&gt;&lt;li&gt;Use strace: Look for ENOENT, excessive stats (lstat), check your realpath_cache_size.&lt;/li&gt;&lt;li&gt;Use a profiler: callgrind, xdebug, xhprof, xhgui.&lt;/li&gt;&lt;li&gt;Set default timezone (look at phpinfo).&lt;/li&gt;&lt;li&gt;Watch for inclusions. Too many inclusions degrade performance. An example of a bad application is Magento which includes thousand pf files.&lt;/li&gt;&lt;li&gt;HipHop-PHP can be used to do static analysis. They need to parse php better than the php parser itself because their goal is to translate PHP to native code. Be prepared to wait for the compiler but this is a good exercise to find problems in your code.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Here are some architecture reminders/suggestions from Rasmus:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Use different domain/subdomain for static assets.&lt;/li&gt;&lt;li&gt;Keep cookies short (MTU size maters and will translate in more roundtrips when it is overflown).&lt;/li&gt;&lt;li&gt;Multiply by 5 times the amount of cores and that is the amount of real concurrent users. I have to say I have been calculating allowed concurrent users for years and the metric about just the cores is not that simple. It even depends on what the application is using of course so a word of advice from my end would be take a look at your system metrics for example with vmstat, top and other system commands. The use of automated stress tests driven by for example jmeter are a must do in my opinion.&lt;/li&gt;&lt;li&gt;Use out-of-band processing with Gearman, PHP-FPM or custom via ZeroMQ.&lt;/li&gt;&lt;li&gt;Tweak ORM and caching.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Finally he talked about the PHP 5.3 features, how PHP 6 development was stopped, the problems inherent to support Unicode and the efforts that are still being made in the current version 5 to support little by little more Unicode functionality. Here are some of those new features:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Better performance through lot of code optimization.&lt;/li&gt;&lt;li&gt;Support for closures.&lt;/li&gt;&lt;li&gt;Namespaces. There is a big debate around the decision to use a backslash instead of a dot for the namespaces. To be honest I do feel that as awkward but as Rasmus said "we will need to get used to it".&lt;/li&gt;&lt;li&gt;Late Static Binding support.&lt;/li&gt;&lt;li&gt;Garbage Colector which you should not need in web apps but if you have long running scripts then ii will definitely help.&lt;/li&gt;&lt;li&gt;NEWDOC which is like HEREDOC but does not perform any parsing inside the string block.&lt;/li&gt;&lt;li&gt;Remember "Go To Statement Considered Harmful", the famous letter from Edsger Dijkstra? The controversy is still on as PHP allows "goto" to eliminate verbosity or the use of break. In my opinion the discussion about the name is secondary. You can use break or continue to a a label in Java for example so if a goto has just the meaning of breaking the loop to a variable I think is awkward but but I am fine with it. However allowing goto to go to any part of the code block is not precisely a multi level break or continue, it is something more than that. If that is harmful, confusing, miss leading or not I leave it to the discussion. Perhaps just adjusting the multi level sysntax from PHP to accept a letter instead of a "magic" number would be a better approach.&lt;/li&gt;&lt;li&gt;DateInterval/DatePeriod classes.&lt;/li&gt;&lt;li&gt;date_create_from_format&lt;/li&gt;&lt;li&gt;FastCGI Process manager (FPM)&lt;/li&gt;&lt;li&gt;For people moving to ngnix from apache .php_ini allows for custom php directives just like .htaccess for Apache.&lt;/li&gt;&lt;li&gt;Traits: &lt;a href="http://simas.posterous.com/new-to-php-54-traits"&gt;Compiler assisted copy and paste to resolve the lack of multiple inheritance.&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Thank you Rasmus for PHP. Let's celebrate how lucky we are that not a single language could ever win all battles. Different languages exist to resolve similar problems unders different scenarios. This is not any different than we, human beings. We need different skills in a team as much as we need different languages in modern computing. &lt;br /&gt;&lt;br /&gt; and different human beings exist to reso&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-7900947650423915263?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/7900947650423915263/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=7900947650423915263' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7900947650423915263'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7900947650423915263'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/12/two-hours-with-rasmus-lerdorf-creator.html' title='Two hours with Rasmus Lerdorf, the creator of PHP'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-2114929823718638783</id><published>2011-12-15T14:12:00.000-08:00</published><updated>2011-12-15T14:12:06.883-08:00</updated><title type='text'>Workspace defines a VM that does not contain a valid jre/lib/rt.jar in Snow Leopard OSX</title><content type='html'>I was getting this error today after trying to use "mvn eclipse:eclipse" to generate Eclipse project files out of a maven project:&lt;br /&gt;&lt;pre class="brush: bash"&gt;[WARNING] Workspace defines a VM that does not contain a valid jre/lib/rt.jar: /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;There is a proposed &lt;a href="http://jira.codehaus.org/browse/MECLIPSE-668"&gt;patch&lt;/a&gt; for this bug but at the time of this writing I could not figure out a better way than running the below commands:&lt;br /&gt;&lt;pre class="brush: bash"&gt;cd /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home&lt;br /&gt;sudo mkdir -p jre/lib&lt;br /&gt;cd jre/lib&lt;br /&gt;sudo ln -s ../../../Classes/classes.jar rt.jar&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-2114929823718638783?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/2114929823718638783/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=2114929823718638783' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/2114929823718638783'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/2114929823718638783'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/12/workspace-defines-vm-that-does-not.html' title='Workspace defines a VM that does not contain a valid jre/lib/rt.jar in Snow Leopard OSX'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-3363446360872280861</id><published>2011-12-15T12:25:00.000-08:00</published><updated>2012-01-03T13:46:24.236-08:00</updated><title type='text'>Remove context from url from Tomcat applications</title><content type='html'>If you are like me you love tomcat simplicity and you are used to deploy your WAR files or exploded directories in webapps folder directly. However in order to serve URLs without the context included you will need to stop that practice.&lt;br /&gt;&lt;br /&gt;You might come with a &lt;a href="http://thinkinginsoftware.blogspot.com/2011/11/tomcat-servlet-context-initialized.html"&gt;hack&lt;/a&gt; like I discover a while back, but as you can read there that will result in the same application being deployed several times.&lt;br /&gt;&lt;br /&gt;Here is how to configure tomcat (Tested in tomcat 7) to serve content from two different URLs&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Make your websites resolve to real IPs. For production you rely on external DNS, for other environments you rely on internal DNS and many times in you /etc/hosts:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ vi /etc/hosts&lt;br /&gt;...&lt;br /&gt;127.0.0.1   bhubdev.nestorurquiza.com&lt;br /&gt;127.0.0.1   bhubdev2.nestorurquiza.com&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;You need to add them both to Engine section in conf/server.xml:&lt;br /&gt;&lt;pre class="brush: xml"&gt;...&lt;br /&gt;  &lt;host name="bhubdev.nestorurquiza.com"  appBase="bhub-app"&lt;br /&gt;    unpackWARs="true" autoDeploy="true"&lt;br /&gt;    xmlValidation="false" xmlNamespaceAware="false" deployOnStartup="true"/&gt;&lt;br /&gt;  &lt;host name="bhubdev2.nestorurquiza.com"  appBase="bhub2-app"&lt;br /&gt;    unpackWARs="true" autoDeploy="true"&lt;br /&gt;    xmlValidation="false" xmlNamespaceAware="false" deployOnStartup="true"/&gt;&lt;br /&gt;&lt;/Engine&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Create these folders (two per domain as you can see). Be sure to change to proper paths in your system:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ mkdir /Users/nestor/Downloads/apache-tomcat-7.0.22/bhub-app&lt;br /&gt;$ mkdir /Users/nestor/Downloads/apache-tomcat-7.0.22/conf/Catalina/bhubdev.nestorurquiza.com&lt;br /&gt;$ mkdir /Users/nestor/Downloads/apache-tomcat-7.0.22/bhub2-app&lt;br /&gt;$ mkdir /Users/nestor/Downloads/apache-tomcat-7.0.22/conf/Catalina/bhub2dev.nestorurquiza.com&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Restart tomcat.&lt;/li&gt;&lt;li&gt;Deploy the WAR files as:&lt;br /&gt;&lt;pre class="brush: bash"&gt;/Users/nestor/Downloads/apache-tomcat-7.0.22/bhub-app/ROOT.war&lt;br /&gt;/Users/nestor/Downloads/apache-tomcat-7.0.22/bhub2-app/ROOT.war&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Alternatively deploy the exploded WAR file as:&lt;br /&gt;&lt;pre class="brush: bash"&gt;unzip bhub-app.war -d /Users/nestor/Downloads/apache-tomcat-7.0.22/bhub-app/ROOT/&lt;br /&gt;unzip bhub-app2.war -d /Users/nestor/Downloads/apache-tomcat-7.0.22/bhub2-app/ROOT/&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Now if you use maven and you want to deploy from there you probably have something like the below if you are still deploying to webapp folder (using ant tasks to deploy either the WAR file or the exploded directory)&lt;br /&gt;&lt;pre class="brush: xml"&gt;...&lt;br /&gt;&lt;properties&gt;&lt;br /&gt;    &lt;tomcat.deploy.dir&gt;${MVN_TOMCAT_HOME_DEPLOY}&lt;/tomcat.deploy.dir&gt;&lt;br /&gt;...&lt;br /&gt;&lt;tasks&gt;&lt;br /&gt;    &lt;echo message="Copying from ${basedir}/src/main/webapp to ${tomcat.deploy.dir}/${project.build.finalName}" /&gt;&lt;br /&gt;    &lt;copy todir="${tomcat.deploy.dir}/${project.build.finalName}"&gt;&lt;br /&gt;        &lt;fileset includes="**/*.*" dir="${basedir}/src/main/webapp" /&gt;&lt;br /&gt;    &lt;/copy&gt;&lt;br /&gt;&lt;/tasks&gt;&lt;br /&gt;...&lt;br /&gt;&lt;tasks&gt;&lt;br /&gt;    &lt;echo&gt;Deploying WAR locally&lt;/echo&gt;&lt;br /&gt;    &lt;copy todir="${tomcat.deploy.dir}" file="target/${project.build.finalName}.war" /&gt;&lt;br /&gt;&lt;/tasks&gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You will need to update that to point to the new directories and not to webapps anymore.&lt;br /&gt;&lt;pre class="brush: xml"&gt;...&lt;br /&gt;&lt;properties&gt;&lt;br /&gt;  &lt;catalina.home.dir&gt;${CATALINA_HOME}&lt;/catalina.home.dir&gt;&lt;br /&gt;...&lt;br /&gt;&lt;tasks&gt;&lt;br /&gt;    &lt;echo message="Copying ${basedir}/src/main/webapp to ${tomcat.deploy.dir}/${project.build.finalName}" /&gt;&lt;br /&gt;    &lt;copy todir="${catalina.home.dir}/${project.build.finalName}/ROOT"&gt;&lt;br /&gt;        &lt;fileset includes="**/*.*" dir="${basedir}/src/main/webapp" /&gt;&lt;br /&gt;    &lt;/copy&gt;&lt;br /&gt;&lt;/tasks&gt;&lt;br /&gt;...&lt;br /&gt;&lt;tasks&gt;&lt;br /&gt;    &lt;echo message="Copying ${basedir}target/${project.build.finalName}.war to ${catalina.home.dir}/${project.build.finalName}/ROOT.war"/&gt;&lt;br /&gt;    &lt;copy tofile="${catalina.home.dir}/${project.build.finalName}/ROOT.war" file="target/${project.build.finalName}.war" /&gt;&lt;br /&gt;&lt;/tasks&gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Do not forget to add to your profile the needed for Maven environment variable:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ vi ~/.profile:&lt;br /&gt;#old needed variable&lt;br /&gt;#export MVN_TOMCAT_HOME_DEPLOY=/opt/tomcat/webapps&lt;br /&gt;#new needed variable&lt;br /&gt;export CATALINA_HOME=/opt/tomcat&lt;br /&gt;$ source ~/.profile&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-3363446360872280861?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/3363446360872280861/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=3363446360872280861' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/3363446360872280861'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/3363446360872280861'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/12/remove-context-from-url-from-tomcat.html' title='Remove context from url from Tomcat applications'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-6931128571857072523</id><published>2011-12-14T08:27:00.000-08:00</published><updated>2011-12-14T08:48:08.426-08:00</updated><title type='text'>Monitor Event Log in Windows 2008</title><content type='html'>I already described how we can &lt;a href="http://thinkinginsoftware.blogspot.com/2011/04/monitor-event-log-in-windows-servers.html"&gt;monitor event logs from windows&lt;/a&gt; but that procedure will not work for Windows 2008 and Windows 7 because eventtriggers.exe has been &lt;a href="http://technet.microsoft.com/en-us/library/ff935309(WS.10).aspx"&gt;deprecated&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Here is a new script you will need to use in Windows 7/2008 together with "Windows Task Scheduler". The comments on the top of the script should be straightforward to understand how to get an email alert every time an application ERROR event is registered in the Event logs.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: vb"&gt;'''''''''''''''''''''''''''''''''''''''''''''''&lt;br /&gt;'&lt;br /&gt;' c:\scripts\events\sendEventErrorByEmail.vbs&lt;br /&gt;'&lt;br /&gt;' @Author: Nestor Urquiza&lt;br /&gt;' @Created: 12/14/2011&lt;br /&gt;'&lt;br /&gt;'&lt;br /&gt;' @Description: Alerts a Windows Admin there are errors in Event Viewer. &lt;br /&gt;' It could be scheduled to run every maxMinutes but&lt;br /&gt;' Using it as an action for a custom Scheduled Task with a trigger on event filters:&lt;br /&gt;'&lt;br /&gt;' Task Scheduler Library: Create Task | Triggers | New Trigger | Begin the Task On an Event | Settings Custom | New Event Filter | Event Level Error | By Log | Event Logs | Windows Logs | Application&lt;br /&gt;'&lt;br /&gt;'&lt;br /&gt;'&lt;br /&gt;'&lt;br /&gt;&lt;br /&gt;'&lt;br /&gt;'&lt;br /&gt;' @Compatibility: Tested so far in WindowsXP, Vista, 7, 2000, 2003, 2008&lt;br /&gt;'&lt;br /&gt;'&lt;br /&gt;' @Parameters&lt;br /&gt;' 1. A prefix body message in case specific errors are to be sent &lt;br /&gt;'    (a combination of batch and eventtriggers will do the trick)&lt;br /&gt;'&lt;br /&gt;'&lt;br /&gt;' @Filters: I am filtering only "Application" events. Change the SQL query if you want to apply a different filter or not filter at all&lt;br /&gt;'&lt;br /&gt;'&lt;br /&gt;'&lt;br /&gt;''''''''''''''''''''''''''''''''''''''''''''''''&lt;br /&gt;&lt;br /&gt;'Constants&lt;br /&gt;strSmartHost = "krms.krco.com"&lt;br /&gt;strSmartPort = 25&lt;br /&gt;maxMinutes = 1&lt;br /&gt;strComputer = "."&lt;br /&gt;emailFrom = "donotreply@nestorurquiza.com"&lt;br /&gt;emailTo = "nurquiza@nestorurquiza.com"&lt;br /&gt;&lt;br /&gt;'System config&lt;br /&gt;Set wshShell = WScript.CreateObject( "WScript.Shell" )&lt;br /&gt;strComputerName = wshShell.ExpandEnvironmentStrings( "%COMPUTERNAME%" )&lt;br /&gt;Set objSWbemServices = GetObject("winmgmts:" _&lt;br /&gt; &amp; "{impersonationLevel=impersonate}!\\" &amp; strComputer &amp; "\root\cimv2")&lt;br /&gt;Set colTimeZone = objSWbemServices.ExecQuery _&lt;br /&gt; ("SELECT * FROM Win32_TimeZone")&lt;br /&gt;For Each objTimeZone in colTimeZone&lt;br /&gt; offset = objTimeZone.Bias&lt;br /&gt;Next&lt;br /&gt;&lt;br /&gt;'Parameters&lt;br /&gt;Dim strBody&lt;br /&gt;If (Wscript.Arguments.Count &gt; 0) Then&lt;br /&gt;  strBody = Wscript.Arguments(0)&lt;br /&gt;End If&lt;br /&gt;&lt;br /&gt;'Start date to look for events&lt;br /&gt;dtmDate = DateAdd("n",-maxMinutes,Now())&lt;br /&gt;dateToWMIDateString = Year(dtmDate) &amp; padZeros(Month(dtmDate)) &amp; padZeros(Day(dtmDate)) &amp; padZeros(Hour(dtmDate)) &amp; padZeros(Minute(dtmDate)) &amp; padZeros(Second(dtmDate)) &amp; ".000000" &amp; offset&lt;br /&gt;&lt;br /&gt;'Get events matching the query&lt;br /&gt;Set objWMIService = GetObject("winmgmts:" _&lt;br /&gt;    &amp; "{impersonationLevel=impersonate}//" &amp; _&lt;br /&gt;        strComputer &amp; "\root\cimv2")&lt;br /&gt;Set colLogFiles = objWMIService.ExecQuery _&lt;br /&gt;    ("Select * from Win32_NTLogEvent " _&lt;br /&gt;        &amp; "Where Logfile='Application' and Type='Error' and TimeGenerated &gt; '" &amp;  dateToWMIDateString &amp; "'" )&lt;br /&gt;&lt;br /&gt;'Accumulate all events dates and details&lt;br /&gt;For Each objLogFile in colLogFiles&lt;br /&gt;    dtmInstallDate = objLogFile.TimeGenerated&lt;br /&gt;    WMIDateStringToDate = CDate(Mid(dtmInstallDate, 5, 2) &amp; "/" &amp; _&lt;br /&gt;     Mid(dtmInstallDate, 7, 2) &amp; "/" &amp; Left(dtmInstallDate, 4) _&lt;br /&gt;         &amp; " " &amp; Mid (dtmInstallDate, 9, 2) &amp; ":" &amp; _&lt;br /&gt;             Mid(dtmInstallDate, 11, 2) &amp; ":" &amp; Mid(dtmInstallDate, _&lt;br /&gt;                 13, 2))&lt;br /&gt;    WMIDateStringToDate = DateAdd("n", offset, WMIDateStringToDate)&lt;br /&gt;    details = details &amp; vbCrLf &amp; WMIDateStringToDate  &amp; " - [" &amp; _&lt;br /&gt;                   objLogFile.Type &amp; "] " &amp; _&lt;br /&gt;                   objLogFile.Message&lt;br /&gt;    'Wscript.Echo details&lt;br /&gt;Next&lt;br /&gt;&lt;br /&gt;'Send email with details about matching events&lt;br /&gt;If (Not IsNull(details) And details &lt;&gt; "") Then&lt;br /&gt;    'Prepare email&lt;br /&gt;    Set objEmail = CreateObject("CDO.Message")&lt;br /&gt;    objEmail.From = emailFrom&lt;br /&gt;    objEmail.To = emailTo&lt;br /&gt;    objEmail.Subject = "[" &amp; strComputerName &amp; "] " &amp; "Event Viewer Alert"&lt;br /&gt;    If (Not IsNull(strBody) And strBody &lt;&gt; "") Then&lt;br /&gt;        objEmail.Textbody = strBody &amp; ". "&lt;br /&gt;    End If&lt;br /&gt;    objEmail.Textbody = objEmail.Textbody &amp; details&lt;br /&gt;&lt;br /&gt;    'Custom server&lt;br /&gt;    objEmail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2&lt;br /&gt;    objEmail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = strSmartHost&lt;br /&gt;    objEmail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = strSmartPort&lt;br /&gt;    objEmail.Configuration.Fields.Update&lt;br /&gt;&lt;br /&gt;    'Send it&lt;br /&gt;    objEmail.Send&lt;br /&gt;End If&lt;br /&gt;&lt;br /&gt;Function padZeros(dtmDate)&lt;br /&gt;If Len(dtmDate) = 1 Then&lt;br /&gt;    padZeros = "0" &amp; dtmDate&lt;br /&gt;Else&lt;br /&gt;    padZeros = dtmDate&lt;br /&gt;End If&lt;br /&gt;End Function&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;BTW you will notice the Event filter contains the below which could give you some hints to research even more powerful ways to control different event alerts:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;querylist&gt;&lt;br /&gt;    &lt;query Id="0" Path="Application"&gt;&lt;br /&gt;      &lt;select Path="Application"&gt;*[System[(Level=2)]]&lt;/Select&gt;&lt;br /&gt;    &lt;/Query&gt;&lt;br /&gt;  &lt;/QueryList&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-6931128571857072523?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/6931128571857072523/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=6931128571857072523' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6931128571857072523'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6931128571857072523'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/12/monitor-event-log-in-windows-2008.html' title='Monitor Event Log in Windows 2008'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-128448978643250960</id><published>2011-12-07T12:42:00.000-08:00</published><updated>2011-12-07T12:42:25.665-08:00</updated><title type='text'>Arial Font from JasperReports for free and legally</title><content type='html'>If you are planning to ship documents to your clients and you need to use one of the commercial most used fonts like Arial you are in your way to pay a good chunk of money. &lt;a href="http://www.ascendercorp.com/contact/"&gt;There is not even a clear price&lt;/a&gt; on what they might cost because basically it depends on your application (read the license goes with how much money you are making with your product).&lt;br /&gt;&lt;br /&gt;Thanks to the information I found in this &lt;a href="http://javaskeleton.blogspot.com/2010/12/embedding-fonts-into-pdf-generated-by.html"&gt;post&lt;/a&gt; it took me little time to legally use a close to the proprietary Arial font in our application. If you plan to distribute or license your application you will need to buy Arial font or find a different project with a more commercial friendly license (like BSD) as the license for Liberation fonts is GPL. must then &lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Download and extract the &lt;a href="https://fedorahosted.org/liberation-fonts/"&gt;Red Hat Open Source GPL licensed Liberation Fonts&lt;/a&gt;&lt;/li&gt;&lt;li&gt;From iReport Preferences on a Mac or Options for the rest of the mortals navigate through "iReport | Fonts | Install Font", then import "LiberationSans-Regular.ttf" from the exploded directory created as part of the above step. Click next and pick the bold, Italic and BoldItalic versions for LiberationSans. LiberationSans is the closest font to Arial. &lt;a href="http://en.wikipedia.org/wiki/Liberation_fonts"&gt;The rest of the Liberation Fonts are closer to Times New Roman and Courier New fonts&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Pick a good family name that will later use in your style fontName property, for example "LiberationSans". Check the "Embed this font in the PDF document"&lt;/li&gt;&lt;li&gt;Include your font in a hello world jrxml like I show below and run it from iReport. Play with the properties and you should see bold and italic rendered as well (I am in this example striking through and underlining as well).&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;&amp;lt;jasperReport xmlns=&amp;quot;http://jasperreports.sourceforge.net/jasperreports&amp;quot; xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot; xsi:schemaLocation=&amp;quot;http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd&amp;quot; name=&amp;quot;hello&amp;quot; language=&amp;quot;groovy&amp;quot; pageWidth=&amp;quot;595&amp;quot; pageHeight=&amp;quot;842&amp;quot; whenNoDataType=&amp;quot;AllSectionsNoDetail&amp;quot; columnWidth=&amp;quot;555&amp;quot; leftMargin=&amp;quot;20&amp;quot; rightMargin=&amp;quot;20&amp;quot; topMargin=&amp;quot;20&amp;quot; bottomMargin=&amp;quot;20&amp;quot;&amp;gt;&lt;br /&gt; &amp;lt;property name=&amp;quot;ireport.zoom&amp;quot; value=&amp;quot;1.0&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;property name=&amp;quot;ireport.x&amp;quot; value=&amp;quot;0&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;property name=&amp;quot;ireport.y&amp;quot; value=&amp;quot;0&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;style name=&amp;quot;Default&amp;quot; isDefault=&amp;quot;true&amp;quot; fontName=&amp;quot;LiberationSans&amp;quot; fontSize=&amp;quot;20&amp;quot; isBold=&amp;quot;true&amp;quot; isItalic=&amp;quot;true&amp;quot; isUnderline=&amp;quot;true&amp;quot; isStrikeThrough=&amp;quot;true&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;background&amp;gt;&lt;br /&gt;  &amp;lt;band splitType=&amp;quot;Stretch&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;/background&amp;gt;&lt;br /&gt; &amp;lt;title&amp;gt;&lt;br /&gt;  &amp;lt;band height=&amp;quot;79&amp;quot; splitType=&amp;quot;Stretch&amp;quot;&amp;gt;&lt;br /&gt;   &amp;lt;staticText&amp;gt;&lt;br /&gt;    &amp;lt;reportElement x=&amp;quot;12&amp;quot; y=&amp;quot;10&amp;quot; width=&amp;quot;531&amp;quot; height=&amp;quot;57&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;textElement/&amp;gt;&lt;br /&gt;    &amp;lt;text&amp;gt;&amp;lt;![CDATA[Hello JasperReports!!!]]&amp;gt;&amp;lt;/text&amp;gt;&lt;br /&gt;   &amp;lt;/staticText&amp;gt;&lt;br /&gt;  &amp;lt;/band&amp;gt;&lt;br /&gt; &amp;lt;/title&amp;gt;&lt;br /&gt; &amp;lt;pageHeader&amp;gt;&lt;br /&gt;  &amp;lt;band height=&amp;quot;35&amp;quot; splitType=&amp;quot;Stretch&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;/pageHeader&amp;gt;&lt;br /&gt; &amp;lt;columnHeader&amp;gt;&lt;br /&gt;  &amp;lt;band height=&amp;quot;61&amp;quot; splitType=&amp;quot;Stretch&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;/columnHeader&amp;gt;&lt;br /&gt; &amp;lt;detail&amp;gt;&lt;br /&gt;  &amp;lt;band height=&amp;quot;125&amp;quot; splitType=&amp;quot;Stretch&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;/detail&amp;gt;&lt;br /&gt; &amp;lt;columnFooter&amp;gt;&lt;br /&gt;  &amp;lt;band height=&amp;quot;45&amp;quot; splitType=&amp;quot;Stretch&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;/columnFooter&amp;gt;&lt;br /&gt; &amp;lt;pageFooter&amp;gt;&lt;br /&gt;  &amp;lt;band height=&amp;quot;54&amp;quot; splitType=&amp;quot;Stretch&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;/pageFooter&amp;gt;&lt;br /&gt; &amp;lt;summary&amp;gt;&lt;br /&gt;  &amp;lt;band height=&amp;quot;42&amp;quot; splitType=&amp;quot;Stretch&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;/summary&amp;gt;&lt;br /&gt;&amp;lt;/jasperReport&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt; &lt;li&gt;Now you should see from iReport your font correctly embedding in the resulting PDF&lt;/li&gt; &lt;li&gt;From iReport navigate to the Fonts settings as I explained before and then pick "LiberationSans" and export it as liberation-sans.jar&lt;/li&gt;&lt;li&gt;Include the jar in your application classpath&lt;/li&gt;&lt;li&gt;Now your app is serving close to Arial font styled content&lt;/li&gt;&lt;/ol&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-11Sfh-baYJw/Tt_PkYGnCJI/AAAAAAAADic/I51Xn_eqlu8/s1600/Screen%2Bshot%2B2011-12-07%2Bat%2B3.40.54%2BPM.png" imageanchor="1" style=""&gt;&lt;img border="0" height="142" width="400" src="http://2.bp.blogspot.com/-11Sfh-baYJw/Tt_PkYGnCJI/AAAAAAAADic/I51Xn_eqlu8/s400/Screen%2Bshot%2B2011-12-07%2Bat%2B3.40.54%2BPM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-128448978643250960?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/128448978643250960/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=128448978643250960' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/128448978643250960'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/128448978643250960'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/12/arial-font-from-jasperreports-for-free.html' title='Arial Font from JasperReports for free and legally'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-11Sfh-baYJw/Tt_PkYGnCJI/AAAAAAAADic/I51Xn_eqlu8/s72-c/Screen%2Bshot%2B2011-12-07%2Bat%2B3.40.54%2BPM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-1410489783417349438</id><published>2011-11-30T09:52:00.000-08:00</published><updated>2011-11-30T09:52:37.783-08:00</updated><title type='text'>svn dump stderr output causing false positives</title><content type='html'>We were getting these annoying alerts every time the svn backup was running. Reason was 'svn dump' by default sends feedback like the below to the stderr:&lt;br /&gt;&lt;pre class="brush: bash"&gt;* Dumped revision 0.&lt;br /&gt;* Dumped revision 1.&lt;br /&gt;* Dumped revision 2.&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;From the help command the reason and the workaround came clear:&lt;br /&gt;&lt;pre class="brush: bash"&gt;# svnadmin dump --help&lt;br /&gt;dump: usage: svnadmin dump REPOS_PATH [-r LOWER[:UPPER] [--incremental]]&lt;br /&gt;&lt;br /&gt;Dump the contents of filesystem to stdout in a 'dumpfile'&lt;br /&gt;portable format, sending feedback to stderr.  Dump revisions&lt;br /&gt;LOWER rev through UPPER rev.  If no revisions are given, dump all&lt;br /&gt;revision trees.  If only LOWER is given, dump that one revision tree.&lt;br /&gt;If --incremental is passed, the first revision dumped will describe&lt;br /&gt;only the paths changed in that revision; otherwise it will describe&lt;br /&gt;every path present in the repository as of that revision.  (In either&lt;br /&gt;case, the second and subsequent revisions, if any, describe only paths&lt;br /&gt;changed in those revisions.)&lt;br /&gt;&lt;br /&gt;Valid options:&lt;br /&gt;  -r [--revision] ARG      : specify revision number ARG (or X:Y range)&lt;br /&gt;  --incremental            : dump incrementally&lt;br /&gt;  --deltas                 : use deltas in dump output&lt;br /&gt;  -q [--quiet]             : no progress (only errors) to stderr&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Just using the --quiet option does the trick:&lt;br /&gt;&lt;pre class="brush: bash"&gt;svnadmin dump -q $MASTER_REPO &gt;  $dump_file_path/dumpfile&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-1410489783417349438?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/1410489783417349438/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=1410489783417349438' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/1410489783417349438'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/1410489783417349438'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/11/svn-dump-stderr-output-causing-false.html' title='svn dump stderr output causing false positives'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-8503413574020472618</id><published>2011-11-28T12:30:00.000-08:00</published><updated>2011-12-07T09:24:35.669-08:00</updated><title type='text'>Jasper Reports Excel Cross Sheet Formulas</title><content type='html'>A member of the Data team brought my attention to an iReports bug affecting Excel Output containing formulas which reference a following sheet. If the formula is let us say referring Sheet1 from Sheet2 there is no problem but if it refers Sheet2 from Sheet1 Jasper will fail to generate a correct XLS file. This is not the case when we try to generate an XLSX file. In that case it does work as expected.&lt;br /&gt;&lt;br /&gt;When running with "Excel 2007 (XLSX) Preview" the cell does get what we expect:&lt;br /&gt;&lt;pre class="brush: vb"&gt;=mySheet2!A1&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;However when we pick "XLS Preview" we get:&lt;br /&gt;&lt;pre class="brush: vb"&gt;=#REF!A1&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here is the screen shot showing a successful rendering when using XLSX:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-PAcwH5glNTg/TtPmJ60cDTI/AAAAAAAADh0/lZ-zx1vGsdw/s1600/Screen%2Bshot%2B2011-11-28%2Bat%2B2.20.41%2BPM.png" imageanchor="1" style=""&gt;&lt;img border="0" height="253" width="383" src="http://1.bp.blogspot.com/-PAcwH5glNTg/TtPmJ60cDTI/AAAAAAAADh0/lZ-zx1vGsdw/s400/Screen%2Bshot%2B2011-11-28%2Bat%2B2.20.41%2BPM.png" /&gt;&lt;/a&gt;&lt;/div&gt;Here is the failure when using XLS:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-gyZHQ8ipCeQ/TtPqefbbG9I/AAAAAAAADiQ/CfFqn9JKY4A/s1600/Screen%2Bshot%2B2011-11-28%2Bat%2B2.19.50%2BPM.png" imageanchor="1" style=""&gt;&lt;img border="0" height="253" width="365" src="http://2.bp.blogspot.com/-gyZHQ8ipCeQ/TtPqefbbG9I/AAAAAAAADiQ/CfFqn9JKY4A/s400/Screen%2Bshot%2B2011-11-28%2Bat%2B2.19.50%2BPM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;This is clearly a problem related to different exporters. I still need to test this from Java with different exporter options but it would be ideal if a simple cross sheets formula like this could work from either XLS or XLSX files.&lt;br /&gt;&lt;br /&gt;Below is the source code for this report. I have picked one &lt;a href="http://jasperforge.org/plugins/espforum/view.php?group_id=102&amp;forumid=103&amp;topicid=42364#42435"&gt;jrxm posted in forums&lt;/a&gt; and I have added a couple of lines just to make sure I generate two different spreadsheets.&lt;br /&gt;&lt;br /&gt;The output contains two sheets which reference each other through a simple formula that brings the content of the A1 cell from one sheet to the other in both directions. As stated at the beginning the formula in Sheet1 will fail when exporting to XLS but will succeed when exporting to XLSX. The formula in Sheet 2 will always succeed. &lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;&amp;lt;jasperReport xmlns=&amp;quot;http://jasperreports.sourceforge.net/jasperreports&amp;quot; xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot; xsi:schemaLocation=&amp;quot;http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd&amp;quot; name=&amp;quot;formulaSample&amp;quot; pageWidth=&amp;quot;595&amp;quot; pageHeight=&amp;quot;842&amp;quot; whenNoDataType=&amp;quot;AllSectionsNoDetail&amp;quot; columnWidth=&amp;quot;535&amp;quot; leftMargin=&amp;quot;0&amp;quot; rightMargin=&amp;quot;0&amp;quot; topMargin=&amp;quot;0&amp;quot; bottomMargin=&amp;quot;0&amp;quot;&amp;gt;&lt;br /&gt; &amp;lt;property name=&amp;quot;net.sf.jasperreports.export.xls.detect.cell.type&amp;quot; value=&amp;quot;true&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;property name=&amp;quot;net.sf.jasperreports.export.xls.one.page.per.sheet&amp;quot; value=&amp;quot;true&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;property name=&amp;quot;net.sf.jasperreports.export.xls.sheet.names.all values&amp;quot; value=&amp;quot;mySheet1/mySheet2&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;property name=&amp;quot;ireport.zoom&amp;quot; value=&amp;quot;1.0&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;property name=&amp;quot;ireport.x&amp;quot; value=&amp;quot;0&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;property name=&amp;quot;ireport.y&amp;quot; value=&amp;quot;0&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;import value=&amp;quot;net.sf.jasperreports.engine.*&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;import value=&amp;quot;java.util.*&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;import value=&amp;quot;net.sf.jasperreports.engine.data.*&amp;quot;/&amp;gt;&lt;br /&gt; &amp;lt;title&amp;gt;&lt;br /&gt;  &amp;lt;band height=&amp;quot;262&amp;quot; splitType=&amp;quot;Stretch&amp;quot;&amp;gt;&lt;br /&gt;   &amp;lt;textField&amp;gt;&lt;br /&gt;    &amp;lt;reportElement key=&amp;quot;textField-1&amp;quot; x=&amp;quot;0&amp;quot; y=&amp;quot;0&amp;quot; width=&amp;quot;68&amp;quot; height=&amp;quot;23&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;textElement/&amp;gt;&lt;br /&gt;    &amp;lt;textFieldExpression&amp;gt;&amp;lt;![CDATA[new Integer(2)]]&amp;gt;&amp;lt;/textFieldExpression&amp;gt;&lt;br /&gt;   &amp;lt;/textField&amp;gt;&lt;br /&gt;   &amp;lt;textField&amp;gt;&lt;br /&gt;    &amp;lt;reportElement key=&amp;quot;textField-2&amp;quot; x=&amp;quot;0&amp;quot; y=&amp;quot;23&amp;quot; width=&amp;quot;68&amp;quot; height=&amp;quot;23&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;textElement/&amp;gt;&lt;br /&gt;    &amp;lt;textFieldExpression&amp;gt;&amp;lt;![CDATA[new Integer(1)]]&amp;gt;&amp;lt;/textFieldExpression&amp;gt;&lt;br /&gt;   &amp;lt;/textField&amp;gt;&lt;br /&gt;   &amp;lt;textField&amp;gt;&lt;br /&gt;    &amp;lt;reportElement key=&amp;quot;textField-3&amp;quot; x=&amp;quot;0&amp;quot; y=&amp;quot;46&amp;quot; width=&amp;quot;68&amp;quot; height=&amp;quot;23&amp;quot;&amp;gt;&lt;br /&gt;     &amp;lt;propertyExpression name=&amp;quot;net.sf.jasperreports.export.xls.formula&amp;quot;&amp;gt;&amp;lt;![CDATA[&amp;quot;mySheet2!A1&amp;quot;]]&amp;gt;&amp;lt;/propertyExpression&amp;gt;&lt;br /&gt;    &amp;lt;/reportElement&amp;gt;&lt;br /&gt;    &amp;lt;textElement/&amp;gt;&lt;br /&gt;    &amp;lt;textFieldExpression&amp;gt;&amp;lt;![CDATA[]]&amp;gt;&amp;lt;/textFieldExpression&amp;gt;&lt;br /&gt;   &amp;lt;/textField&amp;gt;&lt;br /&gt;   &amp;lt;break&amp;gt;&lt;br /&gt;    &amp;lt;reportElement x=&amp;quot;0&amp;quot; y=&amp;quot;116&amp;quot; width=&amp;quot;1&amp;quot; height=&amp;quot;1&amp;quot;/&amp;gt;&lt;br /&gt;   &amp;lt;/break&amp;gt;&lt;br /&gt;   &amp;lt;textField&amp;gt;&lt;br /&gt;    &amp;lt;reportElement key=&amp;quot;textField-3&amp;quot; x=&amp;quot;0&amp;quot; y=&amp;quot;163&amp;quot; width=&amp;quot;68&amp;quot; height=&amp;quot;23&amp;quot;&amp;gt;&lt;br /&gt;     &amp;lt;propertyExpression name=&amp;quot;net.sf.jasperreports.export.xls.formula&amp;quot;&amp;gt;&amp;lt;![CDATA[&amp;quot;mySheet1!A1&amp;quot;]]&amp;gt;&amp;lt;/propertyExpression&amp;gt;&lt;br /&gt;    &amp;lt;/reportElement&amp;gt;&lt;br /&gt;    &amp;lt;textElement/&amp;gt;&lt;br /&gt;    &amp;lt;textFieldExpression&amp;gt;&amp;lt;![CDATA[]]&amp;gt;&amp;lt;/textFieldExpression&amp;gt;&lt;br /&gt;   &amp;lt;/textField&amp;gt;&lt;br /&gt;   &amp;lt;textField&amp;gt;&lt;br /&gt;    &amp;lt;reportElement key=&amp;quot;textField-2&amp;quot; x=&amp;quot;0&amp;quot; y=&amp;quot;140&amp;quot; width=&amp;quot;68&amp;quot; height=&amp;quot;23&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;textElement/&amp;gt;&lt;br /&gt;    &amp;lt;textFieldExpression&amp;gt;&amp;lt;![CDATA[new Integer(9)]]&amp;gt;&amp;lt;/textFieldExpression&amp;gt;&lt;br /&gt;   &amp;lt;/textField&amp;gt;&lt;br /&gt;   &amp;lt;textField&amp;gt;&lt;br /&gt;    &amp;lt;reportElement key=&amp;quot;textField-1&amp;quot; x=&amp;quot;0&amp;quot; y=&amp;quot;117&amp;quot; width=&amp;quot;68&amp;quot; height=&amp;quot;23&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;textElement/&amp;gt;&lt;br /&gt;    &amp;lt;textFieldExpression&amp;gt;&amp;lt;![CDATA[new Integer(7)]]&amp;gt;&amp;lt;/textFieldExpression&amp;gt;&lt;br /&gt;   &amp;lt;/textField&amp;gt;&lt;br /&gt;  &amp;lt;/band&amp;gt;&lt;br /&gt; &amp;lt;/title&amp;gt;&lt;br /&gt;&amp;lt;/jasperReport&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Note that this example contains a useful hint that allows to quickly come up with a test case that can be shared with the community. This is just using java.lang.* package to include hard coded values in text fields (that later become Excel cells) without the need to having a datasource defined (Use "Empty Datasource" to run this jrxml)&lt;br /&gt;&lt;br /&gt;Last but not least we set the property "whenNoDataType=&amp;quot;AllSectionsNoDetail&amp;quot;" because we are not using any datasource. If you are rendering the jrxml with no datasource from Java you will need it otherwise you will just get blank pages or corrupted XML files depending on the options you use.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-8503413574020472618?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/8503413574020472618/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=8503413574020472618' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8503413574020472618'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8503413574020472618'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/11/jasper-reports-excel-cross-sheet.html' title='Jasper Reports Excel Cross Sheet Formulas'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-PAcwH5glNTg/TtPmJ60cDTI/AAAAAAAADh0/lZ-zx1vGsdw/s72-c/Screen%2Bshot%2B2011-11-28%2Bat%2B2.20.41%2BPM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-5843863192616705878</id><published>2011-11-23T14:24:00.000-08:00</published><updated>2011-11-23T14:24:12.725-08:00</updated><title type='text'>The plugin 'org.apache.maven.plugins:maven-jetty-plugin' does not exist or no valid version could be found</title><content type='html'>This issue happens when the repository does reach the Mortbay release repo http://jetty.mortbay.org/maven2/release so to be safe I always add the below to the build/plugins node in the pom.xml.&lt;br /&gt;&lt;br /&gt; &lt;pre class="brush: xml"&gt;&amp;lt;plugin&amp;gt;&lt;br /&gt;        &amp;lt;groupId&amp;gt;org.mortbay.jetty&amp;lt;/groupId&amp;gt;&lt;br /&gt;        &amp;lt;artifactId&amp;gt;maven-jetty-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;        &amp;lt;version&amp;gt;6.0.1&amp;lt;/version&amp;gt;&lt;br /&gt;      &amp;lt;/plugin&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-5843863192616705878?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/5843863192616705878/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=5843863192616705878' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5843863192616705878'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5843863192616705878'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/11/plugin-orgapachemavenpluginsmaven-jetty.html' title='The plugin &apos;org.apache.maven.plugins:maven-jetty-plugin&apos; does not exist or no valid version could be found'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-3077412057928797593</id><published>2011-11-22T13:44:00.000-08:00</published><updated>2011-11-22T13:44:39.775-08:00</updated><title type='text'>SWFUpload plugin in Self Signed Certificate websites</title><content type='html'>When you are developing you will not buy an SSL certificate but instead you will use a self signed one. Adobe Flash is used to provide cool multiple file upload capabilities through the use of the SWFPlugin but it will fail if the Certificate is self signed.&lt;br /&gt;&lt;br /&gt;This was affecting our QA team today and thanks to &lt;a href="http://swfupload.org/forum/generaldiscussion/347"&gt;this&lt;/a&gt; post I figured a solution for the problem:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;In IE double click the "Certificate Error" icon&lt;/li&gt;&lt;li&gt;Pick Install Certificate. Pick the defaults and finish&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;For some people this was not working still so I suggested to restart browsers and even Windows, you know that kind of stuff you need to do when suspicious right? At least one of my team members reported cleaning cookies resolved the problem for him (of course after following the above steps)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-3077412057928797593?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/3077412057928797593/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=3077412057928797593' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/3077412057928797593'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/3077412057928797593'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/11/swfupload-plugin-in-self-signed.html' title='SWFUpload plugin in Self Signed Certificate websites'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-1092369950798236308</id><published>2011-11-22T09:00:00.000-08:00</published><updated>2011-11-22T09:00:42.702-08:00</updated><title type='text'>Cannot determine realm for numeric host address</title><content type='html'>I could not connect today to one of our servers using password less public key ssh login. Using -vvv ssh option would show:&lt;br /&gt;&lt;pre class="brush: bash"&gt;debug1: An invalid name was supplied &lt;br /&gt;Cannot determine realm for numeric host address &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So from the Red Hat target I inspected the secure log:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ tail -f /var/log/secure&lt;br /&gt;...&lt;br /&gt;Nov 22 11:37:03 t2215179 sshd[23123]: Authentication tried for admin with correct key but not from a permitted host (host=tools, ip=xxx.xxx.xxx.xxx).&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This was resolved adding the IP to /etc/hosts&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ tail -f /var/log/secure&lt;br /&gt;...&lt;br /&gt;xxx.xxx.xxx.xxx tools&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-1092369950798236308?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/1092369950798236308/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=1092369950798236308' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/1092369950798236308'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/1092369950798236308'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/11/cannot-determine-realm-for-numeric-host.html' title='Cannot determine realm for numeric host address'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-1752163823921123334</id><published>2011-11-18T05:09:00.000-08:00</published><updated>2011-11-18T05:09:57.074-08:00</updated><title type='text'>Android on the HP Touchpad</title><content type='html'>Just follow instructions from &lt;a href="http://rootzwiki.com/topic/3477-releasealpha2discussion-cyanogenmod-team-touchpad-port/"&gt;cyanogenmod touchpad port team&lt;/a&gt;, Download section. Be sure you install everything including ClockworkMod,&lt;br /&gt;&lt;br /&gt;Then install GoogleApps (http://wiki.cyanogenmod.com/index.php?title=Latest_Version/Google_Apps) so you get Market application:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Boot into Android mode&lt;/li&gt;&lt;li&gt;Connect TouchPad to Computer&lt;/li&gt;&lt;li&gt;Touch the notification icon so you are able to turn on USB&lt;/li&gt;&lt;li&gt;From the computer drop the GoogleApps zip file (for example gapps-gb-20110828-signed.zip) into the TouchPad&lt;/li&gt;&lt;li&gt;Reboot the TouchPad (Power + Home buttons for 15-20sec will do it in case just Power does not give you the reboot option)&lt;/li&gt;&lt;li&gt;In the boot menu quickly move using the volume keys to option "ClockworkMod". Press Home button to accept&lt;/li&gt;&lt;li&gt;choose "install zip from sdcard", then "choose zip from sdcard" and pick the GoogleApps zip file.&lt;/li&gt;&lt;li&gt;Reboot&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-1752163823921123334?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/1752163823921123334/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=1752163823921123334' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/1752163823921123334'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/1752163823921123334'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/11/android-on-hp-touchpad.html' title='Android on the HP Touchpad'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-5845960094081831019</id><published>2011-11-17T11:37:00.000-08:00</published><updated>2011-11-17T11:37:56.526-08:00</updated><title type='text'>Hide link or anchor after click with jquery</title><content type='html'>This is a common need when you provide what I call a naked user interface meaning, well I am waiting for my front end engineer and I provide just the bare minimums so the whole functionality can be used. But this is also a need for complicated user interfaces where sometimes a link is just supposed to trigger something, you want to stay in the page and you do not want the link to be available again (avoiding double submission for example).&lt;br /&gt;&lt;br /&gt;So here is the JQuery code that will keep the link text but will change it by just a spanned text:&lt;br /&gt;&lt;pre class="brush: js"&gt;    //&lt;br /&gt;    // Hide link after click&lt;br /&gt;    //&lt;br /&gt;    $('.hideAfterClickLink').click(function(e) {&lt;br /&gt;     $(this).replaceWith('&lt;span&gt;' + $(this).text() + '&lt;/span&gt;')&lt;br /&gt;    });&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-5845960094081831019?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/5845960094081831019/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=5845960094081831019' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5845960094081831019'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5845960094081831019'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/11/hide-link-or-anchor-after-click-with.html' title='Hide link or anchor after click with jquery'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-5381496912491085747</id><published>2011-11-11T14:12:00.000-08:00</published><updated>2011-11-18T18:04:26.720-08:00</updated><title type='text'>Tomcat Servlet Context initialized twice or multiple times</title><content type='html'>Tomcat Servlet Context initialized twice or multiple times&lt;br /&gt;I have seen this issue both in JBoss and Tomcat and in both cases this is about a miss configuration. It is worth to be said this issue affects the agility of the developer because extra time needs to be spent just waiting for tomcat to finish its initialization. This is also an issue that hits production systems many of which have very slow startup just because of miss configurations.&lt;br /&gt;&lt;br /&gt;The first thing that you need to do is to include a log INFO level message when the servlet context is initialized like below:&lt;br /&gt;&lt;pre class="brush: java"&gt;public class ApplicationServletContextListener implements&lt;br /&gt;        ServletContextListener {&lt;br /&gt;...&lt;br /&gt;    Logger log = LoggerFactory.getLogger(ApplicationServletContextListener.class.getName());&lt;br /&gt;    @Override&lt;br /&gt;    public void contextInitialized(ServletContextEvent event) {&lt;br /&gt;       ...&lt;br /&gt;       log.info("Servlet Context initialized");&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now whenever you restart your server you should get only one line:&lt;br /&gt;&lt;pre class="brush: bash"&gt;2011-11-11 16:29:38,027 INFO [com.nestorurquiza.utils.web.ApplicationServletContextListener] -   Servlet Context initialized&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If you get more than one look for errors in server.xml. Just to show an example of something that actually hit me Today this was the configuration in one of my servers:&lt;br /&gt;&lt;pre class="brush: xml"&gt;...&lt;br /&gt;&amp;lt;Host name=&amp;quot;bhubdev.nestorurquiza.com&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;  &amp;lt;Context path=&amp;quot;&amp;quot; docBase=&amp;quot;/opt/tomcat/webapps/nestorurquiza-app&amp;quot;/&amp;gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Just changing for the below fixed the issue:&lt;br /&gt;&lt;pre class="brush: xml"&gt;...&lt;br /&gt;&amp;lt;Host name=&amp;quot;bhubdev.nestorurquiza.com&amp;quot; appBase=&amp;quot;webapps/nestorurquiza-app&amp;quot;/&amp;gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;However it also screwed the root context you get with the previous configuration. If you still need the application to respond to the root context you will need to deployed as ROOT.war as explained in the &lt;a href="http://tomcat.apache.org/tomcat-7.0-doc/virtual-hosting-howto.html"&gt;official documentation&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-5381496912491085747?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/5381496912491085747/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=5381496912491085747' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5381496912491085747'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5381496912491085747'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/11/tomcat-servlet-context-initialized.html' title='Tomcat Servlet Context initialized twice or multiple times'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-6832449122994001999</id><published>2011-11-10T09:44:00.000-08:00</published><updated>2011-11-10T09:44:36.729-08:00</updated><title type='text'>Excel Jasper Report with default Zoom using a Macro</title><content type='html'>I could not find a way to specify a default zoom for an Excel Report generated from iReport/Jasper Reports so I had to apply the &lt;a href="http://thinkinginsoftware.blogspot.com/2011/11/excel-jasper-reports-with-macros.html"&gt;Macro hack I recently documented&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Here is the Visual Basic for Applications code that make this possible. It zoom out the whole workbook to 75%:&lt;br /&gt;&lt;pre class="brush: vb"&gt;Private Sub Workbook_Open()&lt;br /&gt;    If Application.Sheets.Count &gt; 1 Then&lt;br /&gt;        Worksheets("Macro").Visible = False&lt;br /&gt;    End If&lt;br /&gt;    Application.ScreenUpdating = False&lt;br /&gt;&lt;br /&gt;    For Each ws In Worksheets&lt;br /&gt;        ws.Activate&lt;br /&gt;        ActiveWindow.Zoom = 75&lt;br /&gt;    Next&lt;br /&gt;&lt;br /&gt;    Application.ScreenUpdating = True&lt;br /&gt;End Sub&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-6832449122994001999?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/6832449122994001999/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=6832449122994001999' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6832449122994001999'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6832449122994001999'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/11/excel-jasper-report-with-default-zoom.html' title='Excel Jasper Report with default Zoom using a Macro'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-185486221094591371</id><published>2011-11-09T14:46:00.000-08:00</published><updated>2011-11-10T10:30:32.638-08:00</updated><title type='text'>Excel Jasper Reports With Macros</title><content type='html'>I am a big fan of separation of concerns. Did I say this before? :-)&lt;br /&gt;&lt;br /&gt;When it comes to View technologies there is a miss conception to think about View part as "passive" when in reality we have seen "active" Views for years with Javascript in the center of our HTML pages. Well, the same applies for PDF and Excel.&lt;br /&gt;&lt;br /&gt;So I don't understand why providing Macro support in a Jasper Report should be considered wrong and since especially in the Financial Industry Excel is a core technology where Macros are simply needed I decided to present here a solution to provide Excel Jasper Reports containing Macros.&lt;br /&gt;&lt;br /&gt;Some times the macro is needed by business while other times the macro can save time with formatting features that are unavailable in the iReport/Jasper last version. For the second case I do recommend to fill a feature request because it is a hack to force a formatting capability using a Macro and your users might not be OK with code running in their Excel which imposes a big security concern. But for sure this hack allows to move while keeping separation of concerns, in other words liberating Java developers from the need to touch code, recompile and redeploy (The Controller) just to fulfill a need in formatting that is not available where it should be available (The View).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Create an Excel Workbook containing the Macro to be applied to the Excel Jasper Report&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;Create a brand new Excel Workbook&lt;/li&gt;&lt;li&gt;Rename Sheet1 as 'Macro' and delete the rest of them&lt;/li&gt;&lt;li&gt;Select from the Menu: Tools | Macro | Visual Basic Editor&lt;/li&gt;&lt;li&gt;Right click on "This Workbook" and select "View Code". Paste the below in the code pane:&lt;br /&gt;&lt;pre class="brush: bash"&gt;Private Sub Workbook_Open()&lt;br /&gt;    If Application.Sheets.Count &gt; 1 Then&lt;br /&gt;        Worksheets("Macro").Visible = False&lt;br /&gt;    End If&lt;br /&gt;    MsgBox "Hello Excel Jasper Report!"&lt;br /&gt;End Sub&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Save the Workbook as "myTemplate.xls", close and open. A pop up should come up automatically. This code as you already noticed has a hack which basically allows to hide the unique sheet we are forced to leave in the workbook because of an Excel imposed limitation.&lt;/li&gt;&lt;li&gt;Now we are ready to use myTemplate.xls as the template out of which our Jasper Report will be built.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;h3&gt;Use the Excel Template from jasper API&lt;/h3&gt;First, you will need to extend the exporter. I am showing here how to do it with JRXlsExporter which uses POI API but the same idea should be applicable to other exporters.&lt;br /&gt;&lt;pre class="brush: java"&gt;package com.nestorurquiza.utils;&lt;br /&gt;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.io.InputStream;&lt;br /&gt;import java.io.OutputStream;&lt;br /&gt;&lt;br /&gt;import net.sf.jasperreports.engine.export.JRXlsExporter;&lt;br /&gt;&lt;br /&gt;import org.apache.poi.hssf.usermodel.HSSFWorkbook;&lt;br /&gt;import org.apache.poi.hssf.util.HSSFColor;&lt;br /&gt;import org.slf4j.Logger;&lt;br /&gt;import org.slf4j.LoggerFactory;&lt;br /&gt;&lt;br /&gt;public class CustomJRXlsExporter extends JRXlsExporter {&lt;br /&gt;    private static final Logger log = LoggerFactory.getLogger(CustomJRXlsExporter.class);&lt;br /&gt;    &lt;br /&gt;    private InputStream is;&lt;br /&gt;    &lt;br /&gt;    public InputStream getIs() {&lt;br /&gt;        return is;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public void setIs(InputStream is) {&lt;br /&gt;        this.is = is;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    protected void openWorkbook(OutputStream os) {&lt;br /&gt;        if(is != null) {&lt;br /&gt;            try {&lt;br /&gt;                workbook = new HSSFWorkbook(is);&lt;br /&gt;                emptyCellStyle = workbook.createCellStyle();&lt;br /&gt;                emptyCellStyle.setFillForegroundColor((new HSSFColor.WHITE()).getIndex());&lt;br /&gt;                emptyCellStyle.setFillPattern(backgroundMode);&lt;br /&gt;                dataFormat = workbook.createDataFormat();&lt;br /&gt;                createHelper = workbook.getCreationHelper();&lt;br /&gt;                return;&lt;br /&gt;            } catch (IOException e) {&lt;br /&gt;                log.error("Creating a new Workbook when I was supposed to use an existing one.", e);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        super.openWorkbook(os);&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Second just use the Custom Exporter passing the input stream to your Excel Template file.&lt;br /&gt;&lt;pre class="brush: bash"&gt;...&lt;br /&gt;CustomJRXlsExporter exporter = new CustomJRXlsExporter();&lt;br /&gt;String xlsTemplate = ctx.getParameter("xlsTemplate");&lt;br /&gt;if( xlsTemplate != null ) {&lt;br /&gt;    String xlsTemplateFilePath = getReportsPath() + "/xlsTemplate/" + ctx.getParameter("xlsTemplate");&lt;br /&gt;    exporter.setIs(new FileInputStream(xlsTemplateFilePath));&lt;br /&gt;}&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You end up with your report and a message box popping up from the macro code "Hello Excel Jasper Report!". &lt;br /&gt;&lt;br /&gt;A more realistic example? I will probably start documenting some of them in the near future so search this blog for jasper excel macro. &lt;br /&gt;&lt;br /&gt;This technique is simple but powerful. I hope it is integrated somehow in the different exporters as &lt;a href="http://jasperforge.org/plugins/mantis/view.php?id=2363"&gt;it was proposed 5 years ago&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;As I have said there I think iReport should be able to use a hint so the front end report designer can get the formatting they want using an Excel Template without the need to wait for an external API implementation.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-185486221094591371?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/185486221094591371/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=185486221094591371' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/185486221094591371'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/185486221094591371'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/11/excel-jasper-reports-with-macros.html' title='Excel Jasper Reports With Macros'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-1864393260034309437</id><published>2011-11-07T09:17:00.000-08:00</published><updated>2011-11-07T09:17:45.170-08:00</updated><title type='text'>error running shared postrotate script for /var/log/mysql</title><content type='html'>I am sure the error below can be related to many different circumstances:&lt;br /&gt;&lt;pre class="brush: bash"&gt;/etc/cron.daily/logrotate:&lt;br /&gt;error: error running shared postrotate script for '/var/log/mysql.log /var/log/mysql/mysql.log /var/log/mysql/mysql-slow.log '&lt;br /&gt;run-parts: /etc/cron.daily/logrotate exited with return code 1&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In my case after cloning an ESX Ubuntu VM where MySQL was installed into some other servers where we did not need mysql and after uninstalling mysql I could still see mysqld running.&lt;br /&gt;&lt;br /&gt;Just running the below command was enough to stop the alert:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ sudo apt-get autoremove&lt;br /&gt;Reading package lists... Done&lt;br /&gt;Building dependency tree       &lt;br /&gt;Reading state information... Done&lt;br /&gt;The following packages will be REMOVED:&lt;br /&gt;  mysql-server-5.1 mysql-server-core-5.1&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-1864393260034309437?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/1864393260034309437/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=1864393260034309437' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/1864393260034309437'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/1864393260034309437'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/11/error-running-shared-postrotate-script.html' title='error running shared postrotate script for /var/log/mysql'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-7514761094047616376</id><published>2011-11-06T23:06:00.000-08:00</published><updated>2011-11-07T05:42:18.209-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='switchuser impersonate doasuser spring ldap'/><title type='text'>User impersonation with Spring and LDAP</title><content type='html'>Impersonating a user (also known as "switch a user" or "do as user") in a web application is an important feature for doing Quality Assurance (QA) or any Manual User Acceptance Test (UAT).&lt;br /&gt;&lt;br /&gt;With Spring you can achieve it using the SwitchUserFilter. If you are using LDAP here are the relevant pieces.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: xml"&gt;...&lt;br /&gt;&amp;lt;beans:bean id=&amp;quot;ldapUserDetailsService&amp;quot; class=&amp;quot;org.springframework.security.ldap.userdetails.LdapUserDetailsService&amp;quot;&amp;gt;&lt;br /&gt;  &amp;lt;beans:constructor-arg&amp;gt;&amp;lt;beans:ref bean=&amp;quot;ldapUserSearch&amp;quot;/&amp;gt;&amp;lt;/beans:constructor-arg&amp;gt;&lt;br /&gt;  &amp;lt;beans:constructor-arg&amp;gt;&amp;lt;beans:ref bean=&amp;quot;ldapAuthoritiesPopulator&amp;quot;/&amp;gt;&amp;lt;/beans:constructor-arg&amp;gt;&lt;br /&gt;  &amp;lt;beans:property name=&amp;quot;userDetailsMapper&amp;quot; ref=&amp;quot;customUserDetailsContextMapper&amp;quot; /&amp;gt;&lt;br /&gt;&amp;lt;/beans:bean&amp;gt;&lt;br /&gt;...&lt;br /&gt;&amp;lt;beans:bean id=&amp;quot;ldapUserSearch&amp;quot; class=&amp;quot;org.springframework.security.ldap.search.FilterBasedLdapUserSearch&amp;quot;&amp;gt;&lt;br /&gt;    &amp;lt;beans:constructor-arg type=&amp;quot;String&amp;quot;&amp;gt;&amp;lt;beans:value&amp;gt;ou=people,o=nestorurquiza&amp;lt;/beans:value&amp;gt;&amp;lt;/beans:constructor-arg&amp;gt;&lt;br /&gt;    &amp;lt;beans:constructor-arg type=&amp;quot;String&amp;quot;&amp;gt;&amp;lt;beans:value&amp;gt;mail={0}&amp;lt;/beans:value&amp;gt;&amp;lt;/beans:constructor-arg&amp;gt;&lt;br /&gt;    &amp;lt;beans:constructor-arg&amp;gt;&amp;lt;beans:ref bean=&amp;quot;ldapContextSource&amp;quot;/&amp;gt;&amp;lt;/beans:constructor-arg&amp;gt;&lt;br /&gt;&amp;lt;/beans:bean&amp;gt;&lt;br /&gt; ...                            &lt;br /&gt;&amp;lt;beans:bean id=&amp;quot;switchUserProcessingFilter&amp;quot; class=&amp;quot;org.springframework.security.web.authentication.switchuser.SwitchUserFilter&amp;quot;&amp;gt;&lt;br /&gt;        &amp;lt;beans:property name=&amp;quot;userDetailsService&amp;quot; ref=&amp;quot;ldapUserDetailsService&amp;quot; /&amp;gt;&lt;br /&gt;        &amp;lt;beans:property name=&amp;quot;switchUserUrl&amp;quot; value=&amp;quot;/admin/switchUser&amp;quot; /&amp;gt;&lt;br /&gt;        &amp;lt;beans:property name=&amp;quot;exitUserUrl&amp;quot; value=&amp;quot;/admin/switchUserExit&amp;quot; /&amp;gt;&lt;br /&gt;        &amp;lt;beans:property name=&amp;quot;targetUrl&amp;quot; value=&amp;quot;/&amp;quot; /&amp;gt;&lt;br /&gt;&amp;lt;/beans:bean&amp;gt;&lt;br /&gt;...&lt;br /&gt;&amp;lt;http auto-config=&amp;quot;true&amp;quot; use-expressions=&amp;quot;true&amp;quot; access-decision-manager-ref=&amp;quot;accessDecisionManager&amp;quot; disable-url-rewriting=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;    ...&lt;br /&gt; &amp;lt;custom-filter  after=&amp;quot;FILTER_SECURITY_INTERCEPTOR&amp;quot; ref=&amp;quot;switchUserProcessingFilter&amp;quot; /&amp;gt;&lt;br /&gt;    ...&lt;br /&gt;    &amp;lt;intercept-url pattern=&amp;quot;/admin/**&amp;quot; access=&amp;quot;hasAnyRole('ROLE_ADMIN','ROLE_PREVIOUS_ADMINISTRATOR')&amp;quot; /&amp;gt;&lt;br /&gt;... &lt;br /&gt;&amp;lt;beans:bean id=&amp;quot;ldapContextSource&amp;quot; class=&amp;quot;org.springframework.ldap.core.support.LdapContextSource&amp;quot;&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;beans:property name=&amp;quot;url&amp;quot; value=&amp;quot;${ldap.url}&amp;quot; /&amp;gt;&lt;br /&gt;    &amp;lt;beans:property name=&amp;quot;userDn&amp;quot; value=&amp;quot;${ldap.userDn}&amp;quot; /&amp;gt;&lt;br /&gt;    &amp;lt;beans:property name=&amp;quot;password&amp;quot; value=&amp;quot;${ldap.password}&amp;quot; /&amp;gt;&lt;br /&gt;&amp;lt;/beans:bean&amp;gt;&lt;br /&gt;...&lt;br /&gt;&amp;lt;beans:bean id=&amp;quot;ldapAuthProvider&amp;quot;&lt;br /&gt;        class=&amp;quot;org.springframework.security.ldap.authentication.LdapAuthenticationProvider&amp;quot;&amp;gt;&lt;br /&gt;  &amp;lt;beans:constructor-arg&amp;gt;&lt;br /&gt;    &amp;lt;beans:bean class=&amp;quot;org.springframework.security.ldap.authentication.BindAuthenticator&amp;quot;&amp;gt;&lt;br /&gt;      &amp;lt;beans:constructor-arg ref=&amp;quot;ldapContextSource&amp;quot;/&amp;gt;&lt;br /&gt;      &amp;lt;beans:property name=&amp;quot;userDnPatterns&amp;quot;&amp;gt;&lt;br /&gt;        &amp;lt;beans:list&amp;gt;&amp;lt;beans:value&amp;gt;mail={0},ou=people,o=nestorurquiza&amp;lt;/beans:value&amp;gt;&amp;lt;/beans:list&amp;gt;&lt;br /&gt;      &amp;lt;/beans:property&amp;gt;&lt;br /&gt;    &amp;lt;/beans:bean&amp;gt;&lt;br /&gt;  &amp;lt;/beans:constructor-arg&amp;gt;&lt;br /&gt;  &amp;lt;beans:constructor-arg ref=&amp;quot;ldapAuthoritiesPopulator&amp;quot;/&amp;gt;&lt;br /&gt;&amp;lt;/beans:bean&amp;gt;&lt;br /&gt;...&lt;br /&gt;&amp;lt;beans:bean id=&amp;quot;ldapAuthoritiesPopulator&amp;quot; class=&amp;quot;org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator&amp;quot;&amp;gt;&lt;br /&gt;      &amp;lt;beans:constructor-arg ref=&amp;quot;ldapContextSource&amp;quot;/&amp;gt;&lt;br /&gt;      &amp;lt;beans:constructor-arg value=&amp;quot;ou=groups,o=nestorurquiza&amp;quot;/&amp;gt;&lt;br /&gt;      &amp;lt;beans:property name=&amp;quot;groupSearchFilter&amp;quot; value=&amp;quot;uniquemember={0}&amp;quot; /&amp;gt;&lt;br /&gt;&amp;lt;/beans:bean&amp;gt;&lt;br /&gt;...    &lt;br /&gt;&amp;lt;beans:bean id=&amp;quot;ldapTemplate&amp;quot; class=&amp;quot;org.springframework.ldap.core.LdapTemplate&amp;quot;&amp;gt;&lt;br /&gt;    &amp;lt;beans:constructor-arg ref=&amp;quot;ldapContextSource&amp;quot; /&amp;gt;&lt;br /&gt;&amp;lt;/beans:bean&amp;gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;I18n messages:&lt;br /&gt;&lt;pre class="brush: bash"&gt;switchUser=Impersonate&lt;br /&gt;switchUserExit=Be yourself&lt;br /&gt;&lt;/pre&gt;A link to impersonate in the users listing page should be available only for admins:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;security:authorize access=&amp;quot;hasRole('ROLE_ADMIN')&amp;quot;&amp;gt;&lt;br /&gt;                        | &amp;lt;a href=&amp;quot;&amp;lt;spring:url value=&amp;quot;/admin/switchUser?j_username=${employee.email}&amp;quot; htmlEscape=&amp;quot;true&amp;quot; /&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;                            &amp;lt;spring:message code=&amp;quot;switchUser&amp;quot;/&amp;gt;&amp;lt;/a&amp;gt;&lt;br /&gt;                &amp;lt;/security:authorize&amp;gt;&lt;br /&gt;&lt;/pre&gt;A link to switch back to admin user should be available only if the user is being impersonated:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;security:authorize access=&amp;quot;hasRole('ROLE_PREVIOUS_ADMINISTRATOR')&amp;quot;&amp;gt;&lt;br /&gt;                        | &amp;lt;a href=&amp;quot;&amp;lt;spring:url value=&amp;quot;/admin/switchUserExit&amp;quot; htmlEscape=&amp;quot;true&amp;quot; /&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;                            &amp;lt;spring:message code=&amp;quot;switchUserExit&amp;quot;/&amp;gt;&amp;lt;/a&amp;gt;&lt;br /&gt;                    &amp;lt;/security:authorize&amp;gt;&lt;br /&gt;&lt;/pre&gt;The way you use it is hitting the below URLs to switch the user and come back to the original admin user context:&lt;br /&gt;&lt;pre class="brush: bash"&gt;http://localhost:8080/nestorurquiza-app/admin/switchUser?j_username=nurquiza@nestorurquiza.com&lt;br /&gt;http://localhost:8080/nestorurquiza-app/admin/switchUserExit&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-7514761094047616376?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/7514761094047616376/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=7514761094047616376' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7514761094047616376'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7514761094047616376'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/11/user-impersonation-with-spring-and-ldap.html' title='User impersonation with Spring and LDAP'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-8366475634882295914</id><published>2011-10-31T17:43:00.000-07:00</published><updated>2011-10-31T17:43:36.013-07:00</updated><title type='text'>Spring One 2011 Notes</title><content type='html'>It was quite a week of learning with the folks from Spring Framework in the SpringOne Conference in Chicago.&lt;br /&gt;&lt;br /&gt;Not only I had the opportunity to hear Spring explained from the code committers but I managed to discuss some common architectural, managerial and leadership issues with other attendees.&lt;br /&gt;&lt;br /&gt;The amount of work Spring helps to solve could be enough for four weeks of conference so there were mainly 4 slices (or tracks) of very related conferences from which you had to pick just one. Here are some comments about those that I attended.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Javascript application engineering&lt;/h3&gt;It is clear javascript is now stronger that ever. It was a nice look into how to do Object Oriented Programming with Javascript, Inheritance through prototypes (Object.create(Object.prototype)), closures and more. Between others websites like Javascript weekly and hacker news were recommended to be followed up.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Spring Integration. Practical Tips and tricks&lt;/h3&gt;The Spring Integration project is an attempt to implement the patterns described in the book Enterprise Integration Patterns Book by Gregor Hohpe and Bobby Wolf&lt;br /&gt;It was a discussion about Consumers, Producers and the Publish/Subscribe pattern using AMQP channels and comparing them with JMS channels. Of course SOAP, REST and others are supported. Channel redundancy was presented Rabitt MQ and JSM. The project reminds me of ESB and indeed there was a mention to it. Basically Spring Integration is a different approach than ESB because there is no server needed but just patterns applied.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Messaging&lt;/h3&gt;Messaging was presented through the Rabbit MQ which is the reference implementation of the Advanced Messaging Queue Protocol (AMQP). It was even proposed to start sending messages between Controllers and Services to prepare the application to scale in the future. I have to say I won't buy into that even though for sure I would consider using it to implement future distributed needs in my projects. Messaging was presented as a way around problems inherent to the use of shared class loaders (OSGI). In reality that is what any distributed technique does after all, being more than anything a consequence of the paradigm in use.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Neo4J&lt;/h3&gt;This is a graph database and the presentation was useful especially to eliminate any of the ongoing discussions about what is the best database out there in the NoSQL domain. It is clear they all address different problems while of course there is more than one choice within each possible domain. Basically there is a trade between size and complexity and in that order we have different options like key-value, bigtable, document, graph.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Spring Insight Plugin Development&lt;/h3&gt;This is a project with a mission that sounds familiar: Inspect how the application is behaving performance wise. Classes are annotated with @InsightOperation and @InsightEndPoint to allow performance inspection: Basically how long the method gets to execute. In other words where the time is spent in the application. While this could be definitely useful in many scenarios we have tools doing this already so it looked to me like an effort to build a lightweight profiler focused on vFabric.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Spring Integration. Implementing Scalable Architectures with Spring Integration&lt;/h3&gt;A simple implementation based on a simple relational database locked row was presented to provide a messaging cluster implementation.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Spring Batch&lt;/h3&gt;In a glance this is a project to manipulate data. We learned how Spring Batch works while defining and instantiating jobs (Job and Job Instance) that basically act on data. I will not start a war about if &lt;a href="http://forum.springsource.org/showthread.php?70751-Batch-vs.-ETL"&gt;spring is better or worst that etl&lt;/a&gt; for this task. It is just another way of doing the same and depending on your architecture and team one will be favored over the other. I tend to separate concerns as much as possible  and I think data is better processed and tackle by data centric developers and existing tools. In my experience most of people dealing with Spring actually love to implement business rules that are not necessarily related to data intensive processing. But again this discussion should end here, there is not correct way just different ways.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Deliver Performance and scalability with ehcache and spring&lt;/h3&gt;JSR107 will standardize caching while some of us still use some &lt;a href="http://thinkinginsoftware.blogspot.com/2011/07/caching-with-spring-ehcache-and.html"&gt;caching api from Google code&lt;/a&gt; to cache data at method level. However terracotta ehCache and the new standard add more flexibility to caching. For simple caching you can use the Google code API but for more complicated situations you will use this approach which involves @Cacheable and @CacheEvict annotations. When the source code is not available @Cacheable can be used from XML which is great you will agree. AOP helps here to cache certain methods. Different providers like terracotta backed open source Ehcache offer different possibilities which go beyond the basis of the default ConcurrentMap implementation. Automatic Resource Control now in beta will allow ehacahe to use cache sizing in terms of bytes  or percentage besides the existing object count. Then of course you can even go for enterprise class monitoring with "terracotta Developer Console".&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Eventing Data with RabbitMQ and Riak&lt;/h3&gt;Riak was discussed as the unique NoSQL database that would allow hooking into the data insertion. This is of advantage to create a trigger for RabitMQ when new data gets stored. A very cool example was presented where WebSocket was used to notify the client directly from the server. Again the CAP theorem was discussed: You must pick one from Consistency, Availability and Performance triangle and that would ultimately drive your decision towards one database or another.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Where did my architecture go&lt;/h3&gt;It was disscused how to control the gap between architecture and codebase. Here you have code analysis with JDepend and Sonar, SonarGraph (formerly SonarJ). Suggestions were made about the usage of ServiceLoader or OSGI for externalizing class creation.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Tailoring Spring for Custom Usage&lt;/h3&gt;As we all know Spring is about DI, IOP and Enterprise Service Abstractions (libraries). This lecture went through each of the components giving real case utilization of them. We saw @Configuration and @Bean to provide configuration from Java code instead of XML, @Value to allow the injection of a system property as a string member of a class, Spring scopes to be used for example to eliminate the need to pass custom context objects from method to method (A technique that I still use in my projects for maintaining ControllerContext), even though Singleton and Prototype are the most common scopes there are several others like Request and Session, @Profile annotation to define production or development. The Spring expression language in general was introduced.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Improving Java with Groovy&lt;/h3&gt;There were several trcks of Groovy and Grails. I attended just this one from the latest slice because there was nothing for Spring Java available and even though it was basic stuff we saw examples that did work from an excellent demo showing how to use both Java and Groovy in your project. It was basically a practical presentation of the ideas behind the book "Making Java Groovy". Heavy use of closures showed how the code got smaller while avoiding Interfaces and Inner Classes.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-8366475634882295914?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/8366475634882295914/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=8366475634882295914' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8366475634882295914'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8366475634882295914'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/10/spring-one-2011-notes.html' title='Spring One 2011 Notes'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-1678193822828158997</id><published>2011-10-24T09:10:00.000-07:00</published><updated>2011-10-24T09:11:58.599-07:00</updated><title type='text'>Cron error email notification in Ubuntu</title><content type='html'>A member of the team realized one of the cron processes was failing but we were not getting any alerts. &lt;br /&gt;&lt;br /&gt;While I favor the use of &lt;a href="http://thinkinginsoftware.blogspot.com/2011/01/use-logmonitor-to-email-log-errors.html"&gt;LogMonitor&lt;/a&gt; for absolutely everything there are simple commands you use in cron jobs for which ideally you would like to get notifications without the need of logging.&lt;br /&gt;&lt;br /&gt;If you design your script carefully respecting the fact that error messages mut go to stderr this procedure should work:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Be sure sSMTP is installed and properly configured:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ sudo apt-get install ssmtp&lt;br /&gt;$ sudo vi /etc/ssmtp/ssmtp.conf&lt;br /&gt;...&lt;br /&gt;root=nurquiza@nestorurquiza.com&lt;br /&gt;...&lt;br /&gt;mailhub=mail.nestorurquiza.com&lt;br /&gt;..&lt;br /&gt;hostname=apache1&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Add a line at the beginning of crontab stating who the error email should me sent to:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ crontab -e &lt;br /&gt;MAILTO=alerts@nestorurquiza.com&lt;br /&gt;0 22 * * * /usr/sbin/sftp_backup.sh&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-1678193822828158997?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/1678193822828158997/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=1678193822828158997' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/1678193822828158997'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/1678193822828158997'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/10/cron-error-email-notification-in-ubuntu.html' title='Cron error email notification in Ubuntu'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-5582709039301551613</id><published>2011-10-22T17:42:00.001-07:00</published><updated>2011-10-22T17:42:50.226-07:00</updated><title type='text'>Upgrading CouchDB in Ubuntu</title><content type='html'>I had to upgrade CouchDB to candidate release 1.1.1 and I did it just following the &lt;a href="http://thinkinginsoftware.blogspot.com/2011/10/installing-couchdb-in-ubuntu.html"&gt;same instructions I followed to install it from scratch&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-5582709039301551613?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/5582709039301551613/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=5582709039301551613' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5582709039301551613'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5582709039301551613'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/10/upgrading-couchdb-in-ubuntu.html' title='Upgrading CouchDB in Ubuntu'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-7328959023724720834</id><published>2011-10-22T16:54:00.000-07:00</published><updated>2012-01-03T13:33:57.851-08:00</updated><title type='text'>Installing CouchDB in Ubuntu</title><content type='html'>I have tested the below procedure in Ubuntu 10.10 (Maverick) to install CouchDB 1.1.1 candidate release.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ ps -ef|grep couch|awk '{print $2}'|xargs sudo kill -9&lt;br /&gt;$ sudo apt-get update&lt;br /&gt;$ sudo apt-get autoremove&lt;br /&gt;$ sudo apt-get remove couchdb&lt;br /&gt;$ sudo apt-get build-dep couchdb&lt;br /&gt;$ sudo apt-get install libtool zip&lt;br /&gt;$ cd&lt;br /&gt;$ curl -O http://ftp.mozilla.org/pub/mozilla.org/js/js185-1.0.0.tar.gz&lt;br /&gt;$ tar xvzf js185-1.0.0.tar.gz &lt;br /&gt;$ cd js-1.8.5/js/src&lt;br /&gt;$ ./configure&lt;br /&gt;$ make&lt;br /&gt;$ sudo make install&lt;br /&gt;$ cd&lt;br /&gt;$ curl -O http://www.erlang.org/download/otp_src_R14B04.tar.gz&lt;br /&gt;$ tar xvzf otp_src_R14B04.tar.gz &lt;br /&gt;$ cd otp_src_R14B04&lt;br /&gt;$ ./configure --enable-smp-support --enable-dynamic-ssl-lib --enable-kernel-poll&lt;br /&gt;$ make&lt;br /&gt;$ sudo make install&lt;br /&gt;$ cd&lt;br /&gt;$ svn co http://svn.apache.org/repos/asf/couchdb/branches/1.1.x/ couchdb1.1.x&lt;br /&gt;$ cd couchdb1.1.x&lt;br /&gt;$ ./bootstrap&lt;br /&gt;$ prefix='/usr/local'&lt;br /&gt;$ ./configure --prefix=${prefix} &lt;br /&gt;$ make&lt;br /&gt;$ sudo make install&lt;br /&gt;$ sudo useradd -d /var/lib/couchdb couchdb&lt;br /&gt;$ sudo chown -R couchdb: ${prefix}/var/{lib,log,run}/couchdb ${prefix}/etc/couchdb&lt;br /&gt;$ for dir in `whereis couchdb | sed 's/couchdb: //'`; do echo $dir | xargs sudo chown couchdb; done&lt;br /&gt;$ xulrunner -v #take note of the version x.x.x.x (in my case 1.9.2.24)&lt;br /&gt;$ sudo vi /etc/ld.so.conf.d/xulrunner.conf #change x for the version below:&lt;br /&gt;/usr/lib/xulrunner-x.x.x.x&lt;br /&gt;/usr/lib/xulrunner-devel-x.x.x.x&lt;br /&gt;$ sudo /etc/init.d/couchdb start&lt;br /&gt;$ curl -X GET http://localhost:5984&lt;br /&gt;{"couchdb":"Welcome","version":"1.1.1a1187726"}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-7328959023724720834?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/7328959023724720834/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=7328959023724720834' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7328959023724720834'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7328959023724720834'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/10/installing-couchdb-in-ubuntu.html' title='Installing CouchDB in Ubuntu'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-4432088070371237869</id><published>2011-10-21T16:03:00.000-07:00</published><updated>2011-10-21T16:03:37.712-07:00</updated><title type='text'>Monitoring CouchDB with Monit</title><content type='html'>Just add the below in monitrc and reload config. I am expecting you &lt;a href="http://thinkinginsoftware.blogspot.com/2011/10/hardening-couchdb-more-secure.html"&gt;hardened&lt;/a&gt; CouchDB and so it is listening in an SSL port.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: bash"&gt;#################################################################&lt;br /&gt;# couchdb&lt;br /&gt;################################################################&lt;br /&gt;&lt;br /&gt;check process couchdb&lt;br /&gt;  with pidfile /usr/local/var/run/couchdb/couchdb.pid&lt;br /&gt;  start program = "/usr/local/etc/init.d//couchdb start"&lt;br /&gt;  stop program = "/usr/local/etc/init.d//couchdb stop"&lt;br /&gt;  if failed host couchdb.nestorurquiza.com port 6984 type TCPSSL proto http then restart&lt;br /&gt;  if failed url https://couchdb.nestorurquiza.com:6984/ and content == '"couchdb"' then restart&lt;br /&gt;group couchdb&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-4432088070371237869?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/4432088070371237869/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=4432088070371237869' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/4432088070371237869'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/4432088070371237869'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/10/monitoring-couchdb-with-monit.html' title='Monitoring CouchDB with Monit'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-8859035784302845081</id><published>2011-10-21T03:48:00.000-07:00</published><updated>2012-01-10T05:37:21.655-08:00</updated><title type='text'>Hardening CouchDB: A more secure distributed database</title><content type='html'>Here are the steps to follow in order to harden CouchDB. I am currently using candidate release 1.1.1 and these are instructions for a local environment. For a production environment you have to locate the non dev config files local.ini and default.ini (Just run "ps -ef | grep couchdb" and look for the path, in my case /usr/local/etc/couchdb/local.ini and /usr/local/etc/couchdb/default.ini).&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Install a primary key and generated pem certificate:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ export DOMAIN=couchdb.nestorurquiza.com&lt;br /&gt;$ openssl genrsa -des3 -out ${DOMAIN}.pem 1024&lt;br /&gt;$ openssl req -new -key ${DOMAIN}.pem -out ${DOMAIN}.csr&lt;br /&gt;$ openssl x509 -req -days 3650 -in ${DOMAIN}.csr -signkey ${DOMAIN}.pem -out ${DOMAIN}.cert.pem&lt;br /&gt;$ sudo mkdir -p /opt/couchdb/certs/&lt;br /&gt;$ sudo cp ${DOMAIN}.pem /opt/couchdb/certs/&lt;br /&gt;$ sudo cp ${DOMAIN}.cert.pem /opt/couchdb/certs/&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;br /&gt;Configure couchDB for *SSL only* and restart the server&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ vi /Users/nestor/Downloads/couchdb1.1.x/etc/couchdb/local_dev.ini&lt;br /&gt;[admins]&lt;br /&gt;;admin = mysecretpassword&lt;br /&gt;nestorurquizaadmin = secret1&lt;br /&gt;...&lt;br /&gt;[daemons]&lt;br /&gt;; enable SSL support by uncommenting the following line and supply the PEM's below.&lt;br /&gt;; the default ssl port CouchDB listens on is 6984&lt;br /&gt;httpsd = {couch_httpd, start_link, [https]}&lt;br /&gt;...&lt;br /&gt;[ssl]&lt;br /&gt;cert_file = /opt/couchdb/certs/couchdb.nestorurquiza.com.cert.pem&lt;br /&gt;key_file = /opt/couchdb/certs/couchdb.nestorurquiza.com.pem&lt;br /&gt;$ vi /Users/nestor/Downloads/couchdb1.1.x/etc/couchdb/default_dev.ini&lt;br /&gt;[httpd]&lt;br /&gt;bind_address = a.non.loopback.ip #Use no loopback address only if the server will be exposed to a different machine&lt;br /&gt;...&lt;br /&gt;[daemons]&lt;br /&gt;...&lt;br /&gt;; httpd={couch_httpd, start_link, []}&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Restart&lt;br /&gt;&lt;pre class="brush: bash"&gt;sudo couchdb #local OSX&lt;br /&gt;sudo /usr/local/etc/init.d/couchdb restart #Ubuntu&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;br /&gt;Now we can access the secured CouchDB instance&lt;br /&gt;&lt;pre class="brush: bash"&gt;HOST="https://nestorurquizaadmin:mysecret@127.0.0.1:6984"&lt;br /&gt;curl -k -X GET $HOST&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Note that at this time to remove the insecure http you have to use /usr/local/etc/couchdb/default.ini which gets overwritten after any upgrade.&lt;br /&gt;&lt;br /&gt;Of course we should sign our certificate with a Certificate Authority (CA) to make this really secure but you can also add some security if you play with firewall rules and allow access to the SSL port to certain IP only. I can do so because I do use a middle tear between CouchDB and the browser. If you directly hitting CouchDB from the wild you better use a CA.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-8859035784302845081?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/8859035784302845081/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=8859035784302845081' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8859035784302845081'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8859035784302845081'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/10/hardening-couchdb-more-secure.html' title='Hardening CouchDB: A more secure distributed database'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-3448038688735519200</id><published>2011-10-20T13:10:00.000-07:00</published><updated>2011-12-15T18:53:47.479-08:00</updated><title type='text'>CouchDB filtered replication</title><content type='html'>One of the greatest features of CouchDB is its replication which allows for great distributed computing. It reminds me when in 1999 I met Erlang language for the first time (Working for a Telco). Erlang is made for distributed computing and so CouchDB which of course is built in Erlang.&lt;br /&gt;&lt;br /&gt;I have to say I have successfully tested this in upcoming version 1.1.1 (&lt;a href="http://thinkinginsoftware.blogspot.com/2011/10/upgrading-couchdb-in-osx.html"&gt;built from a branch&lt;/a&gt;) Do not try this in 1.1.0.&lt;br /&gt;&lt;br /&gt;The example below is based on the document that I have been discussing in the &lt;a href="http://thinkinginsoftware.blogspot.com/2011/10/document-management-system-with-couchdb.html"&gt;three part tutorial&lt;/a&gt; about building a Document Management System (DMS) with CouchDB.&lt;br /&gt;&lt;br /&gt;Filtered or selective replication is a two step process:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;First create a filter named for example "clientFilter" in a new document called "replicateFilter". This sample filter will reject any client not matching the clientId parameter (step 2 explains what this parameter is about). Any deleted documents will be deleted from the target as well.&lt;br /&gt;&lt;pre class="brush: bash"&gt;curl -H 'Content-Type: application/json' -X PUT http://127.0.0.1:5984/dms4/_design/replicateFilter -d \&lt;br /&gt;'{&lt;br /&gt;  "filters":{&lt;br /&gt;    "clientFilter":"function(doc, req) {&lt;br /&gt;      if (doc._deleted) {&lt;br /&gt;        return true;&lt;br /&gt;      }&lt;br /&gt; &lt;br /&gt;      if(!doc.clientId) {&lt;br /&gt;        return false;&lt;br /&gt;      }&lt;br /&gt; &lt;br /&gt;      if(!req.query.clientId) {&lt;br /&gt;        throw(\"Please provide a query parameter clientId.\");&lt;br /&gt;      }&lt;br /&gt; &lt;br /&gt;      if(doc.clientId == req.query.clientId) {&lt;br /&gt;        return true;&lt;br /&gt;      }&lt;br /&gt;      return false;&lt;br /&gt;    }"&lt;br /&gt;  }&lt;br /&gt;}'&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Create a replication document called "by_clientId". This example passes clientId=1 as a parameter to the filter we created in step number 1 ("replicateFilter/clientFilter"). You figured we will end up replicating documents for that client.&lt;br /&gt;&lt;pre class="brush: bash"&gt;curl -H 'Content-Type: application/json' -X POST http://127.0.0.1:5984/_replicator -d \&lt;br /&gt;'{&lt;br /&gt;  "_id":"by_clientId",&lt;br /&gt;  "source":"dms4",&lt;br /&gt;  "target":"http://couchdb.nestorurquiza.com:5984/dms4",&lt;br /&gt;  "create_target":true,&lt;br /&gt;  "continuous":true,&lt;br /&gt;  "filter":"replicateFilter/clientFilter",&lt;br /&gt;  "query_params":{"clientId":1}&lt;br /&gt;}'&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Deleting a replication document is how you turn off that replication. This is not any different than deleting any other document:&lt;br /&gt;&lt;pre class="brush: bash"&gt;nestor:~ nestor$ curl -X GET http://127.0.0.1:5984/_replicator/by_clientId&lt;br /&gt;{"_id":"by_clientId","_rev":"5-e177ca7f79d9ba6f91b803a2cb2abc1e","source":"dms4","target":"http://couchdb.nestorurquiza.com:5984/dms4","create_target":true,"continuous":true,"filter":"replicateFilter/clientFilter","query_params":{"clientId":1},"_replication_state":"triggered","_replication_state_time":"2011-10-20T13:09:56-04:00","_replication_id":"d8dc09e97f4948de0294260dda19fc6f"}&lt;br /&gt;nestor:~ nestor$ curl -X DELETE http://127.0.0.1:5984/_replicator/by_clientId?rev=5-e177ca7f79d9ba6f91b803a2cb2abc1e&lt;br /&gt;{"ok":true,"id":"by_clientId","rev":"6-0d20d90cbed22837eb3233e2bd8dfb2c"}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The same applies for getting a list of the current defined "selective replicators". You can use a temporary view like I show here or create a permanent view to list all the replicators:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ curl -X POST http://127.0.0.1:5984/_replicator/_temp_view -H "Content-Type: application/json" -d '{&lt;br /&gt;  "map": "function(doc) {&lt;br /&gt;            emit(null, doc);&lt;br /&gt;          }"&lt;br /&gt;}'&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-3448038688735519200?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/3448038688735519200/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=3448038688735519200' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/3448038688735519200'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/3448038688735519200'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/10/couchdb-filtered-replication.html' title='CouchDB filtered replication'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-4436159349570638289</id><published>2011-10-20T09:59:00.000-07:00</published><updated>2011-10-20T09:59:25.499-07:00</updated><title type='text'>OSX homebrew update: unexpected token near HOMEBREW_BREW_FILE</title><content type='html'>After upgrading &lt;a href="http://mxcl.github.com/homebrew/"&gt;homebrew&lt;/a&gt;:&lt;br /&gt;$ brew update&lt;br /&gt;&lt;br /&gt;I got errors:&lt;br /&gt;$ brew install mozilla-js&lt;br /&gt;/usr/local/bin/brew: line 4: syntax error near unexpected token `('&lt;br /&gt;/usr/local/bin/brew: line 4: `HOMEBREW_BREW_FILE = ENV['HOMEBREW_BREW_FILE'] = File.expand_path(__FILE__)'&lt;br /&gt;&lt;br /&gt;Here is a temporary solution which you will need to do everytime you update brew:&lt;br /&gt;$ sudo vi /usr/local/bin/brew&lt;br /&gt;#!/Users/nestor/.rvm/rubies/ruby-1.9.2-p290/bin/ruby&lt;br /&gt;##!/usr/bin/ruby&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-4436159349570638289?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/4436159349570638289/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=4436159349570638289' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/4436159349570638289'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/4436159349570638289'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/10/osx-homebrew-update-unexpected-token.html' title='OSX homebrew update: unexpected token near HOMEBREW_BREW_FILE'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-163823733936608249</id><published>2011-10-20T07:51:00.001-07:00</published><updated>2011-10-22T09:47:39.892-07:00</updated><title type='text'>Upgrading couchDB in OSX</title><content type='html'>While trying to use couchDB filtered replication I had some problems as documented in &lt;a href="https://gist.github.com/832610"&gt;gist&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I had to upgrade then couchDB to a non released version. Here are the steps I followed.&lt;br /&gt;&lt;br /&gt;Delete all files from previous installations:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ sudo find /usr/local -name couchdb | sudo xargs rm -fR&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Install ICU from http://download.icu-project.org&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ tar xvzf icu4c-4_8_1-src.tgz &lt;br /&gt;$ cd icu/source/&lt;br /&gt;$ ./runConfigureICU MacOSX --with-library-bits=64 --disable-samples --enable-static # if this fails for you try: ./configure --enable-64bit-libs&lt;br /&gt;$ make&lt;br /&gt;$ sudo make install&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Install Spidermonkey&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ curl -O http://ftp.mozilla.org/pub/mozilla.org/js/js185-1.0.0.tar.gz&lt;br /&gt;$ tar xvzf js185-1.0.0.tar.gz &lt;br /&gt;$ cd js-1.8.5/js/src&lt;br /&gt;./configure &lt;br /&gt;$ make&lt;br /&gt;$ sudo make install&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then install latest Erlang:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ curl -O http://www.erlang.org/download/otp_src_R14B04.tar.gz&lt;br /&gt;$ tar xvzf otp_src_R14B04.tar.gz &lt;br /&gt;$ cd otp_src_R14B04&lt;br /&gt;$ ./configure --enable-smp-support --enable-dynamic-ssl-lib --enable-kernel-poll --enable-darwin-64bit&lt;br /&gt;$ make&lt;br /&gt;$ sudo make install&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then checkout the needed version. I tried from git (http://git-wip-us.apache.org/repos/asf/couchdb.git) first but I had several problems with autoconf and beyond (.configure was not available so I needed to go with automake -a; autoconf; autoheader) so I then built from svn:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ svn co http://svn.apache.org/repos/asf/couchdb/branches/1.1.x/ couchdb1.1.x&lt;br /&gt;$ cd couchdb1.1.x&lt;br /&gt;$ ./bootstrap&lt;br /&gt;$ ./configure&lt;br /&gt;$ make&lt;br /&gt;$ sudo make install&lt;br /&gt;$ sudo couchdb&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-163823733936608249?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/163823733936608249/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=163823733936608249' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/163823733936608249'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/163823733936608249'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/10/upgrading-couchdb-in-osx.html' title='Upgrading couchDB in OSX'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-6918766619149381790</id><published>2011-10-16T21:12:00.000-07:00</published><updated>2011-12-14T08:33:24.976-08:00</updated><title type='text'>Document Management System with CouchDB - Third Part</title><content type='html'>This is the last part of my attempt to cover how to use CouchDB to build a DMS. We will be using Java and the Ektorp library for the implementation.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Using Ektorp&lt;/h3&gt;A good place to start is &lt;a href="https://github.com/helun/Ektorp/downloads"&gt;downloading&lt;/a&gt; the BlogPost application (org.ektorp.sample-1.7-project) that shows some of the most important concepts around the Ektorp API. Follow &lt;a href="http://ektorp.org/tutorial.html"&gt;Ektorp Tutorial&lt;/a&gt;  and of course do not miss the &lt;a href="http://www.ektorp.org/reference_documentation.html"&gt;reference documentation&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I will show here the steps on how to make your existing spring project able to interact with CouchDB using the Ektorp project. The code presented here is an implementation of the ideas exposed in the &lt;a href="http://thinkinginsoftware.blogspot.com/2011/10/document-management-system-with-couchdb_16.html"&gt;second part&lt;/a&gt; of this project.&lt;br /&gt;&lt;br /&gt;Here the dependencies for your project. Note that I am adding file upload dependencies because I am building a DMS and I will provide an upload module:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;!-- Ektorp for CouchDB --&amp;gt;&lt;br /&gt;        &amp;lt;dependency&amp;gt;&lt;br /&gt;            &amp;lt;groupId&amp;gt;org.ektorp&amp;lt;/groupId&amp;gt;&lt;br /&gt;            &amp;lt;artifactId&amp;gt;org.ektorp&amp;lt;/artifactId&amp;gt;&lt;br /&gt;            &amp;lt;version&amp;gt;${ektorp.version}&amp;lt;/version&amp;gt;&lt;br /&gt;            &amp;lt;exclusions&amp;gt;&lt;br /&gt;             &amp;lt;exclusion&amp;gt;&lt;br /&gt;              &amp;lt;artifactId&amp;gt;slf4j-api&amp;lt;/artifactId&amp;gt;&lt;br /&gt;              &amp;lt;groupId&amp;gt;org.slf4j&amp;lt;/groupId&amp;gt;&lt;br /&gt;             &amp;lt;/exclusion&amp;gt;&lt;br /&gt;            &amp;lt;/exclusions&amp;gt;&lt;br /&gt;        &amp;lt;/dependency&amp;gt;&lt;br /&gt;        &amp;lt;dependency&amp;gt;&lt;br /&gt;            &amp;lt;groupId&amp;gt;org.ektorp&amp;lt;/groupId&amp;gt;&lt;br /&gt;            &amp;lt;artifactId&amp;gt;org.ektorp.spring&amp;lt;/artifactId&amp;gt;&lt;br /&gt;            &amp;lt;version&amp;gt;${ektorp.version}&amp;lt;/version&amp;gt;&lt;br /&gt;        &amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;br /&gt;        &amp;lt;!-- File Upload --&amp;gt;&lt;br /&gt;        &amp;lt;dependency&amp;gt;&lt;br /&gt;            &amp;lt;groupId&amp;gt;commons-fileupload&amp;lt;/groupId&amp;gt;&lt;br /&gt;            &amp;lt;artifactId&amp;gt;commons-fileupload&amp;lt;/artifactId&amp;gt;&lt;br /&gt;            &amp;lt;version&amp;gt;1.2.2&amp;lt;/version&amp;gt;&lt;br /&gt;        &amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In spring application context xml (Note the upload component which is only needed because again we need it for the DMS upload functionality):&lt;br /&gt;&lt;pre class="brush: xml"&gt;...&lt;br /&gt;xmlns:util="http://www.springframework.org/schema/util"&lt;br /&gt;xmlns:couchdb="http://www.ektorp.org/schema/couchdb"&lt;br /&gt;...&lt;br /&gt;http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd&lt;br /&gt;http://www.ektorp.org/schema/couchdb http://www.ektorp.org/schema/couchdb/couchdb.xsd&lt;br /&gt;...&lt;br /&gt;&amp;lt;context:component-scan base-package=&amp;quot;com.nu.dms.couchdb.ektorp.model&amp;quot;/&amp;gt;&lt;br /&gt;&amp;lt;context:component-scan base-package=&amp;quot;com.nu.dms.couchdb.ektorp.dao&amp;quot;/&amp;gt;&lt;br /&gt;...&lt;br /&gt;&amp;lt;util:properties id=&amp;quot;couchdbProperties&amp;quot; location=&amp;quot;classpath:/couchdb.properties&amp;quot;/&amp;gt;&lt;br /&gt;&amp;lt;couchdb:instance id=&amp;quot;dmsCouchdb&amp;quot; url=&amp;quot;${couchdb.url}&amp;quot; properties=&amp;quot;couchdbProperties&amp;quot; /&amp;gt;&lt;br /&gt;&amp;lt;couchdb:database id=&amp;quot;dmsDatabase&amp;quot; name=&amp;quot;${couchdb.db}&amp;quot; instance-ref=&amp;quot;dmsCouchdb&amp;quot; /&amp;gt;&lt;br /&gt;...&lt;br /&gt;&amp;lt;!-- File Upload --&amp;gt;&lt;br /&gt;&amp;lt;bean id=&amp;quot;multipartResolver&amp;quot; class=&amp;quot;org.springframework.web.multipart.commons.CommonsMultipartResolver&amp;quot;&amp;gt;&lt;br /&gt;        &amp;lt;property name=&amp;quot;maxUploadSize&amp;quot; value=&amp;quot;2000000&amp;quot;/&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In classpath environment properties file:&lt;br /&gt;&lt;pre class="brush: bash"&gt;couchdb.url=http://localhost:5984&lt;br /&gt;couchdb.db=dms4 #my database name&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;File classpath couchdb.properties file:&lt;br /&gt;&lt;pre class="brush: bash"&gt;host=localhost&lt;br /&gt;port=5984&lt;br /&gt;maxConnections=20&lt;br /&gt;connectionTimeout=1000&lt;br /&gt;socketTimeout=10000&lt;br /&gt;autoUpdateViewOnChange=true&lt;br /&gt;caching=false&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A Document POJO following the BlogPost POJO from the Ektorp example:&lt;br /&gt;&lt;pre class="brush: java"&gt;package com.nu.dms.couchdb.ektorp.model;&lt;br /&gt;&lt;br /&gt;import org.ektorp.Attachment;&lt;br /&gt;import org.ektorp.support.CouchDbDocument;&lt;br /&gt;&lt;br /&gt;public class CustomCouchDbDocument extends CouchDbDocument {&lt;br /&gt;&lt;br /&gt;    private static final long serialVersionUID = -9012014877538917152L;&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public void addInlineAttachment(Attachment a) {&lt;br /&gt;        super.addInlineAttachment(a);&lt;br /&gt;    }   &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;package com.nu.dms.couchdb.ektorp.model;&lt;br /&gt;&lt;br /&gt;import java.util.Date;&lt;br /&gt;&lt;br /&gt;import javax.validation.constraints.NotNull;&lt;br /&gt;&lt;br /&gt;import org.ektorp.support.TypeDiscriminator;&lt;br /&gt;&lt;br /&gt;public class Document extends CustomCouchDbDocument {&lt;br /&gt;&lt;br /&gt;    private static final long serialVersionUID = 59516215253102057L;&lt;br /&gt;    &lt;br /&gt;    public Document() {&lt;br /&gt;        super();&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    public Document(String title) {&lt;br /&gt;        this.title = title;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * @TypeDiscriminator is used to mark properties that makes this class's documents unique in the database. &lt;br /&gt;     */&lt;br /&gt;    @TypeDiscriminator&lt;br /&gt;    @NotNull&lt;br /&gt;    private String title;&lt;br /&gt;    &lt;br /&gt;    private int clientId;&lt;br /&gt;    private int createdByEmployeeId;&lt;br /&gt;    private int reviewedByEmployeeId;&lt;br /&gt;    private int approvedByManagerId;&lt;br /&gt;    private Date dateEffective;&lt;br /&gt;    private Date dateCreated;&lt;br /&gt;    private Date dateReviewed;&lt;br /&gt;    private Date dateApproved;&lt;br /&gt;    private int investorId;&lt;br /&gt;    private int categoryId;&lt;br /&gt;    private int subCategoryId;&lt;br /&gt;    private int statusId;&lt;br /&gt;    &lt;br /&gt;    public String getTitle() {&lt;br /&gt;        return title;&lt;br /&gt;    }&lt;br /&gt;    public void setTitle(String title) {&lt;br /&gt;        this.title = title;&lt;br /&gt;    }&lt;br /&gt;    public int getClientId() {&lt;br /&gt;        return clientId;&lt;br /&gt;    }&lt;br /&gt;    public void setClientId(int clientId) {&lt;br /&gt;        this.clientId = clientId;&lt;br /&gt;    }&lt;br /&gt;    public int getCreatedByEmployeeId() {&lt;br /&gt;        return createdByEmployeeId;&lt;br /&gt;    }&lt;br /&gt;    public void setCreatedByEmployeeId(int createdByEmployeeId) {&lt;br /&gt;        this.createdByEmployeeId = createdByEmployeeId;&lt;br /&gt;    }&lt;br /&gt;    public int getReviewedByEmployeeId() {&lt;br /&gt;        return reviewedByEmployeeId;&lt;br /&gt;    }&lt;br /&gt;    public void setReviewedByEmployeeId(int reviewedByEmployeeId) {&lt;br /&gt;        this.reviewedByEmployeeId = reviewedByEmployeeId;&lt;br /&gt;    }&lt;br /&gt;    public int getApprovedByManagerId() {&lt;br /&gt;        return approvedByManagerId;&lt;br /&gt;    }&lt;br /&gt;    public void setApprovedByManagerId(int approvedByManagerId) {&lt;br /&gt;        this.approvedByManagerId = approvedByManagerId;&lt;br /&gt;    }&lt;br /&gt;  &lt;br /&gt;    public Date getDateEffective() {&lt;br /&gt;        return dateEffective;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public void setDateEffective(Date dateEffective) {&lt;br /&gt;        this.dateEffective = dateEffective;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Date getDateCreated() {&lt;br /&gt;        return dateCreated;&lt;br /&gt;    }&lt;br /&gt;    public void setDateCreated(Date dateCreated) {&lt;br /&gt;        this.dateCreated = dateCreated;&lt;br /&gt;    }&lt;br /&gt;    public Date getDateReviewed() {&lt;br /&gt;        return dateReviewed;&lt;br /&gt;    }&lt;br /&gt;    public void setDateReviewed(Date dateReviewed) {&lt;br /&gt;        this.dateReviewed = dateReviewed;&lt;br /&gt;    }&lt;br /&gt;    public Date getDateApproved() {&lt;br /&gt;        return dateApproved;&lt;br /&gt;    }&lt;br /&gt;    public void setDateApproved(Date dateApproved) {&lt;br /&gt;        this.dateApproved = dateApproved;&lt;br /&gt;    }&lt;br /&gt;    public int getInvestorId() {&lt;br /&gt;        return investorId;&lt;br /&gt;    }&lt;br /&gt;    public void setInvestorId(int investorId) {&lt;br /&gt;        this.investorId = investorId;&lt;br /&gt;    }&lt;br /&gt;    public int getCategoryId() {&lt;br /&gt;        return categoryId;&lt;br /&gt;    }&lt;br /&gt;    public void setCategoryId(int categoryId) {&lt;br /&gt;        this.categoryId = categoryId;&lt;br /&gt;    }&lt;br /&gt;    public int getSubCategoryId() {&lt;br /&gt;        return subCategoryId;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public void setSubCategoryId(int subCategoryId) {&lt;br /&gt;        this.subCategoryId = subCategoryId;&lt;br /&gt;    }&lt;br /&gt;    public int getStatusId() {&lt;br /&gt;        return statusId;&lt;br /&gt;    }&lt;br /&gt;    public void setStatusId(int statusId) {&lt;br /&gt;        this.statusId = statusId;&lt;br /&gt;    }&lt;br /&gt;    @Override&lt;br /&gt;    public void setRevision(String s) {&lt;br /&gt;        // downstream code does not like revision set to emtpy string, which Spring does when binding&lt;br /&gt;        if (s != null &amp;amp;&amp;amp; !s.isEmpty()) super.setRevision(s);&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    public boolean isNew() {&lt;br /&gt;        return getId() == null;&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A Document Repository:&lt;br /&gt;&lt;pre class="brush: java"&gt;package com.nu.dms.couchdb.ektorp.dao;&lt;br /&gt;&lt;br /&gt;import org.ektorp.CouchDbConnector;&lt;br /&gt;import org.ektorp.support.CouchDbRepositorySupport;&lt;br /&gt;&lt;br /&gt;public class CustomCouchDbRepositorySupport&amp;lt;T&amp;gt; extends CouchDbRepositorySupport&amp;lt;T&amp;gt; {&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    protected CustomCouchDbRepositorySupport(Class&amp;lt;T&amp;gt; type, CouchDbConnector db) {&lt;br /&gt;        super(type, db);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public CouchDbConnector getDb() {&lt;br /&gt;        return super.db;&lt;br /&gt;    }   &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;package com.nu.dms.couchdb.ektorp.dao;&lt;br /&gt;&lt;br /&gt;import java.io.InputStream;&lt;br /&gt;&lt;br /&gt;@Component&lt;br /&gt;public class DocumentRepository  extends CustomCouchDbRepositorySupport&amp;lt;Document&amp;gt; {&lt;br /&gt;    &lt;br /&gt;    private static final Logger log = LoggerFactory.getLogger(DocumentRepository.class);&lt;br /&gt;    &lt;br /&gt;    @Autowired&lt;br /&gt;    public DocumentRepository(@Qualifier(&amp;quot;dmsDatabase&amp;quot;) CouchDbConnector db) {&lt;br /&gt;        super(Document.class, db);&lt;br /&gt;        initStandardDesignDocument();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @GenerateView @Override&lt;br /&gt;    public List&amp;lt;Document&amp;gt; getAll() {&lt;br /&gt;        ViewQuery q = createQuery(&amp;quot;all&amp;quot;)&lt;br /&gt;                        .includeDocs(true);&lt;br /&gt;        return db.queryView(q, Document.class);&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    public Page&amp;lt;Document&amp;gt; getAll(PageRequest pr) {&lt;br /&gt;        ViewQuery q = createQuery(&amp;quot;all&amp;quot;)&lt;br /&gt;                        .includeDocs(true);&lt;br /&gt;        return db.queryForPage(q, pr, Document.class);&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    @View( name = &amp;quot;tree&amp;quot;, map = &amp;quot;classpath:/couchdb/tree_map.js&amp;quot;, reduce = &amp;quot;classpath:/couchdb/tree_reduce.js&amp;quot;)&lt;br /&gt;    public InputStream getTree(String startKey, String endKey, int groupLevel) {&lt;br /&gt;        ViewQuery q = createQuery(&amp;quot;tree&amp;quot;)&lt;br /&gt;        .startKey(startKey)&lt;br /&gt;        .endKey(endKey)&lt;br /&gt;        .groupLevel(groupLevel)&lt;br /&gt;        .group(true);&lt;br /&gt;        InputStream is = db.queryForStream(q);&lt;br /&gt;        return is;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Map and Reduce javascript functions in src/main/resources/couchdb in other words in the classpath:&lt;br /&gt;&lt;pre class="brush: javascript"&gt;//by_categoryId_map.js&lt;br /&gt;function(doc) { &lt;br /&gt; if(doc.title &amp;&amp; doc.categoryId) {&lt;br /&gt;  emit(doc.categoryId, doc._id)&lt;br /&gt; } &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;//by_categoryId_reduce.js&lt;br /&gt;_count&lt;br /&gt;&lt;br /&gt;//tree_map.js&lt;br /&gt;function(doc) {&lt;br /&gt;  var tokens = doc.dateEffective.split("-");&lt;br /&gt;  var year = null;&lt;br /&gt;  var month = null;&lt;br /&gt;  if(tokens.length == 3) {&lt;br /&gt;    year = tokens[0];&lt;br /&gt;    month = tokens[1];&lt;br /&gt;  }&lt;br /&gt;  var key = [doc.clientId, doc.categoryId, doc.subCategoryId, year, month].concat(doc.title);&lt;br /&gt;  var value = null;&lt;br /&gt;  emit(key, value);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;//tree_reduce.js&lt;br /&gt;_count&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In web.xml we need to allow flash because we will use the swfUpload to upload multiple files:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;url-pattern&amp;gt;*.swf&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;&amp;lt;/servlet-mapping&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Keeping things short I am not using a Service layer so I provide a unique Controller that allows creating a document including the attachment and metadata, uploading multiple documents (using swfupload flash) which follow a strong naming convention to produce all necessary metadata our of their names, viewing the documents as explained in part two in a tree view (using jquery.treeview.async.js)&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: java"&gt;package com.nu.web.controller.dms;&lt;br /&gt;&lt;br /&gt;import java.io.ByteArrayOutputStream;&lt;br /&gt;import java.io.InputStream;&lt;br /&gt;import java.net.URLConnection;&lt;br /&gt;import java.text.SimpleDateFormat;&lt;br /&gt;import java.util.Date;&lt;br /&gt;import java.util.Iterator;&lt;br /&gt;import java.util.Locale;&lt;br /&gt;import java.util.Map;&lt;br /&gt;import java.util.Scanner;&lt;br /&gt;&lt;br /&gt;import javax.servlet.http.HttpServletRequest;&lt;br /&gt;import javax.servlet.http.HttpServletResponse;&lt;br /&gt;&lt;br /&gt;import org.apache.commons.codec.binary.Base64;&lt;br /&gt;import org.apache.commons.fileupload.FileItemFactory;&lt;br /&gt;import org.apache.commons.fileupload.FileUploadException;&lt;br /&gt;import org.apache.commons.fileupload.disk.DiskFileItemFactory;&lt;br /&gt;import org.apache.commons.fileupload.servlet.ServletFileUpload;&lt;br /&gt;import org.apache.commons.lang.StringUtils;&lt;br /&gt;import org.apache.poi.util.IOUtils;&lt;br /&gt;import org.codehaus.jackson.JsonNode;&lt;br /&gt;import org.codehaus.jackson.map.ObjectMapper;&lt;br /&gt;import org.codehaus.jackson.node.ObjectNode;&lt;br /&gt;import org.ektorp.Attachment;&lt;br /&gt;import org.ektorp.AttachmentInputStream;&lt;br /&gt;import org.ektorp.PageRequest;&lt;br /&gt;import org.slf4j.Logger;&lt;br /&gt;import org.slf4j.LoggerFactory;&lt;br /&gt;import org.springframework.beans.factory.annotation.Autowired;&lt;br /&gt;import org.springframework.stereotype.Controller;&lt;br /&gt;import org.springframework.ui.Model;&lt;br /&gt;import org.springframework.validation.BindingResult;&lt;br /&gt;import org.springframework.validation.ObjectError;&lt;br /&gt;import org.springframework.web.bind.WebDataBinder;&lt;br /&gt;import org.springframework.web.bind.annotation.InitBinder;&lt;br /&gt;import org.springframework.web.bind.annotation.ModelAttribute;&lt;br /&gt;import org.springframework.web.bind.annotation.PathVariable;&lt;br /&gt;import org.springframework.web.bind.annotation.RequestMapping;&lt;br /&gt;import org.springframework.web.bind.annotation.RequestParam;&lt;br /&gt;import org.springframework.web.multipart.MultipartFile;&lt;br /&gt;import org.springframework.web.multipart.support.ByteArrayMultipartFileEditor;&lt;br /&gt;import org.springframework.web.servlet.ModelAndView;&lt;br /&gt;&lt;br /&gt;import com.nu.dms.couchdb.ektorp.dao.DocumentRepository;&lt;br /&gt;import com.nu.dms.couchdb.ektorp.model.Document;&lt;br /&gt;import com.nu.web.ControllerContext;&lt;br /&gt;import com.nu.web.RootController;&lt;br /&gt;import com.nu.web.WebConstants;&lt;br /&gt;import com.nu.web.validator.BeanValidator;&lt;br /&gt;import com.windriver.gson.extension.GeneralObjectDeserializer;&lt;br /&gt;&lt;br /&gt;@Controller&lt;br /&gt;@RequestMapping(&amp;quot;/dms/document/*&amp;quot;)&lt;br /&gt;public class DocumentController extends RootController {&lt;br /&gt;    private static final Logger log = LoggerFactory.getLogger(DocumentController.class);&lt;br /&gt;    &lt;br /&gt;    @Autowired&lt;br /&gt;    DocumentRepository documentRepository;&lt;br /&gt;    &lt;br /&gt;    @Autowired&lt;br /&gt;    private BeanValidator validator;&lt;br /&gt;    &lt;br /&gt;    private static final String LIST_PATH = &amp;quot;/dms/document/list&amp;quot;;&lt;br /&gt;    private static final String FORM_PATH = &amp;quot;/dms/document/form&amp;quot;;&lt;br /&gt;    private static final String TREE_PATH = &amp;quot;/dms/document/tree&amp;quot;;&lt;br /&gt;    public static final long UPLOAD_MAX_FILE_SIZE = 20 * 1024 * 1024; //10 MB&lt;br /&gt;    public static final long UPLOAD_MAX_TOTAL_FILES_SIZE = 1 * 1024 * 1024 * 1024; //1 GB&lt;br /&gt;    private static final int DOCUMENT_LEVEL = 5;&lt;br /&gt;    &lt;br /&gt;    @RequestMapping(&amp;quot;/list&amp;quot;)&lt;br /&gt;    public ModelAndView list(   HttpServletRequest request, &lt;br /&gt;                                HttpServletResponse response, &lt;br /&gt;                                Model m, &lt;br /&gt;                                @RequestParam(value = &amp;quot;p&amp;quot;, required = false) String pageLink) {&lt;br /&gt;        ControllerContext ctx = new ControllerContext(request, response);&lt;br /&gt;        init(ctx);&lt;br /&gt;&lt;br /&gt;        if (!isValidCsrfToken(ctx)) {&lt;br /&gt;            return getModelAndView(ctx, LIST_PATH);&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        PageRequest pr = pageLink != null ? PageRequest.fromLink(pageLink) : PageRequest.firstPage(5);&lt;br /&gt;        m.addAttribute(documentRepository.getAll(pr));&lt;br /&gt;        return getModelAndView(ctx, LIST_PATH);&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    @RequestMapping(&amp;quot;/add&amp;quot;)&lt;br /&gt;    public ModelAndView add(HttpServletRequest request,&lt;br /&gt;            HttpServletResponse response,&lt;br /&gt;            @RequestParam(value = &amp;quot;attachment&amp;quot;, required = false) MultipartFile multipartFile,&lt;br /&gt;            @ModelAttribute(&amp;quot;document&amp;quot;) Document document,&lt;br /&gt;            BindingResult result) {&lt;br /&gt;&lt;br /&gt;        //Store constants for JSP&lt;br /&gt;        request.setAttribute(&amp;quot;UPLOAD_MAX_FILE_SIZE&amp;quot;, UPLOAD_MAX_FILE_SIZE); &lt;br /&gt;        &lt;br /&gt;        ControllerContext ctx = new ControllerContext(request, response);&lt;br /&gt;        init(ctx);&lt;br /&gt;&lt;br /&gt;        if (!isValidCsrfToken(ctx)) {&lt;br /&gt;            return getModelAndView(ctx, FORM_PATH);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        if (!isSubmission(ctx)) {&lt;br /&gt;            return getModelAndView(ctx, FORM_PATH);&lt;br /&gt;        } else {&lt;br /&gt;            &lt;br /&gt;            validator.validate(document, result);&lt;br /&gt;            &lt;br /&gt;            if (result.hasErrors()) {&lt;br /&gt;                return getModelAndView(ctx, FORM_PATH);&lt;br /&gt;            } else {&lt;br /&gt;                if(multipartFile == null) {&lt;br /&gt;                    result.addError(new ObjectError(&amp;quot;document&amp;quot;, getMessage(&amp;quot;error.add&amp;quot;, new String[] {&amp;quot;document&amp;quot;})));&lt;br /&gt;                    return getModelAndView(ctx, FORM_PATH);&lt;br /&gt;                }&lt;br /&gt;                &lt;br /&gt;                try {&lt;br /&gt;                    String title = document.getTitle();&lt;br /&gt;                    if(StringUtils.isEmpty(title)) throw new Exception(&amp;quot;Empty title&amp;quot;);&lt;br /&gt;                    document.setId(title);&lt;br /&gt;                    String contentType = multipartFile.getContentType();&lt;br /&gt;                    String base64 = new String (Base64.encodeBase64(multipartFile.getBytes()));&lt;br /&gt;                    if(StringUtils.isEmpty(base64)) throw new Exception(&amp;quot;Empty attachment&amp;quot;);&lt;br /&gt;                    Attachment a = new Attachment(title, base64, contentType);&lt;br /&gt;                    document.addInlineAttachment(a);&lt;br /&gt;                } catch (Exception ex) {&lt;br /&gt;                    result.addError(new ObjectError(&amp;quot;attachmentError&amp;quot;, getMessage(&amp;quot;error.attachingDocument&amp;quot;)));&lt;br /&gt;                    log.error(null, ex);&lt;br /&gt;                    return getModelAndView(ctx, FORM_PATH);&lt;br /&gt;                }   &lt;br /&gt;                &lt;br /&gt;                try {&lt;br /&gt;                    document.setDateCreated(new Date());&lt;br /&gt;                    documentRepository.add(document);&lt;br /&gt;                } catch (Exception ex) {&lt;br /&gt;                    result.addError(new ObjectError(&amp;quot;document&amp;quot;, getMessage(&amp;quot;error.add&amp;quot;, new String[] {&amp;quot;document&amp;quot;})));&lt;br /&gt;                    log.error(null, ex);&lt;br /&gt;                    return getModelAndView(ctx, FORM_PATH);&lt;br /&gt;                }&lt;br /&gt;                return getModelAndView(ctx, LIST_PATH, true, true);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    &lt;br /&gt;    @RequestMapping(&amp;quot;/{id}/edit&amp;quot;)&lt;br /&gt;    public ModelAndView edit(HttpServletRequest request,&lt;br /&gt;            HttpServletResponse response,&lt;br /&gt;            @ModelAttribute(&amp;quot;document&amp;quot;) Document document,&lt;br /&gt;            BindingResult result,&lt;br /&gt;            @PathVariable(&amp;quot;id&amp;quot;) String id,&lt;br /&gt;            Model model) {&lt;br /&gt;&lt;br /&gt;        ControllerContext ctx = new ControllerContext(request, response);&lt;br /&gt;        init(ctx);&lt;br /&gt;&lt;br /&gt;        if (!isValidCsrfToken(ctx)) {&lt;br /&gt;            return getModelAndView(ctx, FORM_PATH);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        try {&lt;br /&gt;            Document storedDocument = documentRepository.get(id);&lt;br /&gt;            if(storedDocument == null) {&lt;br /&gt;                throw new Exception(&amp;quot;No document found with id '&amp;quot; + id + &amp;quot;'&amp;quot;);&lt;br /&gt;            }&lt;br /&gt;           &lt;br /&gt;            if (!isSubmission(ctx)) {&lt;br /&gt;                model.addAttribute(&amp;quot;document&amp;quot;, storedDocument);&lt;br /&gt;                return getModelAndView(ctx, FORM_PATH);&lt;br /&gt;            } else {&lt;br /&gt;                validator.validate(document, result);&lt;br /&gt;                &lt;br /&gt;                if (result.hasErrors()) {&lt;br /&gt;                    return getModelAndView(ctx, FORM_PATH);&lt;br /&gt;                } else {&lt;br /&gt;                    String title = document.getTitle();&lt;br /&gt;                    if(StringUtils.isEmpty(title)) throw new Exception(&amp;quot;Empty title&amp;quot;);&lt;br /&gt;                    document.setId(title);&lt;br /&gt;                    document.setDateCreated(storedDocument.getDateCreated());&lt;br /&gt;                    document.setRevision(storedDocument.getRevision());&lt;br /&gt;                    documentRepository.update(document);&lt;br /&gt;                    return getModelAndView(ctx, LIST_PATH, true, true);&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        } catch (Exception ex) {&lt;br /&gt;            result.addError(new ObjectError(&amp;quot;document&amp;quot;, getMessage(&amp;quot;error.edit&amp;quot;, new String[] {&amp;quot;document&amp;quot;}) + &amp;quot;.&amp;quot; + ex.getMessage()));&lt;br /&gt;            log.error(null, ex);&lt;br /&gt;            return getModelAndView(ctx, FORM_PATH);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    &lt;br /&gt;    @RequestMapping(&amp;quot;/{id}/delete&amp;quot;)&lt;br /&gt;    public ModelAndView delete(HttpServletRequest request,&lt;br /&gt;            HttpServletResponse response,&lt;br /&gt;            @ModelAttribute(&amp;quot;document&amp;quot;) Document document,&lt;br /&gt;            BindingResult result,&lt;br /&gt;            @PathVariable(&amp;quot;id&amp;quot;) String id,&lt;br /&gt;            Model model) {&lt;br /&gt;        ControllerContext ctx = new ControllerContext(request, response);&lt;br /&gt;        init(ctx);&lt;br /&gt;&lt;br /&gt;        if (!isValidCsrfToken(ctx)) {&lt;br /&gt;            return getModelAndView(ctx, LIST_PATH);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        try {&lt;br /&gt;            document = documentRepository.get(id);&lt;br /&gt;            if(document == null) {&lt;br /&gt;                throw new Exception(&amp;quot;No document found with id '&amp;quot; + id + &amp;quot;'&amp;quot;);&lt;br /&gt;            }&lt;br /&gt;            documentRepository.remove(document);&lt;br /&gt;        } catch (Exception ex) {&lt;br /&gt;            result.addError(new ObjectError(&amp;quot;document&amp;quot;, getMessage(&amp;quot;error.add&amp;quot;, new String[] {&amp;quot;document&amp;quot;})));&lt;br /&gt;            log.error(null, ex);&lt;br /&gt;            return getModelAndView(ctx, LIST_PATH);&lt;br /&gt;        }&lt;br /&gt;        return getModelAndView(ctx, LIST_PATH, true, true);&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    @RequestMapping(&amp;quot;/{id}/show&amp;quot;)&lt;br /&gt;    public ModelAndView show(HttpServletRequest request,&lt;br /&gt;            HttpServletResponse response,&lt;br /&gt;            @ModelAttribute(&amp;quot;document&amp;quot;) Document document,&lt;br /&gt;            BindingResult result,&lt;br /&gt;            @PathVariable(&amp;quot;id&amp;quot;) String id,&lt;br /&gt;            Model model) {&lt;br /&gt;        ControllerContext ctx = new ControllerContext(request, response);&lt;br /&gt;        init(ctx);&lt;br /&gt;&lt;br /&gt;        if (!isValidCsrfToken(ctx)) {&lt;br /&gt;            return getModelAndView(ctx, LIST_PATH);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        try {&lt;br /&gt;            document = documentRepository.get(id);&lt;br /&gt;            if(document == null) {&lt;br /&gt;                throw new Exception(&amp;quot;No document found with id '&amp;quot; + id + &amp;quot;'&amp;quot;);&lt;br /&gt;            }&lt;br /&gt;            Map&amp;lt;String, Attachment&amp;gt; attachments = document.getAttachments();&lt;br /&gt;            if(attachments == null || attachments.size() == 0) {&lt;br /&gt;                throw new Exception(&amp;quot;No attachment found for id '&amp;quot; + id + &amp;quot;'&amp;quot;);&lt;br /&gt;            }&lt;br /&gt;            for(Map.Entry&amp;lt;String, Attachment&amp;gt; entry : attachments.entrySet()) {&lt;br /&gt;                String attachmentId = entry.getKey();&lt;br /&gt;                Attachment attachment = entry.getValue();&lt;br /&gt;                //long contentLength = attachment.getContentLength();&lt;br /&gt;                String contentType = attachment.getContentType();&lt;br /&gt;                AttachmentInputStream ais = documentRepository.getDb().getAttachment(id, attachmentId);&lt;br /&gt;                response.setHeader(&amp;quot;Content-Disposition&amp;quot;, &amp;quot;attachment; filename=\&amp;quot;&amp;quot; + document.getTitle() + &amp;quot;\&amp;quot;&amp;quot;);&lt;br /&gt;                response.setContentType(contentType);&lt;br /&gt;                final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();&lt;br /&gt;                IOUtils.copy(ais, outputStream);&lt;br /&gt;                render(response, outputStream);&lt;br /&gt;            }&lt;br /&gt;            return getModelAndView(ctx, LIST_PATH, true, true);&lt;br /&gt;        } catch (Exception ex) {&lt;br /&gt;            result.addError(new ObjectError(&amp;quot;document&amp;quot;, getMessage(&amp;quot;error.internal&amp;quot;, new String[] {&amp;quot;document&amp;quot;})));&lt;br /&gt;            log.error(null, ex);&lt;br /&gt;            return getModelAndView(ctx, LIST_PATH);&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    @RequestMapping(&amp;quot;/tree&amp;quot;)&lt;br /&gt;    public ModelAndView tree(HttpServletRequest request,&lt;br /&gt;            HttpServletResponse response,&lt;br /&gt;            @RequestParam(value = &amp;quot;root&amp;quot;, required = false) String root) {&lt;br /&gt;        ControllerContext ctx = new ControllerContext(request, response);&lt;br /&gt;        init(ctx);&lt;br /&gt;&lt;br /&gt;        if (!isValidCsrfToken(ctx)) {&lt;br /&gt;            return getModelAndView(ctx, TREE_PATH);&lt;br /&gt;        }&lt;br /&gt;        if(root == null) {&lt;br /&gt;            return getModelAndView(ctx, TREE_PATH);&lt;br /&gt;        }&lt;br /&gt;        try {&lt;br /&gt;            Object objTree = getTreeObject(root);&lt;br /&gt;            if(objTree == null) {&lt;br /&gt;                ctx.setRequestAttribute(&amp;quot;treeInfo&amp;quot;, getMessage(&amp;quot;noItemFound&amp;quot;, new String[] {&amp;quot;record&amp;quot;}));&lt;br /&gt;            }&lt;br /&gt;            ctx.setRequestAttribute(&amp;quot;tree&amp;quot;, objTree);&lt;br /&gt;            return getModelAndView(ctx, TREE_PATH);&lt;br /&gt;        } catch (Exception ex) {&lt;br /&gt;            ctx.setRequestViewAttribute(&amp;quot;treeError&amp;quot;, ex.getMessage());&lt;br /&gt;            log.error(null, ex);&lt;br /&gt;            return getModelAndView(ctx, TREE_PATH);&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * The needs for the current treeview plugin makes mandatory certain json structure so parsing the /document/tree service is not an option at the moment&lt;br /&gt;     * @param request&lt;br /&gt;     * @param response&lt;br /&gt;     * @param root&lt;br /&gt;     * @return&lt;br /&gt;     */&lt;br /&gt;    @RequestMapping(&amp;quot;/ajaxTree&amp;quot;)&lt;br /&gt;    public ModelAndView ajaxTree(HttpServletRequest request,&lt;br /&gt;            HttpServletResponse response,&lt;br /&gt;            @RequestParam(value = &amp;quot;root&amp;quot;, required = true) String root) {&lt;br /&gt;        ControllerContext ctx = new ControllerContext(request, response);&lt;br /&gt;        init(ctx);&lt;br /&gt;&lt;br /&gt;        if (!isValidCsrfToken(ctx)) {&lt;br /&gt;            return getModelAndView(ctx, LIST_PATH);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        try {&lt;br /&gt;            InputStream is = getTreeInputStream(root);&lt;br /&gt;            ObjectMapper mapper = new ObjectMapper();&lt;br /&gt;            JsonNode rootNode = mapper.readValue(is, JsonNode.class);&lt;br /&gt;            JsonNode rowsNode = rootNode.get(&amp;quot;rows&amp;quot;);&lt;br /&gt;            Iterator&amp;lt;JsonNode&amp;gt; iter = rowsNode.getElements();&lt;br /&gt;            while (iter.hasNext()) {&lt;br /&gt;                JsonNode row = iter.next();&lt;br /&gt;                JsonNode keyNode = row.get(&amp;quot;key&amp;quot;);&lt;br /&gt;                String[] key = mapper.readValue(keyNode, String[].class);&lt;br /&gt;                if(key == null) {&lt;br /&gt;                    continue;&lt;br /&gt;                }&lt;br /&gt;                String name = key[key.length - 1];&lt;br /&gt;                String classes = null;&lt;br /&gt;                if(key.length == DOCUMENT_LEVEL + 1) {&lt;br /&gt;                    //Listing files&lt;br /&gt;                    String extension = &amp;quot;unknownExtension&amp;quot;;&lt;br /&gt;                    String fileName = key[DOCUMENT_LEVEL];&lt;br /&gt;                    String[] tokens = fileName.split(&amp;quot;\\.&amp;quot;);&lt;br /&gt;                    if(tokens.length &amp;gt;= 2) {&lt;br /&gt;                        extension = tokens[tokens.length - 1];&lt;br /&gt;                    }&lt;br /&gt;                    classes = &amp;quot;file &amp;quot; + extension;&lt;br /&gt;                } else {&lt;br /&gt;                    //Listing folders&lt;br /&gt;                    classes = &amp;quot;folder&amp;quot;;&lt;br /&gt;                }&lt;br /&gt;                &lt;br /&gt;                ((ObjectNode)row).put(&amp;quot;classes&amp;quot;, classes);&lt;br /&gt;                ((ObjectNode)row).put(&amp;quot;name&amp;quot;, name);&lt;br /&gt;                boolean hasChildren = false;&lt;br /&gt;                //To use the key as id for next request&lt;br /&gt;                if(key.length != DOCUMENT_LEVEL + 1) {&lt;br /&gt;                    int value = mapper.readValue(row.get(&amp;quot;value&amp;quot;), Integer.class);&lt;br /&gt;                    if(value &amp;gt; 0) {&lt;br /&gt;                        hasChildren = true;&lt;br /&gt;                    }&lt;br /&gt;                } else {&lt;br /&gt;                    ((ObjectNode)row).put(&amp;quot;url&amp;quot;, request.getContextPath() + &amp;quot;/dms/document/&amp;quot; + name + &amp;quot;/show?ctoken=&amp;quot; + ctx.getSessionAttribute(WebConstants.CSRF_TOKEN, &amp;quot;&amp;quot;));&lt;br /&gt;                }&lt;br /&gt;                ((ObjectNode)row).put(&amp;quot;hasChildren&amp;quot;, hasChildren);&lt;br /&gt;                ((ObjectNode)row).put(&amp;quot;id&amp;quot;, keyNode.toString().replaceAll(&amp;quot;[\\[\\]]&amp;quot;, &amp;quot;&amp;quot;));&lt;br /&gt;                &lt;br /&gt;            }&lt;br /&gt;            &lt;br /&gt;            response.setContentType(&amp;quot;application/json&amp;quot;);&lt;br /&gt;            final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();&lt;br /&gt;            mapper.writeValue(outputStream, rowsNode);&lt;br /&gt;            render(response, outputStream);&lt;br /&gt;            return null;&lt;br /&gt;        } catch (Exception ex) {&lt;br /&gt;            log.error(null, ex);&lt;br /&gt;            return null;&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    private Object getTreeObject(String root) {&lt;br /&gt;        InputStream is = getTreeInputStream(root);&lt;br /&gt;        String tree = new Scanner(is).useDelimiter(&amp;quot;\\A&amp;quot;).next();&lt;br /&gt;        Object objTree = GeneralObjectDeserializer.fromJson(tree);&lt;br /&gt;        return objTree;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    private InputStream getTreeInputStream(String root) {&lt;br /&gt;        //Making it compatible with the jquery tree view in use by Portal in Liferay&lt;br /&gt;        String endKey = null;&lt;br /&gt;        if(&amp;quot;0&amp;quot;.equals(root)) {&lt;br /&gt;            endKey = &amp;quot;[{}]&amp;quot;;&lt;br /&gt;        } else {&lt;br /&gt;            endKey = &amp;quot;[&amp;quot; + root.substring(0,root.length()) + &amp;quot;,{}]&amp;quot;;&lt;br /&gt;        }&lt;br /&gt;         &lt;br /&gt;        String[] tokens = endKey.replaceAll(&amp;quot;[\\[\\]]&amp;quot;, &amp;quot;&amp;quot;).split(&amp;quot;,&amp;quot;);&lt;br /&gt;        int groupLevel = tokens.length;&lt;br /&gt;        String startKey = &amp;quot;[1]&amp;quot;;&lt;br /&gt;        if(!&amp;quot;{}&amp;quot;.equals(tokens[0])) {&lt;br /&gt;            startKey = &amp;quot;[&amp;quot; + root + &amp;quot;]&amp;quot;;&lt;br /&gt;        }&lt;br /&gt;        InputStream is = documentRepository.getTree(startKey, endKey, groupLevel);&lt;br /&gt;        return is;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    @InitBinder&lt;br /&gt;    public void initBinder(WebDataBinder binder) {&lt;br /&gt;        binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor());&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * To be consumed by swfupload.swf which is in charge of uploading multiple files&lt;br /&gt;     * &lt;br /&gt;     * @param request&lt;br /&gt;     * @param response&lt;br /&gt;     * @return&lt;br /&gt;     */&lt;br /&gt;    @RequestMapping(&amp;quot;/addBatch&amp;quot;)&lt;br /&gt;    public void addBatch(HttpServletRequest request,&lt;br /&gt;            HttpServletResponse response,&lt;br /&gt;            @RequestParam(value = &amp;quot;Filedata&amp;quot;, required = false) MultipartFile multipartFile,&lt;br /&gt;            @ModelAttribute(&amp;quot;document&amp;quot;) Document document,&lt;br /&gt;            BindingResult result) {&lt;br /&gt;&lt;br /&gt;        String message = &amp;quot;Completed&amp;quot;;&lt;br /&gt;        ControllerContext ctx = new ControllerContext(request, response);&lt;br /&gt;        init(ctx);&lt;br /&gt;        &lt;br /&gt;        try {&lt;br /&gt;            if (!isValidCsrfToken(ctx)) {&lt;br /&gt;                message = error(&amp;quot;Invalid session token&amp;quot;);&lt;br /&gt;            } else {&lt;br /&gt;                uploadFile(request, multipartFile);&lt;br /&gt;            }&lt;br /&gt;            &lt;br /&gt;        } catch (FileUploadException e) {&lt;br /&gt;            log.error(&amp;quot;FileUploadException:&amp;quot;, e);&lt;br /&gt;            message = error(e.getMessage());&lt;br /&gt;        } catch (Exception e) {&lt;br /&gt;            String errorMessage = e.toString();&lt;br /&gt;            log.error(&amp;quot;FileUploadException:&amp;quot;, e);&lt;br /&gt;            if (errorMessage == null) {&lt;br /&gt;                errorMessage = &amp;quot;Internal Error. Please look at server logs for more detail&amp;quot;;&lt;br /&gt;            }&lt;br /&gt;            message = error(errorMessage);&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        try {&lt;br /&gt;            render(response, message.getBytes());&lt;br /&gt;        } catch (Exception ex) {&lt;br /&gt;            log.error(null, ex);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * File must follow this convention: clientId_categoryId_subCategoryId_year_month_investorId.extension&lt;br /&gt;     * @param req&lt;br /&gt;     * @param multipartFile&lt;br /&gt;     * @throws Exception&lt;br /&gt;     */&lt;br /&gt;    private void uploadFile(HttpServletRequest req, MultipartFile multipartFile) throws Exception {&lt;br /&gt;&lt;br /&gt;            // Create a new file upload handler&lt;br /&gt;            FileItemFactory factory = new DiskFileItemFactory();&lt;br /&gt;            ServletFileUpload upload = new ServletFileUpload(factory);&lt;br /&gt;            upload.setFileSizeMax(UPLOAD_MAX_FILE_SIZE);&lt;br /&gt;            upload.setSizeMax(UPLOAD_MAX_TOTAL_FILES_SIZE);&lt;br /&gt;&lt;br /&gt;            if(multipartFile == null) {&lt;br /&gt;                throw new FileUploadException(getMessage(&amp;quot;error.add&amp;quot;, new String[] {&amp;quot;document&amp;quot;}));&lt;br /&gt;            }&lt;br /&gt;            &lt;br /&gt;            String fileName = multipartFile.getOriginalFilename();&lt;br /&gt;            String[] tokens = fileName.split(&amp;quot;_&amp;quot;);&lt;br /&gt;            if(tokens.length != 6) {&lt;br /&gt;                throw new Exception(&amp;quot;Filename must have 6 tokens&amp;quot;);&lt;br /&gt;            }&lt;br /&gt;            int clientId = Integer.parseInt(tokens[0]);&lt;br /&gt;            int categoryId = Integer.parseInt(tokens[1]);&lt;br /&gt;            int subCategoryId = Integer.parseInt(tokens[2]);&lt;br /&gt;            String year = tokens[3];&lt;br /&gt;            String month = tokens[4];&lt;br /&gt;            //Use whole filename as title&lt;br /&gt;            int investorId = Integer.parseInt(tokens[5].split(&amp;quot;\\.&amp;quot;)[0]);&lt;br /&gt;            String title = fileName;&lt;br /&gt;            //Using swfupload we almost always get &amp;quot;application/octet-stream&amp;quot;&lt;br /&gt;            String contentType = multipartFile.getContentType();&lt;br /&gt;            String guessedContentType = URLConnection.guessContentTypeFromName(fileName);&lt;br /&gt;            if (guessedContentType != null) {&lt;br /&gt;                contentType = guessedContentType;&lt;br /&gt;            }&lt;br /&gt;            String base64 = new String (Base64.encodeBase64(multipartFile.getBytes()));&lt;br /&gt;            if(StringUtils.isEmpty(base64)) throw new Exception(&amp;quot;Empty attachment&amp;quot;);&lt;br /&gt;            Attachment a = new Attachment(fileName, base64, contentType);&lt;br /&gt;            Document document = new Document(title);&lt;br /&gt;            document.setId(fileName);&lt;br /&gt;            document.addInlineAttachment(a);&lt;br /&gt;            document.setDateCreated(new Date());&lt;br /&gt;            document.setClientId(clientId);&lt;br /&gt;            document.setCategoryId(categoryId);&lt;br /&gt;            document.setSubCategoryId(subCategoryId);&lt;br /&gt;            document.setDateEffective(new SimpleDateFormat(&amp;quot;yyyy-MM-dd&amp;quot;, Locale.ENGLISH).parse(year + &amp;quot;-&amp;quot; + month + &amp;quot;-&amp;quot; + 1));&lt;br /&gt;            document.setInvestorId(investorId);&lt;br /&gt;            documentRepository.add(document);&lt;br /&gt;            &lt;br /&gt;            //Within Spring the below does not work&lt;br /&gt;            /*&lt;br /&gt;            List&amp;lt;FileItem&amp;gt; items = (List&amp;lt;FileItem&amp;gt;) upload.parseRequest(req);&lt;br /&gt;            Iterator&amp;lt;FileItem&amp;gt; iter = items.iterator();&lt;br /&gt;            while (iter.hasNext()) {&lt;br /&gt;                FileItem item = iter.next();&lt;br /&gt;                if (item.isFormField()) {&lt;br /&gt;                    String name = item.getFieldName();&lt;br /&gt;                    String value = item.getString();&lt;br /&gt;                    log.debug(&amp;quot;Form field &amp;quot; + name + &amp;quot; with value &amp;quot; + value);&lt;br /&gt;                } else {&lt;br /&gt;                    String fileName = item.getName();&lt;br /&gt;                    String contentType = item.getContentType();&lt;br /&gt;                    Document document = new Document(fileName);&lt;br /&gt;                    String base64 = new String (Base64.encodeBase64(item.get()));&lt;br /&gt;                    if(StringUtils.isEmpty(base64)) throw new Exception(&amp;quot;Empty attachment&amp;quot;);&lt;br /&gt;                    Attachment a = new Attachment(fileName, base64, contentType);&lt;br /&gt;                    document.addInlineAttachment(a);&lt;br /&gt;                    document.setDateCreated(new Date());&lt;br /&gt;                    documentRepository.add(document);&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            */&lt;br /&gt;    }&lt;br /&gt;    /*&lt;br /&gt;     * To format the message so sfwupload understands it&lt;br /&gt;     */&lt;br /&gt;    private String error(String error) {&lt;br /&gt;        return &amp;quot;ERROR: &amp;quot; + error;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Below are some screenshots of our simple user interface:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-DsuhYmiPNJA/TpunrQPCYJI/AAAAAAAADgs/Is4SZVyeyiE/s1600/1-Screen%2Bshot%2B2011-10-16%2Bat%2B11.46.20%2BPM.png" imageanchor="1" style=""&gt;&lt;img border="0" height="400" width="268" src="http://1.bp.blogspot.com/-DsuhYmiPNJA/TpunrQPCYJI/AAAAAAAADgs/Is4SZVyeyiE/s400/1-Screen%2Bshot%2B2011-10-16%2Bat%2B11.46.20%2BPM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-x2BhnRzEhwM/TpunviD1YXI/AAAAAAAADg4/oFd08QLYZcg/s1600/2-Screen%2Bshot%2B2011-10-16%2Bat%2B11.55.46%2BPM.png" imageanchor="1" style=""&gt;&lt;img border="0" height="400" width="319" src="http://1.bp.blogspot.com/-x2BhnRzEhwM/TpunviD1YXI/AAAAAAAADg4/oFd08QLYZcg/s400/2-Screen%2Bshot%2B2011-10-16%2Bat%2B11.55.46%2BPM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-EP3x9WL7alY/Tpun1UUgREI/AAAAAAAADhE/SHOpUqS9vxY/s1600/3-Screen%2Bshot%2B2011-10-16%2Bat%2B7.07.29%2BAM.png" imageanchor="1" style=""&gt;&lt;img border="0" height="400" width="121" src="http://3.bp.blogspot.com/-EP3x9WL7alY/Tpun1UUgREI/AAAAAAAADhE/SHOpUqS9vxY/s400/3-Screen%2Bshot%2B2011-10-16%2Bat%2B7.07.29%2BAM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;h3&gt;Why CouchDB? and not a SQL database?&lt;/h3&gt;Relational Database Management Systems (RDBMS) are good to store tabular data, enforce relationship, remove duplicated information, ensure data consistency and the list goes on and on. There is one thing though that makes relational databases not ideal for distributed computing and that is locking. The need for an alternative comes from impediments related to replication but also from storing hierarchical structures in RDBMS which is not natural. Finally it is difficult to manage inheritance and schema changes can easily become a big problem when the system grows and new simple necessities emerge from business requirements. RDBMS are also slow if you want an index for all fields in a table or multiple complex indexes. If your content management system has a need for distributed computing, fast storage and you can afford the compromise about losing the ability for easy normalization (for example your documents once created are stamped with metadata available at that time and which does not change over time)&lt;br /&gt;&lt;br /&gt;CouchDB is a noSql type database which stores data structures as documents (JSON Strings). Schema less, based on b-tree and with no locking (but Multi Version Concurrency Control) design it is a really fast alternative when you look for a solution to store hierarchical non-strict schema data like for example storing web pages or binary files. All this robustness is exposed with a HTTP REST API where JSON is used to send messages to the server as well as receive messages from it. This makes it really attractive for those looking for lightweight solutions. One of the "trade-offs" in couchDB is that the only way to query CouchDB without creating indexes is a temporary View and that is not an option for production as the mapped result will not be stored in a B-Tree hitting performance in your server.&lt;br /&gt;&lt;br /&gt;I have no other option than considering CouchDB the logical pick for my BHUB Document Management functionality. CouchDB provides fast access to data specified by stored keys. Using Map functions in your Views there is no limit on the efficiency you can get out of the fact that you can create at any point a key composed of several document fields.&lt;br /&gt;&lt;br /&gt;CouchDB has been engineered with replication in mind and that means you get distributed computing on top of the advantages I already discussed above. You just run a command specifying URLs for the source and the destination server and the replication is done. By default latest changes will be favored and if there are conflicts you will get the differences so you can update with changes that resolve the changes. Think about any versioning system like subversion for a comparison on how it works. You can replicate in both directions of course.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-6918766619149381790?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/6918766619149381790/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=6918766619149381790' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6918766619149381790'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6918766619149381790'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/10/document-management-system-with-couchdb_5322.html' title='Document Management System with CouchDB - Third Part'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-DsuhYmiPNJA/TpunrQPCYJI/AAAAAAAADgs/Is4SZVyeyiE/s72-c/1-Screen%2Bshot%2B2011-10-16%2Bat%2B11.46.20%2BPM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-4091433065144211147</id><published>2011-10-16T18:19:00.000-07:00</published><updated>2011-10-16T21:13:29.258-07:00</updated><title type='text'>Document Management System with CouchDB - Second Part</title><content type='html'>In the &lt;a href="http://thinkinginsoftware.blogspot.com/2011/10/document-management-system-with-couchdb.html"&gt;first part&lt;/a&gt; we installed CouchDB and explain how to create databases, documents, update them, create attachments and run queries.&lt;br /&gt;&lt;br /&gt;Now we will design a DMS and plan for the different functionality we will need with one example.&lt;br /&gt;&lt;br /&gt;For simplicity we will say our documents will be imported in batch for which it makes sense to have a convention for the file names client_cat_subcat_year_month_investor.ext. After importing the below 14 documents we can start navigating the tree. Note that in this example the importer code will replace the month by two digits so "3" becomes "03"&lt;br /&gt;&lt;pre class="brush: bash"&gt;1_1_1_2003_1_1.pdf&lt;br /&gt;1_1_1_2003_1_2.pdf&lt;br /&gt;1_1_1_2003_2_3.pdf&lt;br /&gt;1_1_1_2003_2_4.pdf&lt;br /&gt;1_1_1_2004_3_5.pdf&lt;br /&gt;1_1_2_2005_4_6.pdf&lt;br /&gt;1_1_3_2006_5_7.pdf&lt;br /&gt;1_1_3_2006_6_8.pdf&lt;br /&gt;1_2_4_2007_7_9.pdf&lt;br /&gt;2_3_5_2008_8_10.pdf&lt;br /&gt;2_3_5_2009_9_11.pdf&lt;br /&gt;2_3_6_2010_10_12.pdf&lt;br /&gt;2_3_6_2010_11_13.pdf&lt;br /&gt;2_3_7_2011_12_14.pdf&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We want to offer a tree view of our documents. First we show the available clients. When the user clicks one of them we show the categories available. Clicking one of the categories will render all subcategories and so on. Let us go by the example of the first document (1_1_1_2003_1_1.pdf)&lt;br /&gt;&lt;br /&gt;Here is how to pull all clients. Note the use of {} which means "any": &lt;br /&gt;&lt;pre class="brush: bash"&gt;nestor-nu:~ nestor$ curl -X GET "http://127.0.0.1:5984/dms4/_design/Document/_view/tree?group=true&amp;group_level=1" -G --data-urlencode startkey='[1]' --data-urlencode endkey='[{}]'&lt;br /&gt;{"rows":[&lt;br /&gt;{"key":[1],"value":9},&lt;br /&gt;{"key":[2],"value":5}&lt;br /&gt;]}&lt;br /&gt;&lt;/pre&gt;The categories for a client (1)&lt;br /&gt;&lt;pre class="brush: bash"&gt;nestor-nu:~ nestor$ curl -X GET "http://127.0.0.1:5984/dms4/_design/Document/_view/tree?group=true&amp;group_level=2" -G --data-urlencode startkey='[1]' --data-urlencode endkey='[1, {}]'&lt;br /&gt;{"rows":[&lt;br /&gt;{"key":[1,1],"value":8},&lt;br /&gt;{"key":[1,2],"value":1}&lt;br /&gt;]}&lt;br /&gt;&lt;/pre&gt;The subcategories for a client category (1,1)&lt;br /&gt;&lt;pre class="brush: bash"&gt;nestor-nu:~ nestor$ curl -X GET "http://127.0.0.1:5984/dms4/_design/Document/_view/tree?group=true&amp;group_level=3" -G --data-urlencode startkey='[1,1]' --data-urlencode endkey='[1, 1, {}]'&lt;br /&gt;{"rows":[&lt;br /&gt;{"key":[1,1,1],"value":5},&lt;br /&gt;{"key":[1,1,2],"value":1},&lt;br /&gt;{"key":[1,1,3],"value":2}&lt;br /&gt;]} &lt;br /&gt;&lt;/pre&gt;The effective years for the client category subcategory (1,1,1)&lt;br /&gt;&lt;pre class="brush: bash"&gt;nestor-nu:~ nestor$ curl -X GET "http://127.0.0.1:5984/dms4/_design/Document/_view/tree?group=true&amp;group_level=4" -G --data-urlencode startkey='[1,1,1]' --data-urlencode endkey='[1,1,1,{}]'&lt;br /&gt;{"rows":[&lt;br /&gt;{"key":[1,1,1,"2003"],"value":4},&lt;br /&gt;{"key":[1,1,1,"2004"],"value":1}&lt;br /&gt;]}&lt;br /&gt;&lt;/pre&gt;The effective months for the client category subcategory year (1,1,1,"2003"). Note the quotes for 2003 as it is a String obtained from a token.&lt;br /&gt;&lt;pre class="brush: bash"&gt;nestor-nu:~ nestor$ curl -X GET "http://127.0.0.1:5984/dms4/_design/Document/_view/tree?group=true&amp;group_level=5" -G --data-urlencode startkey='[1,1,1,"2003"]' --data-urlencode endkey='[1,1,1,"2003",{}]'&lt;br /&gt;{"rows":[&lt;br /&gt;{"key":[1,1,1,"2003","01"],"value":2},&lt;br /&gt;{"key":[1,1,1,"2003","02"],"value":2}&lt;br /&gt;]}&lt;br /&gt;&lt;/pre&gt;The documents for the client category subcategory year month(1,1,1,"2003","01"). Note "01" instead "1" just because our importer is treating months as 2 digits values.&lt;br /&gt;&lt;pre class="brush: bash"&gt;nestor-nu:~ nestor$ curl -X GET "http://127.0.0.1:5984/dms4/_design/Document/_view/tree?group=true&amp;group_level=6" -G --data-urlencode startkey='[1,1,1,"2003","01"]' --data-urlencode endkey='[1,1,1,"2003","01",{}]'&lt;br /&gt;{"rows":[&lt;br /&gt;{"key":[1,1,1,"2003","01","1_1_1_2003_1_1.pdf"],"value":1},&lt;br /&gt;{"key":[1,1,1,"2003","01","1_1_1_2003_1_2.pdf"],"value":1}&lt;br /&gt;]}&lt;br /&gt;&lt;/pre&gt;So you have figured if we want to navigate to document 2_3_5_2009_9_11.pdf we just have to pass startkey='[2]' and endkey=[2,3,5,"2009","09",{}] and the group_level 6:&lt;br /&gt;&lt;pre class="brush: bash"&gt;nestor-nu:~ nestor$ curl -X GET "http://127.0.0.1:5984/dms4/_design/Document/_view/tree?group=true&amp;group_level=6" -G --data-urlencode startkey='[2,3,5,"2009","09"]' --data-urlencode endkey='[2,3,5,"2009","09",{}]'&lt;br /&gt;{"rows":[&lt;br /&gt;{"key":[2,3,5,"2009","09","2_3_5_2009_9_11.pdf"],"value":1}&lt;br /&gt;]}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If we were to build a JSON web service we just need to accept the startkey. The endkey is always an array containing startkey with a new last element: {}. &lt;br /&gt;&lt;br /&gt;Specifically if I use &lt;a href="http://thinkinginsoftware.blogspot.com/2010/07/business-hub.html"&gt;BHUB&lt;/a&gt; (which is just a concept around Spring Framework) a typical request and response will look like:&lt;br /&gt;&lt;pre class="brush: bash"&gt;http://localhost:8080/nu-app/dms/document/tree?root=2,3,5,"2009","09"&amp;ert=json&lt;br /&gt;{"rows":[&lt;br /&gt;{"key":[2,3,5,"2009","09","2_3_5_2009_9_11.pdf"],"value":1}&lt;br /&gt;]}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In the &lt;a href="http://thinkinginsoftware.blogspot.com/2011/10/document-management-system-with-couchdb_5322.html"&gt;final part&lt;/a&gt; I show how to use Java &lt;a href="https://github.com/helun/Ektorp"&gt;Erktop&lt;/a&gt; library to implement the DMS we have been covering so far.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-4091433065144211147?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/4091433065144211147/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=4091433065144211147' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/4091433065144211147'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/4091433065144211147'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/10/document-management-system-with-couchdb_16.html' title='Document Management System with CouchDB - Second Part'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-4575898181786727850</id><published>2011-10-16T18:10:00.000-07:00</published><updated>2011-10-22T16:57:19.061-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='couchdb tutorial install query view map reduce'/><title type='text'>Document Management System with CouchDB - First Part</title><content type='html'>I will start documenting about my experience using &lt;a href="http://couchdb.apache.org/"&gt;CouchDB&lt;/a&gt; to build a Document Management System (DMS), an important component of any Content Management System (CMS).&lt;br /&gt;&lt;br /&gt;The first part concentrates on installing and using CouchDB in OSX and Ubuntu.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;OSX&lt;/h3&gt;Alternatively you could &lt;a href="http://thinkinginsoftware.blogspot.com/2011/10/upgrading-couchdb-in-osx.html"&gt;install from sources&lt;/a&gt; which I prefer to get later and greatest.&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Download any pending updates for OSX. Then latest version of XCode&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Install homebrew if you still not have it. It is the best package manager for OSX.&lt;br /&gt;&lt;/li&gt;&lt;pre class="brush: bash"&gt;/usr/bin/ruby -e "$(curl -fsSL https://raw.github.com/gist/323731)"&lt;br /&gt;&lt;/pre&gt;&lt;li&gt;Install couchDB. Following instructions from http://wiki.apache.org/couchdb/Installation with just one command. It could take a while, if it hangs then restart again, it will continue from where it broke.&lt;br /&gt;&lt;/li&gt;&lt;pre class="brush: bash"&gt;brew install couchdb&lt;br /&gt;&lt;/pre&gt;&lt;li&gt;Start the server.&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ couchdb&lt;br /&gt;$ curl -X GET http://127.0.0.1:5984/&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;If you need to change the default port:&lt;br /&gt;&lt;pre class="brush: bash"&gt;sudo vi  /usr/local/etc/couchdb/default.ini&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;h3&gt;Ubuntu&lt;/h3&gt;In Ubuntu I decided to &lt;a href="http://thinkinginsoftware.blogspot.com/2011/10/installing-couchdb-in-ubuntu.html"&gt;build from sources&lt;/a&gt; to get latest available version for my 10.10 Maverick. For 11.4 I found this also works but you have to issue 'sudo apt-get remove libmozjs185-dev' in order to build.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Using CouchDB&lt;/h3&gt;Let us start interacting with CouchDB to create a database, a document, attach a file to it, update it etc. We use curl to be sure we can issue different HTTP request method (GET, POST, PUT, DELETE).&lt;br /&gt;&lt;ol&gt;&lt;li&gt;We create our Document Management System database and we confirm it was created:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ curl -X PUT http://127.0.0.1:5984/dms&lt;br /&gt;{"ok":true}&lt;br /&gt;$ curl -X GET http://127.0.0.1:5984/_all_dbs&lt;br /&gt;["_replicator","_users","dms"]&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Let us create a first document. In this example we use POST instead of PUT so we get a UUID generated by CouchDB:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ curl -d '{&lt;br /&gt;    "name":"Investor Document 11",&lt;br /&gt;    "clientId": "1001",&lt;br /&gt;    "createdByEmployeeId": "2",&lt;br /&gt;    "reviewedByEmployeeId": "1",&lt;br /&gt;    "approvedByManagerId": "21",&lt;br /&gt;    "created": "2/2/2011",&lt;br /&gt;    "reviewed": "2/3/2011",&lt;br /&gt;    "approved": "2/4/2011",&lt;br /&gt;    "investorId": "32",&lt;br /&gt;    "categoryId": "2",&lt;br /&gt;    "statusId": "2"&lt;br /&gt;}' -H "Content-Type: application/json" -X POST http://127.0.0.1:5984/dms&lt;br /&gt;&lt;/pre&gt;Result:&lt;br /&gt;&lt;pre class="brush: bash"&gt;{"ok":true,"id":"296ef7cde8fe533efe0c7dded873505b","rev":"1-d5dd0fa82df07553f3a2b82947864fc6"}&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Let us attach a file to the above document. Note we need the document is and rev:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ curl -X PUT -H "Content-Type: application/pdf"  --data-binary @DailyReport.pdf  $DMS/296ef7cde8fe533efe0c7dded873505b/DailyReport.pdf?rev=1-d5dd0fa82df07553f3a2b82947864fc6&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;You can visually manage your CouchDB server via Futon user interface. Just hit http://localhost:5984/_utils/ and start playing with it. &lt;br /&gt;&lt;/li&gt;&lt;li&gt;Create some documents for different combinations of categoryId, clientId and investorId either from curl or from Futon.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Let us start querying our DB. &lt;br /&gt;&lt;br /&gt;You query a View in CouchDB. Views are a combination of two functions that are applied to the original data: Map and Reduce (MapReduce style: Map functions generate indexes and Reduce queries are requests against them). Map function as its name suggests specifies the mapping between the document structure and the structure of the View. Reduce function as its name suggests specifies how to group the resulting data to reduce the results. The View is consequently just a transformation of the document where an index is usually defined. If you need to group a Reduce step will be applied as well.&lt;br /&gt;&lt;br /&gt;You must become familiar with how to write the map and reduce functions for Views. This is done using the javascript language. From Futon select "Temporary View ..." option from the View dropdown. You have two panes now, the left is for your Map function and the right is for the Reduce. By default you see CouchDB proposes the below code which is equivalent to "Do not use any custom key and show all values from the document". There is no transformation nor custom index at all in this case, however if no key is specified couchDB uses the document id as unique identifier). Remember the Map function generates rows containing the id, an optional key and an optional value.&lt;br /&gt;&lt;pre class="brush: javascript"&gt;function(doc) {&lt;br /&gt;  emit(null, doc);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Let us edit the function to "Use the name as key and show only clientId and investorId". When you run the view using both functions you will realize the difference. By now you should be aware that emit() just accepts two parameters, the key for an index and the value that will be returned. Of course the results come ordered by the Key if provided. Both key and value are json expressions as well.&lt;br /&gt;&lt;pre class="brush: javascript"&gt;function(doc) {&lt;br /&gt;  //emit(doc.name, doc);&lt;br /&gt;  if(doc.name &amp;&amp; doc.clientId) {&lt;br /&gt;    var key = doc.name;&lt;br /&gt;    var value = {name: doc.name, clientId: doc.clientId, investorId: doc.investorId}&lt;br /&gt;    emit(key, value);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Here we use a composite key out of the clientId and the investorId so we can find the documents for that combination. Again the results are ordered first by clientId and later by investorId:&lt;br /&gt;&lt;pre class="brush: javascript"&gt;function(doc) {&lt;br /&gt;  if(doc.clientId &amp;&amp; doc.investorId) {&lt;br /&gt;    var key = [doc.clientId, doc.investorId];&lt;br /&gt;    var value = {name: doc.name, clientId: doc.clientId, investorId: doc.investorId}&lt;br /&gt;    emit(key, value);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Save the view. The options you pick will be used in the URL to retrieve the View results. I have decided to use "common" for the design document name and "by_client_investor" for the name of the view:&lt;br /&gt;&lt;pre class="brush: javascript"&gt;Design Document: _design/common&lt;br /&gt;View Name: by_client_investor&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;br /&gt;Now the View is saved so we can query it at any time. The View is now "Permanent" and not longer "Temporary". Let us query it for just one key. Note that as we decided to use an array as key we will need to look for something like: ["1000","30"]. As you might have notice the key contains characters that must be URL encoded, in this case %5B%221000%22%2C%2230%22%5D. Here is how the command will look like:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ curl -X GET http://127.0.0.1:5984/dms/_design/common/_view/by_client_investor?key=%5B%221000%22%2C%2230%22%5D&lt;br /&gt;&lt;/pre&gt;Alternatively you can use a more clear approach using some other curl flags:&lt;br /&gt;&lt;pre class="brush: bash"&gt;curl -X GET http://127.0.0.1:5984/dms/_design/common/_view/by_client_investor -G --data-urlencode key='["1000","30"]'&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Here is how you use curl to create and execute a temporary View from the command line. Here we are using categoryId as a key and getting the whole document as a result of the "non existent" transformation.&lt;br /&gt;&lt;pre class="brush: bash"&gt;curl -X POST http://127.0.0.1:5984/dms/_temp_view -H "Content-Type: application/json" -d \&lt;br /&gt;'{&lt;br /&gt;  "map": "function(doc) {&lt;br /&gt;            if (doc.categoryId) {&lt;br /&gt;              emit(doc.categoryId, doc);&lt;br /&gt;            }&lt;br /&gt;          }"&lt;br /&gt;}'&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Let us explore the results of the below temporary View. Here we are insterested in the total documents by category. As we are grouping we need to use a Reduce function where we take advantage of the provided _count. Note the key is null because it counts all of the existing documents.&lt;br /&gt;&lt;pre class="brush: bash"&gt;curl -X POST http://127.0.0.1:5984/dms/_temp_view -H "Content-Type: application/json" -d \&lt;br /&gt;'{&lt;br /&gt;  "map": "function(doc) {&lt;br /&gt;            if (doc.categoryId) {&lt;br /&gt;              emit(doc.categoryId, doc);&lt;br /&gt;            }&lt;br /&gt;          }",&lt;br /&gt;  "reduce": "_count"&lt;br /&gt;}'&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Here is how we generate the counting by key which translates to use "Grouping=exact" from Futon or as shown below "group=true" from the HTTP request:&lt;br /&gt;&lt;pre class="brush: bash"&gt;curl -X POST http://127.0.0.1:5984/dms/_temp_view?group=true -H "Content-Type: application/json" -d \&lt;br /&gt;'{&lt;br /&gt;  "map": "function(doc) {&lt;br /&gt;            if (doc.categoryId) {&lt;br /&gt;              emit(doc.categoryId, doc);&lt;br /&gt;            }&lt;br /&gt;          }",&lt;br /&gt;  "reduce": "_count"&lt;br /&gt;}'&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;We already saw how to make a View permanent while saving it from Futon. Here is how from an HTTP request you do the same. This time we are adding a View called category_count to a Design Document called category:&lt;br /&gt;&lt;pre class="brush: bash"&gt;curl -X PUT http://127.0.0.1:5984/dms/_design/category -d \&lt;br /&gt;'{&lt;br /&gt;   "_id": "_design/category",&lt;br /&gt;   "language": "javascript",&lt;br /&gt;   "views": {&lt;br /&gt;     "count": {&lt;br /&gt;       "map":&lt;br /&gt;         "function(doc) {&lt;br /&gt;           if (doc.categoryId) {&lt;br /&gt;             emit(doc.categoryId, doc);&lt;br /&gt;           }&lt;br /&gt;         }",&lt;br /&gt;       "reduce": "_count"&lt;br /&gt;      }&lt;br /&gt;   }&lt;br /&gt;}'&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;As we already saw we can query this view like this:&lt;br /&gt;&lt;pre class="brush: bash"&gt;curl -X GET http://127.0.0.1:5984/dms/_design/category/_view/count&lt;br /&gt;{"rows":[&lt;br /&gt;{"key":"1","value":17},&lt;br /&gt;{"key":"2","value":1}&lt;br /&gt;]}&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;At this point you can interact with CouchDB from any language using plain REST commands. You might want to use some abstractions with an API that allows you to go through CRUD operations with CouchDB without being concern about the details of sending and parsing JSON. &lt;br /&gt;&lt;br /&gt;In the &lt;a href="http://thinkinginsoftware.blogspot.com/2011/10/document-management-system-with-couchdb_16.html"&gt;next part&lt;/a&gt; we start the design of the DMS for which we will not use any specific language other than plain HTTP with the help of curl.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-4575898181786727850?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/4575898181786727850/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=4575898181786727850' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/4575898181786727850'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/4575898181786727850'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/10/document-management-system-with-couchdb.html' title='Document Management System with CouchDB - First Part'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-5105560297381177994</id><published>2011-10-03T12:21:00.000-07:00</published><updated>2011-10-23T03:42:34.161-07:00</updated><title type='text'>Tomcat 7 scans all jars for TLDs</title><content type='html'>Tomcat 7 scans all jars for TLDs. I am unsure if tomcat 6 does the same:&lt;br /&gt;&lt;pre class="brush: bash"&gt;INFO: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Once the log level was increased to FINE in conf/logging.properties:&lt;br /&gt;&lt;pre class="brush: bash"&gt;org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = FINE&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We got:&lt;br /&gt;&lt;pre class="brush: bash"&gt;...&lt;br /&gt;FINE: No TLD files were found in [file:/opt/tomcat/webapps/nestorurquiza-app/WEB-INF/lib/org.springframework.transaction-3.0.5.RELEASE.jar]. Consider adding the JAR to the tomcat.util.scan.DefaultJarScanner.jarsToSkip property in CATALINA_BASE/conf/catalina.properties file.&lt;br /&gt;Oct 3, 2011 2:05:58 PM org.apache.jasper.compiler.TldLocationsCache tldScanJar&lt;br /&gt;FINE: No TLD files were found in [file:/opt/tomcat/webapps/nestorurquiza-app/WEB-INF/lib/jsr250-api-1.0.jar]. Consider adding the JAR to the tomcat.util.scan.DefaultJarScanner.jarsToSkip property in CATALINA_BASE/conf/catalina.properties file.&lt;br /&gt;Oct 3, 2011 2:05:58 PM org.apache.jasper.compiler.TldLocationsCache tldScanJar&lt;br /&gt;FINE: No TLD files were found in [file:/opt/tomcat/webapps/nestorurquiza-app/WEB-INF/lib/org.springframework.security.ldap-3.0.5.RELEASE.jar]. Consider adding the JAR to the tomcat.util.scan.DefaultJarScanner.jarsToSkip property in CATALINA_BASE/conf/catalina.properties file.&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Solution&lt;/h3&gt;Add all the project jars to the list in catalina.properties. I will need to see a real impact in performance because of this issue before spending time filling this list out in all of our Tomcat servers.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-5105560297381177994?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/5105560297381177994/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=5105560297381177994' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5105560297381177994'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5105560297381177994'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/10/tomcat-7-scans-all-jars-for-tlds.html' title='Tomcat 7 scans all jars for TLDs'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-6379577050796664259</id><published>2011-10-03T12:17:00.000-07:00</published><updated>2011-10-03T12:17:45.023-07:00</updated><title type='text'>Tomcat 7 reveals log4j memory leak</title><content type='html'>We use &lt;a href="http://thinkinginsoftware.blogspot.com/2011/03/agile-troubleshooting-loggingfilter-to.html"&gt;MDC to log certain information in all traces&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;Tomcat 7 is notifying the following (As a difference with tomcat 6 which was silent):&lt;br /&gt;&lt;pre class="brush: bash"&gt;SEVERE: The web application [/the-app] created a ThreadLocal with key of type [org.apache.log4j.helpers.ThreadLocalMap] (value [org.apache.log4j.helpers.ThreadLocalMap@757e5533]) and a value of type [java.util.Hashtable] (value [{sessionId=5366DB999B9EA1AC4CF30BED024BA44C, remoteAddress=127.0.0.1}]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak. &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Solution&lt;/h3&gt;Still waiting for a resolution on a Log4j memory leak: https://issues.apache.org/bugzilla/show_bug.cgi?id=50486&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-6379577050796664259?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/6379577050796664259/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=6379577050796664259' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6379577050796664259'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6379577050796664259'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/10/tomcat-7-reveals-log4j-memory-leak.html' title='Tomcat 7 reveals log4j memory leak'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-5068157662742314925</id><published>2011-10-03T12:14:00.001-07:00</published><updated>2011-10-04T06:07:58.210-07:00</updated><title type='text'>Tomcat 7 JSTL Failed to parse the expression</title><content type='html'>In Tomcat 7 (v7.0.22) method like isNew() cannot be referred as ${myObject.new} as before:&lt;br /&gt;&lt;pre class="brush: bash"&gt;org.apache.jasper.JasperException: /WEB-INF/jsp/client/form.jsp (line: 5, column: 4) "${client.new}" contains invalid expression(s): javax.el.ELException: Failed to parse the expr&lt;br /&gt;ession [${client.new}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;a href="http://stackoverflow.com/questions/3879794/spring-3-petclinic-owner-new-invalid-expression-in-tomcat-7/7636996#7636996"&gt;This problem was documented a year ago&lt;/a&gt; and someone might be tempted to change the code for something like ${myObject.isNew()} after realizing that does work. However latest version of jasper-el breaks for this case which makes me think I will need to change my code again in future versions of Tomcat.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Solution&lt;/h3&gt;Change the code from ${client.new} to ${client['new']}&lt;br /&gt;&lt;br /&gt;In mailing lists I understood Apache 7 is less permissive and since 'new' is not a valid Java identifier it cannot be part of the EL expression like in ${client.new}. There is flag to make Tomcat 7 more permissive albeit rewriting the code should be preferred:&lt;br /&gt;&lt;pre class="brush: bash"&gt;-Dorg.apache.el.parser.SKIP_IDENTIFIER_CHECK=false&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I came up with an alternative and temporary (and again not recommended) solution to avoid changing the code which is downloading latest jasper-el http://repo1.maven.org/maven2/org/apache/tomcat/jasper-el/6.0.33/jasper-el-6.0.33.jar or even copying the jar from a previous tomcat installation. Just remember to remove the old jar file:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ cd ~&lt;br /&gt;$ curl http://repo1.maven.org/maven2/org/apache/tomcat/jasper-el/6.0.33/jasper-el-6.0.33.jar &gt; jasper-el-6.0.33.jar&lt;br /&gt;$ mv /opt/apache-tomcat-7.0.22/lib/jasper-el.jar .&lt;br /&gt;$ cp jasper-el-6.0.33.jar /opt/apache-tomcat-7.0.22/lib/&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-5068157662742314925?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/5068157662742314925/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=5068157662742314925' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5068157662742314925'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5068157662742314925'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/10/tomcat-7-jstl-failed-to-parse.html' title='Tomcat 7 JSTL Failed to parse the expression'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-7670941306770607163</id><published>2011-10-03T11:52:00.000-07:00</published><updated>2011-10-03T11:52:35.912-07:00</updated><title type='text'>Tomcat 7 TLD skipped ... is already defined</title><content type='html'>After deployment Tomcat 7 would log the below messages. Tomcat 6 did not:&lt;br /&gt;&lt;pre class="brush: bash"&gt;INFO: TLD skipped. URI: http://java.sun.com/jstl/core_rt is already defined&lt;br /&gt;Oct 3, 2011 11:45:44 AM org.apache.catalina.startup.TaglibUriRule body&lt;br /&gt;INFO: TLD skipped. URI: http://java.sun.com/jstl/core is already defined&lt;br /&gt;Oct 3, 2011 11:45:44 AM org.apache.catalina.startup.TaglibUriRule body&lt;br /&gt;INFO: TLD skipped. URI: http://java.sun.com/jsp/jstl/core is already defined&lt;br /&gt;Oct 3, 2011 11:45:44 AM org.apache.catalina.startup.TaglibUriRule body&lt;br /&gt;INFO: TLD skipped. URI: http://java.sun.com/jstl/fmt_rt is already defined&lt;br /&gt;Oct 3, 2011 11:45:44 AM org.apache.catalina.startup.TaglibUriRule body&lt;br /&gt;INFO: TLD skipped. URI: http://java.sun.com/jstl/fmt is already defined&lt;br /&gt;Oct 3, 2011 11:45:44 AM org.apache.catalina.startup.TaglibUriRule body&lt;br /&gt;INFO: TLD skipped. URI: http://java.sun.com/jsp/jstl/fmt is already defined&lt;br /&gt;Oct 3, 2011 11:45:44 AM org.apache.catalina.startup.TaglibUriRule body&lt;br /&gt;INFO: TLD skipped. URI: http://java.sun.com/jsp/jstl/functions is already defined&lt;br /&gt;Oct 3, 2011 11:45:44 AM org.apache.catalina.startup.TaglibUriRule body&lt;br /&gt;INFO: TLD skipped. URI: http://jakarta.apache.org/taglibs/standard/permittedTaglibs is already defined&lt;br /&gt;Oct 3, 2011 11:45:44 AM org.apache.catalina.startup.TaglibUriRule body&lt;br /&gt;INFO: TLD skipped. URI: http://jakarta.apache.org/taglibs/standard/scriptfree is already defined&lt;br /&gt;Oct 3, 2011 11:45:44 AM org.apache.catalina.startup.TaglibUriRule body&lt;br /&gt;INFO: TLD skipped. URI: http://java.sun.com/jstl/sql_rt is already defined&lt;br /&gt;Oct 3, 2011 11:45:44 AM org.apache.catalina.startup.TaglibUriRule body&lt;br /&gt;INFO: TLD skipped. URI: http://java.sun.com/jstl/sql is already defined&lt;br /&gt;Oct 3, 2011 11:45:44 AM org.apache.catalina.startup.TaglibUriRule body&lt;br /&gt;INFO: TLD skipped. URI: http://java.sun.com/jsp/jstl/sql is already defined&lt;br /&gt;Oct 3, 2011 11:45:44 AM org.apache.catalina.startup.TaglibUriRule body&lt;br /&gt;INFO: TLD skipped. URI: http://java.sun.com/jstl/xml_rt is already defined&lt;br /&gt;Oct 3, 2011 11:45:44 AM org.apache.catalina.startup.TaglibUriRule body&lt;br /&gt;INFO: TLD skipped. URI: http://java.sun.com/jstl/xml is already defined&lt;br /&gt;Oct 3, 2011 11:45:44 AM org.apache.catalina.startup.TaglibUriRule body&lt;br /&gt;INFO: TLD skipped. URI: http://java.sun.com/jsp/jstl/xml is already defined&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Solution&lt;/h3&gt;Look for duplicates in the server/project jars. In my case spring JSTL has a dependency of Spring standard and eliminating the second solves the problem (The second includes the same TLDs again)&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;dependency&amp;gt;&lt;br /&gt;            &amp;lt;groupId&amp;gt;javax.servlet&amp;lt;/groupId&amp;gt;&lt;br /&gt;            &amp;lt;artifactId&amp;gt;com.springsource.javax.servlet.jsp.jstl&amp;lt;/artifactId&amp;gt;&lt;br /&gt;            &amp;lt;version&amp;gt;1.2.0&amp;lt;/version&amp;gt;&lt;br /&gt;            &amp;lt;exclusions&amp;gt;&lt;br /&gt;             &amp;lt;exclusion&amp;gt;&lt;br /&gt;              &amp;lt;artifactId&amp;gt;com.springsource.org.apache.taglibs.standard&amp;lt;/artifactId&amp;gt;&lt;br /&gt;              &amp;lt;groupId&amp;gt;org.apache.taglibs&amp;lt;/groupId&amp;gt;&lt;br /&gt;             &amp;lt;/exclusion&amp;gt;&lt;br /&gt;            &amp;lt;/exclusions&amp;gt;&lt;br /&gt;&amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-7670941306770607163?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/7670941306770607163/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=7670941306770607163' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7670941306770607163'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7670941306770607163'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/10/tomcat-7-tld-skipped-is-already-defined.html' title='Tomcat 7 TLD skipped ... is already defined'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-5964182115976778725</id><published>2011-10-01T08:09:00.000-07:00</published><updated>2011-10-01T08:12:59.898-07:00</updated><title type='text'>Upgrade Ubuntu Apache to latest version in available repositories</title><content type='html'>We are using in some server Ubuntu 10.10 (maverick). It ships with Apache 2.2.14 and there is no repository with an upgrade for this highly compromised apache version.&lt;br /&gt;&lt;br /&gt;The latest version of Ubuntu still in beta is 11.10 (Oneiric). It ships Apache 2.2.20 which includes important vulnerabilities fixes.&lt;br /&gt;&lt;br /&gt;When you are in a situation like this you need to look for available debian repositories. A good place to search for them is &lt;a href="http://repogen.simplylinux.ch/"&gt;http://repogen.simplylinux.ch/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;From the site you will be able to obtain the sources.list file for any Ubuntu distro. Once you have the entries you need to add them locally and then run some commands.&lt;br /&gt;&lt;br /&gt;So here is what you can do to upgrade Apache to 2.2.20 in Maverick (and probably other Ubuntu versions)&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ sudo vi /etc/apt/sources.list&lt;br /&gt;...&lt;br /&gt;deb http://us.archive.ubuntu.com/ubuntu/ oneiric main&lt;br /&gt;...&lt;br /&gt;$ sudo apt-get update&lt;br /&gt;$ sudo apt-get install apache2&lt;br /&gt;$ apache2 -v&lt;br /&gt;Server version: Apache/2.2.20 (Ubuntu)&lt;br /&gt;Server built:   Sep  6 2011 18:40:05&lt;br /&gt;$ sudo vi /etc/apt/sources.list&lt;br /&gt;...&lt;br /&gt;#comment it out or delete it completely&lt;br /&gt;#deb http://us.archive.ubuntu.com/ubuntu/ oneiric main&lt;br /&gt;...&lt;br /&gt;$ sudo apt-get update&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-5964182115976778725?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/5964182115976778725/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=5964182115976778725' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5964182115976778725'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5964182115976778725'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/10/upgrade-ubuntu-apache-to-latest-version.html' title='Upgrade Ubuntu Apache to latest version in available repositories'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-5845300889166792848</id><published>2011-09-30T09:39:00.001-07:00</published><updated>2011-11-30T10:00:07.320-08:00</updated><title type='text'>Reusing Talend ETL Jobs</title><content type='html'>The question about how to reuse Talend jobs is always in the forums. I will demonstrate here with a proof of concept how this can be achieved using lightweight JSON HTTP requests.&lt;br /&gt;&lt;br /&gt;You can find the source code in &lt;a href="http://nestorurquiza.googlecode.com/svn/trunk/talend/json-reuse/"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;If you prefer not to hack into the code then just keep on reading for an explanation.&lt;br /&gt;&lt;br /&gt;Build a job (retrieve_stock) like the below which basically uses a Yahoo API to retrieve data about a security symbol:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-LYHxuJr5u_k/ToWjJ_lB-eI/AAAAAAAADgM/w8lGf6hGrAs/s1600/Screen%2Bshot%2B2011-09-30%2Bat%2B6.16.19%2BAM.png" imageanchor="1" style=""&gt;&lt;img border="0" height="184" width="400" src="http://4.bp.blogspot.com/-LYHxuJr5u_k/ToWjJ_lB-eI/AAAAAAAADgM/w8lGf6hGrAs/s400/Screen%2Bshot%2B2011-09-30%2Bat%2B6.16.19%2BAM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-wxzAug5WfaI/ToWkZIg0k2I/AAAAAAAADgc/acbyyhkFKgg/s1600/Screen%2Bshot%2B2011-09-30%2Bat%2B6.17.05%2BAM.png" imageanchor="1" style=""&gt;&lt;img border="0" height="219" width="400" src="http://4.bp.blogspot.com/-wxzAug5WfaI/ToWkZIg0k2I/AAAAAAAADgc/acbyyhkFKgg/s400/Screen%2Bshot%2B2011-09-30%2Bat%2B6.17.05%2BAM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;The job uses a tFileInputJson:&lt;br /&gt;&lt;pre class="brush: bash"&gt;//Basic Settings&lt;br /&gt;//./URL&lt;br /&gt;context.url + "%22" + context.symbol + "%22"&lt;br /&gt;//./Mapping&lt;br /&gt;json="$[query.results.quote]"&lt;br /&gt;&lt;br /&gt;... and a tJavaFlex:&lt;br /&gt;//Advanced Settings/Import&lt;br /&gt;import org.json.simple.JSONObject;&lt;br /&gt;import org.json.simple.JSONArray;&lt;br /&gt;import org.json.simple.parser.JSONParser;&lt;br /&gt;//Basic Settings&lt;br /&gt;//./Start Code&lt;br /&gt;JSONParser parser = new JSONParser(); &lt;br /&gt;//./Main Code&lt;br /&gt;JSONObject json = (JSONObject) parser.parse(row1.json);&lt;br /&gt;System.out.println(json.toString()); &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;As you already noticed you need to declare a context with two variables like below (showing default values):&lt;br /&gt;&lt;pre class="brush: bash"&gt;url: "http://query.yahooapis.com/v1/public/yql?format=json&amp;diagnostics=true&amp;env=http%3A%2F%2Fdatatables.org%2Falltables.env&amp;callback=&amp;q=select%20*%20from%20yahoo.finance.quotes%20where%20symbol%20%3D%20"&lt;br /&gt;symbol: "AAPL"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;For those who like screenshots ;-)&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-9aL71w88Xe4/ToWk2EhqV2I/AAAAAAAADgk/B4dysjvjksY/s1600/Screen%2Bshot%2B2011-09-30%2Bat%2B6.27.55%2BAM.png" imageanchor="1" style=""&gt;&lt;img border="0" height="70" width="400" src="http://2.bp.blogspot.com/-9aL71w88Xe4/ToWk2EhqV2I/AAAAAAAADgk/B4dysjvjksY/s400/Screen%2Bshot%2B2011-09-30%2Bat%2B6.27.55%2BAM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;All we are doing is getting the json from the remote URL (built out of context parameters) and returning it to the stdout. The URL will remain fixed in this example but I use a context variable to show it could change in the future.&lt;br /&gt;&lt;br /&gt;Export the job as "Autonomous", uncompress the zip and run the wrapper shell/batch. I am working on Windows this time so we will need to modify the batch file adding @ECHO ON at the beginning of the file. &lt;br /&gt;&lt;br /&gt;We can run now from command line our Talend job for a different symbol like Google (GOOG)&lt;br /&gt;&lt;pre class="brush: bash"&gt;C:\etl\releases\retrieve_stock_0.1\retrieve_stock&gt;retrieve_stock_run.bat --context_param symbol=GOOG&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here is a typical response&lt;br /&gt;&lt;pre class="brush: bash"&gt;{&amp;quot;Open&amp;quot;:&amp;quot;536.45&amp;quot;,&amp;quot;PriceEPSEstimateCurrentYear&amp;quot;:&amp;quot;14.91&amp;quot;,&amp;quot;BookValue&amp;quot;:&amp;quot;161.128&amp;quot;,&amp;quot;Bid&amp;quot;:&amp;quot;485.00&amp;quot;,&amp;quot;DaysHigh&amp;quot;:&amp;quot;537.30&amp;quot;,&amp;quot;OrderBookRealtime&amp;quot;:null,&amp;quot;ChangeFromFiftydayMovingAverage&amp;quot;:&amp;quot;-6.235&amp;quot;,&amp;quot;ErrorIndi&lt;br /&gt;cationreturnedforsymbolchangedinvalid&amp;quot;:null,&amp;quot;DaysRange&amp;quot;:&amp;quot;519.41 - 537.30&amp;quot;,&amp;quot;MoreInfo&amp;quot;:&amp;quot;cnprmiIed&amp;quot;,&amp;quot;AnnualizedGain&amp;quot;:null,&amp;quot;Change_PercentChange&amp;quot;:&amp;quot;-1.34 - -0.25%&amp;quot;,&amp;quot;DaysRangeRealtime&amp;quot;:&amp;quot;N\/A - N\/&lt;br /&gt;A&amp;quot;,&amp;quot;LowLimit&amp;quot;:null,&amp;quot;PercentChangeFromTwoHundreddayMovingAverage&amp;quot;:&amp;quot;-2.90%&amp;quot;,&amp;quot;ChangeFromTwoHundreddayMovingAverage&amp;quot;:&amp;quot;-15.747&amp;quot;,&amp;quot;DividendYield&amp;quot;:null,&amp;quot;EPSEstimateCurrentYear&amp;quot;:&amp;quot;35.47&amp;quot;,&amp;quot;LastTradeDat&lt;br /&gt;e&amp;quot;:&amp;quot;9\/29\/2011&amp;quot;,&amp;quot;TwoHundreddayMovingAverage&amp;quot;:&amp;quot;543.247&amp;quot;,&amp;quot;AskRealtime&amp;quot;:&amp;quot;620.00&amp;quot;,&amp;quot;DividendPayDate&amp;quot;:null,&amp;quot;PercentChange&amp;quot;:&amp;quot;-0.25%&amp;quot;,&amp;quot;YearRange&amp;quot;:&amp;quot;473.02 - 642.96&amp;quot;,&amp;quot;symbol&amp;quot;:&amp;quot;GOOG&amp;quot;,&amp;quot;Change&amp;quot;:&amp;quot;-1.34&amp;quot;,&lt;br /&gt;&amp;quot;PercentChangeFromFiftydayMovingAverage&amp;quot;:&amp;quot;-1.17%&amp;quot;,&amp;quot;HoldingsGainPercent&amp;quot;:&amp;quot;- - -&amp;quot;,&amp;quot;Notes&amp;quot;:null,&amp;quot;HoldingsGain&amp;quot;:null,&amp;quot;YearHigh&amp;quot;:&amp;quot;642.96&amp;quot;,&amp;quot;Symbol&amp;quot;:&amp;quot;GOOG&amp;quot;,&amp;quot;AfterHoursChangeRealtime&amp;quot;:&amp;quot;N\/A - N\/A&amp;quot;,&lt;br /&gt;&amp;quot;HoldingsGainPercentRealtime&amp;quot;:&amp;quot;N\/A - N\/A&amp;quot;,&amp;quot;MarketCapitalization&amp;quot;:&amp;quot;170.3B&amp;quot;,&amp;quot;BidRealtime&amp;quot;:&amp;quot;485.00&amp;quot;,&amp;quot;LastTradePriceOnly&amp;quot;:&amp;quot;527.50&amp;quot;,&amp;quot;PERatio&amp;quot;:&amp;quot;19.08&amp;quot;,&amp;quot;EPSEstimateNextQuarter&amp;quot;:&amp;quot;10.04&amp;quot;,&amp;quot;MarketCap&lt;br /&gt;Realtime&amp;quot;:null,&amp;quot;AverageDailyVolume&amp;quot;:&amp;quot;3820260&amp;quot;,&amp;quot;PercentChangeFromYearLow&amp;quot;:&amp;quot;+11.52%&amp;quot;,&amp;quot;TickerTrend&amp;quot;:&amp;quot;&amp;amp;nbsp;======&amp;amp;nbsp;&amp;quot;,&amp;quot;LastTradeWithTime&amp;quot;:&amp;quot;4:00pm - &amp;lt;b&amp;gt;527.50&amp;lt;\/b&amp;gt;&amp;quot;,&amp;quot;ChangeFromYearHigh&amp;quot;:&amp;quot;-115&lt;br /&gt;.46&amp;quot;,&amp;quot;PERatioRealtime&amp;quot;:null,&amp;quot;PreviousClose&amp;quot;:&amp;quot;528.84&amp;quot;,&amp;quot;StockExchange&amp;quot;:&amp;quot;NasdaqNM&amp;quot;,&amp;quot;FiftydayMovingAverage&amp;quot;:&amp;quot;533.735&amp;quot;,&amp;quot;LastTradeTime&amp;quot;:&amp;quot;4:00pm&amp;quot;,&amp;quot;DaysLow&amp;quot;:&amp;quot;519.41&amp;quot;,&amp;quot;PriceEPSEstimateNextYear&amp;quot;:&amp;quot;12.6&lt;br /&gt;0&amp;quot;,&amp;quot;DaysValueChange&amp;quot;:&amp;quot;- - -0.25%&amp;quot;,&amp;quot;HighLimit&amp;quot;:null,&amp;quot;TradeDate&amp;quot;:null,&amp;quot;OneyrTargetPrice&amp;quot;:&amp;quot;719.68&amp;quot;,&amp;quot;ChangeRealtime&amp;quot;:&amp;quot;-1.34&amp;quot;,&amp;quot;YearLow&amp;quot;:&amp;quot;473.02&amp;quot;,&amp;quot;ExDividendDate&amp;quot;:null,&amp;quot;EPSEstimateNextYear&amp;quot;:&amp;quot;41.98&lt;br /&gt;&amp;quot;,&amp;quot;PricePaid&amp;quot;:null,&amp;quot;Volume&amp;quot;:&amp;quot;2907381&amp;quot;,&amp;quot;Ask&amp;quot;:&amp;quot;620.00&amp;quot;,&amp;quot;HoldingsValue&amp;quot;:null,&amp;quot;ChangeFromYearLow&amp;quot;:&amp;quot;+54.48&amp;quot;,&amp;quot;PercebtChangeFromYearHigh&amp;quot;:&amp;quot;-17.96%&amp;quot;,&amp;quot;DividendShare&amp;quot;:&amp;quot;0.00&amp;quot;,&amp;quot;EBITDA&amp;quot;:&amp;quot;12.785B&amp;quot;,&amp;quot;Holdin&lt;br /&gt;gsGainRealtime&amp;quot;:null,&amp;quot;PEGRatio&amp;quot;:&amp;quot;0.79&amp;quot;,&amp;quot;Name&amp;quot;:&amp;quot;Google Inc.&amp;quot;,&amp;quot;Commission&amp;quot;:null,&amp;quot;ChangePercentRealtime&amp;quot;:&amp;quot;N\/A - -0.25%&amp;quot;,&amp;quot;DaysValueChangeRealtime&amp;quot;:&amp;quot;N\/A - N\/A&amp;quot;,&amp;quot;LastTradeRealtimeWithTime&amp;quot;:&amp;quot;N\/&lt;br /&gt;A - &amp;lt;b&amp;gt;527.50&amp;lt;\/b&amp;gt;&amp;quot;,&amp;quot;HoldingsValueRealtime&amp;quot;:null,&amp;quot;EarningsShare&amp;quot;:&amp;quot;27.719&amp;quot;,&amp;quot;PriceBook&amp;quot;:&amp;quot;3.28&amp;quot;,&amp;quot;ChangeinPercent&amp;quot;:&amp;quot;-0.25%&amp;quot;,&amp;quot;SharesOwned&amp;quot;:null,&amp;quot;ShortRatio&amp;quot;:&amp;quot;1.60&amp;quot;,&amp;quot;PriceSales&amp;quot;:&amp;quot;5.12&amp;quot;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This job is supposed to be reused from others. If at any time the way the symbol is retrieved changes we will change the logic from just one place. After that we just need to export retrieve_stock job and deploy it in our server. &lt;br /&gt;&lt;br /&gt;We could reuse retrieve_stock job using a tSystem component to invoke the command but I am going to go a step forward and propose something else. &lt;br /&gt;&lt;br /&gt;It is well known that java will use fork() to invoke an external command which means the whole JVM heap memory will be duplicated when the process run. This is of course not efficient. I have &lt;a href="http://thinkinginsoftware.blogspot.com/2011/09/shell-processes-from-java-and-infamous.html"&gt;posted&lt;/a&gt; a solution around this issue before.&lt;br /&gt;&lt;br /&gt;So the proposal here is to run a local server that executes local talend command line scripts. The output will be JSON. As you have already figured I am advocating here to use JSON as a lightweight data structure that can be used to maintain the communication channel between different jobs.&lt;br /&gt;&lt;br /&gt;Using a nodejs server is better than using Jetty or any other java container for just running shell commands. In my tests the memory footprint for nodejs is really low and the performance is similar to a java container.&lt;br /&gt;&lt;br /&gt;After you setup your server you should get the same response after hitting a url like the below:&lt;br /&gt;&lt;pre class="brush: bash"&gt;http://127.0.0.1:8088/?cmd=C%3A%5Cetl%5Creleases%5Cretrieve_stock_0.1%5Cretrieve_stock%5Cretrieve_stock_run.bat%20--context_param%20symbol%3DAAPL&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;What happens if the amount of data generated by one job is too big? Well in that case I recommend to use a shared resource like a file or a DB (or both like the case of sqlite ;-) If a job works generating a file or DB tables of course the interface will need to be documented and still you can invoke it via a REST call like we have explained here returning back to the caller (Parent job) the results of its execution.&lt;br /&gt;&lt;br /&gt;The job we will use to invoke retrieve_stock actually uses the same pattern as you see below:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-VbhUYLl9jIU/ToWj-qOn0rI/AAAAAAAADgU/_mHIJijH1pE/s1600/Screen%2Bshot%2B2011-09-30%2Bat%2B6.08.55%2BAM.png" imageanchor="1" style=""&gt;&lt;img border="0" height="230" width="400" src="http://1.bp.blogspot.com/-VbhUYLl9jIU/ToWj-qOn0rI/AAAAAAAADgU/_mHIJijH1pE/s400/Screen%2Bshot%2B2011-09-30%2Bat%2B6.08.55%2BAM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;You will be able to hit a url like the below and your job will return the AAPL price:&lt;br /&gt;&lt;pre class="brush: bash"&gt;http://127.0.0.1:8088/?cmd=C%3A%5Cetl%5Creleases%5Capple_status_0.1%5Capple_status%5Capple_status_run.bat%20&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;As you can see at the end a job will call external jobs using the same JSON response strategy.&lt;br /&gt;&lt;br /&gt;I am not including any transformations in these examples because the whole purpose of this post is to discuss alternatives when it comes to inter-job communication in Talend. Here is what this architecture is allowing me to do:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Projects can be isolated and so better maintained in version control systems using the (free and open source) TOS version&lt;/li&gt;&lt;li&gt;There is a good separation of concern when it comes to building an ETL (You do not need to be a Java developer, Talend also supports Perl but even if you build Java projects the language is only used in a scripting fashion so there is no overhead of unneeded OOP for ETL. Many developers are tempted to push data logic in their java code once they have the luxury of working from java and using Talend jobs written in java ) The ETL developer can test absolutely everything without the need of merging jars, troubleshooting class loading issues etc.&lt;/li&gt;&lt;li&gt;Reusability.&lt;/li&gt;&lt;li&gt;Deployment is easy, just uncompressing a zip file.&lt;/li&gt;&lt;li&gt;Release is not hard if you keep the versioned zip file in a repository. Of course this should be so much better but unfortunately the Open Source version is still not providing maven integration for example. It would be great to be able to run 'mvn release' and get the project tagged.&lt;/li&gt;&lt;li&gt;Interoperability: For example a job written in version 4 could interact with others written in version 5 as the only interface between them is an HTTP JSON Service call.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;h3&gt;Security&lt;/h3&gt;You are working with shell commands which can be pretty destructive if you do not ensure the json server just run locally attached to a loopback IP (in our case 127.0.0.1). You will need a more robust server implementation if you violate that rule or if you cannot guarantee your server will not be hosting any other service where a different user could run malicious code.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Encoding&lt;/h3&gt;In case you did not notice the below two lines are equivalent. The second is URL encoded. We need that to pass the whole command as a parameter from a query string or a POST request.&lt;br /&gt;&lt;pre class="brush: bash"&gt;C:\etl\releases\retrieve_stock_0.1\retrieve_stock\retrieve_stock_run.bat --context_param symbol=AAPL&lt;br /&gt;C%3A%5Cetl%5Creleases%5Cretrieve_stock_0.1%5Cretrieve_stock%5Cretrieve_stock_run.bat%20--context_param%20symbol%3DAAPL&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-5845300889166792848?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/5845300889166792848/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=5845300889166792848' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5845300889166792848'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5845300889166792848'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/09/reusing-talend-etl-jobs.html' title='Reusing Talend ETL Jobs'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-LYHxuJr5u_k/ToWjJ_lB-eI/AAAAAAAADgM/w8lGf6hGrAs/s72-c/Screen%2Bshot%2B2011-09-30%2Bat%2B6.16.19%2BAM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-1290707301729853868</id><published>2011-09-30T03:51:00.000-07:00</published><updated>2011-09-30T10:52:43.305-07:00</updated><title type='text'>A nodejs server to run shell commands</title><content type='html'>I was tempted to use jetty as a lightweight server to resolve the JVM-fork() memory problem as I already &lt;a href="http://thinkinginsoftware.blogspot.com/2011/09/shell-processes-from-java-and-infamous.html"&gt;posted&lt;/a&gt; however nodejs provides a lighter and so from my simplistic design poit of view better alternative.&lt;br /&gt;&lt;br /&gt;Below is the code for such a server:&lt;br /&gt;&lt;pre class="brush: javascript"&gt;#!/usr/local/bin/node&lt;br /&gt;/*&lt;br /&gt;** shell-server.js returns json response with the stdout and stderr of a shell command&lt;br /&gt;**&lt;br /&gt;**&lt;br /&gt;** @Author: Nestor Urquiza&lt;br /&gt;** @Date: 09/29/2011&lt;br /&gt;**&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;* Dependencies&lt;br /&gt;*/&lt;br /&gt;var http = require('http'),&lt;br /&gt;    url = require('url'),&lt;br /&gt;    exec = require('child_process').exec;&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;* Server Config&lt;br /&gt;*/&lt;br /&gt;var host = "127.0.0.1",&lt;br /&gt;    port = "8088",&lt;br /&gt;    thisServerUrl = "http://" + host + ":" + port;&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;* Main&lt;br /&gt;*/&lt;br /&gt;http.createServer(function (req, res) {&lt;br /&gt;  req.addListener('end', function () {&lt;br /&gt;        &lt;br /&gt;  });&lt;br /&gt;  var parsedUrl = url.parse(req.url, true);&lt;br /&gt;  var cmd = parsedUrl.query['cmd'];&lt;br /&gt; &lt;br /&gt;  res.writeHead(200, {'Content-Type': 'text/plain'});&lt;br /&gt;&lt;br /&gt;  if( cmd ) {&lt;br /&gt;    var child = exec(cmd, function (error, stdout, stderr) {&lt;br /&gt;   var result = '{"stdout":' + stdout + ',"stderr":"' + stderr + '","cmd":"' + cmd + '"}';&lt;br /&gt;   res.end(result + '\n');&lt;br /&gt;    });&lt;br /&gt;  } else {&lt;br /&gt; var result = '{"stdout":"' + '' + '","stderr":"' + 'cmd is mandatory' + '","cmd":"' + cmd + '"}';&lt;br /&gt; res.end(result + '\n');&lt;br /&gt;  }  &lt;br /&gt;  &lt;br /&gt;}).listen(port, host);&lt;br /&gt;console.log('Server running at ' + thisServerUrl );&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Once the server is running you can hit the below URL to get a list of the users loged in a OSX/linux/unix box:&lt;br /&gt;&lt;pre class="brush: bash"&gt;http://localhost:8088/?cmd=who&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Development environment&lt;/h3&gt;If you are running Windows you should download the node &lt;a href="http://nodejs.org/dist/v0.5.7/node.exe"&gt;executable&lt;/a&gt; and run the server as:&lt;br /&gt;&lt;pre class="brush: bash"&gt;node c:\shell-server.js&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If you are running OSX/Linux/Unix you have to install node make the script executable and run the below:&lt;br /&gt;&lt;pre class="brush: bash"&gt;/path/to/shell-server.sh&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Production deployment&lt;/h3&gt;You need to be sure the server runs as a daemon and that it restarts if it fails to serve. In debian/Ubuntu + monit you can follow these steps:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ sudo vi /opt/nodejs/shell-server.js&lt;br /&gt;$ sudo vi /etc/init/shell-server.conf&lt;br /&gt;#!/sbin/upstart&lt;br /&gt;description "shell server runs a command specified by HTTP GET param 'cmd'"&lt;br /&gt;author      "admin"&lt;br /&gt;&lt;br /&gt;start on startup&lt;br /&gt;stop on shutdown&lt;br /&gt;&lt;br /&gt;script&lt;br /&gt;    export HOME="/root"&lt;br /&gt;    exec sudo -u admin /usr/local/bin/node /opt/nodejs/shell-server.js 2&gt;&amp;1 &gt;&gt; /var/log/shell-server.log&lt;br /&gt;end script&lt;br /&gt;$ sudo vi /etc/monit/monitrc &lt;br /&gt;...&lt;br /&gt;#################################################################&lt;br /&gt;# shell-server&lt;br /&gt;################################################################&lt;br /&gt;&lt;br /&gt;check host shell-server with address 127.0.0.1&lt;br /&gt;start = "/sbin/start shell-server"&lt;br /&gt;stop = "/sbin/stop shell-server"&lt;br /&gt;if failed port 8088 protocol HTTP&lt;br /&gt;  request /&lt;br /&gt;  with timeout 10 seconds&lt;br /&gt;then restart&lt;br /&gt;group server&lt;br /&gt;...&lt;br /&gt;$ sudo monit reload&lt;br /&gt;$ sudo vi /opt/nodejs/shell-server.js&lt;br /&gt;$ wget http://localhost:8088 -O -&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-1290707301729853868?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/1290707301729853868/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=1290707301729853868' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/1290707301729853868'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/1290707301729853868'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/09/nodejs-server-to-run-shell-commands.html' title='A nodejs server to run shell commands'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-586086161705823582</id><published>2011-09-27T11:11:00.000-07:00</published><updated>2012-01-22T05:03:11.185-08:00</updated><title type='text'>Mediawiki email notifications for any changes</title><content type='html'>I have to admit I love simplicity of MoinMoin wiki but more than anything I think it is really developer oriented.&lt;br /&gt;&lt;br /&gt;Mediawiki on the other hand is more oriented to a general audience. It is a great product which for developers and other technical teams (my area of expertise) lacks of an important concept: People should be able to get alerts for any change even if they did not visit the page after a previous change was notified. If you want to do something like that in Mediawiki you need to &lt;a href="http://www.mediawiki.org/wiki/Manual:$wgUsersNotifiedOnAllChanges"&gt;add the email addresses in a configuration file&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The first thing you will try in Mediawiki to get notifications about a page update is to watch that page. Every time you get an email notification you must click on the page other wise you will stop getting new updates. Updates will resume once you have visited the page. The steps to get email notifications are:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Go to My Prefernces | Email. You might have a link asking for you to activate your email. Click it or you will never get an email.&lt;/li&gt;&lt;li&gt;Once you have done it you will receive the first email and after clicking on the link if you come back to "My Peferences | Profile | Email Options" you will see something like "Your e-mail address was authenticated on 12 August 2010 at 00:37."&lt;/li&gt;&lt;li&gt;Go to "My Peferences | Watchlist" to set extra options like automatically getting subscribed to pages you create.&lt;/li&gt;&lt;li&gt;Now you can monitor any page clicking on the "watch" link. In the monitored page the link should change from "watch" to "unwatch"&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Your e-mail address was authenticated on 12 August 2010 at 00:37.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;While you can subscribe to an RSS feed I prefer email for one good reason: There is no excuse from developers like "I did not know". Yes you do know because you received an email when your partner updated the documentation.&lt;br /&gt;&lt;br /&gt;Fortunately there is a way around this using a freely available python application called &lt;a href="http://www.allthingsrss.com/rss2email/"&gt;rss2email&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This will send emails with the typical diff format but using colors to highlight conflicts, what is new and what has been changed/removed. See below an example:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-Y8gABmskqaQ/ToIzu4GHBaI/AAAAAAAADgE/h0IDxnSAxc0/s1600/Screen%2Bshot%2B2011-09-27%2Bat%2B4.35.35%2BPM.png" imageanchor="1" style=""&gt;&lt;img border="0" height="121" width="400" src="http://3.bp.blogspot.com/-Y8gABmskqaQ/ToIzu4GHBaI/AAAAAAAADgE/h0IDxnSAxc0/s400/Screen%2Bshot%2B2011-09-27%2Bat%2B4.35.35%2BPM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Below are the steps to install and configure such application:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ sudo apt-get remove rss2email&lt;br /&gt;$ cd&lt;br /&gt;$ tar xvf rss2email-2.70.tar.gz &lt;br /&gt;$ sudo cp -R rss2email-2.70 /opt/&lt;br /&gt;$ sudo mv config.py.example config.py&lt;br /&gt;$ sudo chown -R nestorurquizaadmin:nestorurquizaadmin /opt/rss2email-2.70/&lt;br /&gt;$ cd /opt/rss2email-2.70/&lt;br /&gt;$ sudo vi config.py &lt;br /&gt;...&lt;br /&gt;DEFAULT_FROM = "donotreply@nestorurquiza.com"&lt;br /&gt;...&lt;br /&gt;SMTP_SERVER = "krms.krco.com:25"&lt;br /&gt;...&lt;br /&gt;$ r2e new developers@nestorurquiza.com&lt;br /&gt;$ r2e add http://wiki.nestorurquiza.com/index.php/Special:RecentChanges?feed=rss&lt;br /&gt;$ r2e run --no-send #remove the flag in case you want to receive emails right away. Keep the flag to receive new changes.&lt;br /&gt;$ crontab -e&lt;br /&gt;# Send wiki changes to developers every 10 minutes&lt;br /&gt;*/10 * * * * cd /opt/rss2email-2.70;./r2e run&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-586086161705823582?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/586086161705823582/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=586086161705823582' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/586086161705823582'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/586086161705823582'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/09/mediawiki-email-notifications-for-any.html' title='Mediawiki email notifications for any changes'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-Y8gABmskqaQ/ToIzu4GHBaI/AAAAAAAADgE/h0IDxnSAxc0/s72-c/Screen%2Bshot%2B2011-09-27%2Bat%2B4.35.35%2BPM.png' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-5780838412552440545</id><published>2011-09-22T18:46:00.000-07:00</published><updated>2011-09-22T18:46:51.960-07:00</updated><title type='text'>Securing your Apache SSL site</title><content type='html'>The default apache SSL configuration accepts weak cipher and SSL v2 both of which are vulnerable&lt;br /&gt;&lt;pre class="brush: bash"&gt;SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here is what you have to do to make it secure.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: bash"&gt;SSLProtocol all -SSLv2&lt;br /&gt;SSLCipherSuite RC4-SHA:AES128-SHA:HIGH:!aNULL:!EXP:!MD5:!NULL&lt;br /&gt;SSLHonorCipherOrder on&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If you are in doubts you can use &lt;a href="https://www.ssllabs.com"&gt;ssllabs&lt;/a&gt; free service to find out if your SSL server is secure enough. &lt;br /&gt;&lt;br /&gt;You will be amazed how many websites are vulnerable to MIM attacks just because of the fact that some people still think it is enough to buy a signed certificate. What is perhaps even more sad is that some people were surprised about the recent Diginotar hack but if you actually run the test for www.diginotar.com you will see it rated as "D" because it accepts weak ciphers and still supports insecure SSL 2.0. At the time of this writing that is still the case (https://www.ssllabs.com/ssldb/analyze.html?d=www.diginotar.com). Below are the results I just got:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-dlZI6Xl387M/Tnvk0rDeEcI/AAAAAAAADf0/24uZfSnx5bk/s1600/Screen%2Bshot%2B2011-09-22%2Bat%2B1.05.17%2BPM.png" imageanchor="1" style=""&gt;&lt;img border="0" height="259" width="400" src="http://2.bp.blogspot.com/-dlZI6Xl387M/Tnvk0rDeEcI/AAAAAAAADf0/24uZfSnx5bk/s400/Screen%2Bshot%2B2011-09-22%2Bat%2B1.05.17%2BPM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Please do yourself a favor and make sure your website is hosted in an "A" rated SSL host.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-5780838412552440545?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/5780838412552440545/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=5780838412552440545' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5780838412552440545'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5780838412552440545'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/09/securing-your-apache-ssl-site.html' title='Securing your Apache SSL site'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-dlZI6Xl387M/Tnvk0rDeEcI/AAAAAAAADf0/24uZfSnx5bk/s72-c/Screen%2Bshot%2B2011-09-22%2Bat%2B1.05.17%2BPM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-7724698053579053362</id><published>2011-09-22T18:41:00.000-07:00</published><updated>2011-09-22T18:41:23.879-07:00</updated><title type='text'>Thinking in Security after OWASP AppSec USA 2011</title><content type='html'>Just came back from Minneapolis after two days of application security training where we went through several tools that can be used to find out vulnerabilities in Web Applications.&lt;br /&gt;&lt;br /&gt;OWASP-WTE is a Ubuntu distribution packaging several open source utilities used to perform what is called Penetration Testing (PenTest). The training focused on basic concepts about HTTP specification that any security tester should know, it presented the most common attacks and the available tools and manual procedures the tester is supposed to master.&lt;br /&gt;&lt;br /&gt;Here are some reflections that I have come up with after these two days.&lt;br /&gt;&lt;br /&gt;In my current project I have gone through the &lt;a href="https://www.owasp.org/index.php/Top_10_2010-Main"&gt;top ten application security risks&lt;/a&gt; I  have used skipfish and websecurity and I have documented at least one of my experiences using this product to &lt;a href="http://thinkinginsoftware.blogspot.com/search?q=skipfish"&gt;PenTest Liferay&lt;/a&gt;. We have done the same for our BHUB based application however in terms of automated tools it is never enough. What a tool can find others will miss and vice versa. &lt;br /&gt;&lt;br /&gt;Dealing with false positives is really annoying but in a world where the number of threats only increases we have no other option than going through this practice in a regular basis.&lt;br /&gt;&lt;br /&gt;If you are hosting a web application go through the &lt;a href="https://www.owasp.org/index.php/OWASP_Testing_Guide_v3_Table_of_Contents"&gt;OWASP Testing Guide&lt;/a&gt;. Web apps should be prepared to live in the wild and that is as important as hardening the OS.&lt;br /&gt;&lt;br /&gt;Perhaps the most forgotten point related to security is monitoring. Trying your best with dozens of tools and manual hacking attempts is a must do however that is not enough. Controls must be put in place and your server logs are full of useful information you should analyze looking for patterns, eliminating false positives and hopefully automating blocking when a threat is identified.&lt;br /&gt;&lt;br /&gt;The PenTest individual is someone that must be willing to script, to be a hacker, a programmer, a human being who knows there is a big responsibility on the job s(he) does. S(he) might be saving the company from failure after all.&lt;br /&gt;&lt;br /&gt;The skills for such a person go beyond being a tech savvy. Discipline and persistence are a must have.&lt;br /&gt;&lt;br /&gt;Any additional effort you can put in protecting your web application will be worth it but application security is just the tip of the Iceberg because ultimately it will always rely on some credentials for a user to gain access to certain resources and the credentials can be obtained even in applications for which an exploit has not yet been spotted. &lt;br /&gt;&lt;br /&gt;Social engineering is one example, take just the real life example of an employee from a security related &lt;a href="http://hbgary.com/"&gt;company&lt;/a&gt; who dared to transmit a password via &lt;a href="http://dazzlepod.com/site_media/txt/rootkit.com.txt"&gt;email&lt;/a&gt;. Ultimately your company is as secure as the most careless of the company employees.&lt;br /&gt;&lt;br /&gt;Phishing is another good example. Look &lt;a href="http://thinkinginsoftware.blogspot.com/2011/08/phishing-attack-fake-twitter-email-not_06.html"&gt;here&lt;/a&gt; and &lt;a href="http://thinkinginsoftware.blogspot.com/2011/09/phishing-attack-using-redirection.html"&gt;here&lt;/a&gt; for a couple of posts I have made in the past related to twitter hacking attempts.&lt;br /&gt;&lt;br /&gt;I have left Minneapolis convinced that like in any other aspect of our mortal life there is no silver bullet. For sure there is none when you try to implement security.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-7724698053579053362?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/7724698053579053362/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=7724698053579053362' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7724698053579053362'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7724698053579053362'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/09/thinking-in-security-after-owasp-appsec.html' title='Thinking in Security after OWASP AppSec USA 2011'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-493312830896668323</id><published>2011-09-21T16:40:00.000-07:00</published><updated>2011-09-30T03:54:31.122-07:00</updated><title type='text'>Shell processes from Java and the infamous OutOfMemory</title><content type='html'>If you run shell processes from a Java Application Server you can really easy run out of heap memory because Java will invoke a fork() system call which will duplicate the parent memory (current JVM memory in use) to be able to run the child (the command you are trying to run).&lt;br /&gt;&lt;br /&gt;Here is a workaround for this issue. Basically it relies on a war file containing a servlet that accepts regular get parameters like "cmd" containing the complete command to run. It returns a JSON response with the stderr and stdout content. The war file will need to be deployed in an application server with really small footprint to prevent the child process from originating again an OutOfMemory. &lt;br /&gt;&lt;br /&gt;Note that I on purpose do not use any special jar files because that way I keep memory usage to a minimum. Do not use a JSON parser for this, just build the response string yourself. Simpler is better ;-)&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: java"&gt;import java.io.*;&lt;br /&gt;import javax.servlet.*;&lt;br /&gt;import javax.servlet.http.*;&lt;br /&gt;&lt;br /&gt;public class ShellServlet extends HttpServlet {&lt;br /&gt;    public void doGet(HttpServletRequest request, HttpServletResponse response)&lt;br /&gt;            throws ServletException, IOException {&lt;br /&gt;        String cmd = request.getParameter("cmd");&lt;br /&gt;        StringBuilder stdout = new StringBuilder();&lt;br /&gt;        StringBuilder stderr = new StringBuilder();&lt;br /&gt;        &lt;br /&gt;        &lt;br /&gt;        &lt;br /&gt;        if( cmd != null &amp;&amp; cmd.length() &gt; 0 ) {&lt;br /&gt;            try {&lt;br /&gt;                Process process = new ProcessBuilder(cmd.split(" ")).start();&lt;br /&gt;                &lt;br /&gt;                InputStream is = process.getInputStream();&lt;br /&gt;                InputStreamReader isr = new InputStreamReader(is);&lt;br /&gt;                BufferedReader br = new BufferedReader(isr);&lt;br /&gt;                String line;&lt;br /&gt;                while ((line = br.readLine()) != null) {&lt;br /&gt;                    stdout.append(line);&lt;br /&gt;                }&lt;br /&gt;                &lt;br /&gt;                InputStream errorInputStream = process.getErrorStream();&lt;br /&gt;                isr = new InputStreamReader(errorInputStream);&lt;br /&gt;                br = new BufferedReader(isr);&lt;br /&gt;                while ((line = br.readLine()) != null) {&lt;br /&gt;                    stderr.append(line);&lt;br /&gt;                }&lt;br /&gt;            } catch (Exception e) {&lt;br /&gt;                stderr.append(e.getMessage());&lt;br /&gt;            }&lt;br /&gt;            &lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        StringBuilder sb = new StringBuilder();&lt;br /&gt;        sb.append("{");&lt;br /&gt;        sb.append("'stdout':'" + stdout + "'");&lt;br /&gt;        sb.append(",");&lt;br /&gt;        sb.append("'stderr':'" + stderr + "'");&lt;br /&gt;        sb.append(",");&lt;br /&gt;        sb.append("'cmd':'" + cmd + "'");&lt;br /&gt;        sb.append("}");&lt;br /&gt;        PrintWriter out = response.getWriter();&lt;br /&gt;        out.println(sb);&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;For a request like:&lt;br /&gt;&lt;pre class="brush: bash"&gt;http://localhost:8088/shell-service/?cmd=ls%20-al%20notifier.png&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You will get (provided notifier.png file exist in the working directory) something like:&lt;br /&gt;&lt;pre class="brush: bash"&gt;{'stdout':'-rw-r--r--  1 nestor  staff  886 Oct 18  2010 notifier.png','stderr':'','cmd':'ls -al notifier.png'}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Probably your best bet in Java would be Jetty because it is really lightweight. Simply download the zip file, uncompress it and follow the below steps:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ cd $JETTY_HOME&lt;br /&gt;$ vi etc/jetty.xml #change port to 8088&lt;br /&gt;$ java -jar start.jar &amp; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Edit: A better alternative for a server to run local commands would be to build a lighter nodejs service like I explain &lt;a href="http://thinkinginsoftware.blogspot.com/2011/09/nodejs-server-to-run-shell-commands.html"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-493312830896668323?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/493312830896668323/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=493312830896668323' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/493312830896668323'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/493312830896668323'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/09/shell-processes-from-java-and-infamous.html' title='Shell processes from Java and the infamous OutOfMemory'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-5652880032117820337</id><published>2011-09-21T07:10:00.000-07:00</published><updated>2011-09-21T07:10:27.319-07:00</updated><title type='text'>Mediawiki Error creating thumbnail: sh: convert: No such file or directory</title><content type='html'>The error below should be referring to a non existent ImageMagic executable:&lt;br /&gt;&lt;pre class="brush: bash"&gt;Error creating thumbnail: sh: /usr/local/bin/convert: No such file or directory&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;After installing the executable my MediaWiki installation was still complaining. I fixed the problem using action=purge on the page, for example:&lt;br /&gt;&lt;pre class="brush: bash"&gt;http://wiki.nestorurquiza.com/index.php/Nestor?action=purge&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-5652880032117820337?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/5652880032117820337/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=5652880032117820337' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5652880032117820337'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5652880032117820337'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/09/mediawiki-error-creating-thumbnail-sh.html' title='Mediawiki Error creating thumbnail: sh: convert: No such file or directory'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-925970432380626096</id><published>2011-09-17T06:09:00.000-07:00</published><updated>2011-09-17T06:09:30.745-07:00</updated><title type='text'>error client ip File does not exist: /etc/apache2/htdocs after cloning</title><content type='html'>We cloned an ESX VM today to take advantage of all configurations in there. I went ahead and removed all unnecessary services and just left apache however something was not working:&lt;br /&gt;&lt;pre class="brush: bash"&gt;error] [client 192.168.9.3] File does not exist: /etc/apache2/htdocs&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I knew this was not a configuration issue as in the original VM apache does run without complaining.&lt;br /&gt;&lt;br /&gt;It ended up being something about relative and absolute paths. For some reason Apache was failing to find the sites-enabled directory when using relative paths. A correction in  /etc/apache2/apache2.conf made the trick:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ sudo vi /etc/apache2/apache2.conf &lt;br /&gt;#Include sites-enabled/&lt;br /&gt;Include /etc/apache2/sites-enabled/&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-925970432380626096?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/925970432380626096/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=925970432380626096' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/925970432380626096'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/925970432380626096'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/09/error-client-ip-file-does-not-exist.html' title='error client ip File does not exist: /etc/apache2/htdocs after cloning'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-8518111666384010081</id><published>2011-09-16T09:37:00.000-07:00</published><updated>2011-09-16T10:02:01.649-07:00</updated><title type='text'>monit: fatal: /usr/sfw/lib/libfl-2.5.4.so.0: wrong ELF class: ELFCLASS32</title><content type='html'>Monit Solaris 10 5/9 64 bits installation went good but it was not working:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: bash"&gt;monit: fatal: /usr/sfw/lib/libfl-2.5.4.so.0: wrong ELF class: ELFCLASS32&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A simple ldd showed the library was not 64bits:&lt;br /&gt;&lt;pre class="brush: bash"&gt;# ldd /usr/local/bin/monit&lt;br /&gt;        libfl-2.5.4.so.0 =&gt;      /usr/sfw/lib/libfl-2.5.4.so.0  - wrong ELF class: ELFCLASS32&lt;br /&gt;        libpam.so.1 =&gt;   /lib/64/libpam.so.1&lt;br /&gt;        libpthread.so.1 =&gt;       /lib/64/libpthread.so.1&lt;br /&gt;        libresolv.so.2 =&gt;        /lib/64/libresolv.so.2&lt;br /&gt;        libnsl.so.1 =&gt;   /lib/64/libnsl.so.1&lt;br /&gt;        libsocket.so.1 =&gt;        /lib/64/libsocket.so.1&lt;br /&gt;        libkstat.so.1 =&gt;         /lib/64/libkstat.so.1&lt;br /&gt;        libssl.so.0.9.7 =&gt;       /usr/sfw/lib/libssl.so.0.9.7  - wrong ELF class: ELFCLASS32&lt;br /&gt;        libcrypto.so.0.9.7 =&gt;    /usr/sfw/lib/libcrypto.so.0.9.7  - wrong ELF class: ELFCLASS32&lt;br /&gt;        libc.so.1 =&gt;     /lib/64/libc.so.1&lt;br /&gt;        libcmd.so.1 =&gt;   /lib/64/libcmd.so.1&lt;br /&gt;        libmp.so.2 =&gt;    /lib/64/libmp.so.2&lt;br /&gt;        libmd.so.1 =&gt;    /lib/64/libmd.so.1&lt;br /&gt;        libscf.so.1 =&gt;   /lib/64/libscf.so.1&lt;br /&gt;        libdoor.so.1 =&gt;  /lib/64/libdoor.so.1&lt;br /&gt;        libuutil.so.1 =&gt;         /lib/64/libuutil.so.1&lt;br /&gt;        libgen.so.1 =&gt;   /lib/64/libgen.so.1&lt;br /&gt;        libm.so.2 =&gt;     /lib/64/libm.so.2&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here is how you solve these kind of issues in your 64 bits Solaris box:&lt;br /&gt;&lt;pre class="brush: bash"&gt;export LD_LIBRARY_PATH=/usr/sfw/lib/64:$LD_LIBRARY_PATH&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;However you better use crle in this case as you want the change to affect your whole system, after all this is a 64 machine ins't it?&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: bash"&gt;crle -64 -u -l /usr/sfw/lib/64&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-8518111666384010081?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/8518111666384010081/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=8518111666384010081' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8518111666384010081'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8518111666384010081'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/09/monit-fatal-usrsfwliblibfl-254so0-wrong.html' title='monit: fatal: /usr/sfw/lib/libfl-2.5.4.so.0: wrong ELF class: ELFCLASS32'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-6868487523426574735</id><published>2011-09-12T10:20:00.000-07:00</published><updated>2011-09-12T10:49:02.395-07:00</updated><title type='text'>Allowing changes in subversion log messages or comments</title><content type='html'>When you commit your change to a subversion repository you have an option to change the comment you use:&lt;br /&gt;&lt;pre class="brush: bash"&gt;svn propset --revprop -r 15125 svn:log "Editing this comment with more detail, blah, blah ..."&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;There is a hook script template that you could use to allow this but there is a reason why the default template should not be used as is. It is dangerous to be able to affect good comments with garbage if you just make a mistake with the version number but the most dangerous of all is that you could change comments done by a different user!!! From a GUI like Eclipse or Netbeans it is harder to make a mistake like the first one of course, however you should protect subversion so log changes are restricted to the owner of the commit.&lt;br /&gt;&lt;br /&gt;Use the below to achieve it (Linux, if Windows translate to DOS)&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ sudo vi /var/local/svn/subversion.nestorurquiza.com/hooks/pre-revprop-change&lt;br /&gt;REPOS="$1"&lt;br /&gt;REV="$2"&lt;br /&gt;USER="$3"&lt;br /&gt;PROPNAME="$4"&lt;br /&gt;ACTION="$5"&lt;br /&gt;&lt;br /&gt;owner=`svnlook author -r "$REV" "$REPOS"`&lt;br /&gt;if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" -a "$owner" = "$USER" ]; then exit 0; fi&lt;br /&gt;&lt;br /&gt;echo "Changing revision properties other than svn:log is prohibited and you must be the owner($owner) to change $REPOS@$REV" &gt;&amp;2&lt;br /&gt;exit 1 &lt;br /&gt;$sudo chmod +x  /var/local/svn/subversion.nestorurquiza.com/hooks/pre-revprop-change&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-6868487523426574735?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/6868487523426574735/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=6868487523426574735' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6868487523426574735'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6868487523426574735'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/09/allowing-changes-in-subversion-log.html' title='Allowing changes in subversion log messages or comments'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-5896783793626636316</id><published>2011-09-12T08:44:00.000-07:00</published><updated>2011-09-12T08:44:19.837-07:00</updated><title type='text'>Phishing Attack: Using redirection</title><content type='html'>It has come to my attention the escalation in phishing attempts coming to my gmail account. In 4 days I got 3 emails that managed to pass the spam protection. They all claimed they were my twitter friends and that they found someone faking my account, my twitter picture, twitter avatar and what not.&lt;br /&gt;&lt;br /&gt;I always inspect the url before clicking because I want to explore the vulnerabilities (of course the safest to do is just to report as spam anything looking suspicious) So I took a look at them and they were all referring to well known websites that are "offerring free redirection services". I hope this is just a bug in CNN.com and pepsi.com. Do not hit the urls below before reading the rest of this post. Here are the URLs:&lt;br /&gt;&lt;pre&gt;http://pepsi.com/pepsi_redirect.php?theurl=jurism%2Ecom%2Foldweb%2Ftraffic168&lt;br /&gt;http://mexico.cnn.com/redirectComplete.php?url=%2F%2Fjurism%2Ecom%2Foldweb%2Ftraffic168&lt;br /&gt;http://mexico.cnn.com/redirectComplete.php?url=%2F%2Ftwitmytweets%2Eit%2Etc&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Open Firefox and delete all your cookies. Failure to do that will probably compromise things like your google/gmail account.&lt;br /&gt;&lt;br /&gt;If you take a look at the traces you will notice there were attempts to get some stuff from google.com. If you are logged into gmail or other google services your cookies for google.com will be compromised and the intruder could fake your session resulting in identity theft.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-5896783793626636316?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/5896783793626636316/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=5896783793626636316' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5896783793626636316'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5896783793626636316'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/09/phishing-attack-using-redirection.html' title='Phishing Attack: Using redirection'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-7219064289006307738</id><published>2011-09-08T11:48:00.000-07:00</published><updated>2011-09-08T11:48:58.702-07:00</updated><title type='text'>svnadmin: Couldn't perform atomic initialization database is locked</title><content type='html'>I was trying to use a CIFS (Windows) path as the svn repository but I was having locking issues:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ sudo svnadmin load /repo/path &amp;lt; ~/svn_dump&lt;br /&gt;&amp;lt;&amp;lt;&amp;lt; Started new transaction, based on original revision 1&lt;br /&gt;    * adding path : projects ... done.&lt;br /&gt;svnadmin: Couldn't perform atomic initialization&lt;br /&gt;svnadmin: database is locked&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Taking a look at &lt;a href="http://www.samba.org/samba/docs/man/manpages-3/mount.cifs.8.html"&gt;man pages&lt;/a&gt; I gave option &lt;b&gt;"nobrl"&lt;/b&gt; a try and that seemed to solve the problem which apparently is that our NetApp SAN does not support byte range locks.&lt;br /&gt;&lt;br /&gt;Using the below did the trick then:&lt;br /&gt;&lt;pre class="brush: bash"&gt;mount -t cifs //windows.box/path /mnt/local/path -o credentials=/root/cifs/cifs_credentials.txt,domain=COMPANYX,file_mode=0600,dir_mode=0700,uid=admin,gid=admin,nobrl&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-7219064289006307738?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/7219064289006307738/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=7219064289006307738' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7219064289006307738'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7219064289006307738'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/09/svnadmin-couldnt-perform-atomic.html' title='svnadmin: Couldn&apos;t perform atomic initialization database is locked'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-7411169705065488359</id><published>2011-09-08T07:53:00.000-07:00</published><updated>2011-09-08T07:53:38.045-07:00</updated><title type='text'>CIFS VFS: cifs_mount failed return code -13 0xc000006d NT_STATUS_LOGON_FAILURE</title><content type='html'>Using the following command to mount a CIFS (Windows) path:&lt;br /&gt;&lt;pre class="brush: bash"&gt;mount -t cifs //windows.box/path /mnt/local/path -o credentials=/root/cifs/cifs_credentials.txt,domain=COMPANYX,file_mode=0600,dir_mode=0700,uid=admin,gid=admin&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I was getting this error:&lt;br /&gt;&lt;pre class="brush: bash"&gt;[307906.131366] Status code returned 0xc000006d NT_STATUS_LOGON_FAILURE&lt;br /&gt;[307906.131374] CIFS VFS: Send error in SessSetup = -13&lt;br /&gt;[307906.131575] CIFS VFS: cifs_mount failed w/return code = -13&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;While this can be caused by any permission problems sometimes the lack of more verbose log traces makes it harder to find the exact issue.&lt;br /&gt;&lt;br /&gt;In my case this was related to the credentials file having spaces:&lt;br /&gt;&lt;pre class="brush: bash"&gt;username = user &lt;br /&gt;password = password&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Fixing it was a matter of trimming spaces out:&lt;br /&gt;&lt;pre class="brush: bash"&gt;username=user &lt;br /&gt;password=password&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-7411169705065488359?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/7411169705065488359/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=7411169705065488359' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7411169705065488359'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7411169705065488359'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/09/cifs-vfs-cifsmount-failed-return-code.html' title='CIFS VFS: cifs_mount failed return code -13 0xc000006d NT_STATUS_LOGON_FAILURE'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-6051182430159836737</id><published>2011-09-03T11:14:00.000-07:00</published><updated>2011-09-03T11:14:04.410-07:00</updated><title type='text'>FCKEditor for Mediawiki 1.17: WYSIWYG based on CKEditor</title><content type='html'>Mediawiki is not longer supporting the deprecated FCKEditor. The new CKEditor is supported through the &lt;a href="http://www.mediawiki.org/wiki/Extension:WYSIWYG"&gt;WYSIWYG extension&lt;/a&gt;. I tried WYSIWYG 1.5.6 in my a new installation of Mediawiki 1.17.0 but I would get nothing everytime I would try to switch to the Rich Editor.&lt;br /&gt;&lt;br /&gt;Debugging with Firebug I found a variable not defined error: "CKEDITOR is not defined". This was a result of addType directives in wiki/extensions/WYSIWYG/ckeditor/.htaccess. You either need to comment the lines or move the file (rename it, delete it or move it to somewhere else)&lt;br /&gt;&lt;br /&gt;So below are the commands to get your WYSIWYG editor working in Mediawiki:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ unzip wysiwyg-1.5.6_0.zip&lt;br /&gt;$ sudo cp -R extensions/WYSIWYG /var/www/wiki/extensions/&lt;br /&gt;$ sudo chown -R www-data:www-data /var/www/wiki/&lt;br /&gt;$ sudo mv /var/www/wiki/extensions/WYSIWYG/ckeditor/.htaccess /var/www/wiki/extensions/WYSIWYG/ckeditor/.htaccess.old&lt;br /&gt;$ sudo vi /var/www/wiki/LocalSettings.php&lt;br /&gt;  require_once("$IP/extensions/WYSIWYG/WYSIWYG.php");&lt;br /&gt;  $wgGroupPermissions['*']['wysiwyg']=true;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-6051182430159836737?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/6051182430159836737/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=6051182430159836737' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6051182430159836737'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6051182430159836737'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/09/fckeditor-for-mediawiki-117-wysiwyg.html' title='FCKEditor for Mediawiki 1.17: WYSIWYG based on CKEditor'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-3940651862211050202</id><published>2011-09-02T09:17:00.000-07:00</published><updated>2011-09-02T09:18:08.108-07:00</updated><title type='text'>Upgrading subversion</title><content type='html'>We are using WebDAV with Apache for subversion. Below are the steps I followed to migrate an old subversion repository to a brand new Ubuntu server with latest subversion.&lt;br /&gt;&lt;br /&gt;Note that "admin" is a user which can make administer subversion.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ sudo apt-get install subversion libapache2-svn&lt;br /&gt;$ sudo mkdir -p /var/local/svn/subversion.nestorurquiza.com&lt;br /&gt;$ sudo addgroup svn&lt;br /&gt;$ sudo usermod -a -G svn www-data&lt;br /&gt;$ sudo usermod -a -G svn admin&lt;br /&gt;$ sudo chmod 2770 /var/local/svn/subversion.nestorurquiza.com&lt;br /&gt;$ sudo svnadmin create /var/local/svn/subversion.nestorurquiza.com&lt;br /&gt;$ sudo vi /var/local/svn/subversion.nestorurquiza.com/conf/authz #ACL&lt;br /&gt;$ sudo mkdir /var/log/apache2/subversion.nestorurquiza.com&lt;br /&gt;$ sudo vi /etc/apache2/sites-available/subversion&lt;br /&gt;&amp;lt;VirtualHost *&amp;gt;&lt;br /&gt;&lt;br /&gt; ServerName svn.nestorurquiza.com&lt;br /&gt; ServerAlias subversion.nestorurquiza.com&lt;br /&gt; DocumentRoot /var/local/svn/subversion.nestorurquiza.com&lt;br /&gt;&lt;br /&gt; &amp;lt;Location /repos/reporting&amp;gt;&lt;br /&gt;   DAV svn&lt;br /&gt;   SVNListParentPath off&lt;br /&gt;   AuthType Basic&lt;br /&gt;   AuthName &amp;quot;Subversion repository&amp;quot;&lt;br /&gt;   SVNPath /var/local/svn/subversion.nestorurquiza.com&lt;br /&gt;   AuthzSVNAccessFile /var/local/svn/subversion.nestorurquiza.com/conf/authz&lt;br /&gt;   AuthUserFile /var/local/svn/subversion.nestorurquiza.com/conf/passwd&lt;br /&gt;   Require valid-user&lt;br /&gt;   &amp;lt;LimitExcept GET PROPFIND OPTIONS REPORT&amp;gt;&lt;br /&gt;        Require valid-user&lt;br /&gt;   &amp;lt;/LimitExcept&amp;gt;&lt;br /&gt; &amp;lt;/Location&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;Directory &amp;quot;/var/local/svn/subversion.nestorurquiza.com&amp;quot;&amp;gt;&lt;br /&gt;   Options -Indexes&lt;br /&gt; &amp;lt;/Directory&amp;gt;&lt;br /&gt;&amp;lt;/VirtualHost&amp;gt;&lt;br /&gt;$ sudo cp authz /var/local/svn/subversion.nestorurquiza.com/conf/authz #assuming there is an existing svn access file. Better keep it on SVN ;-)&lt;br /&gt;$ sudo cp passwd /var/local/svn/subversion.nestorurquiza.com/conf #assuming there is an existing password file. Better keep it on SVN ;-)&lt;br /&gt;$ sudo htpasswd /var/local/svn/subversion.nestorurquiza.com/conf/passwd "new username here" #to create individual users&lt;br /&gt;$ sudo ln -s /etc/apache2/sites-available/subversion /etc/apache2/sites-enabled/004-subversion&lt;br /&gt;$ sudo svnadmin load /var/local/svn/subversion.nestorurquiza.com &lt; ~/file_from_command_svnadmin_dump_originalRepoPath&lt;br /&gt;$ sudo chown -R www-data:svn /var/local/svn/subversion.nestorurquiza.com&lt;br /&gt;$ sudo chmod -R g+w /var/local/svn/subversion.nestorurquiza.com&lt;br /&gt;$ sudo /etc/init.d/apache2 restart&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-3940651862211050202?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/3940651862211050202/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=3940651862211050202' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/3940651862211050202'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/3940651862211050202'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/09/upgrading-subversion.html' title='Upgrading subversion'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-5802217533052606057</id><published>2011-09-01T22:05:00.000-07:00</published><updated>2011-09-01T22:05:02.724-07:00</updated><title type='text'>Upgrading Bugzilla</title><content type='html'>This one was pretty straightforward (from version 2 to 4 actually)&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;If this is a new server restore your DB from current bugzilla installation&lt;/li&gt;&lt;li&gt;Uncompress the distro in your document root, commonly /var/www/bugzilla-4.0.2&lt;/li&gt;&lt;li&gt;Update apache virtual host to point to that directory&lt;/li&gt;&lt;li&gt;Replace file 'localconfig' and directory 'local' from the previous installation&lt;/li&gt;&lt;li&gt;If needed change db user and password if needed in 'localconfig'&lt;/li&gt;&lt;li&gt;If needed update urlbase 'data/params' file&lt;/li&gt;&lt;li&gt;Run the below commands from the bugzilla document root directory and make sure you get no warnings nor errors. Correct them all before considering your migration completed&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ sudo ./runtests.pl &lt;br /&gt;$ sudo ./checksetup.pl &lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Restart apache and hit the bugzilla url&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-5802217533052606057?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/5802217533052606057/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=5802217533052606057' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5802217533052606057'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5802217533052606057'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/09/upgrading-bugzilla.html' title='Upgrading Bugzilla'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-7709614306137916299</id><published>2011-09-01T20:00:00.000-07:00</published><updated>2011-09-01T20:00:09.091-07:00</updated><title type='text'>Upgrading Mediawiki</title><content type='html'>This one took a good chunk of time. More than anything the order was the important part:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Install a fresh copy&lt;/li&gt;&lt;li&gt;Modify LocalSettings.php to meet your goals&lt;/li&gt;&lt;li&gt;Restore the previous db backup&lt;/li&gt;&lt;li&gt;Run the below to update the tables so they work with the newest code&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ cd /var/www/mediawiki/maintenance/&lt;br /&gt;$ php update.php&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-7709614306137916299?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/7709614306137916299/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=7709614306137916299' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7709614306137916299'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7709614306137916299'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/09/upgrading-mediawiki.html' title='Upgrading Mediawiki'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-6437036636072667876</id><published>2011-08-29T14:45:00.000-07:00</published><updated>2011-08-29T14:45:28.669-07:00</updated><title type='text'>Transform XML with XSLT in Talend</title><content type='html'>Talend Open Studio is an excellent ETL tool that can be used beyond the typical database and CSV manipulation. XML processing for example is today all over the places in the Enterprise.&lt;br /&gt;&lt;br /&gt;As a consequence XML transformations are a key skill for those folks doing data transformations.&lt;br /&gt;&lt;br /&gt;Even though there are more efficient tools XSLT is a standard which is supported in Talend through the tXSLT component. You just need to provide your XML, XSL and output files and Talend will apply the transformation for you. Talend uses Saxon at the moment so you get the benefit of clear error messages when trying to build your XSL.&lt;br /&gt;&lt;br /&gt;Of course XSLT might be a skill that even scares some people, however XSLT is not difficult at all and the more you work with it the better you get as with any other human skill. Do not try to avoid it, if you have XML to process and your ETL tool is Talend then do your homework and learn some XSL.&lt;br /&gt;&lt;br /&gt;As there is no better way to teach than providing an example I decided to write this quick showcase that will pivot the data resulting from running an Advent Geneva RSL report (A SOAP service) which comes in the form of key value pairs into a tabular output. I will provide two responses: XML and HTML. The first is probably what you need in Talend while the second is probably what you need if you want to provide a quick add hoc HTML report page.&lt;br /&gt;&lt;br /&gt;Here is the XML:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;?xml version='1.0' encoding='UTF-8'?&amp;gt;&lt;br /&gt;&amp;lt;SOAP-ENV:Envelope xmlns:SOAP-ENV=&amp;quot;http://schemas.xmlsoap.org/soap/envelope/&amp;quot; xmlns:SOAP-ENC=&amp;quot;http://schemas.xmlsoap.org/soap/encoding/&amp;quot; xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot; xmlns:xsd=&amp;quot;http://www.w3.org/2001/XMLSchema&amp;quot; xmlns:nsg=&amp;quot;http://geneva.advent.com&amp;quot;&amp;gt;&lt;br /&gt;	&amp;lt;SOAP-ENV:Body id=&amp;quot;_0&amp;quot;&amp;gt;&lt;br /&gt;		&amp;lt;reportResults xmlns=&amp;quot;http://geneva.advent.com&amp;quot;&amp;gt;&lt;br /&gt;			&amp;lt;return xsi:type=&amp;quot;nsg:reportResultsPortfolioStruct&amp;quot;&amp;gt;&lt;br /&gt;				&amp;lt;results xsi:type=&amp;quot;nsg:reportResultsStruct&amp;quot;&amp;gt;&lt;br /&gt;					&amp;lt;portfolioName xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;Fund1&amp;lt;/portfolioName&amp;gt;&lt;br /&gt;					&amp;lt;header xsi:type=&amp;quot;nsg:reportResultsRecordStruct&amp;quot;&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;Head&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;Fund 1 Example&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;Head&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;DIVIDENDS RECEIVABLE AND PAYABLE&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;Head&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;FOR THE PERIOD INCEPTION TO July 31, 2011&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;Head&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;/&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;					&amp;lt;/header&amp;gt;&lt;br /&gt;					&amp;lt;record xsi:type=&amp;quot;nsg:reportResultsRecordStruct&amp;quot;&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;CcyCode&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;BRL&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;Custodian&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;My Custodian&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;IDesc&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;My IDesc&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;TransID&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;10145834&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;TDate&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;March 15, 2011 12:00:00 am&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;SDate&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;December 31, 9999 11:59:59 pm&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;Qty&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;13528.013&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;PerShare&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;0.15100000&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;TaxRate&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;/&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;DivLocal&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;2042.73&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;TaxLocal&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;0.00&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;DivLocalNet&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;2042.73&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;DivBook&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;1225.76&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;TaxBook&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;0.00&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;DivBookNet&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;1225.76&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;UnrealFXGL&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;0.00&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;BookBal&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;1225.76&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;					&amp;lt;/record&amp;gt;&lt;br /&gt;					&amp;lt;record xsi:type=&amp;quot;nsg:reportResultsRecordStruct&amp;quot;&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;CcyCode&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;USD&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;Custodian&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;My Custodian&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;IDesc&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;My IDesc&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;TransID&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;10756740&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;TDate&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;April 27, 2011 12:00:00 am&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;SDate&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;July 1, 2011 12:00:00 am&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;Qty&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;205212.046&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;PerShare&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;0.16918500&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;TaxRate&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;/&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;DivLocal&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;34718.80&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;TaxLocal&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;0.00&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;DivLocalNet&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;34718.80&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;DivBook&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;22153.39&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;TaxBook&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;0.00&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;DivBookNet&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;22153.39&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;UnrealFXGL&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;0.00&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;BookBal&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;23379.15&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;					&amp;lt;/record&amp;gt;&lt;br /&gt;					&amp;lt;addendumErrors xsi:type=&amp;quot;nsg:reportResultsRecordStruct&amp;quot;&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;BegDesc&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;/&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;EndDesc&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;DIVIDENDS RECEIVABLE - CLOSING BALANCE&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;BegBal&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;/&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;EndBal&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;71643.79&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;					&amp;lt;/addendumErrors&amp;gt;&lt;br /&gt;					&amp;lt;addendumErrors xsi:type=&amp;quot;nsg:reportResultsRecordStruct&amp;quot;&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;BegDesc&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;/&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;EndDesc&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;DIVIDENDS PAYABLE - CLOSING BALANCE&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;BegBal&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;/&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;						&amp;lt;field xsi:type=&amp;quot;nsg:reportResultsVectorElement&amp;quot;&amp;gt;&lt;br /&gt;							&amp;lt;name xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;EndBal&amp;lt;/name&amp;gt;&lt;br /&gt;							&amp;lt;value xsi:type=&amp;quot;xsd:string&amp;quot;&amp;gt;-20200.35&amp;lt;/value&amp;gt;&lt;br /&gt;						&amp;lt;/field&amp;gt;&lt;br /&gt;					&amp;lt;/addendumErrors&amp;gt;&lt;br /&gt;				&amp;lt;/results&amp;gt;&lt;br /&gt;			&amp;lt;/return&amp;gt;&lt;br /&gt;		&amp;lt;/reportResults&amp;gt;&lt;br /&gt;	&amp;lt;/SOAP-ENV:Body&amp;gt;&lt;br /&gt;&amp;lt;/SOAP-ENV:Envelope&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If you open it with Firefox and you choose "View XPath" from a right click on the body of the page (contextual menu) you could try several XPATH expresions. Together with XSLT skill it comes XPATH which is just a way to address a node, an attribute or textual content in the XML. See below how I tested one of the XPATH using this tool. Pay attention to the namespace definition, I use simple letters to abbreviate more verbose prefixes.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-3bmb6-2pjq8/Tlv-PZ50cSI/AAAAAAAADew/DZrBkq2wjSk/s1600/Screen%2Bshot%2B2011-08-29%2Bat%2B4.39.37%2BPM.png" imageanchor="1" style=""&gt;&lt;img border="0" height="239" width="400" src="http://3.bp.blogspot.com/-3bmb6-2pjq8/Tlv-PZ50cSI/AAAAAAAADew/DZrBkq2wjSk/s400/Screen%2Bshot%2B2011-08-29%2Bat%2B4.39.37%2BPM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Now that you have a quick tool for finding nodes in the XML document let us see the desired document structure. Here is a screenshot of what we would like to see in HTML:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-LKoaPDPOM5E/TlwBN46YyYI/AAAAAAAADe4/vRHSmW0n2Ow/s1600/Screen%2Bshot%2B2011-08-29%2Bat%2B5.11.41%2BPM.png" imageanchor="1" style=""&gt;&lt;img border="0" height="30" width="400" src="http://2.bp.blogspot.com/-LKoaPDPOM5E/TlwBN46YyYI/AAAAAAAADe4/vRHSmW0n2Ow/s400/Screen%2Bshot%2B2011-08-29%2Bat%2B5.11.41%2BPM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Here is in the XML we would like to obtain for further processing in Talend:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-QzZsrl-q30c/TlwCywknfmI/AAAAAAAADfA/p3NU0hNB81s/s1600/Screen%2Bshot%2B2011-08-29%2Bat%2B5.13.11%2BPM.png" imageanchor="1" style=""&gt;&lt;img border="0" height="400" width="208" src="http://1.bp.blogspot.com/-QzZsrl-q30c/TlwCywknfmI/AAAAAAAADfA/p3NU0hNB81s/s400/Screen%2Bshot%2B2011-08-29%2Bat%2B5.13.11%2BPM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Here is the XSL that will output HTML:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;&amp;lt;xsl:stylesheet version=&amp;quot;1.0&amp;quot;&lt;br /&gt;  xmlns:xsl=&amp;quot;http://www.w3.org/1999/XSL/Transform&amp;quot;&lt;br /&gt;  xmlns:e=&amp;quot;http://schemas.xmlsoap.org/soap/envelope/&amp;quot;&lt;br /&gt;  xmlns:g=&amp;quot;http://geneva.advent.com&amp;quot;&amp;gt;&lt;br /&gt;    &amp;lt;xsl:output omit-xml-declaration=&amp;quot;yes&amp;quot; indent=&amp;quot;yes&amp;quot;/&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;xsl:strip-space elements=&amp;quot;*&amp;quot;/&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;xsl:key name=&amp;quot;kFieldNameByValue&amp;quot; match=&amp;quot;/e:Envelope/e:Body/g:reportResults/g:return/g:results/g:record/g:field/g:name&amp;quot;&lt;br /&gt;         use=&amp;quot;.&amp;quot;/&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;xsl:variable name=&amp;quot;vCols&amp;quot; select=&lt;br /&gt;       &amp;quot;/e:Envelope/e:Body/g:reportResults/g:return/g:results/g:record/g:field/g:name&lt;br /&gt;                [generate-id()&lt;br /&gt;                =          &lt;br /&gt;                 generate-id(key('kFieldNameByValue',.)[1])&lt;br /&gt;                 ]&amp;quot;/&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;xsl:template match=&amp;quot;/&amp;quot;&amp;gt;&lt;br /&gt;             &amp;lt;table&amp;gt;&lt;br /&gt;               &amp;lt;tr&amp;gt;&lt;br /&gt;                 &amp;lt;xsl:apply-templates select=&amp;quot;$vCols&amp;quot;/&amp;gt;&lt;br /&gt;               &amp;lt;/tr&amp;gt;&lt;br /&gt;&lt;br /&gt;               &amp;lt;xsl:for-each select=&lt;br /&gt;                 &amp;quot;/e:Envelope/e:Body/g:reportResults/g:return/g:results/g:record&amp;quot;&amp;gt;                   &lt;br /&gt;                 &amp;lt;tr&amp;gt;&lt;br /&gt;&lt;br /&gt;                   &amp;lt;xsl:variable name=&amp;quot;vPos&amp;quot; select=&amp;quot;position()&amp;quot;/&amp;gt;&lt;br /&gt;&lt;br /&gt;                   &amp;lt;xsl:for-each select=&amp;quot;$vCols&amp;quot;&amp;gt;&lt;br /&gt;                     &amp;lt;td&amp;gt;&lt;br /&gt;                       &amp;lt;xsl:value-of select=&lt;br /&gt;                           &amp;quot;../../../g:record[$vPos]/g:field[g:name = current()]/g:value&amp;quot;/&amp;gt;&lt;br /&gt;                     &amp;lt;/td&amp;gt;&lt;br /&gt;                   &amp;lt;/xsl:for-each&amp;gt;&lt;br /&gt;&lt;br /&gt;                 &amp;lt;/tr&amp;gt;&lt;br /&gt;              &amp;lt;/xsl:for-each&amp;gt;&lt;br /&gt;            &amp;lt;/table&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;/xsl:template&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;xsl:template match=&amp;quot;/e:Envelope/e:Body/g:reportResults/g:return/g:results/g:record/g:field/g:name&amp;quot;&amp;gt;&lt;br /&gt;            &amp;lt;td&amp;gt;&lt;br /&gt;              &amp;lt;xsl:value-of select=&amp;quot;.&amp;quot; /&amp;gt;&lt;br /&gt;            &amp;lt;/td&amp;gt;   &lt;br /&gt;    &amp;lt;/xsl:template&amp;gt;&lt;br /&gt;&amp;lt;/xsl:stylesheet&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here is the XSL that will output XML:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;&amp;lt;xsl:stylesheet version=&amp;quot;1.0&amp;quot;&lt;br /&gt;  xmlns:xsl=&amp;quot;http://www.w3.org/1999/XSL/Transform&amp;quot;&lt;br /&gt;  xmlns:e=&amp;quot;http://schemas.xmlsoap.org/soap/envelope/&amp;quot;&lt;br /&gt;  xmlns:g=&amp;quot;http://geneva.advent.com&amp;quot; &lt;br /&gt;&amp;gt;&lt;br /&gt;    &amp;lt;xsl:output omit-xml-declaration=&amp;quot;no&amp;quot; indent=&amp;quot;yes&amp;quot;/&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;xsl:strip-space elements=&amp;quot;*&amp;quot;/&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;xsl:key name=&amp;quot;kFieldNameByValue&amp;quot; match=&amp;quot;/e:Envelope/e:Body/g:reportResults/g:return/g:results/g:record/g:field/g:name&amp;quot;&lt;br /&gt;         use=&amp;quot;.&amp;quot;/&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;xsl:variable name=&amp;quot;vCols&amp;quot; select=&lt;br /&gt;       &amp;quot;/e:Envelope/e:Body/g:reportResults/g:return/g:results/g:record/g:field/g:name&lt;br /&gt;                [generate-id()&lt;br /&gt;                =          &lt;br /&gt;                 generate-id(key('kFieldNameByValue',.)[1])&lt;br /&gt;                 ]&amp;quot;/&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;xsl:template match=&amp;quot;/&amp;quot;&amp;gt;&lt;br /&gt;	  	&amp;lt;xsl:element name=&amp;quot;root&amp;quot;&amp;gt;&lt;br /&gt;               	&amp;lt;xsl:element name=&amp;quot;header&amp;quot;&amp;gt;&lt;br /&gt;				  &amp;lt;xsl:apply-templates select=&amp;quot;$vCols&amp;quot;/&amp;gt;&lt;br /&gt;				&amp;lt;/xsl:element&amp;gt;  &lt;br /&gt;&lt;br /&gt;               &amp;lt;xsl:for-each select=&amp;quot;/e:Envelope/e:Body/g:reportResults/g:return/g:results/g:record&amp;quot;&amp;gt;                   &lt;br /&gt;                 &amp;lt;xsl:element name=&amp;quot;record&amp;quot;&amp;gt;&lt;br /&gt;                   &amp;lt;xsl:variable name=&amp;quot;vPos&amp;quot; select=&amp;quot;position()&amp;quot;/&amp;gt;&lt;br /&gt;                   &amp;lt;xsl:for-each select=&amp;quot;$vCols&amp;quot;&amp;gt;&lt;br /&gt;                       &amp;lt;xsl:element name=&amp;quot;value&amp;quot;&amp;gt;&amp;lt;xsl:value-of select=&amp;quot;../../../g:record[$vPos]/g:field[g:name = current()]/g:value&amp;quot;/&amp;gt;&amp;lt;/xsl:element&amp;gt;&lt;br /&gt;                   &amp;lt;/xsl:for-each&amp;gt;&lt;br /&gt;                 &amp;lt;/xsl:element&amp;gt;&lt;br /&gt;              &amp;lt;/xsl:for-each&amp;gt;&lt;br /&gt;&lt;br /&gt;        &amp;lt;/xsl:element&amp;gt;&lt;br /&gt;    &amp;lt;/xsl:template&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;xsl:template match=&amp;quot;/e:Envelope/e:Body/g:reportResults/g:return/g:results/g:record/g:field/g:name&amp;quot;&amp;gt;&lt;br /&gt;            &amp;lt;xsl:element name=&amp;quot;name&amp;quot;&amp;gt;&amp;lt;xsl:value-of select=&amp;quot;.&amp;quot;/&amp;gt;&amp;lt;/xsl:element&amp;gt;&lt;br /&gt;    &amp;lt;/xsl:template&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/xsl:stylesheet&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A quick explanation of what the XSL code does:&lt;br /&gt;1-6 Is just stating it is an XML document which uses a namespace prefixed "xsl" for the transaformation instructions, a soap namespace with prefix "e" and an Advent Geneva namespace with prefix "g". Note that I abreviated the original namespace suffixes for the last two.&lt;br /&gt;&lt;br /&gt;7-9 How the final output should look like.&lt;br /&gt;&lt;br /&gt;11-19 We use the Muenchian grouping that allows us to have a list of all possible column names.&lt;br /&gt;&lt;br /&gt;21-37 XSLT is a functional language which works matching nodes and applying transformations to them. The logic can be affected using xsl:apply-template. We use xsl:element to create our custom nodes: First the headers which come out of the Muenchian key and later the records for which we again use the keys while addressing the correct record through an xsl:foreach nested loop.&lt;br /&gt;&lt;br /&gt;39-41 The template responsible for generating the name nodes.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-6437036636072667876?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/6437036636072667876/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=6437036636072667876' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6437036636072667876'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6437036636072667876'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/08/transform-xml-with-xslt-in-talend.html' title='Transform XML with XSLT in Talend'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-3bmb6-2pjq8/Tlv-PZ50cSI/AAAAAAAADew/DZrBkq2wjSk/s72-c/Screen%2Bshot%2B2011-08-29%2Bat%2B4.39.37%2BPM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-7537899370585508917</id><published>2011-08-27T06:30:00.000-07:00</published><updated>2011-08-27T06:30:00.023-07:00</updated><title type='text'>Twitter RSS: Follow silently</title><content type='html'>I blog about the work that I do just because "&lt;a href="http://thinkinginsoftware.blogspot.com/2011/06/palest-ink-is-better-than-best-memory.html"&gt;the palest ink is better than the best memory&lt;/a&gt;"&lt;br /&gt;&lt;br /&gt;I tweet about most of the stuff that I do or I am interested in just because I think for some people is just easier to get an SMS alike message with a title that makes you decide if the article might be of your interest.&lt;br /&gt;&lt;br /&gt;I consume most of the information I am able to digest via RSS.&lt;br /&gt;&lt;br /&gt;These three statements probably make me not a good social network user. I would love to have time to follow 2000 people, interact with them, engage in interesting conversations and so on but I don't believe that is possible for a first generation immigrant, father of three kids and with the hope to remain competitive in the fast paced World of IT.&lt;br /&gt;&lt;br /&gt;The way I live and work push me for automation even when consuming information. RSS, Atom or whatever format for syndicated news is then the answer to my needs.&lt;br /&gt;&lt;br /&gt;Syndicated feeds should be a mandatory option for any website or service providing information. If you are like me you will love this trick which allows you to silently consolidate in your News reader (I use Google Reader BTW) tweets from people you would like to follow.&lt;br /&gt;&lt;br /&gt;With an example here is how you can consume my tweets from your favorite reader:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: bash"&gt;http://api.twitter.com/1/statuses/user_timeline.rss?screen_name=nestor_urquiza&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I make my statements public because I do care about what others think. If you consume them via RSS, email, native program or any other means that is secondary for me.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-7537899370585508917?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/7537899370585508917/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=7537899370585508917' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7537899370585508917'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7537899370585508917'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/08/twitter-rss-follow-silently.html' title='Twitter RSS: Follow silently'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-5330186210994152255</id><published>2011-08-25T11:39:00.000-07:00</published><updated>2011-08-25T11:39:52.249-07:00</updated><title type='text'>TSQL Stored Procedure faster from SSMS than the application</title><content type='html'>No, I have no quick fix for this because &lt;a href="http://www.sommarskog.se/query-plan-mysteries.html"&gt;SQL Server is not about magic&lt;/a&gt; and there is a reason why there are guys that work 100% of the time as SQL developers.&lt;br /&gt;&lt;br /&gt;You have to ensure your queries are optimized and you know what query plan will be used when they are run. This is not about Java or .NET problems because your SQL Server Management Studio (SSMS) is "apparently" running the query 10 or 20 times faster than the application. It is indeed about your SQL code optimizations.&lt;br /&gt;&lt;br /&gt;So if you have this problem then run the stored procedure using something like:&lt;br /&gt;&lt;pre class="brush: sql"&gt;SET ARITHABORT OFF&lt;br /&gt;EXEC sp_custom_procedure 'param1', 2, 'param3'&lt;br /&gt;GO&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then run it again like:&lt;br /&gt;&lt;pre class="brush: sql"&gt;SET ARITHABORT ON&lt;br /&gt;EXEC sp_custom_procedure 'param1', 2, 'param3'&lt;br /&gt;GO&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You should get a slower response for the first and that will only tell you that your stored procedure is using a non efficient query plan. Now it is time to do your homework &lt;a href="http://www.sommarskog.se/query-plan-mysteries.html"&gt;reading the previous link&lt;/a&gt; and make your stored procedure run with a custom well predefined fast and efficient query plan (of course as fast and efficient as you can)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-5330186210994152255?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/5330186210994152255/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=5330186210994152255' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5330186210994152255'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5330186210994152255'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/08/tsql-stored-procedure-faster-from-ssms.html' title='TSQL Stored Procedure faster from SSMS than the application'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-5612234218160225782</id><published>2011-08-25T06:21:00.000-07:00</published><updated>2011-08-25T06:21:12.757-07:00</updated><title type='text'>Error creating bean with name 'liferayTransactionManager' defined in class path resource [META-INF/hibernate-spring.xml]</title><content type='html'>Every once in a while when I install Liferay in different servers I get something like the below:&lt;br /&gt;&lt;pre class="brush: bash"&gt;15:39:20,176 ERROR [ContextLoader:215] Context initialization failed&lt;br /&gt;org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionAdvice' defined in class path resource [META-INF/base-spring.xml]: Cannot resolve reference to bean 'liferayTransactionManager' while setting bean property 'transactionManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'liferayTransactionManager' defined in class path resource [META-INF/hibernate-spring.xml]: Cannot resolve reference to bean 'liferayHibernateSessionFactory' while setting bean property 'sessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'liferayHibernateSessionFactory' defined in class path resource [META-INF/hibernate-spring.xml]: Invocation of init method failed; nested exception is org.hibernate.HibernateException: Hibernate Dialect must be explicitly set&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is an error that relates to a connectivity problem with your database. Be sure you see the *whole* stacktrace and look for a clue about connection/permission problems. Commonly you should be able to get it right if you follow these directions (Using MySQL here):&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Check what IP MySQL is listening to. Below it shows as listening in 192.168.0.6&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ netstat -an|grep 3306&lt;br /&gt;tcp        0      0 192.168.0.6:3306        0.0.0.0:*               LISTEN     &lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Try connecting with the same host and credentials Liferay is using from command line mysql client:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ mysql -u liferay -p -h 192.168.0.6&lt;br /&gt;Enter password: &lt;br /&gt;ERROR 1130 (HY000): Host '192.168.0.6' is not allowed to connect to this MySQL server&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;As the example above shows there is a permision problem so go ahead and correct it. In MySQL password must be specified per each granted user@host combination: &lt;br /&gt;&lt;pre class="brush: bash"&gt;$ mysql -u root -p&lt;br /&gt;...&lt;br /&gt;mysql&gt;  GRANT ALL ON lportal.* TO 'liferay'@'192.168.0.6';&lt;br /&gt;Query OK, 0 rows affected (0.00 sec)&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Review your /etc/hosts in the client to make sure if you are using a host it maps to the specified IP:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ vi /etc/hosts&lt;br /&gt;192.168.0.6  sqlHost&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-5612234218160225782?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/5612234218160225782/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=5612234218160225782' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5612234218160225782'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5612234218160225782'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/08/error-creating-bean-with-name.html' title='Error creating bean with name &apos;liferayTransactionManager&apos; defined in class path resource [META-INF/hibernate-spring.xml]'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-969674568724890424</id><published>2011-08-23T20:19:00.002-07:00</published><updated>2011-08-23T20:19:45.467-07:00</updated><title type='text'>nullmailer smtp Failed 550 5.7.1 Unable to relay for user@myhost.myhost big log files</title><content type='html'>One of our servers was reporting low HDD resources and as usual I took a look at log files. Yes, again /var/log/mail.* were above 1GB, in some cases even 5GB.&lt;br /&gt;&lt;br /&gt;This is the error that I saw in the traces:&lt;br /&gt;&lt;pre class="brush: bash"&gt;Aug 23 21:56:33 myhost nullmailer[782]: Starting delivery: protocol: smtp host: mail.nestorurquiza.com file: 1314082502.18221&lt;br /&gt;Aug 23 21:56:33 myhost nullmailer[27145]: smtp: Failed: 550 5.7.1 Unable to relay for nestorurquizaadmin@myhost.myhost&lt;br /&gt;Aug 23 21:56:33 myhost nullmailer[782]: Sending failed:  Permanent error in sending the message&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This was originated because in etc/hosts I had a line like:&lt;br /&gt;&lt;pre class="brush: bash"&gt;192.168.1.23 myhost&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And the SMTP server was not configured to relay to such a thing like myhost.myhost. So just changing it to an authorized domain should fix it right?&lt;br /&gt;&lt;pre class="brush: bash"&gt;192.168.1.23 myhost.nestorurquiza.com&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Well I kept getting the same error!!!&lt;br /&gt;&lt;br /&gt;The reason was queued emails. I was getting from logs also a line like below:&lt;br /&gt;&lt;pre class="brush: bash"&gt;Aug 23 22:26:47 myhost nullmailer[828]: Delivery complete, 42489 message(s) remain.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So deleting the queue made finally the trick:&lt;br /&gt;&lt;pre class="brush: bash"&gt;sudo rm -fR  /var/spool/nullmailer/queue/&lt;br /&gt;sudo mkdir  /var/spool/nullmailer/queue&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-969674568724890424?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/969674568724890424/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=969674568724890424' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/969674568724890424'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/969674568724890424'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/08/nullmailer-smtp-failed-550-571-unable_23.html' title='nullmailer smtp Failed 550 5.7.1 Unable to relay for user@myhost.myhost big log files'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-8739522039298462402</id><published>2011-08-23T20:19:00.000-07:00</published><updated>2011-08-23T20:19:11.089-07:00</updated><title type='text'>nullmailer smtp Failed 550 5.7.1 Unable to relay for user@myhost.myhost big log files</title><content type='html'>One of our servers was reporting low HDD resources and as usual I took a look at log files. Yes, again /var/log/mail.* were above 1GB, in some cases even 5GB.&lt;br /&gt;&lt;br /&gt;This is the error that I saw in the traces:&lt;br /&gt;&lt;pre class="brush: bash"&gt;Aug 23 21:56:33 myhost nullmailer[782]: Starting delivery: protocol: smtp host: mail.nestorurquiza.com file: 1314082502.18221&lt;br /&gt;Aug 23 21:56:33 myhost nullmailer[27145]: smtp: Failed: 550 5.7.1 Unable to relay for nestorurquizaadmin@myhost.myhost&lt;br /&gt;Aug 23 21:56:33 myhost nullmailer[782]: Sending failed:  Permanent error in sending the message&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This was originated because in etc/hosts I had a line like:&lt;br /&gt;&lt;pre class="brush: bash"&gt;192.168.1.23 myhost&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And the SMTP server was not configured to relay to such a thing like myhost.myhost. So just changing it to an authorized domain should fix it right?&lt;br /&gt;&lt;pre class="brush: bash"&gt;192.168.1.23 myhost.nestorurquiza.com&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Well I kept getting the same error!!!&lt;br /&gt;&lt;br /&gt;The reason was queued emails. I was getting from logs also a line like below:&lt;br /&gt;&lt;pre class="brush: bash"&gt;Aug 23 22:26:47 myhost nullmailer[828]: Delivery complete, 42489 message(s) remain.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So deleting the queue made finally the trick:&lt;br /&gt;&lt;pre&gt;sudo rm -fR  /var/spool/nullmailer/queue/&lt;br /&gt;sudo mkdir  /var/spool/nullmailer/queue&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-8739522039298462402?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/8739522039298462402/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=8739522039298462402' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8739522039298462402'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8739522039298462402'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/08/nullmailer-smtp-failed-550-571-unable.html' title='nullmailer smtp Failed 550 5.7.1 Unable to relay for user@myhost.myhost big log files'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-3471482712951924999</id><published>2011-08-12T07:52:00.000-07:00</published><updated>2011-10-23T03:45:31.156-07:00</updated><title type='text'>apache2: Could not reliably determine the server's fully qualified domain name</title><content type='html'>This error is caused by an apache miss configuration. Below are those cases where I have seen it and how I have corrected them:&lt;br /&gt;&lt;br /&gt;Sometimes it is just about duplicated directive ServerName. If you use Virtual hosts do not include a global directive but instead just define it in the virtual hosts files, for example:&lt;br /&gt;&lt;pre class="brush: xml"&gt;...&lt;br /&gt;&amp;lt;VirtualHost  nestorurquiza.com:443&amp;gt;&lt;br /&gt; SSLEngine on&lt;br /&gt; DocumentRoot &amp;quot;/var/nestorurquiza-app&amp;quot;&lt;br /&gt; ServerName nestorurquiza.com&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Your server IP must be mapped to the server domain in /etc/hosts. It cannot be a loopback IP. Provide a separated line for that entry. Do not include any other mapping for the IP. If you need more just use new lines like below:&lt;br /&gt;&lt;pre class="brush: bash"&gt;...&lt;br /&gt;192.168.0.137   nestorurquiza.com&lt;br /&gt;192.168.0.137   nestorurquiza&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-3471482712951924999?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/3471482712951924999/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=3471482712951924999' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/3471482712951924999'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/3471482712951924999'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/08/apache2-could-not-reliably-determine.html' title='apache2: Could not reliably determine the server&apos;s fully qualified domain name'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-5353485923800033783</id><published>2011-08-11T13:01:00.001-07:00</published><updated>2011-08-11T13:01:25.992-07:00</updated><title type='text'>Install openssl in Ubuntu</title><content type='html'>&lt;pre class="brush: bash"&gt;sudo apt-get install libcurl4-openssl-dev&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-5353485923800033783?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/5353485923800033783/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=5353485923800033783' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5353485923800033783'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/5353485923800033783'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/08/install-openssl-in-ubuntu.html' title='Install openssl in Ubuntu'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-6476461525594232996</id><published>2011-08-11T12:33:00.000-07:00</published><updated>2011-08-11T12:38:30.555-07:00</updated><title type='text'>Using more than one certificate from the JVM for SSL Web Services LDAPS and more</title><content type='html'>There is one time when your JVM hosting your servlets will demand connecting to more than one SSL Web Service, an LDAPS, passing in some cases private keys, using in some cases different keystore files and more.&lt;br /&gt;&lt;br /&gt;In those cases the typical System properties to set up the keystore and password will apparently not work. You can of course pass the properties as part of the java command line that starts the JVM but you can do the same programatically of course.&lt;br /&gt;&lt;pre class="brush: java"&gt;System.setProperty("javax.net.ssl.keyStore", "keystore path");&lt;br /&gt;System.setProperty("", "keystore password");&lt;br /&gt;System.setProperty("javax.net.ssl.keyStoreType", keystore type);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You will be tempted to get into a really &lt;a href="http://alesaudate.com/2010/08/09/how-to-dynamically-select-a-certificate-alias-when-invoking-web-services/"&gt;complicated solution&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;However there is a simple approach around this problem. If you are contraint about having just one keystore to host multiple certificates that should not be a big issue. After all it is a key "store". All you need to do is import all your certificates and keys in just one keystore following a &lt;a href="http://thinkinginsoftware.blogspot.com/2011/08/pkcs12-to-jks-keystore.html"&gt;procedure like the one I described before&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Again what you need to have clear is how you deal with your keys which might be encrypted with a password and that password must be the same you use for your keystore. At that point if you have let us say just one case for a key using a password you can just use the same for your keystore. If not then negotiate a new common password with your service providers. This is after all your client key, yes it is private but it is private to you. A simple phone call and an agreement on the new password will make the trick. Do not use an email for that though!&lt;br /&gt;&lt;br /&gt;We recently spent several days troubleshooting issues like this just because I was relying on "that was already tested and it did not work". It demanded a new developer to take over the task so he could start fresh and try the most basic stuff. Simpler is after all better.&lt;br /&gt;&lt;br /&gt;As always log traces are your friend when Google is not. Be sure to activate them for SSL when you are dealing with certificate issues:&lt;br /&gt;&lt;pre class="brush: bash"&gt;-Djavax.net.debug=ssl,handshake&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-6476461525594232996?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/6476461525594232996/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=6476461525594232996' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6476461525594232996'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6476461525594232996'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/08/using-more-than-one-certificate-from.html' title='Using more than one certificate from the JVM for SSL Web Services LDAPS and more'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-2885045489965845973</id><published>2011-08-10T09:45:00.000-07:00</published><updated>2011-10-23T03:46:08.758-07:00</updated><title type='text'>Premature Optimization is not Agile or should I say it is Evil</title><content type='html'>The team was having an issue after introducing JPASecurity in the project. Some feature suddenly broke so it must be JPASecurity which is buggy, still in its early phase, ...&lt;br /&gt;&lt;br /&gt;Come on, this was working before so ...&lt;br /&gt;&lt;br /&gt;Well the fact that your code is working does not mean it is correct. An Abstract class cannot be instantiated but with so much Dependency Injection and Inversion Of Control sometimes we lose control LOL.&lt;br /&gt;&lt;br /&gt;An Abstract class is not to be used by any other than implementing classes. But what happens if the developer declares the class abstract and after that uses it as a JPA Entity? Of course this is a mistake, a common mistake I would say where the engineer thinks he is comming up with a great design that will save our lifes in the future, some kind of generalization up front that will address all details in the future, the silver bullet or a synonym (not about performance but about feature implementation) of what the Industry knows as &lt;a href="http://c2.com/cgi/wiki?PrematureOptimization"&gt;Premature Optimization&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The class is declared Abstract and there is not a single implementation of it but somehow JPA Hibernate EntityManager.merge(Entity) will not complain (Probably because after all it uses a proxy and not the actual class). All goes "good" until one day when you actually try to use reflection on the entity for example if you introduce JPASecurity to provide ACL in your JPA entities. &lt;br /&gt;&lt;br /&gt;At this point you will end up with exceptions like:&lt;br /&gt;&lt;pre class="brush: bash"&gt;java.lang.SecurityException: java.lang.InstantiationException&lt;br /&gt; at net.sf.jpasecurity.util.ReflectionUtils.throwThrowable(ReflectionUtils.java:109)&lt;br /&gt; at net.sf.jpasecurity.util.ReflectionUtils.newInstance(ReflectionUtils.java:38)&lt;br /&gt; at net.sf.jpasecurity.mapping.DefaultClassMappingInformation.newInstance(DefaultClassMappingInformation.java:216)&lt;br /&gt; at net.sf.jpasecurity.entity.EntityPersister.createUnsecureObject(EntityPersister.java:250)&lt;br /&gt; at net.sf.jpasecurity.entity.AbstractSecureObjectManager.getUnsecureObject(AbstractSecureObjectManager.java:140)&lt;br /&gt; at net.sf.jpasecurity.entity.EntityPersister.getUnsecureObject(EntityPersister.java:240)&lt;br /&gt; at net.sf.jpasecurity.entity.EntityPersister.merge(EntityPersister.java:73)&lt;br /&gt; at net.sf.jpasecurity.persistence.DefaultSecureEntityManager.merge(DefaultSecureEntityManager.java:130)&lt;br /&gt; at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)&lt;br /&gt; at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)&lt;br /&gt; at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)&lt;br /&gt; at java.lang.reflect.Method.invoke(Method.java:597)&lt;br /&gt; at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:365)&lt;br /&gt; at $Proxy1122.merge(Unknown Source)&lt;br /&gt; at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)&lt;br /&gt; at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)&lt;br /&gt; at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)&lt;br /&gt; at java.lang.reflect.Method.invoke(Method.java:597)&lt;br /&gt; at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:240)&lt;br /&gt; at $Proxy1013.merge(Unknown Source)&lt;br /&gt; at com.nestorurquiza.dao.CrudDaoImpl.update(CrudDaoImpl.java:105)&lt;br /&gt; at com.nestorurquiza.service.impl.CrudServiceImpl.update(CrudServiceImpl.java:45)&lt;br /&gt; at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)&lt;br /&gt; at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)&lt;br /&gt; at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)&lt;br /&gt; at java.lang.reflect.Method.invoke(Method.java:597)&lt;br /&gt; at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)&lt;br /&gt; at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)&lt;br /&gt; at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)&lt;br /&gt; at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)&lt;br /&gt; at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)&lt;br /&gt; at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)&lt;br /&gt; at $Proxy1037.update(Unknown Source)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is most likely because the proxy does not implement the default contructor in which case the Abstract class is the one attempted to be instantiated originating the Exception.&lt;br /&gt;&lt;br /&gt;But the bottom line of this post is to serve as a little bit of education to those young fellas who are catching up with programming and those experienced ones that love over bloated Enterprise Design up front. &lt;br /&gt;&lt;br /&gt;Please guys, try to keep it simple and do not fear refactoring. Embrace change and hard work. You will find yourself applying patterns as a result of pragmatism rather than as a result of that new cool design you learned from GOF alike book on Patterns.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-2885045489965845973?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/2885045489965845973/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=2885045489965845973' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/2885045489965845973'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/2885045489965845973'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/08/premature-optimization-is-not-agile-or.html' title='Premature Optimization is not Agile or should I say it is Evil'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-3589420519074778231</id><published>2011-08-09T14:28:00.000-07:00</published><updated>2011-08-09T14:28:36.345-07:00</updated><title type='text'>BHUB: Allow user and password from any URL</title><content type='html'>The &lt;a href="http://thinkinginsoftware.blogspot.com/2010/07/business-hub.html"&gt;BHUB&lt;/a&gt; philosophy is a unique entry point for all the business logic. A Controller makes that happen and there is no question on the big savings. The logic can be used from a CLI script (even pure curl can do the job), it can be of course croned, it can be used from any device (handhelds, desktops, appliances). You get for free every single security you applied in your web tier and I better stop right here because I have written about this several times.&lt;br /&gt;&lt;br /&gt;The common way to interact with BHUB would be to get a session, security token from an entry point let us say a login service and from there on keep requesting using that information.&lt;br /&gt;&lt;br /&gt;It would be ideal though that the entry point could be any page. In other words allow the first request to be actually not just login but a service request where you provide login information.&lt;br /&gt;&lt;br /&gt;In order to accomplish this we will need to hook into Spring once again. Just a custom filter will do the trick. Declare it and assign some permissions for the URLs. In this case I want to allow direct access to certain role (the API role) and I will be opening that functionality to just the pure CLI Controllers which of course does not mean that we cannot allow this functionality for the complete BHUB Service Suite:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;beans:bean id=&amp;quot;customLoginFilter&amp;quot; class=&amp;quot;com.nestorurquiza.web.filter.CustomLoginFilter&amp;quot;/&amp;gt;&lt;br /&gt;&amp;lt;custom-filter  before=&amp;quot;FORM_LOGIN_FILTER&amp;quot; ref=&amp;quot;customLoginFilter&amp;quot; /&amp;gt; &lt;br /&gt;&amp;lt;http auto-config=&amp;quot;true&amp;quot; use-expressions=&amp;quot;true&amp;quot; access-decision-manager-ref=&amp;quot;accessDecisionManager&amp;quot; disable-url-rewriting=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;  &amp;lt;intercept-url pattern=&amp;quot;/cli/**&amp;quot; access=&amp;quot;hasRole('ROLE_API')&amp;quot; /&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here is the Filter code.&lt;br /&gt;&lt;pre class="brush: java"&gt;package com.nestorurquiza.web.filter;&lt;br /&gt;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;&lt;br /&gt;import javax.servlet.Filter;&lt;br /&gt;import javax.servlet.FilterChain;&lt;br /&gt;import javax.servlet.FilterConfig;&lt;br /&gt;import javax.servlet.ServletException;&lt;br /&gt;import javax.servlet.ServletRequest;&lt;br /&gt;import javax.servlet.ServletResponse;&lt;br /&gt;import javax.servlet.http.Cookie;&lt;br /&gt;import javax.servlet.http.HttpServletRequest;&lt;br /&gt;import javax.servlet.http.HttpServletResponse;&lt;br /&gt;&lt;br /&gt;import org.slf4j.Logger;&lt;br /&gt;import org.slf4j.LoggerFactory;&lt;br /&gt;import org.springframework.beans.factory.annotation.Autowired;&lt;br /&gt;import org.springframework.security.core.Authentication;&lt;br /&gt;import org.springframework.security.core.context.SecurityContext;&lt;br /&gt;import org.springframework.security.core.context.SecurityContextHolder;&lt;br /&gt;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;&lt;br /&gt;import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;&lt;br /&gt;import org.springframework.security.web.util.TextEscapeUtils;&lt;br /&gt;&lt;br /&gt;import com.nestorurquiza.utils.CsrfUtil;&lt;br /&gt;import com.nestorurquiza.web.WebConstants;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * Allowing the creation of a session on the fly when requesting any URL&lt;br /&gt; * We authenticate the user if not authenticated in the case a user and password is provided &lt;br /&gt; * @author nestor&lt;br /&gt; *&lt;br /&gt; */&lt;br /&gt;public class CustomLoginFilter implements Filter &lt;br /&gt;{&lt;br /&gt;    private static final Logger log = LoggerFactory.getLogger(CustomLoginFilter.class);&lt;br /&gt;    &lt;br /&gt;    @Autowired&lt;br /&gt;    UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter;&lt;br /&gt;    &lt;br /&gt;    @Override&lt;br /&gt;    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException &lt;br /&gt;    {&lt;br /&gt;        HttpServletRequest httpRequest = ((HttpServletRequest) request);&lt;br /&gt;        String uri = httpRequest.getRequestURI();&lt;br /&gt;        if(! uri.contains(&amp;quot;.&amp;quot;)) {&lt;br /&gt;            HttpServletResponse httpResponse = ((HttpServletResponse) response);&lt;br /&gt;            SecurityContext securityContext = SecurityContextHolder.getContext();&lt;br /&gt;            String method = httpRequest.getMethod();&lt;br /&gt;            String userName = request.getParameter(&amp;quot;j_username&amp;quot;);&lt;br /&gt;            if(userName != null &amp;amp;&amp;amp; &amp;quot;POST&amp;quot;.equals(method) &amp;amp;&amp;amp; (securityContext == null || securityContext.getAuthentication() == null || !securityContext.getAuthentication().isAuthenticated())) {&lt;br /&gt;                try {&lt;br /&gt;                    Authentication auth = usernamePasswordAuthenticationFilter.attemptAuthentication(httpRequest, httpResponse);&lt;br /&gt;                    securityContext.setAuthentication(auth);&lt;br /&gt;                    SecurityContextHolder.setContext(securityContext);&lt;br /&gt;                    httpRequest.getSession().setAttribute(&amp;quot;SPRING_SECURITY_CONTEXT&amp;quot;, securityContext);&lt;br /&gt;                    httpRequest.getSession().setAttribute(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_LAST_USERNAME_KEY, TextEscapeUtils.escapeEntities(userName));&lt;br /&gt;                    if(auth.isAuthenticated()) {&lt;br /&gt;                        //Initialize CSRF token&lt;br /&gt;                        CsrfUtil.initializeCsrfToken(httpRequest);&lt;br /&gt;                        //Set the token as an attribute in the request to make it pass the security check&lt;br /&gt;                        httpRequest.setAttribute(WebConstants.CSRF_TOKEN, httpRequest.getSession().getAttribute(WebConstants.CSRF_TOKEN));&lt;br /&gt;                    }&lt;br /&gt;                } catch (Exception e){&lt;br /&gt;                    log.info(&amp;quot;User could not be authenticated. We go on ...&amp;quot;);&lt;br /&gt;                }&lt;br /&gt;                &lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        chain.doFilter(request, response);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public void destroy() &lt;br /&gt;    {   &lt;br /&gt;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public void init(FilterConfig config) throws ServletException &lt;br /&gt;    {&lt;br /&gt;    &lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    private Cookie getRememberMeCookie(Cookie[] cookies) {&lt;br /&gt;        if (cookies != null)&lt;br /&gt;          for (Cookie cookie : cookies) {&lt;br /&gt;            if (AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY.equals(cookie.getName())) {&lt;br /&gt;              return cookie;&lt;br /&gt;            }&lt;br /&gt;          }&lt;br /&gt;        return null;&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-3589420519074778231?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/3589420519074778231/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=3589420519074778231' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/3589420519074778231'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/3589420519074778231'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/08/bhub-allow-user-and-password-from-any.html' title='BHUB: Allow user and password from any URL'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-1800141313791941465</id><published>2011-08-09T14:04:00.000-07:00</published><updated>2011-08-09T14:04:20.580-07:00</updated><title type='text'>Tainting LDAP data with Talend</title><content type='html'>Even though I solved this through the use of a &lt;a href="http://thinkinginsoftware.blogspot.com/2011/08/ldapsearch-ssl-with-apacheds.html"&gt;nodeJS script&lt;/a&gt; I wanted to get the problem solved from Talend. &lt;br /&gt;&lt;br /&gt;Here I discuss how to do it from Talend. There are still some outstanding &lt;a href="http://jira.talendforge.org/browse/TDI-17080"&gt;issues&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;As you can see in the picture I am reusing the list of white listed emails from a hashmap (tHashOutput_9). Only one connection is done to the input LDAP server which needs normalization (tNormalize) for group/uniquemember. A tJavaRow is responsible for adding a column containing just the uniquemember email for filtering purposes executed by tMap_1. tLDAPOutput_3 inserts the root elements while tLDAPOutput_1 and tLDAPOutput_2 insert users and groups respectively. Note that the latest two are in a separate subjob to be able to create the root entries before. Again the use of hashes allow to communicate both subjobs.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-INkmTRDj4VQ/TkGgcAkYjHI/AAAAAAAADcM/NvTRZaxN-ho/s1600/Screen%2Bshot%2B2011-08-07%2Bat%2B9.53.55%2BAM.png" imageanchor="1" style=""&gt;&lt;img border="0" height="204" width="400" src="http://4.bp.blogspot.com/-INkmTRDj4VQ/TkGgcAkYjHI/AAAAAAAADcM/NvTRZaxN-ho/s400/Screen%2Bshot%2B2011-08-07%2Bat%2B9.53.55%2BAM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-1800141313791941465?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/1800141313791941465/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=1800141313791941465' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/1800141313791941465'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/1800141313791941465'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/08/tainting-ldap-data-with-talend.html' title='Tainting LDAP data with Talend'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-INkmTRDj4VQ/TkGgcAkYjHI/AAAAAAAADcM/NvTRZaxN-ho/s72-c/Screen%2Bshot%2B2011-08-07%2Bat%2B9.53.55%2BAM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-6560185107015179332</id><published>2011-08-09T09:56:00.000-07:00</published><updated>2011-08-09T09:56:35.818-07:00</updated><title type='text'>Install gcc in Ubuntu</title><content type='html'>The Linux sysadmin must build from sources from time to time. It makes sense then to have the C environment ready. In Ubuntu all you need to do is to follow the below commands:&lt;br /&gt;&lt;pre class="brush: bash"&gt;sudo apt-get update&lt;br /&gt;sudo apt-get upgrade&lt;br /&gt;sudo apt-get install build-essential&lt;br /&gt;gcc --version&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-6560185107015179332?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/6560185107015179332/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=6560185107015179332' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6560185107015179332'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6560185107015179332'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/08/install-gcc-in-ubuntu.html' title='Install gcc in Ubuntu'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-4552220349695355183</id><published>2011-08-06T04:38:00.001-07:00</published><updated>2011-08-06T04:38:00.474-07:00</updated><title type='text'>Phishing Attack: Fake Twitter Email not marked as Spam</title><content type='html'>I got an email in my Gmail account from "Twitter Support&lt;support@twitter.com&gt;" "with subject "Your account has been suspended" with no text content but an image (that I have disabled of course for security reasons). The image content was something like "We detected unusual activity ..."&lt;br /&gt;&lt;br /&gt;This phishing email is nothing new but what came to my attention was that Gmail was not able to detect the spam even though the full headers from the message are showing how Google identified it as a candidate for Spam "Authentication-Results: mx.google.com; spf=hardfail (google.com: domain of support@twitter.com does not designate 203.115.131.123 as permitted sender) smtp.mail=support@twitter.com"&lt;br /&gt;&lt;br /&gt;Any software is plenty of bugs. Even with the best developers on board you are still vulnerable. Good that "Report phishing" option is available albeit a little bit hidden behind an arrow close to the Reply link. User experience should be helping better here I would say but regardless the important lesson to learn is to &lt;b&gt;be always suspicious up front. Do not trust any bad news (account hacked or compromised) or too good news (You just won a million dollar) you receive&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;See below for the full headers of the message:&lt;br /&gt;&lt;pre class="brush:bash"&gt;Delivered-To: nestor.urquiza@gmail.com&lt;br /&gt;Received: by 10.236.179.100 with SMTP id g64cs68259yhm;&lt;br /&gt;        Fri, 5 Aug 2011 22:37:44 -0700 (PDT)&lt;br /&gt;Received: from mr.google.com ([10.142.187.15])&lt;br /&gt;        by 10.142.187.15 with SMTP id k15mr3461337wff.111.1312609063904 (num_hops = 1);&lt;br /&gt;        Fri, 05 Aug 2011 22:37:43 -0700 (PDT)&lt;br /&gt;Received: by 10.142.187.15 with SMTP id k15mr2917056wff.111.1312609063502;&lt;br /&gt;        Fri, 05 Aug 2011 22:37:43 -0700 (PDT)&lt;br /&gt;Return-Path: &amp;lt;support@twitter.com&amp;gt;&lt;br /&gt;Received: from vsfilter2.roc.bti.net.ph (vsf-mx4.bti.net.ph [203.115.131.123])&lt;br /&gt;        by mx.google.com with ESMTP id w1si282094wfw.62.2011.08.05.22.37.42;&lt;br /&gt;        Fri, 05 Aug 2011 22:37:43 -0700 (PDT)&lt;br /&gt;Received-SPF: fail (google.com: domain of support@twitter.com does not designate 203.115.131.123 as permitted sender) client-ip=203.115.131.123;&lt;br /&gt;Authentication-Results: mx.google.com; spf=hardfail (google.com: domain of support@twitter.com does not designate 203.115.131.123 as permitted sender) smtp.mail=support@twitter.com&lt;br /&gt;X-IronPort-Anti-Spam-Filtered: true&lt;br /&gt;X-IronPort-Anti-Spam-Result: AqA2AOHRPE7Lc4NugWdsb2JhbAAoEwcXgjgBD4NgjV+EQwGOLRNcAQEWJiVxSxISGQELCk0BAQECDQ4MJAJQh3oKIgGeN5I1jSaDLQyCLl8Eh1qYFoMBgQaCYTA&lt;br /&gt;Received: from smtp4-roc.bti.net.ph (HELO smtp1.skyinet.net) ([203.115.131.110])&lt;br /&gt;  by vsfilter2.roc.bti.net.ph with ESMTP; 06 Aug 2011 13:37:41 +0800&lt;br /&gt;Received: from 110.55.232.159.BTI.NET.PH (unknown [110.55.236.20])&lt;br /&gt; by smtp4-roc.bti.net.ph (Postfix) with ESMTP id 55F8793DB3A&lt;br /&gt; for &amp;lt;nestor.urquiza@gmail.com&amp;gt;; Sat,  6 Aug 2011 13:37:41 +0800 (PHT)&lt;br /&gt;From: &amp;quot;Twitter Support&amp;quot; &amp;lt;support@twitter.com&amp;gt;&lt;br /&gt;Subject: Your account has been suspended&lt;br /&gt;To: &amp;quot;nestor.urquiza&amp;quot; &amp;lt;nestor.urquiza@gmail.com&amp;gt;&lt;br /&gt;Content-Type: multipart/alternative; charset=&amp;quot;iso-8859-10&amp;quot;; boundary=&amp;quot;LMRJGCZhTXlUeMlXLirvgZD=_SMWAE68zR&amp;quot;&lt;br /&gt;MIME-Version: 1.0&lt;br /&gt;Content-Transfer-Encoding: 8bit&lt;br /&gt;Date: Sat, 6 Aug 2011 13:37:37 +0800&lt;br /&gt;Message-Id: &amp;lt;20110806053741.55F8793DB3A@smtp4-roc.bti.net.ph&amp;gt;&lt;br /&gt;&lt;br /&gt;This is a multi-part message in MIME format&lt;br /&gt;&lt;br /&gt;--LMRJGCZhTXlUeMlXLirvgZD=_SMWAE68zR&lt;br /&gt;Content-Type: text/plain ; charset=&amp;quot;iso-8859-10&amp;quot;&lt;br /&gt;Content-Transfer-Encoding: quoted-printable&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;--LMRJGCZhTXlUeMlXLirvgZD=_SMWAE68zR&lt;br /&gt;Content-Type: text/html ; charset=&amp;quot;iso-8859-10&amp;quot;&lt;br /&gt;Content-Transfer-Encoding: quoted-printable&lt;br /&gt;&lt;br /&gt;&amp;lt;HTML&amp;gt;&amp;lt;HEAD&amp;gt;&lt;br /&gt;&amp;lt;META name=3DGENERATOR content=3D&amp;quot;MSHTML 8.00.6001.23019&amp;quot;&amp;gt;&amp;lt;/HEAD&amp;gt;&lt;br /&gt;&amp;lt;BODY&amp;gt;&lt;br /&gt;&amp;lt;P&amp;gt;&amp;lt;A href=3D&amp;quot;mexico.cnn.com/redirectComplete.php?url=3D//emailus%2Eit=&lt;br /&gt;%2Etc/2ule3B&amp;quot;&amp;gt;&amp;lt;IMG border=3D0 src=3D&amp;quot;http://3.bp.blogspot.com/-u_sWLHS=&lt;br /&gt;Yjes/TjyqVZ73vrI/AAAAAAAAAEQ/hX5mKS-R7-g/s1600?2ule3B&amp;quot;&amp;gt;&amp;lt;/A&amp;gt; &amp;lt;/P&amp;gt;&lt;br /&gt;&amp;lt;P&amp;gt;&amp;amp;nbsp;&amp;lt;/P&amp;gt;&amp;lt;/BODY&amp;gt;&amp;lt;/HTML&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;--LMRJGCZhTXlUeMlXLirvgZD=_SMWAE68zR--                                                                                                                                                                                                                                                    &lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-4552220349695355183?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/4552220349695355183/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=4552220349695355183' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/4552220349695355183'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/4552220349695355183'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/08/phishing-attack-fake-twitter-email-not_06.html' title='Phishing Attack: Fake Twitter Email not marked as Spam'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-7762765585548098309</id><published>2011-08-04T13:21:00.000-07:00</published><updated>2011-10-23T03:46:52.873-07:00</updated><title type='text'>Server scripting with NodeJs: Tainting LDAP data</title><content type='html'>This is a real world example of using &lt;a href="http://nodejs.org/"&gt;nodejs&lt;/a&gt; to do server side scripting. It shows how to deal with command line arguments, invoke shell commands and parse responses, interact with mysql and how to use the library (when possible and makes sense) in a synchronous way.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://nodejs.org/"&gt;Node&lt;/a&gt; (also knows and nodejs) is a javascript library that allows to run javascript code out of the browser. It favors event programming which can be difficult for backend developers used to languages like Java. At the same time it is straightforward for front end developers which are already used to javascript callback functions necessary for UI programming.&lt;br /&gt;&lt;br /&gt;If you havent install it yet go ahead and do it now:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;I have tested all this with node-v0.4.10 as you see below.&lt;br /&gt;&lt;pre class="brush: bash"&gt;cd ~/Downloads&lt;br /&gt;wget http://nodejs.org/dist/node-v0.4.10.tar.gz&lt;br /&gt;tar -zxvf node-v0.4.10.tar.gz&lt;br /&gt;cd node-v0.4.10&lt;br /&gt;./configure&lt;br /&gt;make&lt;br /&gt;sudo make install&lt;br /&gt;node --version&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Install npm (package manager for nodejs) in case you need extra modules (and you will when you get serious about nodejs development)&lt;br /&gt;&lt;pre class="brush: bash"&gt;curl http://npmjs.org/install.sh | sudo sh&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;If you use MySQL here is how to install one library that just works (whole API: https://github.com/felixge/node-mysql)&lt;br /&gt;&lt;pre class="brush: bash"&gt;npm install mysql&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;You should of course test with the simplest possible 'Hello World' script&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ vi hello.js&lt;br /&gt;console.log('Hello World');&lt;br /&gt;$ node hello.js&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;The Node project is making a big effort to enforce the use of non blocking code (code that will be triggered but will not block the current thread). That is the reason why most of the code you will see is written with nested callback functions.&lt;br /&gt;&lt;br /&gt;Event programming is good paradigm for certain problems. Responsiveness of a program can be improved a lot without having to deal with blocking in multithreading programming. Those of us that have worked with threads know how hard it can be. I am not nodejs is better than java or any other language to deal with multiprocessing. It is just a different paradigm and I would appreciate if you do not start ranting against or in favor of any programming language in this post.&lt;br /&gt;&lt;br /&gt;Here is one extract from the process api: http://nodejs.org/docs/v0.3.1/api/process.html&lt;br /&gt;&lt;pre class="brush: javascript"&gt;process.argv.forEach(function (val, index, array) {&lt;br /&gt;  console.log(index + ': ' + val);&lt;br /&gt;});&lt;br /&gt;console.log&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The previous code is non blocking, so if you need to work with the arguments you will need to work inside the anonymous function. Look at the code below. After running it you will see the asynchronous code does not run before the console logs the message:&lt;br /&gt;&lt;pre class="brush: javascript"&gt;var asyncArg = 'Async Arguments:';&lt;br /&gt;var syncArg = 'Sync Arguments:';&lt;br /&gt;&lt;br /&gt;var argv = process.argv;&lt;br /&gt;argv.forEach(function (val, index, array) {&lt;br /&gt; setTimeout(function() {asyncArg += ' ' + val; console.log( asyncArg ); }, 0);&lt;br /&gt; &lt;br /&gt;});&lt;br /&gt;console.log( asyncArg );&lt;br /&gt;&lt;br /&gt;for( argIndex in argv ) {&lt;br /&gt; syncArg += ' ' + argv[argIndex];&lt;br /&gt;}&lt;br /&gt;console.log( syncArg );&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So probably to work with command line arguments you are better off the asynchronous flow. One might expect that there is always a way to go synchronous with nodejs but that is not the case. As I said the nodejs library pushes for non blocking === asynchronous code. Look at the below code which comments should be self explanatory:&lt;br /&gt;&lt;pre class="brush: javascript"&gt;var sys = require('sys')&lt;br /&gt;var exec = require('child_process').exec;&lt;br /&gt;&lt;br /&gt;var child = exec("ldapsearch -x -v -H 'ldap://nestorurquiza:10389' -D 'uid=admin,ou=system' -w 'secret' -b 'o=nestorurquiza'", function (error, stdout, stderr) {&lt;br /&gt;  console.log(stdout); //Will print the output of the command&lt;br /&gt;  if (error !== null) {&lt;br /&gt;    console.log('exec error: ' + error);&lt;br /&gt;  }&lt;br /&gt;});&lt;br /&gt;console.log(child.stdout); //Will not print the output of the command but rather the object prototype&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here is a script that clones the data from one LDAP Server (tested with ApacheDS) and imports it into a second LDAP server. The script taints the emails to ensure we do not send messages to real production users while testing. It excludes certain domains and it shows how to interact with mysql to pull a white list of emails for which no tainting should be done. It also changes the passwords to a well known test password so the team can use it to debug what happens when different users interact with the application. BTW this task is better to be done from Talend but I needed a real problem to demonstrate nodejs is ready to work as server side scripting while I was figuring out how to make it happen from Talend:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: javascript"&gt;#!/usr/local/bin/node&lt;br /&gt;/*&lt;br /&gt;** WARNING: This program will wipe out the specified BASE_DN from the target LDAP Server&lt;br /&gt;**&lt;br /&gt;** taintLdap.js A nodejs script to taint data from ldap. Use it to change passwords for all users to a known value and to change their emails to avoid sending messages from testing environment to real users&lt;br /&gt;**&lt;br /&gt;** Example: node taintLdap.js 'ldap://jnestorurquiza:10389' 'uid=admin,ou=system' 'secret' 'ldap://localhost:10389' 'uid=admin,ou=system' 'secret'&lt;br /&gt;**&lt;br /&gt;** @Author: Nestor Urquiza&lt;br /&gt;** @Date: 08/02/2011&lt;br /&gt;**&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;** Imports&lt;br /&gt;*/&lt;br /&gt;var sys = require('sys')&lt;br /&gt;var exec = require('child_process').exec;&lt;br /&gt;var Client = require('mysql').Client;&lt;br /&gt;var client = new Client();&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;** Constants&lt;br /&gt;*/&lt;br /&gt;var BASE_DN = &amp;quot;o=nestorurquiza&amp;quot;;&lt;br /&gt;var EXCLUSION_DOMAINS = ['nestorurquiza.com','nestoru.com'];&lt;br /&gt;var APPEND_DOMAIN = 'nestorurquiza.com'&lt;br /&gt;var COMMON_PASSWORD = 'e1NIQX1JcnFMUVdMT1o3ZXF0WHRBdUlFSFRlUnZkRFk9' //Testtest1 after SHA1. To generate a different password use: echo -n &amp;quot;mypassword&amp;quot; | openssl dgst -sha1&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;** Arguments&lt;br /&gt;*/&lt;br /&gt;var argv = process.argv;&lt;br /&gt;if( argv.length != 8 ) {&lt;br /&gt; usage();&lt;br /&gt; process.exit(1);&lt;br /&gt;}&lt;br /&gt;var fromUrl = argv[2];&lt;br /&gt;var fromUser = argv[3];&lt;br /&gt;var fromPasword = argv[4];&lt;br /&gt;var toUrl = argv[5];&lt;br /&gt;var toUser = argv[6];&lt;br /&gt;var toPasword = argv[7];&lt;br /&gt;&lt;br /&gt;client.user = 'root';&lt;br /&gt;client.password = 'root';&lt;br /&gt;client.host = 'localhost';&lt;br /&gt;client.port = '3306';&lt;br /&gt;client.database = 'nestorurquiza'&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;** Main&lt;br /&gt;*/&lt;br /&gt;//console.log('Cloning and tainting from ' + fromUrl + ' to ' + toUrl);&lt;br /&gt;var ldif;&lt;br /&gt;var authorizedEmails = new Array();&lt;br /&gt;var pattern = /[^\s=,]*@[^\s=,]*/g&lt;br /&gt;var child = exec(&amp;quot;ldapsearch -x -v -H '&amp;quot; + fromUrl + &amp;quot;' -D '&amp;quot; + fromUser + &amp;quot;' -w '&amp;quot; + fromPasword + &amp;quot;' -b '&amp;quot; + BASE_DN + &amp;quot;'&amp;quot;, function (error, stdout, stderr) {&lt;br /&gt;  if (error) {&lt;br /&gt;    throw error;&lt;br /&gt;  }&lt;br /&gt;  ldif = stdout;&lt;br /&gt;  client.connect();&lt;br /&gt;  var authorizedEmailQuery = client.query(&lt;br /&gt;  'SELECT name FROM authorized_test_email',&lt;br /&gt;  function (error, results, fields) {&lt;br /&gt;    if (error) {&lt;br /&gt;      throw error;&lt;br /&gt;    }&lt;br /&gt;    for (var resultIndex in results){&lt;br /&gt;      var result = results[resultIndex];&lt;br /&gt;      authorizedEmails[resultIndex] = result.name;&lt;br /&gt;    }&lt;br /&gt;    client.end();&lt;br /&gt;    //console.log('****************'  + authorizedEmails);&lt;br /&gt;    ldif = taintLdifEmail();&lt;br /&gt; &lt;br /&gt; child = exec(&amp;quot;ldapdelete -r -x -H '&amp;quot; + toUrl + &amp;quot;' -D '&amp;quot; + toUser + &amp;quot;' -w '&amp;quot; + toPasword + &amp;quot;' '&amp;quot; + BASE_DN + &amp;quot;'&amp;quot;, function (error, stdout, stderr) {&lt;br /&gt;   if (error) {&lt;br /&gt;        //throw error;&lt;br /&gt;        console.log(&amp;quot;WARNING: Could not delete &amp;quot; + BASE_DN);&lt;br /&gt;      }&lt;br /&gt;      var command = &amp;quot;echo '&amp;quot; + ldif + &amp;quot;' | ldapmodify -x -c -a -H '&amp;quot; + toUrl + &amp;quot;' -D '&amp;quot; + toUser + &amp;quot;' -w '&amp;quot; + toPasword + &amp;quot;'&amp;quot;;&lt;br /&gt;      //console.log(command);&lt;br /&gt;      child = exec(command, function (error, stdout, stderr) {&lt;br /&gt;     &lt;br /&gt;  if (error) {&lt;br /&gt;   throw error;&lt;br /&gt;  }&lt;br /&gt;   });&lt;br /&gt;    });   &lt;br /&gt; //ldapmodify -x -c -a -H ldap://localhost:10389 -D &amp;quot;uid=admin,ou=system&amp;quot; -w 'secret' &amp;lt; ~/Downloads/taintedLdap.ldif&lt;br /&gt;  }&lt;br /&gt;);&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;** Functions&lt;br /&gt;*/&lt;br /&gt;function taintLdifEmail() {&lt;br /&gt; var matches = ldif.match(pattern);&lt;br /&gt;&lt;br /&gt; for ( var matchIndex in matches ) {&lt;br /&gt;  var taint = true;&lt;br /&gt;  var match = matches[matchIndex];&lt;br /&gt;  for( var exclusionDomainIndex in EXCLUSION_DOMAINS ) {&lt;br /&gt;   if( match.indexOf(EXCLUSION_DOMAINS[exclusionDomainIndex]) &amp;gt;= 0 ) {&lt;br /&gt;    taint = false;&lt;br /&gt;    break;&lt;br /&gt;   }&lt;br /&gt;  }&lt;br /&gt;  if( !taint ) {&lt;br /&gt;   continue;&lt;br /&gt;  }&lt;br /&gt;  for ( var authorizedEmailIndex in authorizedEmails ) {&lt;br /&gt;   var authorizedEmail = authorizedEmails[authorizedEmailIndex];&lt;br /&gt;   if( match == authorizedEmail ) {&lt;br /&gt;    taint = false;&lt;br /&gt;    continue;&lt;br /&gt;   }&lt;br /&gt;  }&lt;br /&gt;  if( taint ) {&lt;br /&gt;   var replacement = match.replace('.', '') + APPEND_DOMAIN;&lt;br /&gt;   //console.log( match + &amp;quot; &amp;gt;&amp;gt;&amp;gt; &amp;quot; + replacement );&lt;br /&gt;   ldif = ldif.replace(match, replacement);&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt; ldif = ldif.replace(/userPassword.*/g, 'userPassword:: ' + COMMON_PASSWORD);&lt;br /&gt; return ldif;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function usage() {&lt;br /&gt; console.log(&amp;quot;Usage: &amp;quot; + &amp;quot;./taintLdap.js &amp;lt;fromUrl&amp;gt; &amp;lt;fromUser&amp;gt; &amp;lt;fromPasword&amp;gt; &amp;lt;toUrl&amp;gt; &amp;lt;toUser&amp;gt; &amp;lt;toPasword&amp;gt;&amp;quot;);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Why I am trying to use nodejs if I already have bash, awk, perl, python, ruby and what not? I am building a team with strong separation of concerns. While we know we cannot be as good as the guy that is spending 100% of the time in just writing SQL stored procedures the whole team could cover for some days to be able to compensate vacation time for example, so yes SQL is a mandatory skill. Javascript is a mandatory skill as well and if I can script with it I could have some of those scripting needs done by anybody in the team as well. I am just trying to keep really low the amount of technologies and languages we use.&lt;br /&gt;&lt;br /&gt;Isn't it better to use RhinoJs? Probably yes, but I am tempted to use something out of the JVM that runs faster and consume less resources. I have recently decomisioned a whole CLI project based in Java just because it was really resource intensive. I have favored the use of Controllers in our &lt;a href="http://thinkinginsoftware.blogspot.com/2010/07/business-hub.html"&gt;Business Hub&lt;/a&gt; which are called from simple CURL or WGET statements. I am not claiming RhinoJs is unnacceptable slow nor that NodeJs is better. I see value in both of them.&lt;br /&gt;&lt;br /&gt;Why I am considering scripting after all if I promote the idea of a Business Hub? There are cases in which definitely using Unix Power Tools, existing CLIs etc do the job quickly and more reliable.&lt;br /&gt;&lt;br /&gt;Do I think NodeJs is a better answer for Server side logic than Java? At the moment I am happy with plain java for my backend. The amount of existing code available for free is amazing. If that will be the case in the future it will depend on the open source community. At least for my current project I stick to Java.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-7762765585548098309?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/7762765585548098309/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=7762765585548098309' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7762765585548098309'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7762765585548098309'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/08/server-scripting-with-nodejs-tainting.html' title='Server scripting with NodeJs: Tainting LDAP data'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-480740971549010777</id><published>2011-08-04T09:04:00.000-07:00</published><updated>2011-08-11T12:13:51.103-07:00</updated><title type='text'>PKCS12 to JKS keystore</title><content type='html'>Java uses a proprietary to Sun format (JKS) to store certificates in what is called a Keystore (A file containing entries of those certificates you trust)&lt;br /&gt;&lt;br /&gt;When you get certificates included in a different keystore type like it is the case of PKCS12 (commonly using the *.p12 extension) you need to extract and add them to the JKS keystore. Failure to do so will make your program depending on individual keystore files rather than just one keystore where all certificates and private keys are kept.&lt;br /&gt;&lt;br /&gt;Here is how you can do it (sample using OSX paths but applicable to other OS as well).&lt;br /&gt;First find the key for the certificate to export. Basically the left first word for the specific entry from this command:&lt;br /&gt;&lt;pre class="brush: bash"&gt;keytool -list -keystore /Users/nestor/Downloads/cert.p12 -storetype pkcs12&lt;br /&gt;&lt;/pre&gt;Then run something like the below. Note that alias.from.cert.p12 comes from the previous command.&lt;br /&gt;&lt;pre class="brush: bash"&gt;keytool -importkeystore -srckeystore /Users/nestor/Downloads/cert.p12 -destkeystore /Library/Java/Home/lib/security/cacerts -srcstoretype PKCS12 -deststoretype JKS -srcstorepass cert.p12.password -deststorepass changeit.is.the.default.password -srcalias alias.from.cert.p12 -destalias alias.for.cacerts.new.certificate&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Note that the private key inside PKCS12 might need a password and that password must be the same as the JKS where you are importing. Failure to do this will end up in Access Denied, Forbidden or any other error comming from the Server where the key is attempted to be used.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-480740971549010777?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/480740971549010777/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=480740971549010777' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/480740971549010777'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/480740971549010777'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/08/pkcs12-to-jks-keystore.html' title='PKCS12 to JKS keystore'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-641162141505384286</id><published>2011-08-03T14:01:00.000-07:00</published><updated>2011-10-23T03:47:32.141-07:00</updated><title type='text'>ldapsearch SSL with ApacheDS</title><content type='html'>Self signed certificates are treated different by the ldap cli tools.&lt;br /&gt;&lt;br /&gt;The task was to connect with ldapsearch to a remote ApacheDS server serving SSL. Long story short the certificate is self signed and only certain IP range can access the server via LDAP over SSL (TLS).&lt;br /&gt;&lt;br /&gt;Here are the steps showing how to configure ldapsearch and the rest of ldap tools to work with SSL (both signed and self signed):&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;If not using self signed certificate then get the server certificate&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ openssl s_client -connect ldap.nestorurquiza.com:636&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Non self signed certificate: Create a file with the contents from "-----BEGIN CERTIFICATE-----" up to "-----END CERTIFICATE-----") from the previous command&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ sudo mkdir /etc/openldap/certs/&lt;br /&gt;$ vi /etc/openldap/certs/ldap.nestorurquiza.com.cert&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Be sure your certificate is not self signed. Basically check for a return code=0, not someting like "Verify return code: 18 (self signed certificate)"&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ openssl s_client -connect ldap.nestorurquiza.com:636 -CAfile ldap.nestorurquiza.com.cert&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Edit the ldap configuration&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ vi /etc/openldap/ldap.conf&lt;br /&gt;...&lt;br /&gt;#Use the below if you want ldapsearch to work with self signed certificate. Probably a better option security wise is to buy a certificate right ;-) Note that the path is for OSX. For Ubuntu it is /etc/ldap/certs...&lt;br /&gt;#TLS_REQCERT    demand&lt;br /&gt;TLS_REQCERT     never&lt;br /&gt;#Use the below for non self signed certificates&lt;br /&gt;#TLS_CERT    /etc/openldap/certs/ldap.nestorurquiza.com.cert&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Run an ldapsearch command to be usre you get the ldif result&lt;br /&gt;&lt;pre class="brush: bash"&gt;ldapsearch -x -v -H ldaps://ldap.nestorurquiza.com:10636 -D "uid=admin,ou=system" -w 'secretPassword' -b "o=nestorurquiza"&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-641162141505384286?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/641162141505384286/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=641162141505384286' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/641162141505384286'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/641162141505384286'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/08/ldapsearch-ssl-with-apacheds.html' title='ldapsearch SSL with ApacheDS'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-4365496040993586150</id><published>2011-07-18T08:46:00.000-07:00</published><updated>2011-07-18T08:50:15.032-07:00</updated><title type='text'>Redirect after login to requested page with Spring after CSRF protection</title><content type='html'>Bookmarks, typing URLs directly in the address bar and getting to the requested page after login are functionalities you should not break as they impact user experience.&lt;br /&gt;&lt;br /&gt;Once you have protected your Spring website against &lt;a href="http://thinkinginsoftware.blogspot.com/2010/11/xss-and-csrf-protection-in-spring-mvc.html"&gt;CSRF using the Synchronizer Token Pattern&lt;/a&gt; you will find that the redirection to the requested page after login functionality (You request a page, the login form shows up and after that you are taken to the page you originally requested) will be broken.&lt;br /&gt;&lt;br /&gt;Basically the user might access a bookmark or just type a URL without the security token and the redirection will use the provided (and expired) security token or no security token at all. Of course you need to hook into Spring in order to change the default functionality.&lt;br /&gt;&lt;br /&gt;First you use "authentication-success-handler-ref" form-login property in the Spring security context:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;beans:bean id=&amp;quot;customAuthenticationHandler&amp;quot; class=&amp;quot;com.nestorurquiza.web.handler.CustomAuthenticationHandler&amp;quot; /&amp;gt;&lt;br /&gt;    &lt;br /&gt;&amp;lt;form-login login-page=&amp;quot;/login&amp;quot;&lt;br /&gt;            authentication-success-handler-ref=&amp;quot;customAuthenticationHandler&amp;quot;&lt;br /&gt;            authentication-failure-url=&amp;quot;/login?error=authorizationFailed&amp;quot; /&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then implement the custom handler. Code should speak for itself.&lt;br /&gt;&lt;pre class="brush: java"&gt;package com.nestorurquiza.web.handler;&lt;br /&gt;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;&lt;br /&gt;import javax.servlet.ServletException;&lt;br /&gt;import javax.servlet.http.HttpServletRequest;&lt;br /&gt;import javax.servlet.http.HttpServletResponse;&lt;br /&gt;&lt;br /&gt;import org.springframework.security.core.Authentication;&lt;br /&gt;import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;&lt;br /&gt;import org.springframework.security.web.savedrequest.DefaultSavedRequest;&lt;br /&gt;&lt;br /&gt;import com.nestorurquiza.utils.UrlTool;&lt;br /&gt;import com.nestorurquiza.web.WebConstants;&lt;br /&gt;&lt;br /&gt;public class CustomAuthenticationHandler extends SavedRequestAwareAuthenticationSuccessHandler {&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public void onAuthenticationSuccess(HttpServletRequest request,&lt;br /&gt;            HttpServletResponse response, Authentication authentication)&lt;br /&gt;            throws ServletException, IOException {&lt;br /&gt;        // TODO Auto-generated method stub&lt;br /&gt;        String ctoken = (String) request.getSession().getAttribute(WebConstants.CSRF_TOKEN);&lt;br /&gt;        DefaultSavedRequest defaultSavedRequest = (DefaultSavedRequest) request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST_KEY");&lt;br /&gt;        if( defaultSavedRequest != null &amp;&amp; ctoken != null ) {&lt;br /&gt;            String requestUrl = defaultSavedRequest.getRequestURL();&lt;br /&gt;            requestUrl = UrlTool.addParamToURL(requestUrl, WebConstants.CSRF_TOKEN, ctoken, true);&lt;br /&gt;            getRedirectStrategy().sendRedirect(request, response, requestUrl);&lt;br /&gt;        } else {&lt;br /&gt;            super.onAuthenticationSuccess(request, response, authentication);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here is the little useful class that allows to override the ctoken parameter (or any other url parameter)&lt;br /&gt;&lt;pre class="brush: java"&gt;package com.nestorurquiza.utils;&lt;br /&gt;&lt;br /&gt;public class UrlTool {&lt;br /&gt;    public static String addParamToURL(String url, String param, String value,&lt;br /&gt;            boolean replace) {&lt;br /&gt;        if (replace == true)&lt;br /&gt;            url = removeParamFromURL(url, param);&lt;br /&gt;        return url + ((url.indexOf("?") == -1) ? "?" : "&amp;") + param + "="&lt;br /&gt;                + value;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public static String removeParamFromURL(String url, String param) {&lt;br /&gt;        String sep = "&amp;";&lt;br /&gt;        int startIndex = url.indexOf(sep + param + "=");&lt;br /&gt;&lt;br /&gt;        if (startIndex == -1) {&lt;br /&gt;            startIndex = url.indexOf("?" + param + "=");&lt;br /&gt;            sep = "?";&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        if (startIndex != -1) {&lt;br /&gt;            int endIndex = url.indexOf(sep, startIndex + 1);&lt;br /&gt;            return url.substring(0, startIndex)&lt;br /&gt;                    + (endIndex &gt; 0 ? (sep == "?" ? "?"&lt;br /&gt;                            + url.substring(endIndex + 1) : url&lt;br /&gt;                            .substring(endIndex)) : "");&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        return url;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-4365496040993586150?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/4365496040993586150/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=4365496040993586150' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/4365496040993586150'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/4365496040993586150'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/07/redirect-after-login-to-requested-page.html' title='Redirect after login to requested page with Spring after CSRF protection'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-6307528427740170213</id><published>2011-07-06T18:13:00.000-07:00</published><updated>2011-07-23T14:20:20.237-07:00</updated><title type='text'>Caching with Spring ehcache and annotations</title><content type='html'>Caching is trivial up to the moment you start wanting to cache too many entities. At that point you realize caching is actually a cross cutting concern which basically should be done the easy way, read using Inversion Of Control. But caching is also about where you cache: memory, file system? &lt;br /&gt;&lt;br /&gt;If you are using Java then Spring in combination with ehCache can be used to provide caching while abstracting the developer from the details.  To be able to achieve caching with minimum effort I recommend using &lt;a href="http://code.google.com/p/ehcache-spring-annotations/"&gt;ehcache spring annotations&lt;/a&gt; project. Note there is no need to add ehcache dependency as it is added by ehcache-spring-annotations project.&lt;br /&gt;&lt;br /&gt;Here are the dependencies I used for Spring 3.0.4:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;!-- ehcache --&amp;gt;&lt;br /&gt;        &amp;lt;dependency&amp;gt;&lt;br /&gt;            &amp;lt;groupId&amp;gt;com.googlecode.ehcache-spring-annotations&amp;lt;/groupId&amp;gt;&lt;br /&gt;            &amp;lt;artifactId&amp;gt;ehcache-spring-annotations&amp;lt;/artifactId&amp;gt;&lt;br /&gt;            &amp;lt;version&amp;gt;1.1.2&amp;lt;/version&amp;gt;&lt;br /&gt;            &amp;lt;type&amp;gt;jar&amp;lt;/type&amp;gt;&lt;br /&gt;        &amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Create WEB-INF/ehcache.xml with the below content:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;  &amp;lt;ehcache xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot; xsi:noNamespaceSchemaLocation=&amp;quot;http://ehcache.org/ehcache.xsd&amp;quot;&amp;gt;&lt;br /&gt;      &amp;lt;defaultCache eternal=&amp;quot;true&amp;quot; maxElementsInMemory=&amp;quot;100&amp;quot; overflowToDisk=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;      &amp;lt;cache name=&amp;quot;findAllClients&amp;quot; maxElementsInMemory=&amp;quot;10000&amp;quot; eternal=&amp;quot;true&amp;quot; overflowToDisk=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;  &amp;lt;/ehcache&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In application context add the below lines:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;beans ...xmlns:ehcache=&amp;quot;http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring&amp;quot;...&lt;br /&gt;...&lt;br /&gt;xsi:schemaLocation=&amp;quot;&lt;br /&gt;...&lt;br /&gt;http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring/ehcache-spring-1.1.xsd&lt;br /&gt;...&lt;br /&gt;&amp;lt;!-- ehcache --&amp;gt;&lt;br /&gt;    &amp;lt;ehcache:annotation-driven /&amp;gt;&lt;br /&gt; &lt;br /&gt;    &amp;lt;ehcache:config cache-manager=&amp;quot;cacheManager&amp;quot;&amp;gt;&lt;br /&gt;        &amp;lt;ehcache:evict-expired-elements interval=&amp;quot;60&amp;quot; /&amp;gt;&lt;br /&gt;    &amp;lt;/ehcache:config&amp;gt;&lt;br /&gt; &lt;br /&gt;    &amp;lt;bean id=&amp;quot;cacheManager&amp;quot; class=&amp;quot;org.springframework.cache.ehcache.EhCacheManagerFactoryBean&amp;quot;&amp;gt;&lt;br /&gt;        &amp;lt;property name=&amp;quot;configLocation&amp;quot;  value=&amp;quot;/WEB-INF/ehcache.xml&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;/bean&amp;gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Look for a method that returns a collection of entities and annotate it like:&lt;br /&gt;&lt;pre class="brush: java"&gt;@Cacheable(cacheName = &amp;quot;findAllClients&amp;quot;)&lt;br /&gt;    public List&amp;lt;Client&amp;gt; findAll() {&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If there is a method that inserts an entity related to the created cache (with name "findAllClients" in this case) then we annotate it so it cleans the cache after insertion:&lt;br /&gt;&lt;pre class="brush: java"&gt;@TriggersRemove(cacheName = &amp;quot;findAllClients&amp;quot;, when = When.AFTER_METHOD_INVOCATION, removeAll = true)&lt;br /&gt;    public void addClient(Client client) {&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Of course there are cases where we are just consuming a collection let us say from a web service. We would like to force the cache to be cleaned even though we are never inserting an entity. Here is where you need to use a Controller that can be invoked to clean all or specific caches with a URL like:&lt;br /&gt;&lt;pre class="brush: bash"&gt;http://localhost:8080/ehCache/remove?cacheName=findAllClients&amp;amp;cacheName=anotherCacheToRemove&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here is the Controller that will make this possible:&lt;br /&gt;&lt;pre class="brush: java"&gt;import java.util.ArrayList;&lt;br /&gt;import java.util.List;&lt;br /&gt;&lt;br /&gt;import javax.servlet.http.HttpServletRequest;&lt;br /&gt;import javax.servlet.http.HttpServletResponse;&lt;br /&gt;&lt;br /&gt;import net.sf.ehcache.Cache;&lt;br /&gt;import net.sf.ehcache.CacheManager;&lt;br /&gt;&lt;br /&gt;import org.springframework.beans.factory.annotation.Autowired;&lt;br /&gt;import org.springframework.stereotype.Controller;&lt;br /&gt;import org.springframework.web.bind.annotation.RequestMapping;&lt;br /&gt;import org.springframework.web.servlet.ModelAndView;&lt;br /&gt;&lt;br /&gt;@Controller&lt;br /&gt;@RequestMapping(&amp;quot;/ehCache/*&amp;quot;)&lt;br /&gt;public class EhCacheController extends RootController {&lt;br /&gt;    private static final String EHCACHE_SHOW_PATH = &amp;quot;/ehCache/show&amp;quot;;&lt;br /&gt;    &lt;br /&gt;    @Autowired&lt;br /&gt;    private CacheManager cacheManager;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Removes all caches if no parameter is passed&lt;br /&gt;     * Removes all caches for the specified &amp;quot;cacheName&amp;quot; parameters&lt;br /&gt;     * &lt;br /&gt;     * @param request&lt;br /&gt;     * @param response&lt;br /&gt;     * @return&lt;br /&gt;     */&lt;br /&gt;    &lt;br /&gt;    @RequestMapping(&amp;quot;/remove&amp;quot;)&lt;br /&gt;    public ModelAndView home(HttpServletRequest request, HttpServletResponse response) {&lt;br /&gt;        ControllerContext ctx = new ControllerContext(request, response);&lt;br /&gt;        init(ctx);&lt;br /&gt;        &lt;br /&gt;        String[] storedCacheNames = cacheManager.getCacheNames();&lt;br /&gt;        String[] cacheNames = ctx.getParameterValues(&amp;quot;cacheName&amp;quot;);&lt;br /&gt;        &lt;br /&gt;        List&amp;lt;Cache&amp;gt; caches = new ArrayList&amp;lt;Cache&amp;gt; (storedCacheNames.length);&lt;br /&gt;        &lt;br /&gt;        for( String storedCacheName : storedCacheNames ){&lt;br /&gt;            Cache storedCache = cacheManager.getCache(storedCacheName);&lt;br /&gt;            if( cacheNames == null ) {&lt;br /&gt;                storedCache.removeAll();&lt;br /&gt;            } else {&lt;br /&gt;                for( String cacheName : cacheNames ) {&lt;br /&gt;                    if( cacheName.equalsIgnoreCase(storedCacheName) ) {&lt;br /&gt;                        storedCache.removeAll();&lt;br /&gt;                    }&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            caches.add(storedCache);&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        ctx.setRequestAttribute(&amp;quot;caches&amp;quot;, caches);&lt;br /&gt;&lt;br /&gt;        return getModelAndView(ctx, EHCACHE_SHOW_PATH);&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The show.jsp would be something like:&lt;br /&gt;&lt;pre class="brush: java"&gt;&amp;lt;%@ include file=&amp;quot;/WEB-INF/jsp/includes.jsp&amp;quot; %&amp;gt;&lt;br /&gt;&amp;lt;%@ include file=&amp;quot;/WEB-INF/jsp/header.jsp&amp;quot; %&amp;gt;&lt;br /&gt;&amp;lt;div class=&amp;quot;global_error&amp;quot;&amp;gt;&amp;lt;c:out value=&amp;quot;${csrfError}&amp;quot; /&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;&amp;lt;div&amp;gt;&amp;lt;c:out value=&amp;quot;${caches}&amp;quot; /&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;&amp;lt;%@ include file=&amp;quot;/WEB-INF/jsp/footer.jsp&amp;quot; %&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;To add more caching you will need to add the cache entry in the ehcache.xml (thing that I do not like to be honest as I think the annotation should be parsed and if there is no declaration for it in XML then use just the default values or better allow customization as part of the annotation itself) and you need to annotate the method which return value will be cached. To clean that cache you can have either one or a combination of the @TriggersRemove annotation and a URL for cache cleaning like explained before.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-6307528427740170213?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/6307528427740170213/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=6307528427740170213' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6307528427740170213'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/6307528427740170213'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/07/caching-with-spring-ehcache-and.html' title='Caching with Spring ehcache and annotations'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-8285408829800308014</id><published>2011-06-29T18:29:00.000-07:00</published><updated>2011-06-29T18:29:08.628-07:00</updated><title type='text'>The palest ink is better than the best memory</title><content type='html'>To document or not to document, that's the question.&lt;br /&gt;&lt;br /&gt;Documentation saves hours of research and frustration and that translates into money. Yet so many IT managers tolerate systems without documentation while others go crazy after formalizations that end up in unmaintained material. &lt;br /&gt;&lt;br /&gt;I still have to see documentation that is kept current. I am doing my best with my current team trying to enforce that practice. My opinion is documenting what you do  is just about showing respect to your partners.&lt;br /&gt;&lt;br /&gt;I am usually asked for the best anti-spyware or anti-virus for Windows and I always say: Rebuild your system every six months. Just have it documented: Basically keep a backup of your applications and related metadata and clear instructions as to how to recreate your system from scratch. Some years ago when I was using Windows as my main OS I found myself rebuilding my Windows box so often that I developed a procedure (part of it scripted I have to admit) that allowed me in just 2 hours to recreate my whole environment. That included: Apache, PHP, Java, Tomcat, JBoss, Eclipse, MS Office just to name few of them.&lt;br /&gt;&lt;br /&gt;Nowadays I use VirtualBox and recovering Windows is just a matter of going to a safe Snapshot so the only reason for me to rebuild from scratch would be a company change (because of licensing)&lt;br /&gt;&lt;br /&gt;The same happens in the world of OS virtualization. If you find that your P2V is difficult, go for a rebuild. With good documentation this could take anytime from 2 to 8 hours. Way less of what you will spend troubleshooting incompatibilities with your tools, different source and destination hardware etc. And if you do not have documentation you better start it.&lt;br /&gt;&lt;br /&gt;This is exactly what happened to me when I tried to convert my Ubuntu 10 Server box into a VMware Server VM.&lt;br /&gt;&lt;br /&gt;VMware converter will not work for Ubuntu 10. It is simply not supported http://www.vmware.com/support/converter/doc/releasenotes_conv40.html#platf&lt;br /&gt;&lt;br /&gt;I tried anyway to install it (you know we always try just in case :-) but as you see below it is totally incompatible:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: bash"&gt;[several lines like below]&lt;br /&gt;...&lt;br /&gt;The script you are attempting to invoke has been converted to an Upstart&lt;br /&gt;job, but lsb-header is not supported for Upstart jobs.&lt;br /&gt;insserv: warning: script 'atd' missing LSB tags and overrides&lt;br /&gt;insserv: Default-Start undefined, assuming empty start runlevel(s) for script `atd'&lt;br /&gt;insserv: Default-Stop  undefined, assuming empty stop  runlevel(s) for script `atd'&lt;br /&gt;The script you are attempting to invoke has been converted to an Upstart&lt;br /&gt;job, but lsb-header is not supported for Upstart jobs.&lt;br /&gt;insserv: warning: script 'udevmonitor' missing LSB tags and overrides&lt;br /&gt;insserv: Default-Start undefined, assuming empty start runlevel(s) for script `udevmonitor'&lt;br /&gt;insserv: Default-Stop  undefined, assuming empty stop  runlevel(s) for script `udevmonitor'&lt;br /&gt;...&lt;br /&gt;[more lines like above]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;While I could have spent more time trying some other converters I decided to spend 8 hours building the box from scratch. But of course I had good old school documentation for building that box. WIKI is your fiend, don't forget that team member!&lt;br /&gt;&lt;br /&gt;As the Chinese proverb says "The palest ink is better than the best memory"&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-8285408829800308014?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/8285408829800308014/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=8285408829800308014' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8285408829800308014'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8285408829800308014'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/06/palest-ink-is-better-than-best-memory.html' title='The palest ink is better than the best memory'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-7123599890229317074</id><published>2011-06-22T09:48:00.000-07:00</published><updated>2011-09-27T10:15:00.351-07:00</updated><title type='text'>Installing Configuring and Using Monit in Ubuntu</title><content type='html'>This is so straight forward and well documented in the Monit project that I wouldn't post about this if not just to allow those users that are starting with Linux and monitoring is their next concern. &lt;br /&gt;&lt;br /&gt;The steps here should allow the user to install monit and monitor Google home page in no more than 5 minutes. It is always gratifying to get things running while following simple steps. Dealing with environment issues is the most difficult and frustrating part when you are trying to prove your concept. &lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Install monit:&lt;br /&gt;&lt;pre class="brush: xml"&gt;sudo apt-get install monit&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Using sudo edit /etc/default/monit to setup automatic monit startup&lt;br /&gt;&lt;pre class="brush: xml"&gt;startup=1&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Using sudo edit /etc/monit/monitrc as per the content below. Customize your web interface password, an IP allowed to connect via WEB (if you are a GUI guy), email addressee for alerts and mailserver. The rest of the settings should be OK for you. Note that the only check I am providing is checking Google availability just as a proof of concept. The web is full of samples to monitor anything in your servers &lt;br /&gt;&lt;pre class="brush: xml"&gt;##################################################################&lt;br /&gt;&lt;br /&gt;# CUSTOM MONIT SETTINGS&lt;br /&gt;&lt;br /&gt;##################################################################&lt;br /&gt;&lt;br /&gt;set mail-format {&lt;br /&gt;&lt;br /&gt;  subject: [ $SERVICE ] $EVENT - $DATE&lt;br /&gt;&lt;br /&gt;  message: Action: $ACTION, Description: $DESCRIPTION, Service: $SERVICE, Tested From Host: $HOST }&lt;br /&gt;&lt;br /&gt;# number of seconds between monit checks&lt;br /&gt;&lt;br /&gt;set daemon 60&lt;br /&gt;&lt;br /&gt;# Location of the monit log file&lt;br /&gt;&lt;br /&gt;# /var/log/messages is just ok&lt;br /&gt;&lt;br /&gt;# set logfile /var/log/monit.log&lt;br /&gt;&lt;br /&gt;set logfile syslog facility log_daemon&lt;br /&gt;&lt;br /&gt;# Change to the email address you want alerts to be sent to&lt;br /&gt;&lt;br /&gt;set alert nestorurquizaappmon@nestorurquiza.com&lt;br /&gt;&lt;br /&gt;# Port that the monit status page can be viewed&lt;br /&gt;&lt;br /&gt;set httpd port 2812&lt;br /&gt;&lt;br /&gt;# Allow localhost to connect&lt;br /&gt;&lt;br /&gt;allow localhost&lt;br /&gt;&lt;br /&gt;# The next line must be included to allow access from your computer&lt;br /&gt;&lt;br /&gt;# change the number to what your local ip is&lt;br /&gt;&lt;br /&gt;allow 0.0.0.0&lt;br /&gt;&lt;br /&gt;# The next line assigns a username:password combination to login.&lt;br /&gt;&lt;br /&gt;# Please change the password to something random.&lt;br /&gt;&lt;br /&gt;allow admin:myAdminPassword&lt;br /&gt;&lt;br /&gt;# Set the mailserver to send notification email.&lt;br /&gt;&lt;br /&gt;set mailserver mail.nestorurquiza.com&lt;br /&gt;&lt;br /&gt;################################################################# &lt;br /&gt;#REMOTE GOOGLE CHECKS&lt;br /&gt;################################################################&lt;br /&gt;check host google-test with address google.com&lt;br /&gt;  if failed port 80 proto http then alert&lt;br /&gt;group server&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Start, stop, restart, force reload as needed&lt;br /&gt;&lt;pre class="brush: xml"&gt;sudo /etc/init.d/monit start&lt;br /&gt;sudo /etc/init.d/monit stop&lt;br /&gt;sudo /etc/init.d/monit restart&lt;br /&gt;sudo /etc/init.d/monit force-reload&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;See monit status, summary, unmonitor or monitor all as needed&lt;br /&gt;&lt;pre class="brush: xml"&gt;sudo monit status&lt;br /&gt;sudo monit summary&lt;br /&gt;sudo monit monitor all&lt;br /&gt;sudo monit unmonitor all&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Get extra help. Learn how to start or stop processes. Master monit! &lt;br /&gt;&lt;pre class="brush: xml"&gt;sudo monit -h&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;If you prefer a GUI then get it with the below URL (Use the password you set)&lt;br /&gt;&lt;pre class="brush: xml"&gt;http://monit.server.address:2812/&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;h3&gt;Updating monit&lt;/h3&gt;Updating monit is easy. Just follow this steps after downloading latest version to your server home directory:&lt;br /&gt;&lt;pre class="brush: bash"&gt;$ tar xvfz monit-5.3.tar.gz &lt;br /&gt;$ ./configure --sysconfdir=/etc/monit/&lt;br /&gt;$ make&lt;br /&gt;$ sudo make install&lt;br /&gt;$ sudo monit quit&lt;br /&gt;$ sudo monit&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-7123599890229317074?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/7123599890229317074/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=7123599890229317074' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7123599890229317074'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/7123599890229317074'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/06/installing-configuring-and-using-monit.html' title='Installing Configuring and Using Monit in Ubuntu'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-8298515402487201682</id><published>2011-06-21T21:48:00.000-07:00</published><updated>2011-06-22T11:01:39.620-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ACL based security in JPA with jpasecurity spring'/><title type='text'>ACL based security in JPA with jpasecurity: the next step after spring security</title><content type='html'>I had a clear need for ACL in my current project. Just protecting URLs is not enough and protecting method by method smells spaghetti code. Furthermore Spring solutions demand several hooks and still in my opinion they are still not addressing the real issue which is access control was removed from the database layer once ORM got mature but at the same time ORM did not provide a clean ACL solution. &lt;br /&gt;&lt;br /&gt;If you are using JPA then you are in luck because &lt;a href="http://jpasecurity.sourceforge.net/"&gt;jpasecurity&lt;/a&gt; project promises to resolve this limitation.&lt;br /&gt;&lt;br /&gt;Rules are expressed in XML (I tested this so far) or Annotations (I had no luck with them so far). It allows granular access to CREATE, READ, UPDATE, DELETE operations based on roles and the current logged in user. It does that while wrapping all your JPA queries with the rules you specify. Basically you define access on let us say Client entity and every time Client entity appears in a JPA statement it wraps that statement adding the constraint. No need to say how powerful this is.&lt;br /&gt;&lt;br /&gt;I used the &lt;a href="https://jpasecurity.svn.sourceforge.net/svnroot/jpasecurity/trunk/"&gt;trunk&lt;/a&gt; just because I was following up on some &lt;a href="https://sourceforge.net/tracker/?group_id=219346&amp;atid=1045997"&gt;bugs&lt;/a&gt; that got corrected as I &lt;a href="https://sourceforge.net/projects/jpasecurity/forums/forum/790334/"&gt;posted&lt;/a&gt; my questions. Probably you will get lucky and a stable 0.4.x release will be available by the time you decide to try it.&lt;br /&gt;&lt;br /&gt;The examples here are based on a spring project using JPA + Hibernate + JTA + LDAP&lt;br /&gt;&lt;br /&gt;Here are the main steps I followed:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Edit your pom.xml&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;properties&amp;gt;&lt;br /&gt;...&lt;br /&gt;&amp;lt;jpasecurity.version&amp;gt;0.4.0-SNAPSHOT&amp;lt;/jpasecurity.version&amp;gt;&lt;br /&gt;...&lt;br /&gt;&amp;lt;/properties&amp;gt;&lt;br /&gt;...&lt;br /&gt;&amp;lt;dependency&amp;gt;&lt;br /&gt;          &amp;lt;groupId&amp;gt;net.sf.jpasecurity&amp;lt;/groupId&amp;gt;&lt;br /&gt;          &amp;lt;artifactId&amp;gt;jpasecurity-spring&amp;lt;/artifactId&amp;gt;&lt;br /&gt;          &amp;lt;version&amp;gt;${jpasecurity.version}&amp;lt;/version&amp;gt;&lt;br /&gt;          &amp;lt;exclusions&amp;gt;&lt;br /&gt;           &amp;lt;exclusion&amp;gt;&lt;br /&gt;            &amp;lt;artifactId&amp;gt;geronimo-ejb_3.1_spec&amp;lt;/artifactId&amp;gt;&lt;br /&gt;            &amp;lt;groupId&amp;gt;org.apache.geronimo.specs&amp;lt;/groupId&amp;gt;&lt;br /&gt;           &amp;lt;/exclusion&amp;gt;&lt;br /&gt;           &amp;lt;exclusion&amp;gt;&lt;br /&gt;            &amp;lt;artifactId&amp;gt;geronimo-jpa_2.0_spec&amp;lt;/artifactId&amp;gt;&lt;br /&gt;            &amp;lt;groupId&amp;gt;org.apache.geronimo.specs&amp;lt;/groupId&amp;gt;&lt;br /&gt;           &amp;lt;/exclusion&amp;gt;&lt;br /&gt;          &amp;lt;/exclusions&amp;gt;&lt;br /&gt;&amp;lt;/dependency&amp;gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;If you need to debug some jpasecurity issues it is always useful to increase log level for the package&lt;br /&gt;&lt;pre class="brush: xml"&gt;log4j.logger.net.sf.jpasecurity=DEBUG&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;I tried to use rules from annotations but the feature is still not well supported. I ended up putting my rules in XML. So here some samples. One of them as you can see quite complex:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;security xmlns=&amp;quot;http://jpasecurity.sf.net/xml/ns/security&amp;quot;&lt;br /&gt;          xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;&lt;br /&gt;          xsi:schemaLocation=&amp;quot;http://jpasecurity.sf.net/xml/ns/security&lt;br /&gt;                              http://jpasecurity.sf.net/xml/ns/security/security_1_0.xsd&amp;quot;&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;persistence-unit name=&amp;quot;nestorurquizaPersistenceUnit&amp;quot;&amp;gt;&lt;br /&gt;&amp;lt;access-rule&amp;gt;GRANT CREATE READ        ACCESS TO Client c&amp;lt;/access-rule&amp;gt;&lt;br /&gt;&amp;lt;access-rule&amp;gt;GRANT                    ACCESS TO Client c WHERE 'ROLE_ADMIN' IN (CURRENT_ROLES)&amp;lt;/access-rule&amp;gt;&lt;br /&gt;&amp;lt;access-rule&amp;gt;GRANT ACCESS TO Client c WHERE c.id IN (SELECT cs.client.id FROM ClientStaffing cs, ClientStatus cst, Employee e WHERE e.email=CURRENT_PRINCIPAL AND cs.employee=e AND cs.client=c AND cs.endDate IS NULL AND ( cst.name &amp;amp;lt;&amp;amp;gt; 'Closed' OR cst.name IS NULL) )&amp;lt;/access-rule&amp;gt;&lt;br /&gt;  &amp;lt;/persistence-unit&amp;gt;&lt;br /&gt;&amp;lt;/security&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;In persistence.xml for your container:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;!-- Comment the below if using jpasecurity --&amp;gt;&lt;br /&gt;&amp;lt;!--    &amp;lt;provider&amp;gt;net.sf.jpasecurity.persistence.SecurePersistenceProvider&amp;lt;/provider&amp;gt;--&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;!-- Uncomment the below if not using jpasecurity --&amp;gt;&lt;br /&gt;    &amp;lt;provider&amp;gt;org.hibernate.ejb.HibernatePersistence&amp;lt;/provider&amp;gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;    &amp;lt;properties&amp;gt;&lt;br /&gt;            &amp;lt;!-- Comment the below if not using jpasecurity --&amp;gt;&lt;br /&gt;&amp;lt;!--            &amp;lt;property name=&amp;quot;net.sf.jpasecurity.persistence.provider&amp;quot; value=&amp;quot;org.hibernate.ejb.HibernatePersistence&amp;quot; /&amp;gt;--&amp;gt;&lt;br /&gt;&amp;lt;!--            &amp;lt;property name=&amp;quot;net.sf.jpasecurity.security.authentication.provider&amp;quot; value=&amp;quot;com.nestorurquiza.security.JpasecuritySpringAuthenticationProvider&amp;quot;/&amp;gt;--&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Note the need for a custom SpringAuthenticationProvider. This is just a hook to guarantee that CURRENT_PRINCIPAL maps to the user email which is the username in LDAP.&lt;br /&gt;&lt;pre class="brush: java"&gt;package com.nestorurquiza.security;&lt;br /&gt;&lt;br /&gt;import net.sf.jpasecurity.spring.authentication.SpringAuthenticationProvider;&lt;br /&gt;&lt;br /&gt;import org.springframework.security.authentication.AnonymousAuthenticationToken;&lt;br /&gt;import org.springframework.security.core.Authentication;&lt;br /&gt;import org.springframework.security.core.context.SecurityContextHolder;&lt;br /&gt;import org.springframework.security.core.userdetails.UserDetails;&lt;br /&gt;&lt;br /&gt;public class JpasecuritySpringAuthenticationProvider extends SpringAuthenticationProvider{&lt;br /&gt;    @Override&lt;br /&gt;    public Object getPrincipal() {&lt;br /&gt;        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();&lt;br /&gt;        if (authentication == null || (authentication instanceof AnonymousAuthenticationToken)) {&lt;br /&gt;            return null;&lt;br /&gt;        }&lt;br /&gt;        UserDetails userDetails = (UserDetails) authentication.getPrincipal();&lt;br /&gt;        return userDetails.getUsername();&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;I had to create a Custom Converter so Spring Binding works for forms. There is a &lt;a href="https://jira.springsource.org/browse/SPR-8441"&gt;bug&lt;/a&gt; I reported to Spring on this regard but anyway here is the workaround:&lt;br /&gt;&lt;pre class="brush: java"&gt;package com.nestorurquiza.converter;&lt;br /&gt;&lt;br /&gt;import net.sf.jpasecurity.SecureObject;&lt;br /&gt;&lt;br /&gt;import org.springframework.core.convert.converter.Converter;&lt;br /&gt;&lt;br /&gt;public class SecureObjectToStringConverter implements Converter&lt;SecureObject, String&gt; {&lt;br /&gt;&lt;br /&gt;    @Override&lt;br /&gt;    public String convert(SecureObject source) {&lt;br /&gt;        return (source != null ? source.toString() : null);&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Then we need a custom ConversionServiceFactoryBean&lt;br /&gt;&lt;pre class="brush: java"&gt;package com.nestorurquiza.converter;&lt;br /&gt;&lt;br /&gt;import org.springframework.beans.factory.annotation.Autowired;&lt;br /&gt;import org.springframework.core.convert.support.GenericConversionService;&lt;br /&gt;import org.springframework.format.FormatterRegistry;&lt;br /&gt;import org.springframework.format.support.FormattingConversionServiceFactoryBean;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * Not being used.&lt;br /&gt; * @author jia&lt;br /&gt; */&lt;br /&gt;public class CustomConversionServiceFactoryBean extends FormattingConversionServiceFactoryBean {&lt;br /&gt;&lt;br /&gt;    @Autowired&lt;br /&gt;    private GenericConversionService genericConversionService;&lt;br /&gt;    &lt;br /&gt;    @Override&lt;br /&gt;    protected void installFormatters(FormatterRegistry registry) {&lt;br /&gt;        super.installFormatters(registry);&lt;br /&gt;        //registry.addConverter(new BooleanToStringConverter());&lt;br /&gt;        &lt;br /&gt;        //Using org.springframework.format.support.FormattingConversionServiceFactoryBean from the xml declaration will not work&lt;br /&gt;        //registry.addConverter(new SecureObjectToStringConverter());&lt;br /&gt;        genericConversionService.addConverter(new SecureObjectToStringConverter());&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Then configure spring servlet with the necessary bean&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;!-- Registering custom ConversionService --&amp;gt; &lt;br /&gt;    &amp;lt;bean id=&amp;quot;conversionService&amp;quot; class=&amp;quot;com.nestorurquiza.converter.CustomConversionServiceFactoryBean&amp;quot; /&amp;gt;&lt;br /&gt;    &amp;lt;mvc:annotation-driven conversion-service=&amp;quot;conversionService&amp;quot; /&amp;gt;&lt;br /&gt;    &amp;lt;!-- The below will not work at least for Binding --&amp;gt; &lt;br /&gt;    &amp;lt;!-- &amp;lt;bean id=&amp;quot;conversionService&amp;quot; class=&amp;quot;org.springframework.format.support.FormattingConversionServiceFactoryBean&amp;quot;&amp;gt; &lt;br /&gt;        &amp;lt;property name=&amp;quot;converters&amp;quot;&amp;gt; &lt;br /&gt;            &amp;lt;list&amp;gt; &lt;br /&gt;                &amp;lt;bean class=&amp;quot;com.nestorurquiza.converter.SecureObjectToStringConverter&amp;quot;/&amp;gt; &lt;br /&gt;            &amp;lt;/list&amp;gt; &lt;br /&gt;        &amp;lt;/property&amp;gt; &lt;br /&gt;    &amp;lt;/bean&amp;gt; &lt;br /&gt;    --&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Include the jpasecurity taglib for access rules in JSP&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;%-- Comment the below if not using jpasecurity --%&amp;gt;&lt;br /&gt;&amp;lt;%--@ taglib prefix=&amp;quot;access&amp;quot; uri=&amp;quot;http://jpasecurity.sf.net/access&amp;quot; --%&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Use rules as needed in JSP&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;access:updating entity=&amp;quot;client&amp;quot;&amp;gt;&lt;br /&gt;       &amp;lt;security:authorize url=&amp;quot;/client/${client.id}/edit&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;&amp;lt;spring:url value=&amp;quot;/client/${client.id}/edit?ctoken=${sessionScope.ctoken}&amp;quot;/&amp;gt;&amp;quot;&amp;gt;&amp;lt;spring:message code=&amp;quot;edit&amp;quot; /&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/security:authorize&amp;gt;&lt;br /&gt;    &amp;lt;/access:updating&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Typical response when security is violated:&lt;br /&gt;&lt;pre class="brush: xml"&gt;java.lang.SecurityException: The current user is not permitted to update the specified object of type com.nestorurquiza.model.Client&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Some Jpasecurity limitations so far:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Does not accept CONCAT function so we must pass the percentages for the LIKE clauses within the parameters (which is best practice anyway)&lt;/li&gt;&lt;li&gt;count(*) is not supported. Use the entity alias instead like "SELECT count(c) FROM Client c"&lt;/li&gt;&lt;li&gt;LOWER and CONCAT functions are not supported but probably you can live without them and the less functions the best performance.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;Existing JUnit tests will fail if you enable jpasecurity and do not use a user with valid roles in the current context. The way you correct them is presented below:&lt;br /&gt;&lt;pre class="brush: xml"&gt;...&lt;br /&gt;String adminEmail = "Admin.User@nestorurquiza.com";&lt;br /&gt;injectCurrentUser(adminEmail, Roles.ROLE_ADMIN);&lt;br /&gt;…&lt;br /&gt;private void injectCurrentUser(String email, String role) {&lt;br /&gt;        TestingAuthenticationToken token = new TestingAuthenticationToken(&lt;br /&gt;                email, email, new GrantedAuthority[]{&lt;br /&gt;                    new GrantedAuthorityImpl(role)});       &lt;br /&gt;        SecurityContextHolder.getContext().setAuthentication(token);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-8298515402487201682?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/8298515402487201682/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=8298515402487201682' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8298515402487201682'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8298515402487201682'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/06/acl-based-security-in-jpa-with.html' title='ACL based security in JPA with jpasecurity: the next step after spring security'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-8515361897297500421</id><published>2011-06-19T08:42:00.000-07:00</published><updated>2011-06-19T08:42:42.634-07:00</updated><title type='text'>Raw data and format in Jasper Reports</title><content type='html'>Raw data let us say a Float or a BigDecimal can be formatted in Excel following some patterns in a way that it is clearer for printing. However the raw data remains intact in a way that formulas can be applied later on those raw numbers. In accounting and financing this is a simple yet important concept to understand: separation between data and format.&lt;br /&gt;&lt;br /&gt;JasperReports JRXML format uses its own formatting which is different from Excel and so when you want to export to Excel directly from iReport for example two things are going to happen:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Probably you will have no formatting rule available and you will end up with an approximation of the real formatting you are after&lt;/li&gt;&lt;li&gt;Your raw data could disappear and you get in the Excel output just exacty what the formatting in JRXML is specifying&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;When my attention was brought to this problem by a member of my current team I saw a &lt;a href="http://stackoverflow.com/questions/2925261/show-nothing-when-0-jasperreports-format"&gt;post&lt;/a&gt; with some solutions I did not like. &lt;br /&gt;&lt;br /&gt;The reason why I did not like the solutions (besides the fact they are not addressing the two points I described above) is that they basically violate separation of concerns. &lt;br /&gt;&lt;br /&gt;The &lt;a href="http://jasperforge.org/plugins/espforum/view.php?group_id=102&amp;forumid=103&amp;topicid=32273"&gt;solution&lt;/a&gt; for this problem is indeed addressing the problem from the right angle.&lt;br /&gt;&lt;br /&gt;It allows to format anything for non excel output from iReport (jrxml). It then relies on mapping to translate the custom jrxml format to the proprietary to Excel syntax when that format is needed.&lt;br /&gt;&lt;br /&gt;I only came to that post after some research and the more I research the more convince I am people get easily lost nowadays when searching for solutions on the web. This is of course a result of a non semantic web markup and consequently a poor search engine filtering. Still there is something that can be done if you stick to concepts and you refine your terms following them.&lt;br /&gt;&lt;br /&gt;Here is my personal story when I researched this issue. This could probably help others. Remember do never get the first response you get from Google as the final response to your problem:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;&lt;br /&gt;I found a similar &lt;a href="http://jasperforge.org/plugins/espforum/view.php?group_id=102&amp;forumid=103&amp;topicid=6095#88731"&gt;issue&lt;/a&gt; within the JasperReports forums, not from Google. Then I asked if a solution was found&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;br /&gt;I &lt;a href="http://jasperforge.org/plugins/espforum/view.php?group_id=102&amp;forumid=103&amp;topicid=77583"&gt;realized&lt;/a&gt; using JRXlsAbstractExporter was apparently the way to go.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;br /&gt;And so I finally &lt;a href="http://jasperforge.org/plugins/espforum/view.php?group_id=102&amp;forumid=103&amp;topicid=32273"&gt;got it&lt;/a&gt;.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;As this is a solution addressable only from the JR Excel API Exporter, if you are really stuck with POI Exporter then what to do? I could not find anything but as I knew from simple concept this was a task to be done by the exporter and the exporter uses the POI API then I searched for the necessary formatting and I got into this &lt;a href="http://osdir.com/ml/jakarta.poi.user/2002-11/msg00143.html"&gt;post&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;I feel like posting how you research is as useful as posting a solution. A good researcher cannot give up. A path must be follow till the end which is a Proof Of Concept (POC). There is no other way. &lt;br /&gt;&lt;br /&gt;If you are the architect you must do that job. If you are the CTO be sure you do it yourself or have an architect to delegate to. If you are a developer and you do this you are on your way to become a good architect and a good hands on CTO.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37642571-8515361897297500421?l=thinkinginsoftware.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thinkinginsoftware.blogspot.com/feeds/8515361897297500421/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37642571&amp;postID=8515361897297500421' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8515361897297500421'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37642571/posts/default/8515361897297500421'/><link rel='alternate' type='text/html' href='http://thinkinginsoftware.blogspot.com/2011/06/raw-data-and-format-in-jasper-reports.html' title='Raw data and format in Jasper Reports'/><author><name>Nestor Urquiza</name><uri>http://www.blogger.com/profile/12351754666722274569</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37642571.post-1296368270576535724</id><published>2011-06-14T09:44:00.000-07:00</published><updated>2011-06-14T09:44:15.556-07:00</updated><title type='text'>Test Driven Bug Fixing for HTML Jasper Reports</title><content type='html'>Test Driven Bug Fixing (TDBF) is a must do as &lt;a href="http://thinkinginsoftware.blogspot.com/2010/11/acceptance-test-driven-development-atdd.html"&gt;I have discussed before&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The structure of the site (HTML) can be effectively tested as we can assert the elements position (layout). The content can be asserted as we inspect the DOM. And that is just the beginning: Behaviour (javascript) and look and feel (CSS) can be tested as well.&lt;br /&gt;&lt;br /&gt;Your reports might have a component of behavior and look and feel but the most important part to test is structure and content. Jasper Reports has several exporters and one of them is HTML. The HTML is absolute positioned and that creates the perfect scenario for testing.&lt;br /&gt;&lt;br /&gt;If a bug has to be addressed then be sure you provide an automated test to ensure it will never come back.&lt;br /&gt;&lt;br /&gt;All you need to do is:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Business Analyst provides a test scenario where assertion on a cell content are made: The number for the current balance for this period for this account must be $1,000.00.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Developer runs the Jasper Report from Firefox (Firebug plugin installed and if you want to get serious about it XPath Checker plugin installed as well) using HTML output. &lt;br /&gt;&lt;/li&gt;&lt;li&gt;Developer runs the Jasper Report from Firefox using HTML output. He right click on the element and select "Inspect Element":&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;span style=&amp;quot;position:absolute;left:388px;top:226px;width:72px;height:10px;text-align: right;&amp;quot;&amp;gt;&lt;br /&gt;&amp;lt;span style=&amp;quot;font-family: 'DejaV
