This blog is mainly about Java...

Showing posts with label jodconverter 3. Show all posts
Showing posts with label jodconverter 3. Show all posts

Monday, March 21, 2011

API helper / wrapper for ProcessFinder in Hyperic Sigar PTQL

In my previous blog post I wrote that I was going to implement Hyperic Sigar in JODConverter. Well the alpha is done, and you can go try it out here.

When rewriting the code to use the ProcessFinder in Sigar, I found it very cumbersome to work with PTQL, which is the Process Table Query Language for Sigar.

Sigar describes PTQL as:
Hyperic SIGAR provides a mechanism to identify processes called Process Table Query Language. All operating systems assign a unique id (PID) to each running process. However, the PID is a random number that may also change at any point in time when a process is restarted. PTQL uses process attributes that will persist over time to identify a process.

PTQL is string based, and the processFinder takes the ptql as string as parameter.

processFinder.find("State.Name.eq=java"); //Returns all the process id's named java

I have jboss already running and the ps command locally gives me the pid: 28258
> ps -ef | grep jboss
> shervin  28258 27055 20 13:53 pts/1    00:02:36 /usr/lib/jvm/java-6-sun-1.6.0.24/bin/java -agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:54988 -javaagent:/home/shervin/opt/jrebel/jrebel.jar -noverify -Dprogram.name=JBossTools: JBoss EAP 5.0 Runtime -Xms512m -Xmx768m -XX:MaxPermSize=512m -Djava.net.preferIPv4Stack=true -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 -Djava.endorsed.dirs=/home/shervin/opt/jboss/lib/endorsed -Djava.library.path=/usr/lib/jvm/java-6-sun/jre/lib/i386/server:/usr/lib/jvm/jdk1.5.0_22/jre/lib/i386:/usr/lib/jvm/jdk1.5.0_22/jre/../lib/i386:/usr/lib/jvm/jdk1.5.0_22/jre/lib/i386/client:/usr/lib/jvm/jdk1.5.0_22/jre/lib/i386:/usr/lib/xulrunner-addons:/usr/lib/xulrunner-addons:/usr/lib/ure/lib/:/home/shervin/lib/lib-src/hyperic-sigar-1.6.4-src/bindings/java/sigar-bin/lib -Dfile.encoding=UTF-8 -classpath /home/shervin/opt/jboss/bin/run.jar org.jboss.Main --configuration=default -b localhost
When running the sigar shell (java -jar sigar.jar) and writing
sigar> pargs 28258
It gives the output (the numbers are arguments)
pid=28258
exe=/usr/lib/jvm/java-6-sun-1.6.0.24/jre/bin/java
cwd=/home/shervin/opt/jboss-eap-5.1/jboss-as/bin
   0=>/usr/lib/jvm/java-6-sun-1.6.0.24/bin/java<=
   1=>-agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:54988<=
   2=>-javaagent:/home/shervin/opt/jrebel/jrebel.jar<=
   3=>-noverify<=
   4=>-Dprogram.name=JBossTools: JBoss EAP 5.0 Runtime<=
   5=>-Xms512m<=
   6=>-Xmx768m<=
   7=>-XX:MaxPermSize=512m<=
   8=>-Djava.net.preferIPv4Stack=true<=
   9=>-Dsun.rmi.dgc.client.gcInterval=3600000<=
   10=>-Dsun.rmi.dgc.server.gcInterval=3600000<=
   11=>-Djava.endorsed.dirs=/home/shervin/opt/jboss/lib/endorsed<=
   12=>-Djava.library.path=/usr/lib/jvm/java-6-sun/jre/lib/i386/server:/usr/lib/jvm/jdk1.5.0_22/jre/lib/i386:/usr/lib/jvm/jdk1.5.0_22/jre/../lib/i386:/usr/lib/jvm/jdk1.5.0_22/jre/lib/i386/client:/usr/lib/jvm/jdk1.5.0_22/jre/lib/i386:/usr/lib/xulrunner-addons:/usr/lib/xulrunner-addons:/usr/lib/ure/lib/:/home/shervin/lib/lib-src/hyperic-sigar-1.6.4-src/bindings/java/sigar-bin/lib<=
   13=>-Dfile.encoding=UTF-8<=
   14=>-classpath<=
   15=>/home/shervin/opt/jboss/bin/run.jar<=
   16=>org.jboss.Main<=
   17=>--configuration=default<=
   18=>-b<=
   19=>localhost<=


Now imagine you want to create a more advanced query which takes the one or more of the 19 arguments the java process takes.

The query would look something like this:

processFinder.find("State.Name.ct=java,Args.5.eq=-Xms512m,Args.6.eq=-Xmx768m,Args.7.re=.*MaxPermSize=512m,Args.17.ct=configuration=default,Args.19.eq=localhost");
or you are building the query based on variables which you must retrieve somewhere else
public void testArgs() throws Exception {
        String state_name= "State.Name.ct=java";
        String arg5 = ",Args.5.eq=-Xms512m";
        String arg6 = ",Args.6.eq=-Xmx768m";
        String arg7 = ",Args.7.re=.*MaxPermSize=512m";
        String arg17 = ",Args.17.ct=configuration=default";
        String arg19 = ",Args.19.eq=localhost"; 
        ProcessFinder processFinder = new ProcessFinder(new Sigar());
        processFinder.find(state_name + arg5 + arg6+ arg7 + arg17 + arg19);
    }
It doesn't take a genius to see that it is easy to make mistakes. Hence the more type-safe way of creating PTQL queries.

SimplePTQL


I took the liberty to create a helper/wrapper class for ProcessFinder which tries to eliminate some of the errors you can do.
For instance, instead of the code above, you would write the following:

SimplePTQL ptql = new SimplePTQL.Builder(SimplePTQL.STATE_NAME(), SimplePTQL.CT(), "java")
                    .addArgs(5, SimplePTQL.EQ(), "-Xms512m", Strategy.NOT_ESCAPE)
                    .addArgs(6, SimplePTQL.EQ(), "-Xmx768m", Strategy.NOT_ESCAPE)
                    .addArgs(7, SimplePTQL.RE(), ".*MaxPermSize=512m", Strategy.ESCAPE)
                    .addArgs(17, SimplePTQL.CT(), "configuration=default", Strategy.NOT_ESCAPE)
                    .addArgs(19, SimplePTQL.EQ(), "localhost", Strategy.NOT_ESCAPE)
                    .createQuery();
        
ProcessFinder processFinder = new ProcessFinder(new Sigar());
processFinder.find(ptql.getQuery());
The constructor of the Builder takes the first part of the query as parameters and you can optionally add arguments. You can escape the input which regular expressions should do. It will basically replace all commas (,) with period (.), this because the find method doesn't like commas in the searchValue inside a regular expression.
/**
     * This method will escape Comma (,) to Period (.)
     * Because the PTQL cannot escape those correctly, so we will use '.' in regular expression.
     * We also have to remove \Q and \E because they are not correctly interpreted as literal characters
     * 
     * @param s - The string you want to espace
     * 
     * NB: Note that you should only espace the value of the PTQL, not the query it self
     * ie: State.Name.ct=pipe,name=office1 should be converted to State.Name.ct=pipe.name.office1
     * @return - The escaped string
     */
    public static String escapePTQLForRegex(String s) {
        return s.replaceAll(",", ".").replaceAll("\\\\Q|\\\\E", "");
    }

The latest version of SimplePTQL can be found here

And the source (subject to change, see the former link for latest version) is:
At the time of writing, the SimplePTQL class does not support Env and Modules

//
// JODConverter - Java OpenDocument Converter
// Copyright 2011 Art of Solving Ltd
// Copyright 2004-2011 Mirko Nasato
//
// JODConverter is free software: you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// JODConverter is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General
// Public License along with JODConverter.  If not, see
// <http://www.gnu.org/licenses/>.
//
package org.artofsolving.jodconverter.sigar;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.artofsolving.jodconverter.util.PlatformUtils;

import com.google.common.base.Preconditions;

/**
 * A simplified PTQL so to simplify the query building and make it less error prone and typesafe. 
 * 
 * See more information http://support.hyperic.com/display/SIGAR/PTQL
 * 
 * @author Shervin Asgari - <a href="mailto:shervin@asgari.no">shervin@asgari.no</a>
 */
public class SimplePTQL {
    private final Operator operator;
    private final Attribute attribute;
    private final String searchValue;
    private final String args;
    private final Strategy strategy;
    
    public enum Strategy {
        ESCAPE, NOT_ESCAPE
    }

    private SimplePTQL(Attribute attribute, Operator operator, String searchValue, String args, Strategy strategy) {
        this.attribute = attribute;
        this.operator = operator;
        this.searchValue = searchValue;
        this.args = args;
        this.strategy = strategy;
    }
    
    public static class Builder {
        private final Operator operator;
        private final Attribute attribute;
        private final String searchValue;
        
        private Strategy strategy = Strategy.NOT_ESCAPE; 
        private StringBuilder args = new StringBuilder(""); //Default blank
        
        public Builder(Attribute attribute, Operator operator, String searchValue) {
            this.attribute = attribute;
            this.operator = operator;
            this.searchValue = searchValue;
        }

        public Builder addArgs(int argument, Operator operator, String searchValue, Strategy strategy) {
            Preconditions.checkNotNull(searchValue);
            
            if(strategy == Strategy.ESCAPE) {
                args.append(",Args." + String.valueOf(argument) + "." + operator.toString() + PlatformUtils.escapePTQLForRegex(searchValue));
            } else {
                Pattern pattern = Pattern.compile(",|=");
                Matcher matcher = pattern.matcher(searchValue);
                Preconditions.checkArgument(!matcher.find(), "searchValue cannot contain comma or equals sign. Either set Strategy.ESCAPE or remove it from the search value");
                args.append(",Args." + String.valueOf(argument) + "." + operator.toString() + searchValue);
            }
            
            return this;
        }
        
        public Builder setStrategy(Strategy strategy) {
            this.strategy = strategy;
            return this;
        }

        public SimplePTQL createQuery() {
            return new SimplePTQL(attribute, operator, searchValue, args.toString(), strategy);
        }
    }
    
    /**
     * Returns the entiry PTQL query.
     * ie: 
     * <ul>
     * <li>State.Name.eq=java</li>
     * <li>Pid.Pid.eq=4245</li>
     * <li>State.Name.re=^(https?d.*|[Aa]pache2?)$</li>
     * </ul>
     * @return
     */
    public String getQuery() {
        return attribute.toString() + operator.toString() + (strategy == Strategy.ESCAPE ? PlatformUtils.escapePTQLForRegex(searchValue): searchValue) + args;
    }    

    private interface Operator {
        String toString();
    }

    private interface Attribute {
        String toString();
    }

    /**
     * Equal to value
     */
    public static Operator EQ() {
        return new Operator() {
            @Override
            public String toString() {
                return "eq=";
            }
        };
    }

    /**
     * Not Equal to value
     */
    public static Operator NE() {
        return new Operator() {
            @Override
            public String toString() {
                return "ne";
            }
        };
    }

    /**
     * Ends with value
     */
    public static Operator EW() {
        return new Operator() {
            @Override
            public String toString() {
                return "ew=";
            }
        };
    }

    /**
     * Starts with value
     */
    public static Operator SW() {
        return new Operator() {
            @Override
            public String toString() {
                return "sw=";
            }
        };
    }

    /**
     * Contains value (substring)
     */
    public static Operator CT() {
        return new Operator() {
            @Override
            public String toString() {
                return "ct=";
            }
        };
    }

    /**
     * Regular expression value matches
     */
    public static Operator RE() {
        return new Operator() {
            @Override
            public String toString() {
                return "re=";
            }
        };
    }

    /**
     * <i>Only for numeric value<i> Greater than value
     */
    public static Operator GT() {
        return new Operator() {
            @Override
            public String toString() {
                return "gt=";
            }
        };
    }

    /**
     * <i>Only for numeric value<i> Greater than or equal value
     */
    public static Operator GE() {
        return new Operator() {
            @Override
            public String toString() {
                return "ge=";
            }
        };
    }

    /**
     * <i>Only for numeric value<i> Less than value
     */
    public static Operator LT() {
        return new Operator() {
            @Override
            public String toString() {
                return "lt=";
            }
        };
    }

    /**
     * <i>Only for numeric value<i> Less than value or equal value
     */
    public static Operator LE() {
        return new Operator() {
            @Override
            public String toString() {
                return "le=";
            }
        };
    }

    /**
     * The Process ID
     */
    public static Attribute PID_PID() {
        return new Attribute() {
            @Override
            public String toString() {
                return "Pid.Pid.";
            }
        };
    }

    /**
     * File containing the process ID
     */
    public static Attribute PID_PIDFILE() {
        return new Attribute() {
            @Override
            public String toString() {
                return "Pid.PidFile.";
            }
        };
    }

    /**
     * Windows Service name used to pid from the service manager
     */
    public static Attribute PID_SERVICE() {
        return new Attribute() {
            @Override
            public String toString() {
                return "Pid.Service.";
            }
        };
    }

    /**
     * Base name of the process executable
     */
    public static Attribute STATE_NAME() {
        return new Attribute() {
            @Override
            public String toString() {
                return "State.Name.";
            }
        };
    }

    /**
     * User Name of the process owner
     */
    public static Attribute CREDNAME_USER() {
        return new Attribute() {
            @Override
            public String toString() {
                return "CredName.User.";
            }
        };
    }

    /**
     * Group Name of the process owner
     */
    public static Attribute CREDNAME_GROUP() {
        return new Attribute() {
            @Override
            public String toString() {
                return "CredName.Group.";
            }
        };
    }

    /**
     * User ID of the process owner
     */
    public static Attribute CRED_UID() {
        return new Attribute() {
            @Override
            public String toString() {
                return "Cred.Uid.";
            }
        };
    }

    /**
     * Group ID of the process owner
     */
    public static Attribute CRED_GID() {
        return new Attribute() {
            @Override
            public String toString() {
                return "Cred.Gid.";
            }
        };
    }

    /**
     * Effective User ID of the process owner
     */
    public static Attribute CRED_EUID() {
        return new Attribute() {
            @Override
            public String toString() {
                return "Cred.Euid.";
            }
        };
    }

    /**
     * Effective Group ID of the process owner
     */
    public static Attribute CRED_EGID() {
        return new Attribute() {
            @Override
            public String toString() {
                return "Cred.Egid.";
            }
        };
    }

    /**
     * Full path name of the process executable
     */
    public static Attribute EXE_NAME() {
        return new Attribute() {
            @Override
            public String toString() {
                return "Exe.Name.";
            }
        };
    }

    /**
     * Current Working Directory of the process
     */
    public static Attribute EXE_CWD() {
        return new Attribute() {
            @Override
            public String toString() {
                return "Exe.Cwd.";
            }
        };
    }
}

The unit test for this class can also be found here

Or the source (at the time of writing)
//
// JODConverter - Java OpenDocument Converter
// Copyright 2011 Art of Solving Ltd
// Copyright 2004-2011 Mirko Nasato
//
// JODConverter is free software: you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// JODConverter is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General
// Public License along with JODConverter.  If not, see
// <http://www.gnu.org/licenses/>.
//
package org.artofsolving.jodconverter.sigar;

import java.util.List;

import org.artofsolving.jodconverter.process.ProcessManager;
import org.artofsolving.jodconverter.process.SigarProcessManager;
import org.artofsolving.jodconverter.sigar.SimplePTQL.Strategy;
import org.artofsolving.jodconverter.util.PlatformUtils;
import org.testng.Assert;
import org.testng.SkipException;
import org.testng.annotations.Test;

@Test
public class SimplePTQLTest {
    
    public void simpleQuery() throws Exception {
        ProcessManager spm = new SigarProcessManager();
        SimplePTQL ptql = new SimplePTQL.Builder(SimplePTQL.STATE_NAME(), SimplePTQL.EQ(), "java").createQuery();
        Assert.assertEquals(ptql.getQuery(), "State.Name.eq=java");
        
        List<Long> find = spm.find(ptql);
        Assert.assertTrue(find.size() > 0);
        
        if(find.size() > 1) {
            try {
                spm.findSingle(ptql);
                Assert.fail("Should not reach here, should get exception");
            } catch(NonUniqueResultException nre) {
                //More than one results where found
            }    
        }
        
        ptql = new SimplePTQL.Builder(SimplePTQL.PID_PID(), SimplePTQL.EQ(), String.valueOf(find.get(0))).createQuery();
        Long findSingle = spm.findSingle(ptql);
        Assert.assertEquals(findSingle, find.get(0));
        
        ptql = new SimplePTQL.Builder(SimplePTQL.STATE_NAME(), SimplePTQL.EQ(), "Hopefully There is no Process called this").createQuery();
        List<Long> find2 = spm.find(ptql);
        Assert.assertTrue(find2.size() == 0);
        
        ptql = new SimplePTQL.Builder(SimplePTQL.PID_PID(), SimplePTQL.GT(), "1").createQuery();
        List<Long> find3 = spm.find(ptql);
        Assert.assertTrue(find3.size() > 1);
    }
    
    public void args() throws Exception {
        SimplePTQL ptql = new SimplePTQL.Builder(SimplePTQL.STATE_NAME(), SimplePTQL.RE(), "office.*")
        .addArgs(1, SimplePTQL.RE(), "\\Qpipe,name,office1\\E", Strategy.ESCAPE)
        .createQuery();
        Assert.assertEquals(ptql.getQuery(), "State.Name.re=office.*,Args.1.re=pipe.name.office1");
        
        ptql = new SimplePTQL.Builder(SimplePTQL.STATE_NAME(), SimplePTQL.EQ(), "office.*")
        .addArgs(1, SimplePTQL.RE(), "\\Qpipe,name,office1\\E", Strategy.ESCAPE)
        .addArgs(2, SimplePTQL.EQ(), "\\Qpipe,name=office2\\E", Strategy.ESCAPE)
        .setStrategy(Strategy.ESCAPE)
        .createQuery();
        
        Assert.assertEquals(ptql.getQuery(), "State.Name.eq=office.*,Args.1.re=pipe.name.office1,Args.2.eq=pipe.name=office2");
        
        try {
            ptql = new SimplePTQL.Builder(SimplePTQL.STATE_NAME(), SimplePTQL.EQ(), "office.*")
            .addArgs(1, SimplePTQL.RE(), "\\Qpipe,name,office1\\E", Strategy.ESCAPE)
            .addArgs(2, SimplePTQL.EQ(), "\\Qpipe,name,office2\\E", Strategy.NOT_ESCAPE)
            .createQuery();
        
            Assert.fail("Method should have thrown IllegalArgumentException");
        } catch(IllegalArgumentException ex) {}
    }

    public void realArguments() throws Exception {
        if (PlatformUtils.isWindows()) {
            throw new SkipException("Sleep only works on unix");
        }
        
        Process process = new ProcessBuilder("sleep", "60s").start();
        Assert.assertNotNull(process);
        
        SimplePTQL ptql = new SimplePTQL.Builder(SimplePTQL.STATE_NAME(), SimplePTQL.EQ(), "sleep")
                            .addArgs(1, SimplePTQL.EQ(), "60s", Strategy.NOT_ESCAPE).createQuery();
        
        
        ProcessManager spm = new SigarProcessManager();
        Long findSingle = spm.findSingle(ptql);
        Assert.assertTrue(findSingle > 0L);
        
        spm.kill(findSingle, 9);
        Assert.assertEquals(spm.findSingle(ptql).longValue(), 0L);
    }
}

Monday, March 14, 2011

Commit access to JodConverter

I have gotten commit access to jodconverter 3 so that I can work on some issues.

The first issue I am going to work on is refactoring the code to use Sigar.

Sigar "provides a portable interface for gathering system information" with native libraries for many OSes and it has an Apache License Version 2.

This is my first commit access to an open source project. Looking forward to contribute.


 

Monday, August 2, 2010

Migrating from JODConverter 2 to JODConverter 3 and converting PDF to PDF/A

In the previous posting I showed you how you could automate conversions of documents to PDF & PDF/A using JODConverter 2.  

JODConverter 3.0.beta has been out for some time, and even though it is still beta, it is very stable. Maybe even more stable than JODConverter 2.

In this blog posting I will highlight the benefits of JODConverter 3 compared to its predecessor and show you how you can modify your code to create PDF/A documents with JODConverter 3.  
To be able to convert an existing PDF document to PDF/A in OpenOffice.org, you will need to install Sun PDF Import extension!

JODConverter 2 versus 3
JODConverter 3 still uses OpenOffice.org to perform its conversion. It is still a wrapper to the OOo API. It is only a complete rewrite of the JODConverter core library which is much cleaner and easier to use.

Whats new? 
  • No more init script(!) 
    • You don't have to manually start OpenOffice.org as a service anymore. This will be handled automatic.
    • You can even create multiple processes which is useful for multi-core CPU's. Best practise is one process for each CPU core.
  • Automatically restart an OOo instance if it crashes.
    • If for some reason your process crashes, JODConverter will detect this, and restart the process automatic. This was a hassle with JODConverter 2, as you needed to manually do this in Linux.
  • Abort conversions that take too long (according to a configurable timeout parameter)
  • Automatically restart an OOo instance after n conversions (workaround for OOo memory leaks)
Additionally the new architecture will make it easier to use the core JODConverter classes as a generic framework for working with OOo - not just limited to document conversions.
I am sure there will be more features when JODConverter 3 goes final.

Configuration

All you need to do do is point your OpenOffice.org installation to the OfficeManager, and you are good to go.

OfficeManager officeManager = new DefaultOfficeManagerConfiguration()
        .setOfficeHome("/usr/lib/openoffice")
        .buildOfficeManager().start();

This manager will use the default settings for Task Queue Timeout, Task Execution Timeout, Port Number etc but you can easily change them

OfficeManager officeManager = new DefaultOfficeManagerConfiguration()
        .setOfficeHome("/usr/lib/openoffice")
        .setTaskExecutionTimeout(240000L)
        .setTaskQueueTimeout(60000L)
        .buildOfficeManager().start();

If you want to utilize piping (Recommended is one process per CPU-core), you will need to set VM argument and point java.library.path to the location of $URE_LIB which on my Ubuntu machine is /usr/lib/ure/lib/
For instance:
-Djava.library.path="/usr/lib/ure/lib"

And then you can change your OfficeManager.

OfficeManager officeManager = new DefaultOfficeManagerConfiguration()
        .setOfficeHome("/usr/lib/openoffice")
        .setConnectionProtocol(OfficeConnectionProtocol.PIPE)
        .setPipeNames("office1","office2") //two pipes
        .setTaskExecutionTimeout(240000L) //4 minutes
        .setTaskQueueTimeout(60000L)  // 1 minute
        .buildOfficeManager().start();



ConverterService3Impl
The following codes performs all the converting. It supports a File or byte[] as input.

This is how you use it:
Lets say you have a PDF file as byte[], and you want to convert this byte to PDF/A as byte.
All you would have to do is call method:



byte[] pdfa = converterService.convertToPDFA(pdfFile);

Similarly, if you have a Document (say a OpenOffice.org writer document) and you want to convert this to PDF you would call the method:

File doc = new File("myDocument.odt");
File pdfDocument = converterService.convert(doc, ".pdf");

Note that you will always get a PDF/A compliant pdf. All you need to do is change the extension from ".pdf" to ".html" and the converter would do the magic.


Here is the source. Please read the comments in the source code if you want to understand it, or just ask in the comment section below.

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.ejb.Local;
import javax.ejb.Stateless;

import lombok.Cleanup;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.artofsolving.jodconverter.OfficeDocumentConverter;
import org.artofsolving.jodconverter.document.DefaultDocumentFormatRegistry;
import org.artofsolving.jodconverter.document.DocumentFamily;
import org.artofsolving.jodconverter.document.DocumentFormat;
import org.artofsolving.jodconverter.document.DocumentFormatRegistry;

/**
 * This service converts files from one thing to another ie ODT to PDF, DOC to ODT etc
 * @author Shervin Asgari
 *
 */
@Stateless
@Local(ConverterService.class)
public class ConverterService3Impl implements ConverterService {

  private static final String PDF_EXTENSION = ".pdf";
  private static final String PDF = "pdf";  

  // Uncomment these when we want to use them

  // private final int PDFXNONE = 0;
  private final int PDFX1A2001 = 1;
  // private final int PDFX32002 = 2;
  // private final int PDFA1A = 3;
  // private final int PDFA1B = 4; 


  @Logger //Your favourite logger (ie Log4J) could be injected here 
  private Log log;

  public File convert(File inputFile, String extension) throws IOException, ConnectException {
    if (inputFile == null) {
      throw new IOException("The document to be converted is null");
    }

    Pattern p = Pattern.compile("^.?pdf$", Pattern.CASE_INSENSITIVE);
    Matcher m = p.matcher(extension);
    OfficeDocumentConverter converter;
    
    //If inputfile is a PDF you will need to use another FormatRegistery, namely DRAWING
    if(FilenameUtils.isExtension(inputFile.getName(), PDF) && m.find()) {
      DocumentFormatRegistry formatRegistry = new DefaultDocumentFormatRegistry();
      formatRegistry.getFormatByExtension(PDF).setInputFamily(DocumentFamily.DRAWING);
      converter = new OfficeDocumentConverter(officeManager, formatRegistry);
    } else {
      converter = new OfficeDocumentConverter(officeManager);
    }
    
    String inputExtension = FilenameUtils.getExtension(inputFile.getName());
    File outputFile = File.createTempFile(FilenameUtils.getBaseName(inputFile.getName()), extension);

    try {
      long startTime = System.currentTimeMillis();
      //If both input and output file is PDF
      if (FilenameUtils.isExtension(inputFile.getName(), PDF) && m.matches()) {
        //We need to add the DocumentFormat with DRAW
        converter.convert(inputFile, outputFile, toFormatPDFA_DRAW());
      } else if(FilenameUtils.isExtension(outputFile.getName(), PDF)) {
        converter.convert(inputFile, outputFile, toFormatPDFA());
      } else {
        converter.convert(inputFile, outputFile);
      }
      long conversionTime = System.currentTimeMillis() - startTime;
      log.info(String.format("successful conversion: %s [%db] to %s in %dms", inputExtension, inputFile.length(), extension, conversionTime));

      return outputFile;
    } catch (Exception exception) {
      log.error(String.format("failed conversion: %s [%db] to %s; %s; input file: %s", inputExtension, inputFile.length(), extension, exception, inputFile.getName()));
      exception.printStackTrace();
      throw new IOException("Converting failed");
    } finally {
      //outputFile.deleteOnExit();
      //inputFile.deleteOnExit();
    }
  }
  
  /**
   * Convert pdf file to pdf/a
   * You will need to install OpenOffice extension (pdf viewer) to get it working
   * @param pdf
   * @return Byte array
   * @throws IOException
   */
  public byte[] convertToPDFA(byte[] pdfByte) throws IOException, ConnectException {
    @Cleanup InputStream is = new ByteArrayInputStream(pdfByte);
    File pdf = createFile(is, PDF_EXTENSION);
    log.debug("PDF is: #0 #1", pdf.getName(), pdf.isFile());
    return convert(pdf);
  }

  
  private byte[] convert(File pdf) throws IOException {
    if (pdf == null) {
      throw new IOException("The document to be converted is null");
    }

    File convertedPdfA = convert(pdf, PDF_EXTENSION);
    @Cleanup final InputStream inputStream = new BufferedInputStream(new FileInputStream(convertedPdfA));
    byte[] pdfa = IOUtils.toByteArray(inputStream);
    return pdfa;
  }

  /**
   * Creates a temp file and writes the content of InputStream to it. doesn't close input
   * 
   * @return File
   */
  private java.io.File createFile(InputStream in, String extension) throws IOException {
    java.io.File f = File.createTempFile("tmpFile", extension);
    @Cleanup BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(f));
    IOUtils.copy(in, out);
    return f;
  }
  
  /**
   * This DocumentFormat must be used when converting from document (not pdf) to pdf/a
   * For some reason "PDF/A-1" is called "SelectPdfVersion" internally; maybe they plan to add other PdfVersions later.
   */
  private DocumentFormat toFormatPDFA() {
    DocumentFormat format = new DocumentFormat("PDF/A", PDF, "application/pdf");
    Map<String, Object> properties = new HashMap<String, Object>();
    properties.put("FilterName", "writer_pdf_Export");

    Map<String, Object> filterData = new HashMap<String, Object>();
    filterData.put("SelectPdfVersion", this.PDFX1A2001);
    properties.put("FilterData", filterData);

    format.setStoreProperties(DocumentFamily.TEXT, properties);

    return format;
  }
  
  /**
   * This DocumentFormat must be used when converting from pdf to pdf/a
   * For some reason "PDF/A-1" is called "SelectPdfVersion" internally; maybe they plan to add other PdfVersions later.
   */
  private DocumentFormat toFormatPDFA_DRAW() {
    DocumentFormat format = new DocumentFormat("PDF/A", PDF, "application/pdf");
    Map<String, Object> properties = new HashMap<String, Object>();
    properties.put("FilterName", "draw_pdf_Export");

    Map<String, Object> filterData = new HashMap<String, Object>();
    filterData.put("SelectPdfVersion", this.PDFX1A2001);
    properties.put("FilterData", filterData);

    format.setStoreProperties(DocumentFamily.DRAWING, properties);

    return format;
  }

}


Remember to close the connection when your application is quit/shutdown

Labels