Java EE 6 Testing with Arquillian Persistence Extension

Posted by admin | Posted in General Programming, Learning Materials, Tools | Posted on 22-12-2011

2

Today I will describe my newly discovered library which is an extension to the great JBoss Arquillian project.

The mission of the Arquillian project is to provide a simple test harness that abstracts away all container lifecycle and deployment from the test logic so developers can easily produce a broad range of integration tests for their enterprise Java applications.

Arquillian persistence extension allows you to test JPA related code without filling up the database with test data. All you have to do is to populate .yml file with your data specified. That will be best described with an example.

First of all add arquillian persistence dependencies to your project:

<dependency>
            <groupId>org.jboss.arquillian.extension</groupId>
            <artifactId>arquillian-persistence-api</artifactId>
            <version>${version.arquillian_persistence}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.extension</groupId>
            <artifactId>arquillian-persistence-impl</artifactId>
            <version>${version.arquillian_persistence}</version>
            <scope>test</scope>
        </dependency>

where ‘${version.arquillian_persistence}’ is equal to ’1.0.0.Alpha2′. I assume that you have the basic arquillian tests (for EJB’s and CDI) working, If not read on.

I have found myself setting up arquillian enabled project quite cumbersome, always ending up with some classpath dependency problems while trying to remote test my classes. For those of you new to Arquillian I would strongly recommend starting up with working example provided by Bartosz Majsak and available on github as a ‘Beer-Advisor‘ project.

The ‘Beer-Advisor’ project can be quickly refactored to suit your needs, it lack comments but its so clearly written that it shouldn’t be a problem to grasp the idea.

Ok then, lets get going with the arquillian persistence extension, we are going to use two nice annotations to help us testing our JPA layer: @UsingDataSet and @ShouldMatchDataSet.

We start off with doing some setup, I will copy part of the example from the Beer-Advisor project which you can easily clone and try for yourself:

Having an entity class Beer:

@Entity
public class Beer implements Serializable
{
   private static final long serialVersionUID = 5892013208071126314L;

   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   private Long id;

   @Basic
   private String name;

   @Basic
   private BigDecimal price;

   @Basic
   private BigDecimal alcohol;

   @Basic
   private String code;

   @Enumerated(EnumType.STRING)
   private Type type;

   // If lazy then Glassfish Embedded is facing this problem: https://bugs.eclipse.org/bugs/show_bug.cgi?id=323403
   @ManyToOne(optional = false, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
   private Brewery brewery;

   Beer()
   {
      // to satisfy JPA
   }

   public Beer(Brewery brewery, Type type, String name, BigDecimal price, BigDecimal alcohol)
   {
      this.name = name;
      this.price = price;
      this.alcohol = alcohol;
      this.type = type;
      this.brewery = brewery;
      this.brewery.addBeer(this);
   }
........ Getters and Setters ...............

and simple DAO for managing Beer objects on the persistence layer:

@RequestScoped
public class JpaBeerRepository implements BeerRepository
{
   @PersistenceContext
   private EntityManager em;

   @Override
   public Beer getById(Long id)
   {
      return em.find(Beer.class, id);
   }
..........

@Override
   public Set<Beer> fetchAll()
   {
      CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
      CriteriaQuery<Beer> query = criteriaBuilder.createQuery(Beer.class);
      Root<Beer> from = query.from(Beer.class);
      CriteriaQuery<Beer> select = query.select(from);

      Set<Beer> result = new HashSet<Beer>();
      result.addAll(em.createQuery(select).getResultList());

      return result;
   }

   @Override
   public void save(Beer beer)
   {
      if (beer.getId() == null)
      {
         em.persist(beer);
      }
      else
      {
         em.merge(beer);
      }
   }
..................

We can easily test our DAO by creating datasets in yml format and test our logic against it:

Beer:
  - id: 1
    alcohol: 4.5
    name: "Mocny Full"
    price: 1.0
    type: LAGER
    brewery_id: 1
  - id: 2
    alcohol: 55.0
    name: "End of history"
    price: 765
    type: BLOND_ALE
    brewery_id: 2
  - id: 3
    alcohol: 41.0
    name: "Sink The Bismarck!"
    price: 64.0
    type: QUADRUPEL_IPA
    brewery_id: 2
  - id: 4
    alcohol: 8.5
    name: "Delirium Tremens"
    price: 10.0
    type: PALE_ALE
    brewery_id: 3
  - id: 5
    alcohol: 8.4
    name: "Pauwel Kwak"
    price: 4.0
    type: AMBER
    brewery_id: 4

You should put your .yml file into test/resources/datasets folder in you maven project

@UsingDataSet annotation will populate our database with the data specified in beers.yml file

   @Test
   @UsingDataSet("beers.yml")
   public void shouldReturnBeerByItsId() throws Exception
   {
      // given
      Long beerId = 1L;
      String expectedName = "Mocny Full";

      // when
      Beer beers = beerRepository.getById(beerId);

      // then
      assertThat(beers.getName()).isEqualTo(expectedName);
   }

Now, lets say we want to add new Beer and see if the database will have a specific data in it, for this purpose we are going to use @ShouldMatchDataSet(..)

@Test
    @ShouldMatchDataSet("expected-insert-beer.yml")
    public void shouldSaveNewBeer() throws Exception{
        //when
        Beer b = new Beer();
                //set the necessary fields on the beer object
        beerRepository.save(b);
        assertThat(beerRepository).isNotNull();
    }

after inserting our Beer object to the database through the EntityManager we should end up with a single beer record in our database, exactly like the one specified in ‘expected-insert-beer.yml’.

Oracle Certified Master, Java EE 5 Enterprise Architect

Posted by admin | Posted in Learning Materials | Posted on 16-12-2011

2

Today I have received very long awaited email from Oracle:

Congratulations! You have passed the Oracle Certified Master, Java EE 5 Enterprise Architect certification. You are among the elite 1% of certified Java professionals who have gone on to achieve the Java Enterprise Architect certification.

I was waiting for the result for almost 4 months since I’ve finished the last part of the certification process and started to fear that I won’t pass while reading on JavaRanch about problems some people had with the diagrams.

Anyway, now I know what it is to be a certified architect ;)

Why you should join Stackoverflow

Posted by admin | Posted in General Programming, Learning Materials | Posted on 04-10-2011

0

Back in May 2008 when I first heard about stackoverflow idea I was really skeptical about it. You can read my rants about the whole idea here. Since then, I have used stackoverflow more and more and I have to say that for the last couple of months I actually love it.This was a long process for me but I can assure you, that this post is going to be completely different the the one written in the middle of 2008.
Once I started contributing and become a part of stackoverflow community I have discovered quite a few things about me and about the website itself.

I think many developers agree, that if you are into programming you are willing to learn all the time, learning is fun and if you have a source of learning repeatedly every day something new, you will be a happy developer. Of course many of us read books, blogs, community sites like reddit, dzone or digg, looking for something cool to read every day. Many of us do certifications just for fun, just to get some feeling of knowledge gaining. For me Stackoverflow is becoming one of the major sources of self development which I can do at my own peace.

Possibly, every person will find something different for himself at stackoverflow, and it all depends on our personal preferences and goals. If you are reading this and you still don’t use stackoverflow much, I urge you to try at least for a month.

1. You can learn by teaching


Everyone who has ever thought about improving his own learning efficiency knows this. When you try to teach what you have just learned, everything auto-magically get nicely organized in your head. All you have to do is to find a way to share your knowledge – now you can, use a stackoverflow of course.

You can read more about effective learning here.

2. You can collect stuff



Turbo bubble gum pictures by KnightRiderDDR


The first and last thing in my life I was actually collecting, was a chewing gum stickers in the middle eighties, they were called ‘Turbo’ and most of the people reading this won’t probably know them, as they were sold in eastern europe only(I can be wrong), it was fun though, but now I have stackoverflow :) .
For all the people collecting all kind of stuff, well, by using a stackoverflow you can fullfill your desire and collect something new – stackoverflow points and different kinds of badges

3. You can compete

People like competition of many kinds and they do it for whatever reason. Whatever reason you have, you can now compete on stackoverflow when providing better answers than others, collecting more points etc.
Remember, competition is a driving force of progress.

4. You can revise your knowledge

This is for me actually the most important point. Remember “forgetting curve”? No? Well, its because you have probably not revised that information and you have forgotten :)
This is simple, if you don’t repeat what you have learned you will finally forget it. By using a stackoverflow you can highly minimize or prevent totally your memory loss.

Forgetting Curve - Wikipedia

5. You can expand your knowledge

If you’ll jump into a stackoverflow mania you will spend there a bit more time than just the time answering/asking questions. You will probably be browsing the web app, reading notification of posted comments to your questions/answers, at the same time you notice other people answers, you’ll often find out a better approach of doing something than your own answer gave, links to an on-line articles, documentation, other interesting questions in your field of expertise etc, you’ll just find the best of the web posted by other people in one place, all related to whats the most interesting for you.

6. You can ask questions yourself

I almost forgot about this one. You can of course ask questions there and if they are not too difficult someone will probably answer them pretty fast :)

7. You can join Careers 2.0

I’ve just joined Careers 2.0 and I can say that it’s well done web app, unfortunately there is not a single company from Poland :) . Anyway I guess it can be useful for others (especially in the US + UK state)

8. You can meet people at Stackoverflow meet-up days



Haven’t tried that yet but It’s always good to have a good excuse to go for a beer, or isn’t it?

Getting started with Drools Flow

Posted by admin | Posted in Code Snippets, Jboss Drools, Learning Materials | Posted on 10-02-2011

14

Today I will present my latest discovery called JBoss Drools and Drools Flow in particular. I am completely new to the drools framework and because I couldn’t find myself a quick and simple intro to the framework I will post it myself. Presented example is completely useless from the business perspective and it doesn’t describe any real world scenario.

According to the Drools Flow website itself:

Drools Flow provides workflow or (business) process capabilities to the Drools platform. A business process or workflow describes the order in which a series of steps need to be executed, using a flow chart. This makes it much easier to describe a complex composition of various tasks. Processes are especially useful in describing state-based, long-running processes. Drools Flow allows end users to specify, execute and monitor (a part of) their business logic using these processes. The Drools Flow process framework is easily embeddable into any Java application (as a simple Java component) or can run standalone in a server environment.

First of all you need to prepare your development environment. You will need the following:

For the purpose of this tutorial that’s enough, you can try to download full Drools stack using the installer (including JBoss AS, Eclipse IDE, Birt reporting, Guvnor etc) but I didn’t have any luck with this installer and I guess you will be better off once you do install everything by hand as you learn.

Unpack drools binaries somewhere on your machine and Drools Eclipse workbench into eclipse folder. Restart your IDE and you should be able to create new Drools projects by now.

Select File -> New Project from the Eclipse file menu:

Provide descriptive name for your project:

Un-click all the proposed examples which eclipse plugin wants to generate for you, you can do that later if you want to learn more on drools.

Select Drools runtime, if you have no runtime configured, click ‘Configure Workspace settings’ and point to the directory where you have downloaded your binaries.

After you click finish, you should have a basic Drools project structure created for you:

Design Patterns Series – Part One: Strategy Pattern

Posted by admin | Posted in Code Snippets, General Programming, Learning Materials | Posted on 28-01-2011

1

The Strategy Pattern is a first pattern described in a series of post I’m planning to make regarding this topic. The Strategy pattern allows us to select a specific algorithm at runtime and its one of the simples design patterns to grasp.
The scenario described on the diagram below shows a simple usage of the strategy pattern.

Let’s say we have a HttpServlet which creates a report using data retrieved from the request parameters, or some other sources like database. and depending on which kind of report we want to create (PDF, DOC or HTML) chooses appropriate algorithm to do just that.
So w end up with common interface for creating reports called ‘ReportGenerator’ with a single method ‘generateReport(ReportData rd)’:

/*
 * Created by softwarepassion.com
 * Any information regarding license for this code snippets and
 * other copyright info can be found @softwarepassion.com
 * The author doesn't take any reposnsibility for the presented
 * code and design patterns solutions.
 * Please be advised that this code can contain errors!
 *
 */


package strategydesignpattern;

/**
 * Common interface for generating reports using different formats
 *
 * @author Krzysztof Grajek
 * @version 28-01-2011
 */

public interface ReportGenerator {

    /**
     * Generates report using provided report data
     * @param rd ReportData object carrying all info needed to produce report
     */

    public void generateReport(ReportData rd);
}

We have three different implementations of this interface, allowing us to generate report in a form of PDF, DOC or HTML format.
PDF Report Generator:

/*
 * Created by softwarepassion.com
 * Any information regarding license for this code snippets and
 * other copyright info can be found @softwarepassion.com
 * The author doesn't take any reposnsibility for the presented
 * code and design patterns solutions.
 * Please be advised that this code can contain errors!
 *
 */


package strategydesignpattern;

/**
 * Responsible for handling PDF reports
 *
 * @author Krzysztof Grajek
 * @version 28-01-2011
 */

public class PDFReportGenerator implements ReportGenerator {

    public void generateReport(ReportData rd) {
        System.out.println("Generating PDF report..." + rd.getTitle());
    }

}

Microsoft Word Generator:

/*
 * Created by softwarepassion.com
 * Any information regarding license for this code snippets and
 * other copyright info can be found @softwarepassion.com
 * The author doesn't take any reposnsibility for the presented
 * code and design patterns solutions.
 * Please be advised that this code can contain errors!
 *
 */


package strategydesignpattern;

/**
 * Responsible for handling reports in MS Word format
 *
 * @author Krzysztof Grajek
 * @version 28-01-2011
 */

public class MsDOCReportGenerator implements ReportGenerator{

    public void generateReport(ReportData rd) {
        System.out.println("Generating MS Word report.. " + rd.getTitle());
    }

}

HTML Report Generator:

/*
 * Created by softwarepassion.com
 * Any information regarding license for this code snippets and
 * other copyright info can be found @softwarepassion.com
 * The author doesn't take any reposnsibility for the presented
 * code and design patterns solutions.
 * Please be advised that this code can contain errors!
 *
 */


package strategydesignpattern;

/**
 * Responsible for generating reports in HTML format
 *
 * @author Krzysztof Grajek
 * @version 28-01-2011
 */

public class HTMLReportGenerator implements ReportGenerator{

    public void generateReport(ReportData rd) {
        System.out.println("Generating HTML report..." + rd.getTitle());
    }

}

All mentioned above generators use a single object (simple POJO class) containing the data needed to generate report ReportData

/*
 * Created by softwarepassion.com
 * Any information regarding license for this code snippets and
 * other copyright info can be found @softwarepassion.com
 * The author doesn't take any reposnsibility for the presented
 * code and design patterns solutions.
 * Please be advised that this code can contain errors!
 */


package strategydesignpattern;

/**
 * Used to transport data needed to produce report
 * in any form using {@link ReportGenerator}
 *
 * @author Krzysztof Grajek
 * @version 28-01-2011
 */

public class ReportData {

    private String title;
    private String description;
    //...other report related stuff needed to produce report

    //..getters and setters for the class fields
    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
   
}

All of different kinds of generators can be used in our simple report servlet:

/*
 * Created by softwarepassion.com
 * Any information regarding license for this code snippets and
 * other copyright info can be found @softwarepassion.com
 * The author doesn't take any reposnsibility for the presented
 * code and design patterns solutions.
 * Please be advised that this code can contain errors!
 *
 */


package strategydesignpattern;

/**
 * Example class using {@code ReportGenerator} interface
 * This class mimmicks the behaviour of a http servlet, assuming that all needed
 * parameters for generating report are coming from the database or in the form
 * of servlet request parameters contained in a single {@code ReportData } object,
 * we choose one of the available report generators
 * to generate our report.
 *
 * @author Krzysztof Grajek
 * @version 28-01-2011
 */

public class MockReportServlet {

    private ReportGenerator reportGenerator;

    public void onGet(String request, String response){
        //1. get the data from the database, request parameters or any other source
        //in the form of ReportData object
        ReportData reportData = new ReportData();
        reportData.setTitle("Design Patterns Report for: Strategy Pattern");
        //2. Using the request parameter decide on which one of three report generators
        //to use when generating the report
       
        //3. Assuming that PDF generator was selected by the user generate PDF report
        if("PDF".equals(request)){
            reportGenerator = new PDFReportGenerator();
            reportGenerator.generateReport(reportData);
        }
    }
}

The source code for this example (in the form of Netbeans project) can be found here

Jasper Reports Book

Posted by admin | Posted in Learning Materials | Posted on 05-09-2010

0

One of the latest books on my bookshelve is ‘Jasper Reports 3.6 Development Cookbookby Bilal Siddiqui. If you have ever wrote/maintained an application which has to
gather some data into a report form using java technology this can be a book for you.
The book is divided into 50 sections, each being a separate recipe, describing how to build a particular part of the
report like headers, footers, grids etc.
Every recipe found in this book has a form of a well prepared tutorial with screen shots and ‘extensive’ explanation about a
particular topic.
This book goes straight to the point which some of the developers will really appreciate, it’s a 50 quick recipes on how to
use iReport software and Jasper Reports library effectively and with no fuss.
You can find more info about the book here
Hope you’ll like it too.

jasper reports 3.6 development cookbook

Add ‘count #’ column to JTable

Posted by admin | Posted in Code Snippets, General Programming, Graphics And Design, Learning Materials | Posted on 05-02-2010

2

Important note first: This is mostly not my code, I was strugling with this problem for some time, I couldn’t google it at all, and one day, I’ve just found it. Now I use it in one of my projects but I cannot find the original post again, therefore I have decided to post it on my blog (slightly modified version of the original) so other people with similar problem will have a better chance of finding the solution.
So, the idea is very simple. The whole point of displaying additional column which is kind of independent from the other columns is to simply use another table. This way you can sort one table by column and the number column will not sort with it. – thats exactly what I was looking for and I guess its kind of popular problem.
Becouse you put two tables in one scroll pane the gui looks like its just one column!


I have created one simple method to apply this technique to any tables in my application (I use JXTable actually but JTable will do as well)

private void addLPColumn(JXTable table, JScrollPane scp){
        JTable rowTable = new RowNumberTable(table);
        scp.setRowHeaderView(rowTable);
        scp.setCorner(JScrollPane.UPPER_LEFT_CORNER, rowTable.getTableHeader());
        scp.repaint();
    }


RowNumberTable is simple one column table displaying the ‘count’ number of rows in the second table.


public class RowNumberTable extends JTable
    implements ChangeListener, PropertyChangeListener
{
    private JTable main;

    public RowNumberTable(JTable table)
    {
        main = table;
        main.addPropertyChangeListener( this );

        setFocusable( false );
        setAutoCreateColumnsFromModel( false );
        setModel( main.getModel() );
        setSelectionModel( main.getSelectionModel() );

        TableColumn column = new TableColumn();
        column.setHeaderValue("Lp.");
        addColumn( column );
        column.setCellRenderer(new RowNumberRenderer());

        getColumnModel().getColumn(0).setPreferredWidth(40);
        setPreferredScrollableViewportSize(getPreferredSize());
    }

    @Override
    public void addNotify()
    {
        super.addNotify();

        Component c = getParent();

        //  Keep scrolling of the row table in sync with the main table.

        if (c instanceof JViewport)
        {
            JViewport viewport = (JViewport)c;
            viewport.addChangeListener( this );
        }
    }

    /*
     *  Delegate method to main table
     */

    @Override
    public int getRowCount()
    {
            if(main != null)
                return main.getRowCount();
            else
                return 0;
    }

    @Override
    public int getRowHeight(int row)
    {
        return main.getRowHeight(row);
    }

    /*
     *  This table does not use any data from the main TableModel,
     *  so just return a value based on the row parameter.
     */

    @Override
    public Object getValueAt(int row, int column)
    {
        return Integer.toString(row + 1);
    }

    /*
     *  Don't edit data in the main TableModel by mistake
     */

    @Override
    public boolean isCellEditable(int row, int column)
    {
        return false;
    }
//
//  Implement the ChangeListener
//
    public void stateChanged(ChangeEvent e)
    {
        //  Keep the scrolling of the row table in sync with main table

        JViewport viewport = (JViewport) e.getSource();
        JScrollPane scrollPane = (JScrollPane)viewport.getParent();
        scrollPane.getVerticalScrollBar().setValue(viewport.getViewPosition().y);
    }
//
//  Implement the PropertyChangeListener
//
    public void propertyChange(PropertyChangeEvent e)
    {
        //  Keep the row table in sync with the main table

        if ("selectionModel".equals(e.getPropertyName()))
        {
            setSelectionModel( main.getSelectionModel() );
        }

        if ("model".equals(e.getPropertyName()))
        {
            setModel( main.getModel() );
        }
    }

    /*
     *  Borrow the renderer from JDK1.4.2 table header
     */

    private static class RowNumberRenderer extends DefaultTableCellRenderer
    {
        public RowNumberRenderer()
        {
            setHorizontalAlignment(JLabel.CENTER);
        }

        public Component getTableCellRendererComponent(
            JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
        {
            if (table != null)
            {
                JTableHeader header = table.getTableHeader();

                if (header != null)
                {
                    setForeground(header.getForeground());
                    setBackground(header.getBackground());
                    setFont(header.getFont());
                }
            }

            if (isSelected)
            {
                setFont( getFont().deriveFont(Font.BOLD) );
            }

            setText((value == null) ? "" : value.toString());
            setBorder(UIManager.getBorder("TableHeader.cellBorder"));

            return this;
        }
    }
}

count_jtable_column

In my case the ‘count’ column is called ‘Lp.’ – in polish



I have created one simple method to apply this technique to any tables in my application (I use JXTable actually but JTable will do as well)

private void addLPColumn(JXTable table, JScrollPane scp){
        JTable rowTable = new RowNumberTable(table);
        scp.setRowHeaderView(rowTable);
        scp.setCorner(JScrollPane.UPPER_LEFT_CORNER, rowTable.getTableHeader());
        scp.repaint();
    }


Emergent Design: The Evolutionary Nature of Professional Software Development

Posted by admin | Posted in Learning Materials | Posted on 04-09-2008

0


Well, I have just read another comment left on my blog and I had realized, well, I had realized that I have a blog :) . Completly forgot about its existence, and since last post I was writing as a student at University of Greenwich, London, now I’m writing as an owner of a really small startup software company in Elblag, Poland :) , things change quickly.

Anyway, student or not, I still read a lot, maybe not that much as I used to do but stil quite a lot. I have decided to write a short note on the latest book I read as I’ve found it really interesting.

The title you know already, this book is not about programming but about more general concepts, about programming as a profession. It’s a nice book about software design and life as a software developer. Once you become proficient in any programming language and you start being interested in methodologies, patterns etc you should read this book!
Highly recommended!

Head First Software Development completed!

Posted by admin | Posted in Learning Materials | Posted on 01-03-2008

1

Another example of the book proving that software books can be fun! Head First series books are usualy easy to read and thats what makes them most valuable for me. I have read a few of them, since I started learning how to program, and I was never disappointed, I think I have read HF Servlets and JSP about 3 times :) and I know that if I start a project with Servlets or JSPs I will read a few bits again :) .
HF Software Development is of course no exception. This book on my bookshelf is a result of my new subscription to Safari, I should actualy say it’s on my virtual bookshelf :) .
I belive there are experienced software developers which will find this book boring or maybe not bringing anything new to their ‘toolbox’.
For me this book is superb! In my opinion every CS student should read it before going into the wild and look for a job.
This is in short what you can get:

  • A feel of profesional software development process
  • Reason for manager’s daily headache :)
  • What is TDD and how it works
  • What is Continous Integration
  • How tos: gathering requirements, desing and implement
  • A few bits on proper design, UML and refactoring
  • Many others which I either considered common knowledge or haven’t notice :)

Go to Safari books online or Amazon for detailed table of contents.
I would give it 5/5 mark.
Highly recommended

Mythical Man Month On My Bookshelf

Posted by admin | Posted in Learning Materials | Posted on 27-01-2008

0


I was reading so often about this one on the net and in other books that it was on top of my reading list quite a long time. I had no idea what to expect from this book and I haven’t found there anything what recently I like the most, like tips on software development, refactorings to patterns or code tunning. Well it’s non of those things but of course it’s not the author’s fault. Definietly I should do some more research before buying this one, as I think this book is not for startups in software industry like me. I belive, people who manage projects and people resources enjoy reading this book much more than those who write software only.
I cannot say I didn’t like it though, overall is always nice to read about such a huge project like soft for IBM mainframe.
Maybe not really what I’m looking for these days but definietly valuable read!
Recommended!