Blog do projektu Open Source JavaHotel

poniedziałek, 26 grudnia 2011

XML or not XML

Problem
For some time I have been working on adding invoice making to javahotel application. I had to resolve a problem how to keep invoice data in the database. One invoice contains a lot of data of different data types and it is possible to design a table with a set of columns reflecting invoice data.
But I found the following problems:
  1. There are a lot of different invoice types and every invoice type contains a set of data common to all invoices and some data specific to the invoice type given. So I have to design a different relational table for every invoice type or one common table containing the superset of all possible data. The second approach means that probably a lot of columns would be empty for  a particular invoice.
  2. Invoice contains also a list of invoice details (different  for every invoice). So I have to design the second table with invoices details ("details" could be different for every invoice type) and connect them by one-to-many relation.
  3. I cannot predict at this moment  all possible invoice types. Probably in the future it will be necessary to add new data to the invoice or redesign existing. But modifying relational data in a production environment is a complicated task and should be avoided as much as possible.
Solution idea
The solution is to keep invoice data as XML string in relational table. But XML format is convenient at server-side and less convenient at client-side. Although GWT provides tools for handling XML types (link) it requires some additional effort and not reads well. Designing a POJO class for keeping invoice data and deserialize XML into POJO object is having the same disadvantages as described above.
But I found that very flexible and useful entity for keeping data is simple Map class. Instead of :
 POJO container;
 Object val =  container.getInvoiceDate();
just use:
Map container;
Object val = container("InvoiceData");
Map does not require redesigning if invoice structure changes and could be the same for any invoice type.
So the solution is to keep invoice data as XML string in the database and transform it to the Map class before transporting to client side and opposite.
Solution
So I created a general solution for transforming XML to Map (and opposite) at server side and use Map as a transient object. Client is receiving and using Map and is sending Map back to the server. General sequence of actions is as follows.
  1. Client request
  2. Server retrieves XML string from database and transforms to Map
  3. Map is sent back to client
  4. Client sends Map to server
  5. Server transforms Map to XML and XML string is stored in the database.
Common data structure
This package is common for client and server.

It is an interface (Map) for keeping data. Should be extended for concrete implementation.  It is a subset of Map interface. Keys are XPath selector for the node containing data.

package com.gwtmodel.table.mapxml;

import java.util.Set;

/**
 * @author hotel
 *
 */
public interface IDataContainer {
   
    public Set getKeys();

    public Object get(Object key);

    public Object put(String key, Object val);

}
It is an abstract template class for the whole data (invoice). It contains main body (as IDataContainer) and list of invoice details (as list of IDataContainer). This class should extended for concrete implementation.
package com.gwtmodel.table.mapxml;

import java.io.Serializable;
import java.util.List;

/**
 * @author hotel Template class for keeping IDataContainer data.
 */
@SuppressWarnings("serial")
abstract public class DataMapList implements
        Serializable {

    /** Main body of data. */
    private T dFields;
    /** List of lines in shape of IDataContainer. */
    private List dLines;

    public DataMapList(T dFields, List dLines) {
        this.dFields = dFields;
        this.dLines = dLines;
    }

    /**
     * @return the dFields
     */
    public T getdFields() {
        return dFields;
    }

    /**
     * @return the dLines
     */
    public List getdLines() {
        return dLines;
    }

    /**
     * Adds next line to the dLines and returns IDataContainer just added
     * 
     * @return IDataContainer added
     */
    abstract public T addToLines();
}

It is limited to one list of data but can be easily extended.
Solution for server-side
The package is available here.
The solution requires to define pattern XML. An example is available here. Pattern is an XML file with empty content (only tag structure is defined) and type attribute which describes the content of the tag. Interpreting types and transforming between Map object and XML texts is done by IXMLTypeFactory interface (look below).
So creating XML string from IDataContainer map is done by means of filling the pattern XML with text content. Map keys are XPath pointers for XML document.
And opposite - deserialize XML string into IDataContainer is done by scanning XML file, taking 'type' info from pattern XML and filling the IDataContainer with key values.
For concrete implementation it is necessary to customize IXMLTypeFactory interface.

package com.gwtmodel.mapxml;

/**
 * @author hotel Factory interface providing user specific data
 */
public interface IXMLTypeFactory {

    /** Common types. */
    String DATE = "date";
    String DECIMAL = "decimal";
    String INT = "int";
    String INTEGER = "integer";
    String LONG = "long";

    /** Name of type attribute. */
    String TYPE = "type";

    /** Returns name of 'lines' element. */
    String getLinesTag();

    /** Returns name of single line in 'lines' section. */
    String getLineTag();

    /**
     * Creates object from string (element text). Object will be inserted into
     * IDataContainer map.
     * 
     * @param xType
     *            Type string (or null). Values is taken from 'type' attribute
     * @param s
     *            String value
     * @return Object
     */
    Object contruct(String xType, String s);

    /**
     * Opposite to construct. Transforms object to string
     * 
     * @param xType
     *            Type string (or null).
     * @param o
     *            Object taken from IDataContainer map
     * @return String to be inserted into XML string
     */
    String toS(String xType, Object o);
}

There is also available default implementation of this interface. It can be used "as is" or extended if customization is necessary.
package com.gwtmodel.mapxml;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.gwtmodel.table.common.CUtil;
import com.javahotel.common.command.PaymentMethod;

/**
 * @author hotel Default implementation of IXMLType Factory. Can be used 'as is'
 *         or extended
 */
public class SimpleXMLTypeFactory implements IXMLTypeFactory {

    protected final SimpleDateFormat fo = new SimpleDateFormat("yyyy-MM-dd");

    public final static String PAYMENT = "pay";

    protected boolean isInt(String xType) {
        return xType.equals(INT) || xType.equals(INTEGER);
    }

    @Override
    public Object contruct(String xType, String s) {
        if (CUtil.EmptyS(xType)) {
            return s;
        }
        if (xType.equals(PAYMENT)) {
            return PaymentMethod.valueOf(s);
        }
        if (xType.equals(DATE)) {
            try {
                return fo.parse(s);
            } catch (ParseException e) {
                e.printStackTrace();
                return null;
            }
        }
        if (xType.equals(DECIMAL)) {
            return new BigDecimal(s);
        }
        if (isInt(xType)) {
            return new Integer(s);
        }
        if (xType.equals(LONG)) {
            return new Long(s);
        }
        return s;
    }

    @Override
    public String toS(String xType, Object o) {
        if (CUtil.EmptyS(xType)) {
            return (String) o;
        }
        if (xType.equals(DATE)) {
            Date d = (Date) o;
            return fo.format(d);
        }
        if (xType.equals(PAYMENT)) {
            PaymentMethod pa = (PaymentMethod) o;
            return pa.toString();
        }
        if (xType.equals(DECIMAL)) {
            BigDecimal b = (BigDecimal) o;
            return b.toString();
        }
        if (xType.equals(LONG)) {
            Long l = (Long) o;
            return l.toString();
        }
        if (isInt(xType)) {
            Integer i = (Integer) o;
            return i.toString();
        }
        return (String) o;
    }

    @Override
    public String getLinesTag() {
        return "//Lines";
    }

    @Override
    public String getLineTag() {
        return "Line";
    }

}
Additional classes description

Example implementation

Implementation of IDataContainer.

Implementation of DataMapList.

Entity bean for keeping invoice data: link. Invoice data is stored as:
@Basic(optional = false)
private Text invoiceXML;
Transient object used at client side: link. All keys (XPath selector) are defined as constants

Source code for Map->XML transformation.


      sou.getInvoiceD().getdFields()
                .put(InvoiceP.INVOICENUMBER, sou.getName());
        String xml = HotelCreateXML.constructXMLFile(iC, IMess.INVOICEPATTERN,
                sou.getInvoiceD());
        boolean ok = false;
        if (xml != null && HotelVerifyXML.verify(iC, xml, IMess.INVOICEXSD)) {
            ok = true;
        }
        if (!ok) {
            iC.getLog().getL().info(xml);
            String mess = iC.logEvent(IMessId.INPROPERINVOICEXML,
                    dest.getName(), IMess.INVOICEXSD);
            throw new HotelException(mess);
        }
        dest.setInvoiceXML(xml);

Code for opposite XML->Map transformation.
        HotelChangeXMLToMap.constructMapFromXML(iC, dest.getInvoiceD(),
                sou.getInvoiceXML());
        String i = (String) dest.getInvoiceD().getdFields()
                .get(InvoiceP.INVOICENUMBER);

Additional information
There is also additional disadvantage for XML - searching and query running. Because it is not stored as a regular relational columns standard SQL statement cannot be used. So searching in XML requires additional effort - for instance in Google App Engine there is no standard method for scanning through XML. In order to find something one has to scan through table, retrieve XML string and by means of Java programming search through XML.
But there is good news for DB2 users. DB2  (available also for DB2-Express free edition) provides built-in engine (pureXML) for querying XML data without extracting them to the application.
For instance:
Finding all invoices greater then 10000$ can be as easy as running a query:

select * from invoice

where xmlexists('$c/Invoice/Total/Total > "10000"'

passing invoice.invoiceXML as "c")

wtorek, 6 grudnia 2011

Oracle, awk, tablespace

Problem
Assume we have the Oracle database schema export and want to deploy this schema to another database.
CREATE TABLE "SCOTT"."BONUS" 
( "ENAME" VARCHAR2(10), 
"JOB" VARCHAR2(9), 
"SAL" NUMBER, 
"COMM" NUMBER 
) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING 
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT) 
TABLESPACE "T_SPACE1" 
Probably after deploying this table definition we receive the error message ORA-00959 (Table space T_SPACE1 does not exist).  Of course - no problem to create T_SPACE1 table space manually but what to do if we have thousands of tables with tens or even hundreds different table spaces ?
Solution
I found the following awk script useful. It simply extracts all table spaces names from table schema export and creates a script for creating all table spaces used. Datafile name is the same as table space name.
BEGIN {  }

/TABLESPACE/ {
   for (i=1; i < NF; i++) {
     if ($i == "TABLESPACE") {
       TSPACENAME=toupper($(i+1))
       gsub(",.*","",TSPACENAME);
       namespaces[TSPACENAME] = 1;
     }   
   }
}
END { 
  for (i in namespaces) {
    TSPACENAME=i;
    TFILENAME=tolower(TSPACENAME); gsub("\"","",TFILENAME); 
    TFILENAME = TFILENAME ".dbf";

    print "CREATE TABLESPACE " TSPACENAME
    print "DATAFILE '/home/oracle/app/oracle/oradata/test/" TFILENAME "'"
    print "size 1m autoextend on next 1m maxsize 2048m EXTENT MANAGEMENT LOCAL;"
    print
  }
}
You can catch the standard output and modify it before deployment - for instance remove creating TEMP and USERS table space. An example of usage:
awk -f namespace.awk < MY_USERS_TABLES.SQL >create_tablespace.sql
Additional remarks

  1. This script assumes that TABLESPACE clause and the following table space name is in the same line. It does not hold true every time. It is possible to extend this script but it requires more coding.
  2. The predicat i<NF (not i<=NF) is on purpose. Just skips if TABLESPACE clause is the last in the line. Probably it makes sense to add a warning message on that.
  3. The gsub(",.*","",TSPACENAME); regards the case when table space name is followed by , (coma). For instance  TABLESPACE "T_SPACE", PARTITION .. Unfortunately awk does not allow to specify more than one field deliminator, so coma is included as a part of table space name and should be removed after that.

poniedziałek, 28 listopada 2011

Logging and Eclipse plugin

I added logging to my first Eclipse plugin. It seems to have been much simpler than I previously thought. Just simple class like that is enough:

package com.db2.sb.scriptlauncher;

import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.Status;

import scriptlaucherdb2.Activator;

public class DB2LogUtil {

        private static ILog getLog() {
                ILog log = Activator.getDefault().getLog();
                return log;
        }

        private static void log(String msg, int id, Exception e) {
                getLog().log(new Status(Status.INFO, Activator.PLUGIN_ID, id, msg, null));
        }

        static void log(String msg) {
                log(msg, Status.OK, null);
        }

        static void log(String msg, Exception e) {
                log(msg, Status.ERROR, e);
        }

        static void logError(String msg) {
                log(msg, Status.ERROR, null);
        }

        static void launchererrorLog(Exception e) {
                log(Messages.DB2LauncherError, e);
        }

}

wtorek, 22 listopada 2011

Delphi, DB2, Oracle compatibility

Introduction
DB2 Compatibility Mode allows to use Oracle PL/SQL server code to run on DB2. Starting from version 9.7 DB2 recognizes two SQL dialects. SQL/PL - native DB2 and PL/SQL - native Oracle. SQL/PL code is compiled into DB2 binary form and run by DB2 engine directly - no emulation, no wrappers. It makes migration from Oracle to DB2 more easy. What's more - it is possible to maintain the same server code for two databases. It could be very interesting option for ISV.
Import : unfortunately Oracle compatibility mode is not available for DB2 Express version (free). But one can download and install 90-day trial version of full DB2 release.
Also application client code is Oracle compatibility enabled. For instance DB2 JDBC driver is also enhanced to give the developer the access to Oracle features, for instance SYS_REFCURSOR OUT parameter in PL/SQL stored procedure.
Unfortunately - Delphi is not on the IBM list of supported languages or application frameworks. But DBExpress DB2 client available with every edition of Delphi can also run application against DB2 enabled for Oracle, although not all features are supported.
What is under test
To prove it I created a simple server code and simple Delphi application. Server code and application code contains only some features related to Oracle.
  • Execute methods in Oracle style packages with IN and OUT parameters.
  • Execute Oracle PL/SQL stored procedure outside package.
  • Execute Oracle PL/SQL UDF
  • Query the table having Oracle NUMBER type (added with DB 9.7)
  • Query the table having DATE column (there is difference between DB2 and Oracle implementation)
  • Read cursor returned from stored procedure.
Delphi code is created and executed as DUNIT test.
The application and server code is available here.
The purpose is to pass all test using the same application and server code for both databases.
Server code and migration
It is very simple and contains three tables and several simple PL/SQL code objects : packages, UDF and stored procedures. The migration of the server code from Oracle to DB2 was very simple - just deploy all object to DB2 without changing single line. Only stored procedure returning SYS_REFCURSOR should have been modified because of limitation related to Delphi client (not server)
Application code and migration
Application code is divided into two parts. One class is a general purpose class used for encapsulating some methods giving access to the database server and the second is the DUNIT test code.
Migration of the application code was more complicated than the server code, more changes were necessary. But - to my thinking - after acquiring know-how and following good programming practices it is possible to migrate application from Delphi and maintain the same application code for both databases.
Connecting to database
It is pretty easy for DB2. Just install DB2 Server Client  and it is ready to use by Delphi application. Oracle is a little bit more complicated if one uses instant-client. After unpacking client bundle it is necessary to manually set ORACLE_HOME variable and %ORACLE_HOME%\network\admin\tnsnames.ora connection profile.
After that the connection code is simple and easy:

procedure DBTest.ConnectOracle;
begin
  With Conn do
  begin
    Conn.DriverName := 'Oracle';
    Params.Values['USER_NAME'] := 'testuser';
    Params.Values['PASSWORD'] := 'testuser';
    Params.Values['DATABASE'] := 'TEST';
  end;
end;

procedure DBTest.ConnectDB2;
begin
  With Conn do
  begin
    DriverName := 'DB2';
    VendorLib := 'db2cli.dll';
    LibraryName := 'dbxdb2.dll';
    GetDriverFunc := 'getSQLDriverDB2';
    Params.Values['USER_NAME'] := 'db2inst1';
    Params.Values['PASSWORD'] := 'db2inst1';
    Params.Values['DATABASE'] := 'tsample';
  end;
end;

Calling stored procedure
For some reason to run stored procedure on Oracle requires ParamCheck attribute set as true and while running on DB2 it should be set as false. The solution is to create construct method for creating TSqlStoredProc class which has this attribute set accordingly and reuse this method accross application.

function DBTest.getSP(packName: String; procName: String): TSQLStoredProc;
var
  SP: TSQLStoredProc;
begin
  SP := TSQLStoredProc.Create(Nil);
  with SP do
  begin
    SQLConnection := Conn;
    SchemaName := '';
    PackageName := packName;
    StoredProcName := procName;
    case T of
      Occcracle:
        ParamCheck := true;
      DB2:
        ParamCheck := false;
    end;
    Params.Clear;s
  end;ddd
  result := SP;
end;
Keeping this limitation in mind there is  no problem with running PL/SQL stored procedure on DB2 database the same way as on Oracle. IN/OUT parameters are working as expected. Also stored PL/SQL procedure outside package can be called by omitting PackagName atrribute.
Example
procedure DBTest.addMessageToPackageLog(mess: String);
var
  SP: TSQLStoredProc;
begin
  SP := getSP('A_TESTLOG', 'ADD_MESSAGE');
  With SP do
  begin
    Params.CreateParam(TFieldType.ftString, 'MESS', TParamType.ptInput);
    ParamByName('MESS').AsString := mess;
    ExecProc;
  end;

PL/SQL UDF function
Any PL/SQL UDF function can be used as a part of SQL statement in DB2 also.
UDF Example
CREATE OR REPLACE FUNCTION ORACLE_FUNC 
RETURN INTEGER
AS
VARCOUNT INTEGER := 123;
BEGIN
    RETURN VARCOUNT;
END;

Statement
procedure TestDBTest.Testfunc;
var   Q: TSQLQuery;
  res : integer;
begin
  FDBTest.connect;
  { important to add ORACLE_FUNC alias, DB2 returns column number }
  Q := FDBTest.getQ('SELECT ORACLE_FUNC AS ORACLE_FUNC FROM DUAL');
  with Q do begin
    Open;
    res := FieldByName('ORACLE_FUNC').AsInteger;
    CheckEquals(123,res);
  end;

  FDBTest.disconnect;
end;

One has to remember that column name in this type of  a statement is different in DB2 and Oracle. So to keep the same statement text for both platforms it is necessary to use alias for column name.

NUMBER data type
To use NUMBER data type in DB2 DBExpress client it is necessary to update db2ini.cli file and set MapDecimalFloatDescribe parameter.
Example:
[TSAMPLE]
MapDecimalFloatDescribe = 3
DBALIAS=TSAMPLE
PWD=db2inst1
UID=db2inst1
It will map DECFLOAT datatype (NUMBER) to float data type in Delphi. One has to remember about the risk related to the loss of precision.
For some reason DBExpress requires for NUMBER stored procedure parameter ftBCD for Oracle and ftFloat for DB2. It can be resolved by introducing a function:

function DBTest.getBCDType : TFieldType;
begin
  case T of
  Oracle : result := ftBCD;
  DB2: result := ftFloat;
  end;
end;
After that call sequence can look like:
function DBTest.getNumberOfMessage: integer;
var
  SP: TSQLStoredProc;
begin
  SP := getSP('A_TESTLOG', 'GET_NUM');
  With SP do
  begin
    Params.CreateParam(getBCDType, 'NUM', TParamType.ptOutput);
    ExecProc;
    result := ParamByName('NUM').AsInteger;
  end;
end;
NUMBER datatype in different forms (NUMBER, NUMBER(10), NUMBER(14,4)) works as expected.
DATE data type
DATE data type behaves differently in DB2 and Oracle. In DB2 it keeps year, month and day, in Oracle also hours, minute and millisecond. But in Oracle Compatibility Mode the DATE behaves the same way as in Oracle.
ftCursor
The stored procedure:
CREATE OR REPLACE PROCEDURE PROC_CURSOR (VARRESULT OUT SYS_REFCURSOR)
AS
STMT VARCHAR2(2000);
BEGIN
  A_TESTNUM.PREPARE_DATA; 
  OPEN VARRESULT FOR SELECT * FROM TABLE_NUM;
END;
This stored procedure can be deployed and used in DB2 without any problems. But DBExpress DB2 client does not support ftCursor output parameter although it supports returning cursor from native SQL/PL stored procedure. Unfortunately, there is no way to transform PL/SQL cursor to returning cursor in DB2 SQL/PL- even by means of C++/Java/SQL stored procedure just to keep original PL/SQL procedure untouched.
In order to avoid code duplicating (two versions of the cursor stored procedure) small refactoring is necessary in the server and client code to minimize code branching.
Server code:
CREATE OR REPLACE PROCEDURE PROC_CURSOR (VARRESULT OUT SYS_REFCURSOR)
AS
STMT VARCHAR2(2000);
BEGIN
  PREPARE_PROC_CURSOR(STMT);
  OPEN VARRESULT FOR STMT;
END;
@

CREATE OR REPLACE PROCEDURE PREPARE_PROC_CURSOR (STMT OUT VARCHAR) AS
BEGIN
  A_TESTNUM.PREPARE_DATA; 
  STMT :=  'SELECT * FROM TABLE_NUM';
END; 
@

CREATE OR REPLACE PROCEDURE INSERT_NUMS(NUM1 IN A_TESTNUM.N1%TYPE, NUM2 IN A_TESTNUM.N2%TYPE,NUM3 IN A_TESTNUM.N3%TYPE) AS
  BEGIN
    A_TESTNUM.PA_INSERT_NUMS(NUM1,NUM2,NUM3); 
  END INSERT_NUMS;
@

CREATE PROCEDURE DB2_PROC_CURSOR ()
        DYNAMIC RESULT SETS 1
P1: BEGIN
        -- Declare cursor
        DECLARE STMTA VARCHAR2(2000);
        BEGIN
            CALL PREPARE_PROC_CURSOR(STMTA);
        PREPARE STMT_P FROM STMTA;
    END;        
    BEGIN
          DECLARE cursor1 CURSOR WITH RETURN for STMT_P;
          OPEN cursor1;
        END;
END P1

Assuming that the most important logic is hidden in preparing statement text this part of the code can be reused. Only small part responsible for creating a cursor is database dependent. But this parts are pure mechanical ones and there is nothing special there.
Also calling sequence at the application side should be modified.

function TestDBTest.getCursorSP: TSQLStoredProc;
var
  SP: TSQLStoredProc;
begin
  case FDBTest.getT of
    Oracle:
      begin
        SP := FDBTest.getSP('PROC_CURSOR');
        SP.Params.CreateParam(TFieldType.ftCursor, 'VARRESULT',
          TParamType.ptOutput);
      end;
    db2:
      SP := FDBTest.getSP('DB2_PROC_CURSOR');
  end;
  result := SP;
end;
It is bad news. But good news is that the rest of the code (Open, Next, Eof) is exactly the same for both platforms.
Devart DBExpress
Devart DBExpress client for Oracle client (purchasable) covers much more functionality than the standard DBExpress client delivered with Delphi. For instance BOOLEAN datatype parameters in stored procedure are supported. Also it is possible to call UDF directly (not as a part of SELECT statement) and retrieve the result. This functionality is not supported by DB2 DBExpress client.  Migrating application heavily dependent on this additional functionality requires much more effort.
Conclusion
Although one cannot say that migrating Delphi application from Oracle to DB2 is a piece of cake it is astonishing that so much can be achieved even the if DBExpress client for DB2 is not Oracle Compatibility Mode enabled. What is more important - ISV can maintain the same code for both platforms just to cover both market shares.
The Grande Finale
DB2 and Oracle

wtorek, 15 listopada 2011

My next Eclipse plugin

I created my next simple Eclipse plugin in shape of application launcher. The source code is available here.
Introduction
Assume that we want to create a launcher for an external application. The behavior of this application can be customized by a list (tree) of features. Our purpose is to create an application launcher which contains tab allowing to select/enable/disable features available. We also expect current features selection to be persisted.
The output looks like:
























By virtue of the application launcher manager it is possible to create and use more than one feature set.
Additional information
This plugin does not contain any logic to define external application to run. The launch extension is empty. The purpose is only to implement displaying and handling list of features.
Handling (displaying) tree of features is done by a separate package. This package can be reused outside configuration launcher context. It contains also 'Main' class to be launched as SWT application.
In the ILaunchConfigurationWorkingCopy interface only features value (true/false) are persisted, without tree structure. So in order to save and restore the content it is necessary to apply the same (here pre-order) tree traverse algorithm.
Future extension
  • The tree structure is hardcoded in the plugin. It could be nice to read the tree structure from external resource (e.g. XML file)
  • Only tree values (without tree structure) are persisted. In case of modifying tree structure the actual list of features could not match the changed tree structure. It could be the cause of problems. This problem can be mitigated by keeping additional tree identifier. In case of changing this modifier simply reset the list to default values.

niedziela, 13 listopada 2011

Byliśmy na koncercie

Dnia 9 listopada 2011 byliśmy na koncercie w wykonaniu Zespołu Instrumentów Dawnych Warszawskiej Opery Kameralnej. Podobało nam się bardzo, ale trudno żeby się nie podobał repertuar złożony z koncertów Bacha i Vivaldiego, w pięknym wnętrzu Sali Balowej Zamku Królewskiego, w bardzo dobrym wykonaniu.
W pierwszej części słyszeliśmy koncerty skrzypcowe Bach i Vivaldiego na skrzypce solo, dwoje i czworo skrzypiec. Nie wiem, czy było dobrym pomysłem żeby partie solowe w każdym koncercie grali kolejno wszyscy członkowie zespołu. Jak na moje ucho, czasami cierpiała na tym precyzja wykonania.
Ale wszystko przyćmiła druga część koncertu na którą złożyły się koncerty klawesynowe Bach, kolejno na klawesyn solo, dwa, trzy i cztery klawesyny. Zwłaszcza, że niemal przez ramię mogliśmy podglądać grę znakomitego klawesynisty Władysława Kłosiewicza. Tak samo oceniła to wykonanie licznie zgromadzona publiczność, która oklaskami zmusiła zespół do wykonania bisu, jakim była brawurowa i dynamiczna ostatnia część Koncertu a-moll na cztery klawesyny.
Dobrym pomysłem było zestawienie w ramach jednego wieczoru koncertów klawesynowych z ich skrzypcowymi pierwowzorami. Wymieniony wyżej koncert a-moll na cztery klawesyny i koncert h-moll Vivaldiego na czworo skrzypiec oraz koncert c-moll na dwa klawesyny który jest identyczny z koncertem d-moll na dwoje skrzypiec. I skrzypcowe oryginały i ich klawesynowe transkrypcje podobały się bardzo.

sobota, 29 października 2011

DB2 script launcher, Eclipse plugin (continue)

I added two small enhancements to DB2 script launcher.
Enhancement 1
Launch configuration dialog automatically for the first time when user right-clicked "Run as script launcher" and any configuration has been created so far (source).
Code snippet:
// Open launch configuration dialog for enter first configuration
  String name = manager.generateLaunchConfigurationName(NEW_CONFIGURATION);
  ILaunchConfigurationWorkingCopy wc = type.newInstance(null,name);
  configuration = wc.doSave();
  LaunchConfigurationManager lcm = DebugUIPlugin.getDefault().getLaunchConfigurationManager();
                                
  ILaunchGroup group = lcm.getLaunchGroup(type, ILaunchManager.RUN_MODE);
  DebugUITools.openLaunchConfigurationPropertiesDialog(getShell(), configuration, group.getIdentifier());

Enhancement 2
The second is related to the problem of invalidating launch configuration when user is changing something. It was achieved be means of updateLaunchConfigurationDialog() method. Code snippet (source):
ModifyListener listener = new ModifyListener() {

  @Override
  public void modifyText(ModifyEvent arg0) {
     updateLaunchConfigurationDialog();
  }
};
............
  db2Alias.addModifyListener(listener);

poniedziałek, 24 października 2011

Byliśmy na koncercie

23 października 2011 byliśmy na koncercie Warsaw Camerata w praskiej bazylice na Kawęczyńskiej, podobało nam się bardzo.
Orkiestra Warsaw Camerata, którą kieruje Paweł Kos-Nowicki, powstała w 2010 roku i jej celem jest prezentowanie muzyki znanych i trochę mniej znanych kompozytorów w bardzo dobrym wykonaniu, podobającej się widzom. I udało się to znakomicie.
Vivaldi napisał 39 koncertów na fagot i orkiestrę, tutaj usłyszeliśmy dwa, solistą był Leszek Wachnik. Wszyscy znamy doskonale 40 symfonię g-moll Mozarta, z przyjemnością wysłuchaliśmy jeszcze raz.
W ostatniej części koncertu orkiestra dała widzom możliwość wyboru jednego z trzech utworów. Do wysłuchania każdego z nich zachęcał jeden z muzyków orkiestry. Trochę żałowałem, że przez aklamację została wybrania "Orawa" Wojciecha Kilara, chętniej bym usłyszał "Lituanię" Romualda Twardowskiego, utwór premierowy, więc muzyka zupełnie nowa. Ale "Orawa", kompozycja rytmiczna i dynamiczna, też podobała się  bardzo.
Orkiestra dała też widzom możliwość wyboru repertuaru na następny koncert dnia 20 listopada. My wybraliśmy koncert skrzypcowy D-dur Beethovena oraz symfonię C-Dur Georgesa Bizeta (utwór mniej znany od pozostałych, ale na pewno wart poznania). Jesteśmy ciekawi, czy nasze gusta się pokryją z gustami pozostałych widzów.

niedziela, 16 października 2011

Byliśmy w teatrze

Byliśmy 6 października 2011 na spektaklu "Sprawa" w Teatrze Narodowym, podobało nam się bardzo.
"Samuel Zborowski" Słowackiego należy do tzw. "okresu genezyjskiego" w twórczości poety. Nie jest to oczywiście dramat z historii Polski ani głos w dyskusji, jakie właściwie motywy kierowały Zamojskim,  że doprowadził do ścięcia Zborowskiego. Temat ten budzi emocje także współcześnie, o czym świadczy znakomita książka Rymkiewicza "Samuel Zborowski" .
W dramacie Słowackiego nie sam Zborowski jest nawet głównym bohaterem, w inscenizacji w Teatrze Narodowym nazwa utworu została zamieniona na "Sprawa". Utwór nie ma wyraźnego zakończenia, sprawia wrażenie niekompletnego, jakby autor dopiero tworzył szkice dla późniejszej, bardziej spójnej, finalnej wersji. Dramat nie dzieje się w jakimś określonym czasie, czas jest tutaj rzeczą płynną. Nie dzieje się także w jakimś określonym miejscu, przestrzeni. Duchy i osoby pojawiają się i znikają, jedne zamieniają się w drugie, sam Lucyfer przyjmuje kilka imion i ról. Ale jest rzeczą niezwykłą, że to zdawać by się mogło niesceniczne dzieło, doczekało się już 13 realizacji.
Głównym bohaterem jest Lucyfer, ale nie jest to szatan, przeciwnik Boga. Bardziej przypomina Mefistofelesa z legendy o Fauście, ale Mefistofeles to przecież także szatan starający się wyrwać duszę Fausta Bogu. Lucyfer jest tutaj bardziej stworzeniem duchowym szukającym drogi do Boga, ale poza ramami jakiejkolwiek zorganizowanej religii czy oficjalnej teologii.
Dramat wyraża wiarę w istnienie Ducha przenikającego przez ludzkie dzieje i dążącego do osiągnięcia pełnego zrozumienia planów Boga.
W pierwszym akcie widzimy marzenie senne Eoliona wcielającego się w egipskiego faraona, łącznika między światem bogów i ludzi. W drugim akcie Eolion stawia się na wezwanie Walkirii i spotyka Dziewicę, córkę rybaka. Zaczynają wspólną wędrówkę, ale gdy są już o krok od ostatecznego poznania tajemnicy, Lucyfer wtrąca ich w przepaść, bo nie nadszedł jeszcze na to czas. W trzecim akcie Lucyfer pojawia się na dworze Amfitryty. Królestwo Amfitryty to nie tyle ocean, ale piekło, coś co powstało na samym początku i znajduje się w najniższym kręgu świata. Z tego świata kiedyś wyrwał się Lucyfer, przyoblekł się w ciało i rozpoczął mozolną i bolesną wędrówkę ku górze. W czwartym akcie przenosimy się do Zamku. Książę (ojciec Heliona) stracił zmysły, umiera i przybyli mnisi przygotowują się do wyprawienia pogrzebu. W piątym akcie Zamek zamienia się w sąd, gdzie stają dwaj protagoniści : Książę, w którego wcielił się duch kanclerza Zamojskiego i Samuel Zborowski. Końcową część utworu wypełnia ogromna przemowa Lucyfera-Adwokata. Ale celem nie jest ustalenie czyjejś winy w tym sporze. Tak jak przywoływany kilkakrotnie Jan (autor nowotestamentowej Apokalipsy) pokazuje niebo i wydarzenia na ziemi widziane z nieba, tak Adwokat ukazuje konflikt Zborowski-Zamojski widziany ze świata duchów. Duch objawił się w Samuelu Zborowskim. Miał się rozprzestrzenić na cały naród polski, wynieść go nad inne narody i z Lucyferem na czele wstąpić do niebiańskiego miasta. Ale Zamojski, kierowany suchym legalizmem kazał ściąć Zborowskiego i ten zamysł legł w gruzach.
Przedstawienie w Teatrze Narodowym było bardzo dobrą inscenizacją tego niejasnego i nie wszędzie spójnego dramatu. Autorzy nie starali się za wszelką cenę opowiedzieć jakiejś historii czy szukać współczesnych analogii, ale oddać jak najlepiej treść dramatu. Ale mam wątpliwość czy bez lektury samego tekstu jest możliwe ogarnięcie sensu. Zaś współczesne analogie się nasuwają - słynne słowa Papieża wypowiedziane 2 czerwca 1979 w Warszawie "Niech zstąpi Duch Twój i odnowi oblicze tej ziemi" (odniesienie do Ps 104) nasuwają skojarzenie do ducha, który poprzez Zborowskiego miał zmienić bieg historii. Jak wiemy - tym razem było to skuteczne.
Głównym bohaterem jest Lucyfer znakomicie zagrany przez Mariusza Bonaszewskiego. Lucyfer ma wiele wcieleń, raz wije się jak wąż, mówi głośno i cicho, dużo i mało, spokojnie i gwałtownie, jak przystało na Lucyfera wydrwiwa "urzędników" oficjalnej religii w czwartym akcie.  W finalnej scenie Lucyfer-Adwokat uderza w dramatycznie i poważne tony i również brzmi to znakomicie. Dominika Kluźniak doskonale odnalazła się w roli młodzieńczego Eoliona. Pozostałe postacie mają mniej do powiedzenia, ale z przyjemnością oglądaliśmy Jerzego Radziwiłłowicza w roli Księcia/kanclerza Zamojskiego.
W środku przedstawienia wstawiony jest przerywnik w postaci relacji z pogrzebu Słowackiego na Wawelu jaki miał miejsce w 1927 roku. Brzmi to trochę jak relacja nie z tego świata, gdyż współcześnie nie potrafimy sobie wyobrazić żeby państwo miało z takim rozmachem czcić pamięć jakiegokolwiek poety. Na końcu słyszymy - suflowane przez Lucyfera - słynne słowa: "bo królom był równy" i widzowie zadają sobie pytanie "czy był na pewno?". W drugim akcie Amfitryta w otoczeniu "oceanek" (przychodzi na pamięć Tytania z orszakiem elfów w "Śnie Nocy Letniej"), ubrane w stroje płetwonurków tańczą w rytmie hard-metalowej muzyki o natężeniu na progu wytrzymałości widzów. Gdy Lucyfer-Bukary przekonuje mnichów, że potrafi śpiewać dyszkantem, śpiew przechodzi w muzykę z finału "Czarodziejskiego Fletu" Mozarta.
Do końcowej przemowy Lucyfera (w dramacie zajmującej 1/3 całości) znakomicie została wykorzystana nowoczesna scena Teatru Narodowego. Dzięki temu przemowa, z trudnego dla widzów do wytrzymania monologu, zamieniła się w dynamiczne widowisko rozgrywające na kilku poziomach i scenach, nic nie gubiąc z samej treści.
Ponieważ sam dramat nie ma wyraźnego zakończenia, więc realizatorzy obudowali przestawienie na początku i końcu monologiem wygłoszonym przez młodego aktora wyrażający respekt wobec ogromu wszechświata. Ale bym skłamał, gdybym powiedział że treść tego przesłania utkwiła mi w pamięci.
Przedstawianie dramatów genezyjskich Słowackiego to na pewno ciężki kawałek chleba. Parafrazując znane powiedzenie, jeśli realizacja wierna, to ciężka i nużąca, jeśli ma być piękna, to trzeba odejść od samego dramatu. Ale można z całą pewnością powiedzieć, że  "Sprawa" w Teatrze Narodowym uniknęła tej pułapki, jest piękna i wierna.

piątek, 7 października 2011

DB2 script launcher, Eclipse plugin

Introduction

I created my first Eclipse plug-in. It is quite simple and allows running from Eclipse a script (sql) file against DB2 database.
It is based on CLP - Command Line Processor - and runs script which are valid in terms of this tool. It does not use JDBC connection.
In order to run  this plugin it is necessary to install DB2 client package. It is free and available from IBM web page. DB2 database (remote or local) should be cataloged and ready to use.

How it runs
Firstly we have to create DB2 script launcher.





















"Database alias name" is an alias (catalog name) to DB2 database.
After having launcher defined we can simply right-click on any file (with .db2 or .sql extension) and run this script using database access connection defined.























More then one database configuration can be defined to have an access to more than one database. In this case the selection dialog will be displayed allowing choosing the database (configuration).
It is also possible to run script launcher in editor. The current content of the editor is copied to temporary file and CLP is launched.
If no configuration has been defined yet than launcher dialog will displayed allowing defining the first one.

Source code
Source code and plugin.xml file file available here. It is my first plugin and probably not everything is perfect.
Several problems have been especially troublesome and required scanning through the documentation.

How to launch the configuration programmatically to allow entering the very first configuration (source).
if (configurations.length == 0) {
    // Open launch configuration dialog for enter first configuration
    String name = manager.generateLaunchConfigurationName(NEW_CONFIGURATION);
    ILaunchConfigurationWorkingCopy wc = type.newInstance(null,name);
    configuration = wc.doSave();
    LaunchConfigurationManager lcm = DebugUIPlugin.getDefault().getLaunchConfigurationManager();
                                
    ILaunchGroup group = lcm.getLaunchGroup(type, ILaunchManager.RUN_MODE);
    DebugUITools.openLaunchConfigurationPropertiesDialog(getShell(), configuration, group.getIdentifier());

How to extract file path name (necessary to launch CLP) from eclipse IPath (source)
@Override
 public void launch(ISelection arg0, String arg1) {
      ITreeSelection iSel = (ITreeSelection) arg0;
      TreePath[] t = iSel.getPaths();
      TreePath fPath = t[0];
      Object o = fPath.getLastSegment();
      File f = (File) o;
      URI locationURI = f.getLocationURI();
      URI u = locationURI;
      String fileName = u.getPath();
      runDB2(fileName, null);
}

How to extract content from the open editor (source)
@Override
public void launch(IEditorPart arg0, String arg1) {
  ITextEditor i = (ITextEditor) arg0;
  IDocumentProvider dProvider = i.getDocumentProvider();
  IDocument iDok = dProvider.getDocument(i.getEditorInput());
  String content = iDok.get();
  runDB2(null, content);
} 
Future
This launcher is very simple but can be the basis for future developing. For instance
  1. Enter the separator between different sql statements inside the file. Now ; is hardcoded.
  2. Keep the history of launching to match previous results.
  3. Catalog database (connection method) from plugin directly.
  4. etc.

wtorek, 20 września 2011

GWT, editable table

I found very usable a Cell Table which allows to edit its cells (sample). It is quite easy to use it : while adding new column to the table use 'editable' cell in the constructor.
But standard editable cell types (EditTextCell, TextInputCell, DatePickerCell) have one limitation :  these columns in all rows are eligible to edit. So I found impossible to implement the following scenario:
  1. User is browsing through the table. Edit mode is blocked to avoid changing something by accident.
  2. Decides to edit one row.  Set on the switch 'edit this row' and this row is transformed to edit mode. But edit mode for all other rows is blocked.
  3. After finishing editing set off the switch and the row is again in look mode.
So in order to implement this scenario I was forced to create my own 'editable' cells by extending standard editable cell type.
Example - switchable version of 'TextInputCell'
private class EditStringCell extends TextInputCell implements IGetField {

        private final IVField v;

        EditStringCell(IVField v) {
            this.v = v;
        }

        @Override
        public void render(Context context, String value, SafeHtmlBuilder sb) {
            Object key = context.getKey();
            Integer i = (Integer) key;
            boolean editenabled = eCol.isEditable(i, v);
            if (editenabled) {
                super.render(context, value, sb);
                return;
            }
            // Get the view data.
            // copy and paste from TextInputCell
            // the only difference is different HTML for displaying value
            TemplateDisplay template = GWT.create(TemplateDisplay.class);
            ViewData viewData = getViewData(key);
            if (viewData != null && viewData.getCurrentValue().equals(value)) {
                clearViewData(key);
                viewData = null;
            }
            String s = (viewData != null) ? viewData.getCurrentValue() : value;
            if (s != null) {
                sb.append(template.input(s));
            } else {
                sb.appendHtmlConstant("");
            }
        }

The difference is only in 'render' method. If 'edit mode' is on it simply invokes standard method. If 'edit mode' is off it overrides standard and displays the content. In order to have it working correctly I had to copy and modify the body of standard method to deal with specific way of storing value of the cell in 'ViewData' type.
Although I'm not very happy with that is seems working for me.
The all 'switchable' edit cell I'm using are implemented in this source.

niedziela, 11 września 2011

New version Boa tester

New version of Boa test framework.
In order to avoid CR/LF (DOS) and LF (Linux) hell I had to change a method for file comparison. I replaced standard filecmp.cmp function with manual line by line comparison. Source file.

Before:
  eq = filecmp.cmp(sou, dest)
  if not eq:
    logging.info("  different")
    res = 0

Now:
f1 = open(sou, "r")
 f2 = open(dest, "r")
 li1 = f1.readlines()
 li2 = f2.readlines()
 if len(li1) != len(li2) :
    logging.info(" number of lines is different")
    res = 0
    continue
 for i in range(0, len(li1))  :
 line1 = li1[i].rstrip()
 line2 = li2[i].rstrip()
 if line1 != line2 : 
    logging.info(" line number: " + str(i)  + " different")
    logging.info(line1)
    logging.info(line2)
    res = 0
    break

niedziela, 21 sierpnia 2011

Trusted context and LBAC

Introduction
I decided to create another example to demonstrate the power of trusted connection. This time I will use LBAC Label Based Access Control in DB2. It enables more granular control on access  to the data than standard SQL GRANT and REVOKE clause. We can limit access not only the whole database objects (like tables and views) but also to the particular rows and columns in tables. Important: LBAC is not available in DB2 Express Edition (free). But it is accessible in demo (90 day) version of DB 9.7.
More information on LBAC: http://www.ibm.com/developerworks/data/tutorials/dm0605wong/

Example
Let's use the standard SAMPLE database. Assume that out of the blue very restrictive access policy in our HR department has been announced - women can only look at the data of female workers and men only male workers (or opposite). How to enforce that policy without costly and time consuming rewrtiting of our HR software ?
The rescue is the usage of LBAC. To cut the long story short it is based on labels which are assigned to the rows in the EMPLOYEE table. Than the same label is assigned to the user and by comparing these labels DB2 limits access to the row in the table.
Enabling LBAC policy on EMPLOYEE table is little bit complicate and requires several steps. But very important: it is done at the database level, does not involve any changed in the software.

Step1 - create LBAC enabled table
In order to avoid spoiling SAMPLE database I will create additional LBAC_EMPLOYEE table being a copy of the EMPLOYEE_table

create table lbac_employee like employee
grant select on table lbac_employee to role samplereadaccess
insert into lbac_employee (select * from employee)

Step 2 - create security label component and policy
CREATE SECURITY LABEL COMPONENT SEX SET {'F','M'}
CREATE SECURITY POLICY SEX_POLICY COMPONENTS SEX WITH DB2LBACRULES
ALTER SECURITY POLICY SEC_POLICY USE ROLE AUTHORIZATIONS

Step 3 - enable LBAC on a table

ALTER TABLE LBAC_EMPLOYEE ADD SECURITY POLICY SEX_POLICY

ALTER TABLE lbac_employee ADD COLUMN SEX_PROTECTION DB2SECURITYLABEL
DB21034E The command was processed as an SQL statement because it was not a
valid Command Line Processor command. During SQL processing it returned:
SQL20402N Authorization ID "DB2INST1" does not have the LBAC credentials to
perform the "ALTER" operation on table "DB2INST1.LBAC_EMPLOYEE".
SQLSTATE=42519

The second command failed - it means that our LBAC protection over EMPLOYEE table is working. Only an user having valid LBAC security level can modify the table. Even instance owner (db2inst1) cannot modify LBAC protected table without LBAC authorization !

Step 4 - create role and security labels

CREATE SECURITY LABEL SEX_POLICY.ADM COMPONENT SEX 'F','M'
GRANT SECURITY LABEL SEX_POLICY.ADM TO user db2inst1 FOR WRITE ACCESS
DB21034E The command was processed as an SQL statement because it was not a
valid Command Line Processor command. During SQL processing it returned:
SQL0554N An authorization ID cannot grant a privilege or authority to itself.
SQLSTATE=42502

Again - the second command failed. Starting from DB2 9.7 security role has been separated from the database administrator role. Only the user having SECAMD (security administrator) role can assign LBAC privileges to users.

Step 5 - create SECADM user
# linux commmand
adduser db2sec1
grant secadm on database to user db2sec1
# connect to SAMPLE database as db2sec1 user and rerun the command
GRANT SECURITY LABEL SEX_POLICY.ADM TO user db2inst1 FOR WRITE ACCESS
(passed this time)

Step 6 - Finalize LBAC table protection
Reconnect as db2inst1 user and rerun the command again
ALTER TABLE lbac_employee ADD COLUMN SEX_PROTECTION DB2SECURITYLABEL
(passed this time)

Step 7 - Create roles and additional security labels
Assume that role LBAC_F enables access to female employees and LBAC_M to male employees.

create role LBAC_F
create role LBAC_M
CREATE SECURITY LABEL SEX_POLICY.F COMPONENT SEX 'F'
CREATE SECURITY LABEL SEX_POLICY.M COMPONENT SEX 'M'
alter security policy sex_policy user role LBAC_F
alter security policy sex_policy user role LBAC_E
GRANT SECURITY LABEL SEX_POLICY.F TO ROLE LBAC_F for all access
GRANT SECURITY LABEL SEX_POLICY.M TO ROLE LBAC_M for all access
GRANT samplereadaccess to role LBAC_F
GRANT samplereadaccess to role LBAC_M
Step 8 - assign labels to the LBAC_EMPLOYEE rows

update lbac_employee set sex_protection=seclabel('SEX_POLICY','M') where Sex='M'
update lbac_employee set sex_protection=seclabel('SEX_POLICY','F') where Sex='F'
select substr(seclabel_to_char('SEX_POLICY',sex_protection),1,6) from lbac_employee
Step 9 - modify trusted connection object
Assume the user 'PETER' is allowed to read only males workers and user 'DOROTHY' only female workers.
ALTER TRUSTED CONTEXT MYTCX ADD USE FOR PETER ROLE LBAC_M WITHOUT AUTHENTICATION
ALTER TRUSTED CONTEXT MYTCX ADD USE FOR DOROTHY ROLE LBAC_F WITHOUT
AUTHENTICATION

Step 10 - run application and watch the output

I modified the application a little bit - look at the source code. It is very simply - just run and print the "SELECT * FROM LBAC_EMPLOYEE" statement. The same code is executed firstly by 'PETER' and secondly by 'DOROTHY'.  The first time the code yields 23 rows and the second time 11 rows. What more important - the user is unaware about the presence of the other rows so the access to this rows is forbidden at the database (not application) level.

piątek, 29 lipca 2011

Find all subsets recursively

Problem
Having a set of positive numbers from 0 to 9 find recursively all subsets which sums up to 10.

Algorithm 1
Take a subset. If sums up to 10 then print it. Is sums less than 10 then break. Then apply the same algorithm for any subset received by removing from the first subset one element.
Start with the initial set.
C++ code :
#include <iostream>
#include <vector>

const int SET_SIZE = 10;
typedef std::vector<bool> settype;
const int SUM=10;

void print(const settype &set) {
  for (int i=0; i<SET_SIZE; i++) {
    if (set[i]) std::cout<<i+1<<' ';
  }
  std::cout<<std::endl;
}

void run_set(settype set,int sum, int remove) {
  if (remove != -1) {
    set[remove] = false;
    sum -= (remove + 1);
  }
  if (sum < SUM) return;
  if (sum == SUM) print(set);

  for (int i = remove+1; i<SET_SIZE; i++) {
    if (set[i]) {
      run_set(set,sum,i);
    }
  }
}

int main(int argc, char **argv) {
    bool init_set[SET_SIZE] = { true,false,true,true,false,true,true,true,false,true};
    settype setOfNum(init_set,init_set + sizeof(init_set) / sizeof(bool));
    int sum = 39;
    run_set(setOfNum,sum,-1);   
    return 0;
}


Algorithm 2
Apply a variant of 0-1 snapsack problem.
Take a subset.
  1. If subset sums to less than 10 then break.
  2. Remove last element and find all subsets which sums to 10.
  3. Remove last element and find all subsets which sums to 10 minus the last element.
  4. Sums set of sets received in point 1) and set of sets received in point 2) plus the last (removed) element.
C++ code:
#include <iostream>

#include <vector>
#include <algorithm>
using namespace std;

const int SUM=10;

typedef std::vector<int> settype;
typedef std::vector<settype> listofset;

void print(const settype &set) {
    for (int k=0; k<set.size(); k++) {
      cout << set[k] << ' ';
    }
    cout << endl;
}  

void D(int i,int w,const settype &set,listofset &outlist) {
  listofset list1,list2;
  i--;
  if ((w == 0) || (i == -1)) {
    outlist = list1;
    return;
  }
  D(i,w,set,list1);
  D(i,w-set[i],set,list2);
  outlist = list1;
  for (int k=0; k<list2.size(); k++) {
    list2[k].push_back(set[i]);
    outlist.push_back(list2[k]);
  }
  if (set[i] == w) {
    settype t;
    t.push_back(set[i]);
    outlist.push_back(t);
    return;
  }
}
  

int main(int argc, char **argv) {
    int init_set[] = { 1,9,10,2,7 };
    settype set(init_set,init_set + sizeof(init_set) / sizeof(int));
    listofset outset;
    D(set.size(),SUM,set,outset);
    for (int i=0; i<outset.size(); i++) {      
     cout << i << ":";
      print(outset[i]);
    }  
    return 0;
}

Remarks
Of course - it is an artificial problem, the best way is to apply simple iteration without recursion.
Another problem is the complexity. The number of all subsets of n-element set is exponential (2 to power n). So the algorithm is no worse than that. But is it possible to do it faster - in polynomial complexity.

niedziela, 24 lipca 2011

BenchmarkSQL

I started playing with the opensource tool "BenchmarkSQL". It allows to run TPC-C like test allowing measuring OLTP performance on different databases. Unfortunately, I had to fix several minor bugs - it seemed that loading data does not work correctly - the order of columns in several tables does not match INSERT and LOAD command. I also added additional run-time parameters for setting some settings related to test.
-DNoW={number of warehouses}
-DNoT=10 {number of terminales}
-DNoM=5 {number of minutes for test to run}
The fixed version can be downloaded from : BenchmarkSQL
I run the test again DB2, Oracle and Postgress databases running on the same box : Ubuntu, ThinkPad T-42, 1.7 GH IntelPentium, 2 GB RAM.
The databases have been installed out of the box, using default settings. The current score is as follows (10 warehouses, 10 terminals, 5 minute test)
Oracle: tpmC = 410
DB2: tpmC = 260
Postgres : tpmC = 240
I will try to improve DB2 performance by providing some tunning.

wtorek, 19 lipca 2011

Byliśmy na przedstawieniu

Dnia 18 lipca 2011 roku byliśmy na przedstawieniu opery Mozarta "Idomeneo, król Krety" wystawianego w ramach XXI Festiwalu Mozartowskiego, ale wyszliśmy mocno rozczarowani.
"Idomeneo" to mniej znana opera Mozarta, pozostająca trochę w cieniu sławniejszych oper tego kompozytora. A zupełnie niesłusznie, to także piękna muzyka i efektowne arie. Tego samego zdania był przecież sam Mozart, który kilka zapożyczeń z "Idomeneo" przeniósł do późniejszych dzieł. Na przykład, marsz z towarzyszeniem recytatywu Elektry z pierwszego aktu jako żywo przypomina słynny "Ecco la marcia" z trzeciego aktu "Wesela Figara". Tylko, że w tym przedstawieniu zupełnie nie było tego słychać, jakby realizatorzy chcieli nas przekonać, że mniejsza popularność tej opery jest w pełni zasłużona.
Zupełnie zawiódł odtwórca głównej roli, wyraźnie czuł się bardzo niepewnie, kilkakrotnie niezbędna była pomoc suflera. W konsekwencji, w miarę upływu czasu każde pojawienie się tej postaci powodowało rosnący niepokój, czy coś znowu nie pójdzie źle. Zły nastrój udzielił się chyba także orkiestrze, która wyraźnie nie była w formie, czasami bardziej przeszkadzała wykonawcom niż pomagała. 
Także inscenizacja nie trafiła mi do przekonania, nie potrafiłem zrozumieć zamiłowania inscenizatora do pogrążania sceny w mroku, cały czas odnosiłem wrażenie, że wykonawcy śpiewają z piwnicy. W operze istotną rolę pełni morski potwór pustoszący Kretę jako karę za złamaną obietnicę Idomenea. Na scenie widzimy dwie istoty z zaświatów - raz to prawdziwe monstrum, zaś potem na scenie nam towarzyszy ubrane w złoto bóstwo, sądząc z trojzębu trzymanego w ręku miał to być bóg Posejdon. Ale to bóstwo pada pod ciosem Idamanta, zaś potem głos z zaświatów oznajmia ostateczną wolę Posejdona i sztuka znajduje szczęśliwe zakończenie. Więc jeśli był tutaj jakiś zamysł inscenizatorski, to sens całkowicie mi umknął.
Na szczęście pozostali wykonawcy stanęli na wysokości zadania. Nie zawiódł ani Sylweser Smulczyński jako Idamante, ani Marta Boberska jako Ilia. Zaś każde pojawienie się Gabrieli Kamińskiej w roli Elektry podnosiło temperaturę o kilka stopni. Końcowe wykonanie brawurowej arii ""D'Oreste, d'Ajace" zapadało w pamięć.
Także chór wypadł znakomicie. W "Idomeneo", inaczej niż w innym operach Mozarta, chór ma dużą rolę do odegrania, w tym partie solowe.
Ale złe wrażenie jednak pozostało. Trzeba mieć tylko nadzieję, że jeśli Warszawska Opera Kameralna utrzyma w programie to przedstawienie w następnym roku, to poprzedzi występ bardziej starannymi przygotowaniami i z lepszym przemyśleniem, co zaprezentować widzom.

poniedziałek, 18 lipca 2011

Byliśmy na koncercie

16 lipca 2011 roku byliśmy na przedstawieniu (a właściwie projekcji) inscenizacji "Króla Rogera" w ramach tegorocznego Festiwalu "Ogrody Muzyczne", podobało nam się bardzo, chociaż z pewnymi zastrzeżeniami. Była to rejestracja przedstawienia, które miało swoją premierę w 2009 roku w paryskiej "Opéra Bastille". Przedstawienie odbyło się w atmosferze skandalu spowodowanego kontrowersyjną inscenizacją Krzysztofa Warlikowskiego. Przykłady druzgocących recenzji można znaleźć w Gazecie Wyborczej lub w polskiej edycji Newsweeka
Zacytuję fragment z tej drugiej recenzji:
Widzowie znający utwór słabo – Francuzi, którzy go dotąd nie widzieli, albo Polacy z rzadka bywający w operze – skłonni byli tej inscenizacji bronić.
 Jestem skłonny bronić tej inscenizacji, aczkolwiek z przyczyn innych niż to zakłada autor recenzji.
Gdyż o czym opowiada nam libretto opery (autorstwa Iwaszkiewicza) ? Na dwór mądrego króla Rogera, spędzającego wieczór w towarzystwie uroczej żony Roksany (równolegle odprawiana jest liturgia religijna) przybywa tajemniczy Pasterz, szukający zbłąkanych stad w imieniu nieznanego boga. W zetknięciu z Pasterzem, cały ten uporządkowany i zorganizowany świat świecki i religijny rozpada się w gruzy porywając także Rogerowi ukochaną Roksanę.
Pod to libretto można podkładać czy doszukiwać się dowolnych znaczeń, można także tego nie robić. I wyraźnie tą drugą drogą poszedł Warlikowski. Nie próbuje nam opowiadać jakiejś historii czy sugerować, że kryją się pod tym głębokie i ponadczasowe treści. Na scenie widzimy absurdalny i nierzeczywisty świat, przypominający atmosferą dawne filmy science-fiction. Chociaż końcowa scena, gdzie Pasterz ma założoną głowę Myszki Miki, zaś tłum oddaje cześć słońcu wyobrażonemu w postaci neonu przypominającego wejście do baru sugeruje, że owym nowym bogiem, do którego prowadzi Pasterz są nasze współczesne bożki kultury masowej.
Można próbować to zrozumieć, ale wtedy ryzykujemy porażkę niczym tytułowy król Roger usiłujący zrozumieć, co się wokół niego dzieje.Oczywiście, tego typu inscenizacja odniesiona do np. opery Mozarta pewnie by raziła tanim efekciarstwem.
Głównym zarzutem jaki można postawić tej inscenizacji, jak zauważył we wprowadzeniu Zygmunt Krauze, jest to, że tutaj gubi się niestety piękna i oryginalna muzyka Szymanowskiego. Zamiast słuchać muzyki śledzimy na scenie kolejne pomysły inscenizatorskie.
Ogromnym atutem przedstawienie jest także doskonałe wykonawstwo: Mariusz Kwiecień, Olga Pasiecznik oraz Eric Cutler (jako Pasterz). Chociaż też można odnieść wrażenie, że czasami ekspresja o charakterze aktorskim przytłacza stronę muzyczną.
Część osób zgromadzonych w namiocie, gdzie odbyła się projekcja, była zniecierpliwiona długim wprowadzeniem w muzykę Szymanowskiego Zygmunta Krauze i Krzysztofa Baculewskiego. My jednak słuchaliśmy z zainteresowaniem, obaj rozmówcy doskonale znali temat, zaś Szymanowski - chociaż "pierwszy po Chopinie" - to ciągle człowiek i kompozytor mało znany, także w Polsce.
Uderzała także wysoka frekwencja, namiot na kilkaset osób był prawie pełny, i to pomimo tego, że muzyka Szymanowskiego nie jest łatwa w odbiorze, wymaga pewnego przygotowania od słuchacza. Dzieło Szymanowskiego zostawia dużo swobody wykonawcom,  na pewno jest dużo pola do popisu dla innych inscenizatorów i na pewno znajdą się widzowie i słuchacze.

czwartek, 7 lipca 2011

GWT+DB2+Spring

Next version of the simple database/GWT application. The whole source code is available and can be downloaded.
Introduction
The application displays the content of EMPLOYEE table from DB2 sample database. But what is worth personal data without keeping some  additional data like: documents, images, scans etc ? So I decided to extend the application by this feature.
Database
Data like that can be kept in database as blob column. Because more than one document related to a single employee can be stored I decided to create additional table to implement this one-to-many relationship.
create table empattach (id INTEGER GENERATED ALWAYS AS IDENTITY, empno char(6) NOT NULL, filename varchar2(100) NOT NULL, comment varchar2(100), adddate DATE DEFAULT CURRENT DATE, attach BLOB)
alter table empattach FOREIGN KEY FOREIGN_EMPNO (EMPNO) REFERENCES EMPLOYEE ON DELETE NO ACTION

Defining foreign key (the second statement) is not necessary from the application point of view. But by introducing this constraint we are sure that we would not have "dangling" attachment in our database, it is enforced by the database engine.
Because it is one-to-many relationship the "empno" column is not a primary key for empattach table, additional identity column (generated automatically) is defined.
Client application
Additional column "Att" was added. This column displays number of attachments related to the employee and also - after clicking at this cell - additional window pop-ups enabling some actions on attachments.


In order to activate this additional window GWT ActionCell extended as Button was used. The code is something like:
 
private class AttachClass implements ActionCell.Delegate {

  @Override
  public void execute(OneRecord object) {
   AttachmentDialog a = new AttachmentDialog(object, rInfo);
   PopupDraw p = new PopupDraw(a, false);
   p.draw(100, 100);
  }

 }

 private class ActionNumberCell extends ActionCell {

  private final RowFieldInfo f;

  ActionNumberCell(RowFieldInfo f) {
   super("", new AttachClass());
   this.f = f;
  }

  @Override
  public void render(Cell.Context context, OneRecord value,
    SafeHtmlBuilder sb) {
   FieldValue numb = GetField.getValue(f, value);
   sb.appendHtmlConstant("");
   sb.append(numb.getiField());
   sb.appendHtmlConstant("");
  }

 }

       private Column constructAction(RowFieldInfo f) {
  ActionNumberCell ce = new ActionNumberCell(f);
  return new ButtonColumn(ce);
 }


This column cell behaves like a normal button and activates an action after clicking at it.
Sending attachments to and fro
Uploading and downloading attachments is done by a traditional servlet, it does not make any sense to do it via RPC call.
So web.xml file keeps additional definitions for servlets implementing GET and POST method.
In GWT code uploading (sending attachment from client to the server) is done via FormPanel and triggering "SUBMIT" action.
Downloading (sending attachment from server to the client) is implemented via enabling Anchor (link) to click on.
Because together with attachment also content-type is sent (based on file name extension) the web browser will try to apply the proper action for the file sent - for instance: to display an image. It is very nice part of it, not trivial action  affordable for a song.
Server side
There is a standard definition of two servlets on the server side, one for uploading and the second for downloading. Storing and extracting blob column to and from the database is done via Spring LobHandler class. In order to send a valid content-type very nice feature of Java EE is used  :
MimetypesFileTypeMap (starting from Java 1.6 it comes also with JRE).
Refactoring
Unfortunately, by adding this feature the code size doubled. On the other by refactoring the code responsible for displaying a table on the screen (RecordPresentation.java) I was able to reuse this code twicely: to display the rows from the employee table and also for displaying the list of attachments (empattach table).

poniedziałek, 4 lipca 2011

BoaTester - new version

I added a simple enhancement do Selenium extension to BoaTest framework. I found very annoying using long XPath selectors for GWT Presentation CellTable. It looks like:
xpath=/html/body/table/tbody/tr[2]/td[2]/table/tbody/tr[3]/td/table/tbody/tr[2]/td/table/tbody/tr/td/table/tbody/tr[2]/td[2]/div
or
xpath=/html/body/table/tbody/tr[2]/td[2]/table/tbody/tr/td/table/tbody/tr[2]/td/table/tbody/tr/td/table/tbody/tr[2]/td/div[text() = '6'] 
But - if test case is related to the same table - all that selectors share the same starting xpath. So the solution is to introduce a "macro"or interpolation just avoiding entering repeating sequences. There is very useful option in Python ConfigParser class. But action sequence (selenium.file)  for Selenium based test case is read by basic File object so this option is not available immediately. So it was necessary to implement it manually through the following procedure:
"""Replace variables using format (like ConfigParser) %(..)s
    
    Args:
      line : line to be changed
      param : TestParam 
      teparam : OneTestParam
      
    Returns:
      Fixed line
      
    Raises: 
      Exception if variable for replacement not found
      
    """


def replaceLine(line,  param,  teparam):
    while 1 :
        low = line.rfind("%(")
        if low == -1 : break
        up = line.rfind(")s")
        if up == -1 : break
        before = line[0: low]
        after = line[up+2: len(line)]
        key = line[low+2: up]
        value = teparam.getPar(key)
        if value == None : value = param.getPar(key)
        if value == None : 
            raise TestException(key + " variable for replacement not found !")
        line = before + value + after
        
    return line     

I decided to use the same syntax (%(...)s) like in ConfigParser to avoid reinventing the wheel. "Variables" for macro substitution are taken for test.properties file.

So now it is possible to put long XPath selected sharing the same theme in much simpler and less error prone manner.

test.properties
tabletestbase=xpath=/html/body/table/tbody/tr[2]/td[2]/table/tbody/tr[3]/td/table/tbody/tr[2]/td/table/tbody/tr/td/table/tbody/tr[2]/td
and Selenium action file :
isPresent : %(tabletestbase)[2]/div
waitFor :  %(tabletestbase)s/td/div[text() = '6']

poniedziałek, 13 czerwca 2011

Byliśmy na koncercie

12 czerwca 2011 byliśmy na koncercie w ramach XXI Festiwalu Muzyki Sakralnej, ale wyszliśmy z mieszanymi uczuciami.
Był to koncert zamykający tegoroczną edycje festiwalu, wypełniła go muzyka polska. W pierwszej części usłyszeliśmy Gloria Tibi Trinitas Wincentego Maxylewicza żyjącego w pierwszej połowie XVIII wieku. Zachowało się po nim niewiele kompozycji, zapewne z tej przyczyny nie ma nawet wpisu w polskiej Wikipedii, chociaż jest notka o nim w niemieckiej edycji. Ale to co usłyszeliśmy bardzo się podobało, to melodyjna i efektowna muzyka.
Głównym punktem programu była Msza Polska Franciszka Lessela, kompozytora żyjącego 100 lat później. Po śmierci popadł w kompletne zapomnienie, część jego twórczości przepadła, ale ostatnio ponownie przypomina się tego twórcę publiczności. Jest rzeczą słuszną wydobywanie polskiej muzyki z niepamięci,  ale mam wątpliwość czy dotyczy to również tej kompozycji. W połowie tego liczącego  siedem części dzieła zaczęło nas ogarniać ogromne znużenie, po prostu jedna część niewiele się różniła od drugiej. Dopiero dwie ostatnie części wniosły pewne ożywienie, linia melodyczna była żywsza i ciekawsza.
Trudno powiedzieć, gdzie został popełniony błąd. Może dzieło wymaga lepszego osłuchania, może powinna być zmieniona koncepcja wykonawcza, może po prostu dzieła tego kompozytora powinny podlegać ściślejszej selekcji przez zaprezentowaniem przed publicznością. Tym bardziej, że sądząc z licznego udziału słuchaczy , muzyka polska, nawet jeśli są to dzieła nieznanych i zapomnianych autorów, może liczyć na duże zainteresowanie.

niedziela, 12 czerwca 2011

NTILE and DB2

Problem
NTILE is an analytic function. It is implemented in Oracle and MS/SQL database servers. It simply distributes rows into some number of buckets, something like dealing cards between players. Unfortunately it is not implemented in DB2 and there is no simple and obvious substitution for it. But it is possible to evaluate a similar solution in DB2 although it requires a little more consideration.

Solution
In DB2 it is not possible to write UDF which works as analytic/ranking function so we cannot provide exactly the same syntax.
But let's try to write a regular function which behaves like NTILE.

FUNCTION NTILE_FUN(A IN INTEGER, BUCKETNO IN INTEGER, NOFROWS IN INTEGER) 
  RETURN INTEGER
  AS
  MINROWS INTEGER;
  MINREST INTEGER;
  X INTEGER;
  FIRSTB INTEGER;
  LASTB INTEGER;
  ACTB INTEGER;
  BEGIN
     MINROWS := FLOOR(NOFROWS / BUCKETNO);
     MINREST := NOFROWS - (BUCKETNO * MINROWS);

     ACTB := 1;
     FIRSTB := 1;
     WHILE FIRSTB <= NOFROWS LOOP
       LASTB := FIRSTB + MINROWS - 1;
       IF MINREST > 0 THEN
         LASTB := LASTB + 1;
         MINREST := MINREST - 1;
       END IF;
       IF A <= LASTB THEN
         EXIT;
       END IF;
       FIRSTB := LASTB + 1;
       ACTB := ACTB + 1;
     END LOOP;      
     RETURN ACTB;
  END;

This function takes three parameters:
  • A : number of row to be inserted into bucket
  • BUCKETNO ; number of buckets
  • NOFROWS : number of all rows to be distributed
Returns the number of bucket (between 1 and BUCKETNO) where row A should be placed. So it works exactly the same way like NTILE. Probably it is possible to get rid of an iteration and provide some arithmetic to calculate the bucket number  but it works for me for the time being.

Examples
Now let's  take the following NTILE example.

select ntile(4)over(order by empno) grp,
         empno,
         ename
     from empno';

DB2 equivalence using function define above:

select NTILE_FUN(row_number( )over(order by empno),4,
             (SELECT COUNT(*) FROM empno)) grp,
         empno,
         ename
    from empno order by empno 
So in place of calling NTILE function we use NTILE_FUN function with appropriate  parameters. The most important point is using ranking function ROW_NUMBER to provide valid row number (the first parameter for NTILE_FUN).

Another example:

SELECT last_name, salary, NTILE(4) OVER (ORDER BY salary DESC) 
             AS quartile FROM empsalary
DB2 equivalence:
SELECT last_name, salary, 
             NTILE_FUN(row_number( )over(ORDER BY salary DESC),4, 
             (SELECT COUNT(*) FROM empsalary)) 
             AS quartile FROM empsalary
More complicated example with PARTITION BY:
SELECT
        Category,
        Weight,
        Entrant,
        NTILE(2) OVER (
            PARTITION BY Category
            ORDER BY Weight DESC
        ) AS Ntile
   FROM ContestResults
DB2 equivalence:
SELECT
        Category,
        Weight,
        Entrant,
        NTILE_FUN(row_number( )over(
            PARTITION BY Category
            ORDER BY Weight DESC),2, 
            (SELECT COUNT(*) FROM ContestResults as C WHERE C.Category = CC.Category) 
        ) AS Ntile
   FROM ContestResults AS CC
This time NOFROWS parameter is a little more complicated because we have to provide the number of record in one partition - not the number of records in the whole table.

Working code
The working code is available: DDL and SQL package.
Important: this code works in DB2 Oracle compatible mode. Unfortunately - this mode is not supported in DB2 Express (free edition) although is planned for the future. It is necessary to download and install DB2 trial (90-day).
After creating a database with PL/SQL support test tables should be created:
db2 -tvf create_ddl.sql
and deploy package:
db2 -td@ -vf create_package.sql
Then run command in order to see the output:
db2 set serveroutput on
and run example:
db2 call a_ntiletest.run_test1
  db2 call a_ntiletest.run_test2
  db2 call a_ntiletest.run_test3
  db2 call a_ntiletest.run_test4

Because this example is using PL/SQL syntax it is possible also to run it (after changing the value of ORACLE constant in package specification) in Oracle directly and compare the results.

czwartek, 9 czerwca 2011

Byliśmy na koncercie

5 czerwca 2011 roku byliśmy na koncercie w Bazylice Świętego Krzyża na koncercie w ramach XXI Miedzynarodowego Festiwalu Muzyki Sakralnej, podobało nam się bardzo, chociaż nie wychodziliśmy całkowicie zachwyceni.
Franciszek Liszt jest znany głównie jako autor utworów fortepianowych, brawurowych Rapsodi Węgierskich czy popularnych poematów symfonicznych. Mniej jest znany jako autor subtelnej muzyki religijnej, a właśnie taki repertuar był wykonany na koncercie.
Wykonawcami był chór chłopięcy Mozart Knabenchor Wien, orkiestra i soliści Opery Narodowej z Warszawy, zaś dyrygentem Peter Lang z Wiednia.
Głównym punktem programu była - chyba nigdy nie wykonywana w Warszawie - "Węgierska Msza Koronacyjna" skomponowana w latach 1866-1867, która poprzedziły trzy krótkie chóralne dzieła śpiewane a cappella. "Węgierska Msza Koronacyjna" to bardzo piękna muzyka, wielka szkoda, że tak rzadko wykonywana. W dawnych czasach bardzo popularny był w Warszawie sklep płytowy przy Węgierskim Instytucie Kultury, można było tam kupić płyty wydawane przez Hungaroton i jednym z moich nabytków była właśnie ta muzyka. Sama płyta i sprzęt do odtwarzania takich płyt już dawno mi przepadły, zaś Hungaroton i sklep muzyczny także nie wytrzymały naporu wolnego rynku, ale muzyka utkwiła mi w pamięci.
Dlaczego wychodziliśmy nie do końca usatysfakcjonowani ? Chór chłopięcy (wśród chórzystów byli nawet kilkuletni chłopcy) doskonale brzmiał w krótkich utworach wykonywanych a cappella, trochę przypominających polifoniczne kompozycje Palestriny. Natomiast w Mszy Koronacyjnej głosy chłopców jakoś gubiły się w obszernym kościele, przytłumiane przez orkiestrę. W konsekwencji muzyka nie robiła takiego wrażenia jak powinna i pozostał pewien niedosyt. Mam nadzieję, że będę kiedyś miał okazję ponownego wysłuchania tej muzyki, gdzie orkiestra i chór będą lepiej ze sobą współpracować.
Mieliśmy dodatkową atrakcję jeszcze przed wejściem do kościoła. Otóż uliczny grajek - sądząc z wyglądu zewnętrznego Rom (dawniej Cygan) albo przybysz z jakiegoś bałkańskiego państwa - dał koncert na harmonii. Koncert był niezwykły z dwóch powodów : kunsztu wykonawcy oraz repertuaru. Usłyszeliśmy - i to znakomicie wykonane - uwerturę do "Wesela Figara" Mozarta i jeden z tańców węgierskich Jana Brahmsa. W ten sposób koncert w kościele, gdzie muzykę wielkiego węgierskiego kompozytora wykonywał Mozart Knabenchor Wien znalazł godne siebie uzupełnienie.

czwartek, 2 czerwca 2011

New version of BoaTester

New version of BoaTester was uploaded. Two new features have been added. Details

piątek, 27 maja 2011

Trusted context and auditing

Auditing
Security is very important matter nowadays. One of the method to make our data more secure is auditing, monitoring and discovering unexpected or forbidden behavior. For instance - in the previous example - user 'Mary' tried to update the bonus column in 'Employee' table but was rejected. Let's go further - assume that dishonest user 'Mary' was able to overcome the application security control and tried to perform some forbidden activity directly at the database level. Because our well designed solution is protected at the two levels - application and database - this attack was repulsed. But it can be the matter of time that the malicious user devises the second plan to find a security hole and will be successful. The only way to protect our confidential data is to pin down the suspicious behavior as early as possible and react before a mischief is done.
Good news is that DB2 provides very rich set of tools for auditing and security monitoring.

Preparation
Unfortunately the auditing is not enabled by default.  So it is necessary to make some preparations.
1. Create (if not created so far)  tables necessary to keep audit data - there is a db2audit.dll file provided with every DB2 installation.
db2 -tvf {DB2 home directory} /misc/db2audit.ddl
2. Create AUDIT POLICY database object. There are a lot of options what to monitor and what audit granularity is expected. Here is the simple example for monitoring everything.
CREATE AUDIT POLICY BIGBROTHER
CATEGORIES ALL
STATUS BOTH
ERROR TYPE AUDIT;
3. Enable monitoring for roles or users.
AUDIT ROLE SAMPLEFULLACCESS  USING POLICY BIGBROTHER;
AUDIT ROLE SAMPLEREADACCESS  USING POLICY BIGBROTHER;
4. From now on all activities are monitored.
Pave the way for Sherlock Holmes
Several steps are necessary before audit data can be looked through.
1. Make an audit data snapshot.
db2 call "SYSPROC.AUDIT_ARCHIVE(NULL,NULL)"
2. Convert binary data to text CSV file
CALL SYSPROC.AUDIT_DELIM_EXTRACT(NULL, '/home/db2inst1/sqllib/security/auditdata', NULL, null, NULL);
3. Load CSV text files into relational tables created by running db2audit.dll script file
LOAD FROM /home/db2inst1/sqllib/security/auditdata/checking.del OF DEL MODIFIED BY DELPRIORITYCHAR LOBSINFILE INSERT INTO CHECKING;

-- To load the OBJMAINT table, issue the following command:
LOAD FROM /home/db2inst1/sqllib/security/auditdata/objmaint.del OF DEL MODIFIED BY DELPRIORITYCHAR LOBSINFILE INSERT INTO OBJMAINT;

-- To load the SECMAINT table, issue the following command:
   LOAD FROM /home/db2inst1/sqllib/security/auditdata/secmaint.del OF DEL MODIFIED BY DELPRIORITYCHAR LOBSINFILE INSERT INTO SECMAINT;

-- To load the SYSADMIN table, issue the following command:
   LOAD FROM /home/db2inst1/sqllib/security/auditdata/sysadmin.del OF DEL MODIFIED BY DELPRIORITYCHAR LOBSINFILE INSERT INTO SYSADMIN;

-- To load the VALIDATE table, issue the following command:
   LOAD FROM /home/db2inst1/sqllib/security/auditdata/validate.del OF DEL MODIFIED BY DELPRIORITYCHAR LOBSINFILE INSERT INTO VALIDATE;

-- To load the CONTEXT table, issue the following command:
   LOAD FROM /home/db2inst1/sqllib/security/auditdata/context.del OF DEL MODIFIED BY DELPRIORITYCHAR LOBSINFILE INSERT INTO CONTEXT;

--# To load the EXECUTE table, issue the following command:
   LOAD FROM /home/db2inst1/sqllib/security/auditdata/execute.del OF DEL MODIFIED BY DELPRIORITYCHAR LOBSINFILE INSERT INTO EXECUTE;

Description of all audit objects

5. Tables are ready and we can query them.

Example
We want to check if any user belonging to SAMPLEREADACCESS role has tried to update EMPLOYEE table :
This type of event is stored in CHECKING audit table:
So after running query like:
db2 "SELECT TIMESTAMP,SUBSTR(USERID,1,20),SUBSTR(APPNAME,1,20),STATUS  FROM CHECKING WHERE ROLEINHERITED = 'SAMPLEREADACCESS' AND OBJTYPE='TABLE' AND OBJNAME='EMPLOYEE' AND ACCESSATT='0x00000040000000000000000000000000'"
The horrible truth is revealed:
TIMESTAMP                  2                    3                    STATUS     
-------------------------- -------------------- -------------------- -----------
2011-05-27-22.47.38.565086 mary                 db2jcc_application          -551
Our database is under attack and we have to evaluate the whole situation. It is bad news. Good news that by virtue of applying DB2 trusted context and two layers security control this time the attack was repulsed, by virtue of DB2 auditing feature the security hole is unveiled  and we have some time to tighten our security policy before any harm is done.