Donnerstag, 16. Februar 2017

# c0ders wanted #

If I believe in the statistics there seem to be some followers on this blog. Now it's up to you. Inspired by a friend of mine I have created a little programming riddle.

I pinned it at the MENSA of the tech university near our office. The purpose is to find a student who will be able to support us.
Unfortunately the semester holidays just started. Perfect timing ;)

Feel free to solve the task and let me know (peter AT pinnau.biz).



Donnerstag, 12. Januar 2017

PDF File comparison for automated unit testing

Unit testing and automation of those tests is one of the key concepts to implement solid testing for your software project.

Unit tests of System Concept DMS project need to validate produced PDF documents in various scenarios, e.g.:

  • Split a (scanned) stack into page documents
  • Split a scanned stack into single semantic documents using a barcode/ QR-code mechanism
  • Combine single page documents to one document
  • ...
To abstract document orientated unit tests I implemented a base class which can process a flexible number of test document sets in a directory structure.
A document set consists of defined input documents and the corresponding output documents. The following image illustrated the test case AutoIndexBarcodeTest. The test contains 1 document set. The set consists of 1 input document (input1.pdf) and 4 output documents in the subfolder output.

Image 1: test document set, input and output documents
The concrete implementation for the barcode split unit test overrides the method
doFile(File inputFile). The implementation processes the input document and than needs to validate the created output documents against the output templates from the test definition (see image)

@Override
protected void doFile(File inputFile) throws Exception {
   
   // Process input file

   // Validate output files
   //  - count of files
   //  - location/name of the files
   //  - file content
}

The checks for count and location/name of the output documents were quite simple.

Problem - PDF file comparison using checksum


To compare the PDF documents by content a file checksum like MD5 seemed to be an appropriate solution. Easy to implement and more or less 'bug-save' which is an important thing concerning tests.

Unfortunately valid PDF output results produced different checksums from the test templates. The diff commandline tool reported 1 difference near the end of the documents:

peter@grogonzola:~$ diff -Naur result.pdf template.pdf
 /Root 1 0 R
 /Info 4 0 R
-/ID [<0e90025fbadc9f6434f3d192980836f3> <0e90025fbadc9f6434f3d192980836f3>]
+/ID [<bf69242a5582735a45e72ab0ed370876> <bf69242a5582735a45e72ab0ed370876>]
 /Size 11

The reason for the different checksum is the PDF file identifier (/ID) which is indvidually created per file and always differs even if 2 documents are produced using the exact identical piece of code.

If you want further information about it take a look at the following discussion at stackoverflow:

http://stackoverflow.com/questions/20039691/why-are-pdf-files-different-even-if-the-content-is-the-same

Mr. Lowagie explains the reason for different checksums and the need for the PDF file identifier. So far so good - but that is really awful for document validation against defined templates and test automation.


Solution 1 - Adapted checksum calculation


One possible solution is to implement an adapted checksum calculation. Look for the /ID line and do not pass the data into the checksum calculation (the same for /CreationDate).
I implemented a quick hack which does the trick but uses a BufferedReader. That is not the first choice when dealing with binary data but it works for now:


public byte[] getChecksum(InputStream stream) throws Exception {
  
  BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
  
  try {
    MessageDigest md = MessageDigest.getInstance("MD5");
   
    String line;
   
    while ((line = reader.readLine()) != null) {   
      if (!line.startsWith("/ID [<")) {
        md.update(line.getBytes());
      }     
    }
         
    return md.digest();
      
  } finally {
    // close the stream
    try { reader.close(); } catch (Exception e) {}
  }
}

The problem here is that BufferedReader is for text files and line orientated. The plan is to implement a real binary solution using a byte[] buffer array. Basically it is easy to find the "/ID [<" snippet in the byte array. The problem is to also find it when it is splitted into different buffers during the read proccess.


Solution 2 - Using external tools/ libraries for comparison


Another solution is to use external tools/ libraries to compare PDF documents. Possible tools can be:

  • http://jpdfunit.sourceforge.net - Apache PDFBox based framework for JUnit
  • diffpdf (GUI) to get an inspiration
  • http://www.qtrac.eu/comparepdf.html command line base tool
  • imagemagick (compare)

All of these tools more or less interprete/ render the documents and so do a comparison based on their structure and appearance and not on the binary file content itself.

Especially concerning Jpdfunit I see a problem when using the same library for test validation that is used within the project to create the PDF documents (System Concept DMS uses Apache PDFBox).

Conclusion


Two PDF files created by the same piece of code at different times are not binary equal. So a checksum algorithm produces differend fingerprints and a comparison fails.

Reasons:

  • Unique PDF file identifier (/ID)
  • Creation date (/CreationDate)

Possible solutions are

  1. Adapted checksum calculation which ommits /ID and /CreationDate
  2. Externals tools/ libraries to compare PDF documents by their structure/appearance and not binary content

Advantages of 1. (adapted checksum)

  • no dependencies
  • fast
  • metadata is validated, too
  • will fail if anything but the ommited parts differs

Advantages of 2. (tools)

  • may also work for document comparison from different sources



To keep tests simple and reduce dependencies on external tools I decided to use the adapted checksum solution.

Dienstag, 11. Oktober 2016

ResponsiveGridLayout - a responsive grad based Layout for SWT

I have implemented a grid based responsive SWT layout for my Document Management Software.
I believe it could be quite useful for other project and release it under the Eclipse Public License (EPL).

The layout arranges components in a grid where all cells have equal dimensions. Number of columns and column width adapts repsonsive to the available width.
Row height adapt to column width using a configurable ratio.

To see it in action please take a look at the demo video:



Here comes the source code. Please leave the header with the license and copyright information

 package biz.pinnau.dms.rcp;  
   
   
 import org.eclipse.swt.SWT;  
 import org.eclipse.swt.graphics.Point;  
 import org.eclipse.swt.widgets.Composite;  
 import org.eclipse.swt.widgets.Control;  
 import org.eclipse.swt.widgets.Layout;  
   
 /**  
  * Released under Eclipse Public License (EPL) 1.0  
  *   
  * Copyright: Peter Pinnau (peter@pinnau.biz)  
  *   
  * Gridbased SWT Layout  
  *   
  * 1. All cells have equal dimensions  
  * 2. Cell widths increases/decreases automatically and adjusts to   
  *  available width of the container  
  * 3. Cell height is calculated with a ratio + offset from calculated  
  *  cell width: height = width * ratio + offset  
  * 4. Column count can be automatically calculated according to available width  
  *   
  * @author Peter Pinnau (peter@pinnau.biz)  
  * @version 1.0  
  *   
  * Last modified: 2016-10-11  
  *   
  * Class was implemented for System Concept DMS  
  *   
  * System Concept DMS - Das papierlose Büro  
  *   
  * www.pinnau.biz/dms.html  
  *  
  */  
 public class ResponsiveGridLayout extends Layout {  
   
      /**  
       * Fixed number if columns. Default = 0 => adaptive number of columns  
       */  
      private int fixedNumColumns = 0;  
   
      /**  
       * Mimimum column width  
       */  
      private int minColumnWidth;  
        
      /**  
       * ratio to calculate row height  
       */  
      public float ratio = 1f;  
        
      /**  
       * Offset for row height calculation  
       */  
      public int offset = 0;  
        
      /**  
       * margin around grid  
       */  
      public int margin = 0;  
        
      /**  
       * spacing between cells  
       */  
      public int spacing = 10;  
             
      /**  
       * Creates an instance of the layout using a fixed number of columns  
       *   
       * @param numColumns  
       */  
      public ResponsiveGridLayout(int numColumns, int minimumColumnWidth) {  
           this.fixedNumColumns = numColumns;            
           this.minColumnWidth = minimumColumnWidth;  
      }  
        
      /**  
       * Creates an instance of the layout with adaptive number of columns depending  
       * on available width and minColumnWidth  
       *   
       * @param columnCounts  
       */  
      public ResponsiveGridLayout(int minimumColumnWidth) {  
           this.minColumnWidth = minimumColumnWidth;  
      }       
        
      private int getBoxHeight(int boxWidth) {  
           return (int) (boxWidth * ratio) + offset;  
      }  
        
      private int getNumColumns(int availableWidth) {  
           // Fixed number of columns  
           if (fixedNumColumns > 0) return fixedNumColumns;  
             
           // No available width specified  
           if (availableWidth == SWT.DEFAULT) {  
                // assume 1 column  
                return 1;  
           }  
             
           // Start with width of 1 column  
           int totalWidth = minColumnWidth + 2 * margin;  
           int numColumns = 0;  
             
           // Increase column count until totalWidth exceeds avaiblableWidth  
           while (totalWidth < availableWidth) {  
                // Add spacing + minColumnWidth (1 new column)  
                totalWidth = totalWidth + spacing + minColumnWidth;  
                                 
                numColumns++;  
           }  
             
           if (numColumns == 0) numColumns = 1;  
             
           return numColumns;            
      }  
        
      @Override  
      protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {  
           Point size = new Point(0, 0);  
             
           // Get number of columns  
           int numColumns = getNumColumns(wHint);  
           int boxWidth = getAvailableBoxWidth(wHint, numColumns);  
                       
           int componentCount = composite.getChildren().length;  
           int rows = (int) Math.ceil(componentCount / (double) numColumns);  
             
           if (rows == 0) rows = 1;  
             
           size.x = numColumns * boxWidth + (numColumns-1) * spacing + 2 * margin;  
           size.y = rows * getBoxHeight(boxWidth) + (rows-1) * spacing + 2 * margin;  
             
           return size;  
      }  
        
      /**  
       * Calculates the available cell width  
       *   
       * @param width  
       * @param numColumns  
       * @return  
       */  
      private int getAvailableBoxWidth(int width, int numColumns) {  
           if (width == SWT.DEFAULT) return minColumnWidth;  
             
           int boxWidth = (width - 2 * margin - (numColumns * spacing)) / numColumns;  
             
           if (boxWidth < minColumnWidth) return minColumnWidth;  
             
           return boxWidth;  
      }  
             
      @Override  
      protected void layout(Composite composite, boolean flushCache) {  
           Control[] children = composite.getChildren();  
        
           // Get number of columns  
           int numColumns = getNumColumns(composite.getClientArea().width);  
             
           int boxWidth = getAvailableBoxWidth(composite.getSize().x, numColumns);  
           int boxHeight = getBoxHeight(boxWidth);  
             
           // Build the grid  
           int x = margin;  
           int y = margin;  
             
           for (int i=0; i<children.length; i++) {  
                if (i % numColumns == 0) {  
                     x = margin;  
                       
                     if (i > 0) {  
                          y = y + spacing + boxHeight;   
                     } else {  
                          y = margin;  
                     }  
                } else {  
                     x = x + boxWidth + spacing;                      
                }  
                                 
                children[i].setSize(boxWidth, boxHeight);  
                children[i].setLocation(x, y);                                
           }  
      }  
 }  
   



The following example shows how to use the layout together with a ScrolledComposite.


 scroll = new ScrolledComposite(parent, SWT.V_SCROLL | SWT.H_SCROLL );  
   
container = new Composite(scroll, SWT.None);  
               
// Create adaptive Grid  
layout = new ResponsiveGridLayout(150);  
layout.offset = 50;  
layout.ratio = 1.5f;  
layout.margin = 5;  
layout.spacing = 5;  
               
container.setLayout(layout);              
                           
// Set the child as the scrolled content of the ScrolledComposite  
scroll.setContent(container);  
               
// Expand both horizontally and vertically  
scroll.setExpandHorizontal(true);  
scroll.setExpandVertical(true);  
               
scroll.addListener(SWT.Resize, new Listener() {                    
      @Override  
      public void handleEvent(Event event) {  
            scroll.setMinSize(container.computeSize(scroll.getClientArea().width, SWT.DEFAULT));  
      }  
});  
               
// Add some controls to container ...  

Dienstag, 13. September 2016

Kerberos Authentication with Tomcat 8.0 and pure Java

I am working on service/web based search platform for indexing and querying file servers. One of the key challenges is authentication against the same resource as the fileserver itself does. In most cases this will be an Active Directory or something similar.

My plan is to use Apache Tomcat to deploy Web GUI/ REST/ SOAP or whatever services so I tried to configure Java's buildin Kerberos JAAS authentication in Tomcat.

There is a complicated howto on the web:
https://tomcat.apache.org/tomcat-7.0-doc/windows-auth-howto.html

The howto says that configuration is hard and tricky and does not allow much flexibility.


Kerberos and single sign on (SSO)


The Tomcat howto focuses on single sign on. This means that your Windows login credentials (or precisely a ticket) is passed from the browser to the Tomcat webserver and is used for authentication. There is no need to prompt for credentials.

For real Kerberos single sign on you need a perfectly configured Tomcat and a so called "kerberised" client, i.e. a modern web broswer.
If the Tomcat deploys a REST service the REST client must be kerberised, too.

In a perfect world all that will be easily available. But we are still far away from that world ;)


Kerberos without SSO


To be honest I do not need SSO for my purpose. It will really be OK if the user or the service consumer has to specify login credentials.

I just do not want to manage these credentials in an extra source. So the authentication must be done against a Kerberos server (i.e. Windows domain controller).

My focus is a central user/password database which does NOT mean SSO.

Unfortunately the howto and other web resoruces do not cover that. I spend some hours on testing and finally got a working solution.


Howto


The following steps show how to setup simple Kerberos authentication for a servlet. I do not explain how to setup and deploy the servel itself. This should be common knowledge.

A real simple Servlet

Let's start with a real simple servlet. It Just prints the system time.

public class HelloWorld extends HttpServlet {
 
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {  

    response.getWriter().append("Served at: ").append(request.getContextPath());
  }

  protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    
    doGet(request, response);
  }

}

Please configure your project and web.xml so that you have that simple servlet working. Of course there is no authentication at the moment.

Defining security contraints in web.xml

To force authentication on a servlet you need to define a security constraint in the web.xml


 <servlet-mapping>  
  <servlet-name>HelloWorld</servlet-name>  
  <url-pattern>/admin/HelloWorld</url-pattern>  
 </servlet-mapping>   
        
 <security-constraint>  
  <web-resource-collection>  
   <web-resource-name>Admin</web-resource-name>  
   <url-pattern>/admin/*</url-pattern>  
  </web-resource-collection>  
   
  <auth-constraint>             
   <role-name>peter@SC.LOCAL</role-name>  
  </auth-constraint>  
 </security-constraint>  
        
 <security-role>  
  <role-name>peter@SC.LOCAL</role-name>        
 </security-role>  
        
 <login-config>  
  <auth-method>BASIC</auth-method>  
  <realm-name>DMS</realm-name>  
 </login-config>  

Notes for web.xml


  • the servlet URL is set to admin/HellWorld
  • a security constraint is defined for the pattern admin/*. So that constraint applies to the servlet.
  • the security constraint requires that the user has the role peter@SC.LOCAL
  • the roles has to be defined (security-role)
  • the login-config element configures the JAAS realm to use and the way to prompt for the credentials. The JAAS realm is named DMS. With BASIC auth the browser pops up the little htaccess credentials window.

When you deploy the project you will not be able to access the servlet anymore. The browser will show a popup to enter credentials but no matter what you type - access will be denied.

JAAS config

We need a JAAS configuration file and pass that file with the -Djava.security.auth.login.config VM argument to the tomcat server.

Here comes my jaas config example:


 DMS {  
      com.sun.security.auth.module.Krb5LoginModule required  
      debug=true  
      useTicketCache=false;    
 };  
   

Note: The name of the config (here: DMS) matches the realm-name in web.xml

Edit your Tomcat startup script and specify the path to the jaas config file via:

-Djava.security.auth.login.config=/path/to/jaas.conf

We need 2 other VM arguments:

-Djava.security.krb5.realm=SC.LOCAL
-Djava.security.krb5.kdc=IP/hostname of Kerberos server (domain controller)

Set -Djava.security.krb5.realm to your windows domain (including .local and all uppercase!)
Set -Djava.security.krb5.kdc to your domain controller. If you use a hostname here DNS must work. If you receive problems try to setup an IP here.

Alternative: default realm and kdc can also be set in a krb5.conf file. You can also specify multiple kdcs there. The path to krb5.conf must be passed via a VM option.


context.xml


Last but not least you must create or modify the file META-INF/context.xml in your project. The context.xml must be deployed with the project.


 <Context>  
  <Realm className="org.apache.catalina.realm.JAASRealm" appName="DMS"      
     
   roleClassNames="javax.security.auth.kerberos.KerberosPrincipal"  
  />  
 </Context>  


Notes:

  • the realm className has to be set to org.apache.catalina.realm.JAASRealm. That tells tomcat to use JAAS athentication.
  • The appName must match the JAAS config name and the realm-name from web.xml
  • Important: set roleClassNames to javax.security.auth.kerberos.KerberosPrincipal. Otherwise the subject (here peter@SC.LOCAL) will not be added to the roles and authorisation fails. You will be authenticated but authorisation fails (since you do not have any role).

Test


You are now ready to test the setup. Start Tomcat and enter your servlet URL into a browser. Enter a valid login (without domain name) when the popup appears and your servlet will report the system time.

In the Tomcat console you will see a message similar to:

principal is peter@SC.LOCAL
Commit Succeeded

This message reports a successful authentication.


Defining real roles


Congratulations if you got it working so far. But what about roles/groups? For now we need to specify users in the web.xml security constraint.
The problem is that the builtin Java Kerberos implementation just authenticates. It does not query groups for the user. So we have no roles.

I will spend more time on that. For the moment I have a very simple solution:

Create a subclass of Krb5LoginModule. I believe it will be perfect to override commit() and do the role logic there but unfortunately there is no way to access the subject there (private member).
So override initialize(...):


public void initialize(Subject subject,  
     CallbackHandler callbackHandler,  
     Map<String, ?> sharedState,  
     Map<String, ?> options) {  
             
     if (subject != null) {  
         subject.getPrincipals().add(new RolePrincipal("Everyone"));  
     }  
             
     super.initialize(subject, callbackHandler, sharedState, options);            
}  

This code simply adds a role "Everybody" to the subject. The class RolePrincipal is a simple Java bean which implements javax.security.Principal


public class RolePrincipal implements Principal {  
         
       private String name;  
         
       public RolePrincipal(String name) {  
        super();  
        this.name = name;  
       }  
   
       public void setName(String name) {  
        this.name = name;  
       }  
   
       @Override  
       public String getName() {  
        return name;  
       }  
}  


Now there are 3 things to change:


  • Replace com.sun.security.auth.module.Krb5LoginModule in jaas.conf with the subclassed implementation
  • Replace/add RolePrincipal class to roleClassNames attribute in context.xml
  • Modify web.xml and use the pseudo role "Everyone" instead of individual users

No every authenticated user will be able to access the servlet. There is no need to define individual users in web.xml.


TODOs


There are some points to spend further work on:


  1. Use https instead of http. Ensure that credentials are submitted in a secure way
  2. Implement flexible role configuration. Either configure role assignment in simple way or retrieve groups from AD (but what about nested groups?)
  3. Test authentication with SOAP and REST services.


Donnerstag, 4. August 2016

Berechnete Datensätze mit SQL INSERT INTO () SELECT ergänzen

Ein Kunde setzt seit 2011 eine zentrale Verwaltung und Erfassung von Zählern ein. Die Ablesung erfolgt monatlich mit Hilfe eines mobilen Gerätes. Die abgelesenen Stände werden anschließend per LAN in eine Oracle Datenbank synchronisiert.
Dabei werden gleichzeitig neue Stammdaten (Zähler) auf das mobile Gerät übernommen.

Die Auswertung der Daten erfolgt über ein zentrales Berichtssystem mit JasperReports unabhängig von der Zählerverwaltung.

In der Auswertung werden monatliche Verbrauche dargestellt. Die Ermittlung erfolgt ganz einfach durch Subtraktion eines Zählerstands mit dem Stand des Vormonats.
Dies funktioniert natürlich nur, wenn für alle Monate lückenlos Stände erfasst wurden.

Aufgrund von Urlaub wurde nun für den Monat Juni 2016 kein Stand abgelesen. Damit waren die Auswertungen unvollständig, da ohne den Stand für Juni keine Verbrauche für Mai und Juni ermittelt werden können.
Das Problem ist erst Anfang August aufgefallen und nun musste eine Lösung gefunden werden.

Lösung:

  • Die Stände für Juli werden regulär erfasst.
  • Die Stände für Juni werden berechnet und nachgetragen. Dabei wird die Mitte zwischen Mai und Juli angenommen.


Im System sind ca. 120 Zähler abzulesen. Eine manuelle Berechnung und Nacherfassung der Stände für Juni hätte Arbeit und vor Allem Fehlerpotenzial bedeutet.

Ich habe daher eine SQL Abfrage erstellt, die die fehlenden Stände nachträgt. Dafür eignet sich die INSERT INTO (...) SELECT ... Syntax.
Mit dieser Syntax werden Datensätze eingefügt, die aus einer SELECT Abfrage gewonnen werden.


Vorabprüfung und Backup

Man muss bei solchen Aktionen natürlich aufpassen, da man mit fehlerhaftem SQL ganz fix den Datenbestand zerstören kann. Man startet also am Besten mit einer Datensicherung.

Danach habe ich geprüft, ob für Mai und Juli gleich viele Stände abgelesen wurden.

Anzahl Stände für Mai

select count(*) from zaehler_stand zs
  where zs.PERIODE_BEGINN = TO_DATE('1.5.2016')

Ergebnis: 122


Anzahl Stände für Juli

select count(*) from zaehler_stand zs
  where zs.PERIODE_BEGINN = TO_DATE('1.7.2016')

Ergebnis: 123

Die Abweichung resultiert aus einem neuen Zähler, der erst im Juni in Betrieb gegangen ist. Die Basis für die INSERT INTO Abfrage bilden also alle Zähler für die im Mai ein Stand erfasst wurde.


Hier nun die entsprechende Abfrage:


insert into zaehler_stand (periode_beginn, periode_ende,
     zaehler_nummer, datum_ablesung, version, zaehler_stand)

select 

TO_DATE('1.6.2016'),
TO_DATE('30.6.2016'),
zaehler.zaehler_nummer,
TO_DATE('4.8.2016'),
1,
(
   (select zaehler_stand from zaehler_stand zs where 
       zs.PERIODE_BEGINN = TO_DATE('1.7.2016')
       and zs.zaehler_nummer = zaehler.zaehler_nummer) + 
   (select zaehler_stand from zaehler_stand zs where
       zs.PERIODE_BEGINN = TO_DATE('1.5.2016') 
       and zs.zaehler_nummer = zaehler.zaehler_nummer)
) / 2

from zaehler

where zaehler_nummer in 
 (select zaehler_nummer from zaehler_stand where
     PERIODE_BEGINN = TO_DATE('1.5.2016'))


Die Datumsangaben entsprechend den Werten für Juni 2016. Das Feld version wird mit 1 belegt. Die Zählernummer wird aus dem aktuellen Datensatz der Tabelle zaehler übernommen.

Über 2 Subqueries werden die Stände des aktuellen Zählers für Mai und Juli 2016 ermittelt. Die Stände werden addiert und durch 2 dividiert. Dies ergibt den Mittelwert für Juni.

Im WHERE Teil der Hauptabfrage werden per Subquery die Zähler eingeschränkt, so dass nur die Zähler verarbeitet werden für die im Mai 2016 ein Stand abgelesen wurde.


Donnerstag, 28. Juli 2016

XulRunner deprecation, Eclipse SWT and Mozilla PDFJS

I currently spend a lot of work on System Concept DMS Document Management System. Last month I brought the project to the recent Apache PDF Box version 2.0.2 and Eclipse release.

Today I checked the Mozilla PDFJS version. PDFJS is used to have a full featured and fast PDF viewer in the DMS. PDFJS is JavaScript based and is intergrated via org.swt.Broswer.

I had to use the SWT.MOZILLA flag with the browser since PDFJS did not run with GTK webkit (Linux native) and Internet Explorer (Windows native).
The application has to be shipped with XulRunner 24.0 which is compatible with the SWT library.

With the old PDFJS version 1.2.109 this worked fine.

For testing I switched to current stable PDFJS 1.4.20. Unfortunately lots of images are not rendered anymore.
I believe that the outdated XulRunner version is the reason. I tried to find out if there is support for a newer one in the Eclipse Neon platform.

The result:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=474625

The XulRunner project is deprecated and there is no effort in the Eclipse team to get the latest version working with SWT. This is bad news.

Compatibiliy table


I tried to leave XulRunner and work with the native OS browsers (IE 11 for windows and GTK Webkit on Linux). The tests started hopefully but in the end serious problems occured.

For now XulRunner has to be used and shipped with the SWT application to get a stable PDFJS integration.

The follwing table summarized the test results.


Date: 07-28-2016

Eclipse: Neon 4.6, Build I20160606-1100

SWT: I found two jars in the plugins folder:

swt_3.105.0.v20160127-0039.jar
swt_3.105.0.v20160603-0902.jar

SWT.getVersion() returns 4623


OSSWT Browser flagBrowserPDFJSResult
Windows 7SWT.NONEIE 11All versionsPDF is not rendered
In standalone IE 11 PDF 1.5.188 works but not embedded in SWT
SWT.MOZILLAXulRunner 241.2.109OK
1.4.20Not tested
1.5.188Not tested
XulRunner 311.2.109Not tested
1.4.20OK
1.5.188OK
Ubuntu 14.04.1SWT.NONEGTK WebKit1.2.109PDF is not rendered
GTK WebKit1.4.20, 1.5.188PDF rendered but VM crashes when resizing browser widget
SWT.MOZILLAXulRunner 241.2.109, 1.5.188OK
1.4.20Rendered but images missing
XulRunner 31All versionsBrowser widget cannot be created. Known bug.
Windows 10Not systematically tested yet. SWT.MOZILLA + XulRunner 24 + PDFJS 1.2.109 works

Browser features and versions is a mess!


Donnerstag, 21. Juli 2016

System Concept DMS: Release 1.0.11

Nach 4 Monaten habe ich heute ein neues Release des System Concept DMS online gestellt. Die Software funktioniert inzwischen wirklich gut. Allerdings ist die Ersteinrichtung nach wie vor nicht ganz einfach.
In diesem Bereich und auch bei der Dokumentation ist noch viel zu tun.

Die wesentlichen Neuerungen sind:

  • Unterstützung für das EML Format. Damit können E-Mails jetzt genau wie PDF Dokumente mit dem System verschlagwortet und archviert werden.
    Ein E-Mail Archiver, der selbstständig auf Postfächer zugreift und Mails regelbasiert archiviert ist in Entwicklung.
    Fürs erste müssen für die Archivierung relevante Mails also als EML-Datei gespeichert und regulär verschlagwortet werden.
  • Integration eines einfachen Text E-Mail Viewer
  • Update auf aktuelle PDF Library: Apache PDFBox 2.0.2
  • Update auf aktuelle Eclipse Platform
  • Bugfixes
  • Neue Option zum Einstellen der beim Programmstart aktiven Resource


Für die private Nutzung kann das DMS kostenfrei verwendet werden. Für die Nutzung im Unternehmensumfeld wird ein Pilotprojekt gesucht. Unsere Kunden sind dafür entweder zu groß, zu chaotisch oder haben etwas anderes.

Bei Interesse bitte Kontakt aufnehmen.


Download und weitere Infos: www.pinnau.biz/dms.html