Compiling Courier-MTA from sources on Debian buster/sid

Updated on June 26, 2019, for the upcoming Debian GNU/Linux Buster release.

This aims to be a complete, but not necessarily friendly guide, at least as of today. It contains a list of commands and actions I used to compile Courier-MTA from sources on a very basic Debian stable/testing/sid instance (e.g. a mix of stable, testing and sid).

Why? Because current Courier-MTA packages in Debian are nearly unmantained. I’m sure Markus Wanner (current mantainer) is doing his best and I thank him for his work, but I need a way out, should Courier-MTA ever fall orphaned. I’d like to help him or anyone else if I could, but I understand nothing of Debian packaging tecniques, nor Debian packaging policies. I just happen to use Debian because I like it and Courier-MTA for the very same reason, so I better play it safe and learn how to compile Courier-MTA on Debian myself.

Enough words, now the guide:

I started from the Debian setup itself, using a KVM guest. Please adjust things to match your needs (volume group name, logical volume name, ram size, disk size, network bridge name). You can install Debian in many other ways, this is just one of them. I started from the latest stable release (stretch) and added buster and sid afterwards.

root@kvmhost:~# virt-install \
  --name couriermta --ram 2048 \
  --disk vol=tumblevolgroup/vm-couriermta,bus=virtio \
  --vcpus 2 --os-type linux --os-variant generic \
  --network bridge=virbr20,model=virtio \
  --graphics none --console pty,target_type=serial --location \
  http://mirror.switch.ch/ftp/mirror/debian/dists/buster/main/installer-amd64/ \
  --extra-ar gs 'console=ttyS0,115200n8 serial'

When tasksel asked what packages to install, I selected only standard system utilities and SSH server. Then I added my favourite packages after the install:

root@couriermta:~# apt-get install vim vim-tiny- screen ntp

Let’s add sid sources, because I like having recent packages available if needed:

root@couriermta:~# cd /etc/apt/sources.list.d
root@couriermta:/etc/apt/sources.list.d# cat > sid.list << EOF

deb http://ftp.de.debian.org/debian/ sid main contrib
deb-src http://ftp.de.debian.org/debian/ sid main contrib
EOF

But please note that I configure priorities so that my system will prefer stable, and it will install buster/sid packages only if I explicitly ask it to do so:

root@couriermta:/etc/apt/sources.list.d# cd /etc/apt/preferences.d/
root@couriermta:/etc/apt/preferences.d# cat > sid << EOF
Package: *
Pin: release a=stable
Pin-Priority: 990

Package: *
Pin: release a=testing
Pin-Priority: 700

Package: *
Pin: release a=unstable
Pin-Priority: 600
EOF

Next we need to install the gcc compiler and toolchain. While we are at it, we also install some libs that we’ll need later, when we’ll be compiling authlib and Courier-MTA itself. Some of these libraries might already be installed in your system, depending on how you installed Debian and what you choose to install in the tasksel step. If they are, no worries, apt will just skip their installation.

# apt-get update
[...]
# apt-get install build-essential libltdl-dev \
  libgdbm-dev libgdbm-compat4 \
  libpcre3-dev libidn11-dev libidn2-0-dev \
  libgnutls28-dev libgcrypt20-dev libperl-dev gnutls-bin
[...] Do you want to continue? [Y/n]

Time to add a non-root user I’ll use to compile Courier-MTA source code:

root@couriermta:~# adduser couriermta
Adding user `couriermta' ...
Adding new group `couriermta' (1001) ...
Adding new user `couriermta' (1001) with group `couriermta' ...
Creating home directory `/home/couriermta' ...
Copying files from `/etc/skel' ...
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully 
Changing the user information for couriermta
Enter the new value, or press ENTER for the default
Full Name []:
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n]
root@couriermta:/etc/apt/preferences.d# mkdir \
  -p /opt/courier/sources
root@couriermta:/etc/apt/preferences.d# chown \
  -R couriermta.couriermta /opt/courier/
root@couriermta:/etc/apt/preferences.d# su - couriermta
couriermta@couriermta:~$ cd /opt/courier/sources/
couriermta@couriermta:/opt/courier/sources$

Let the party begin. We download the source code (unicode library, authlib and Courier-MTA itself):

couriermta@couriermta:/opt/courier/sources$ wget 'https://sourceforge.net/projects/courier/files/courier-unicode/2.1/courier-unicode-2.1.tar.bz2'
[...]
couriermta@couriermta:/opt/courier/sources$ wget 'https://sourceforge.net/projects/courier/files/authlib/0.69.0/courier-authlib-0.69.0.tar.bz2'
[...]
couriermta@couriermta:/opt/courier/sources$ wget 'https://sourceforge.net/projects/courier/files/courier/1.0.8/courier-1.0.8.tar.bz2'
[...]
couriermta@couriermta:/opt/courier/sources$ for i in *.bz2 ; do tar xjf $i ; done

We start compiling and installing the unicode library. Please note that I chose to install it in /opt/courier/unicode, but you can choose any other directory that suits your needs. All commands below, until the end of this giude, will assume you chose /opt/courier/unicode, so you might need to customize them too if you choose a different location:

couriermta@couriermta:/opt/courier/sources$ cd courier-unicode-2.1/
couriermta@couriermta:/opt/courier/sources/courier-unicode-2.1$ ./configure --prefix=/opt/courier/unicode
[...]
couriermta@couriermta:/opt/courier/sources/courier-unicode-2.1$ make
[...]
couriermta@couriermta:/opt/courier/sources/courier-unicode-2.1$ su
root@couriermta:/opt/courier/sources/courier-unicode-2.1# make install
[...]
root@couriermta:/opt/courier/sources/courier-unicode-2.1# exit

Now we compile the authentication library. Likewise, I chose /opt/courier/authlib, but it’s up to you.

$ cd /opt/courier/sources/courier-authlib-0.69.0
$ mkdir ../include
$ ln -s /opt/courier/sources/courier-unicode-2.1/ /opt/courier/sources/include/unicode
$ ./configure --prefix=/opt/courier/authlib CFLAGS='-I/opt/courier/sources/include/unicode' LDFLAGS='-L/opt/courier/unicode/lib'
[...]
$ make
[...]
$ su
root@couriermta:/opt/courier/sources/courier-authlib-0.69.0# make install
[...]
root@couriermta:/opt/courier/sources/courier-authlib-0.69.0# exit

We’re all set, we can now compile Courier-MTA itself:

$ cd ../courier-1.0.8/
$ ln -s /opt/courier/sources/courier-authlib-0.69.0/ /opt/courier/sources/include/authlib
$ export COURIERAUTHCONFIG=/opt/courier/authlib/bin/courierauthconfig 
$ export CPATH=/opt/courier/sources/include/authlib:/opt/courier/sources/include/unicode
$ ./configure --prefix=/opt/courier/courier --with-gnutls --with-notice=unicode CFLAGS="-I/opt/courier/sources/include/authlib -I/opt/courier/sources/include/unicode" LDFLAGS="-L/opt/courier/authlib/lib/courier-authlib -L/opt/courier/unicode/lib"
[...]
$ make
[...]
$ make check
[...]
$ su
root@couriermta:/opt/courier/sources/courier-1.0.8# make install
[...]
root@couriermta:/opt/courier/sources/courier-1.0.8# make install-configure
[...]

And that’s all about it, meaning Courier-MTA is compiled, assuming you got no error messages with the commands above. However barely compiling it is of no use if we don’t actually run it,  and, before running it, we need a bit of configuration. Let’s add some essential paths to out PATH variable:

# echo 'export PATH=$PATH:/opt/courier/courier/bin:/opt/courier/courier/sbin' >> $HOME/.bashrc

There seemed to be a bug in the imapd script, at least in the August 2018 development release that I used for the first version of this document. Now it seems fixed, anyway I keep this here for a while until I’m sure it’s not needed anymore. The imapd script did not correctly set permissions on the TLS_CACHEFILE. If that happens to you, either manually fix permissions:

# chmod g+rw \
  /opt/courier/courier/var/couriersslpop3cache
# chgrp daemon \
  /opt/courier/courier/var/couriersslpop3cache

or manually fix the script:

# vi /opt/courier/courier/share/imapd
replace @authmailuser@ and @authmailgroup@ 
with "daemon", save and exit.

I won’t delve into details of configuring and running Courier-MTA here, because you can find extensive documentation about it at the Courier-MTA website, and there’s nothing Debian specific in that procedure.  However I feel like giving a few final hints.

if you are running a KVM guest in a NAT-ted network of virtual servers, which happens to be my setup, don’t forget to limit the smtpaccess that comes by default with Courier-MTA:

# vi /opt/courier/courier/etc/smtpaccess/default

Comment out the following lines, that are line 13 and line 14 in the release of Courier-MTA I downloaded:

10 allow,RELAYCLIENT
192.168 allow,RELAYCLIENT

Those lines should look like this:

#10 allow,RELAYCLIENT
#192.168 allow,RELAYCLIENT

Then run, as root

# makesmtpaccess

That will avoid a open relay inside the virtual servers network (and also outsite it, if you ever map the public IP 25/tcp port to your Courier-MTA instance).

If you plan to use SSL/TLS enctypted connections (and you SHOULD!), you need to provide Courier with valid certificates. You can use LetsEncrypt to obtain valid certificates for your serve for free, but, before that, you may need at least a test (hence not valid) certificate, just to check encrypted connections are actually working.

The mkimapdcert script can be used to create such test certificate. Just run it and you’re done. You can now test your IMAP server with any client: just make sure you tell it to accept your invalid certificate.

That’s all, you can now continue reading the official documentation from the “Installation” chapter on, which will work without modifications in Debian too.

After you have configured Courier basic settings, you may need a handy script to start, stop and gracefully restart Courier. Here’s mine /opt/courier/scripts/courier.sh:

#!/bin/bash

/opt/courier/authlib/sbin/authdaemond $1
/opt/courier/courier/sbin/courierfilter $1
/opt/courier/courier/sbin/courier $1
/opt/courier/courier/sbin/esmtpd $1
/opt/courier/courier/sbin/esmtpd-msa $1

Then I added a /etc/rc.local file containing

#!/bin/bash
/opt/courier/scripts/courier.sh start

So far so good. However if you reached this point, you’ll likely want to send emails too. Nowadays sending emails can be a tough job. Times when you could script telnet to forge a sender address and drop messages into random mailboxes are over. A default Courier setup is far better than that, but it does not include DKIM signatures. Please note, DKIM is not mandatory, but its adoption is rising and you might want to jump on that bandwagon too. So let’s install some packages required to compile and run zdkimfilter:

# apt-get install libopendkim-dev opendkim opendkim-tools libunistring-dev

Then we download and compile zdkimfilter. We need to tell configure where to find the courer-config binary and specify a fake courier version manually (the latter because of a bug in version 1.6 of zdkimfilter):

# su - couriermta
$ mkdir /opt/courier/sources/zdkimfilter/
$ cd /opt/courier/sources/zdkimfilter/
$ wget 'https://www.tana.it/sw/zdkimfilter/zdkimfilter-1.6.tar.gz'
$ tar xzf zdkimfilter-1.6.tar.gz
$ cd zdkimfilter-1.6/
$ ./configure --with-courier-version=60 --prefix=/opt/courier/zdkimfilter COURIER_CONFIG=/opt/courier/courier/bin/courier-config
$ make
$ su
# make install

All the rest is up to you and not documented here, e.g. adding SPF and DKIM DNS records, and DMARC policies as well. You can find extensive documentation about those out there on the internet. The same is true for zdkimfilter configuration too, but I like to share this last bit, which is a script I use to enable DKIM for a particular domain:

#!/bin/bash
#
# Simple script to create DKIM keys for Courier/zdkimfilter.
# Copyright © 2017 Lucio Crusca <lucio@sulweb.org>
# No warranty whatsoever. 
# This is PUBLIC DOMAIN code: you can use it for whatever you want, except for 
# holding me responsible for anything. If you choose to use this code, you agree
# to take full responsibility for your actions.
#
# End of legal nonsense

HEADEND=$(grep -n 'End of legal nonsense' "$0" | head -n 1 | cut -d':' -f1)
HEADEND=$(( ${HEADEND} - 1))
cat "$0" | head -n ${HEADEND} | tail -n $(( ${HEADEND} - 1 )) | sed -e 's/^#//'

DOMAIN="$1"
COURIERCONF=/opt/courier/courier/etc
KEYSDIR="${COURIERCONF}/filters/keys"

if [ "${DOMAIN}" == "" ] ; then
  echo "Please specify the domain for the new DKIM key, e.g.:"
  echo
  echo "$0 example.com"
  echo
  exit 1
fi

if ! [ -d "${KEYSDIR}" ] ; then
  echo "${KEYSDIR} is not a directory. Please edit $0 to match your Courier setup."
  exit 2
fi

NOW=$(date +%F+%T | sed -e 's/-//g' | sed -e 's/+//g' | sed -e 's/://g')
SELECTOR="${DOMAIN}${NOW}"

cd "${KEYSDIR}"
if [ -L ${DOMAIN} ] ; then
  echo "DKIM keys seem to already exist for ${DOMAIN}."
  echo "You may replace them if you want, further confirmation will be asked before actual replacement."
  echo -n "Do you want to proceed and create new keys (y/N)? "
  read REPLACE
  if [ "${REPLACE}" != "y" ] && [ "${REPLACE}" != 'Y' ] ; then
    echo "Nothing changed."
    exit 0
  fi
fi

opendkim-genkey -b 4096 -d "${DOMAIN}" -D "${KEYSDIR}" -s ${SELECTOR} -r nosubdomains -v
chmod 640 ${KEYSDIR}/${SELECTOR}.*
chown root.daemon ${KEYSDIR}/${SELECTOR}.*
echo "DKIM Keys created."
echo "Please add the following TXT record to ${DOMAIN} domain and let it propagate."
echo
cat ${SELECTOR}.txt
echo
if [ "${REPLACE}" != "" ] ; then
  echo "Since you have choosen to replace the current DKIM keys, you need to decide when the new keys will be activated."
  echo "By now your system is still using the current keys to sign messages."
  while [ "${ACTIVATENOW}" != "NOW" ] ; do
    echo -n "Please type NOW (uppercase) followed by Return to replace the current keys with the new ones: "
    read ACTIVATENOW
    if [ "${ACTIVATENOW}" != "" ] && [ "${ACTIVATENOW}" != "NOW" ] ; then
      echo "You need to type NOW. You may also hit Ctrl+C to break the script and handle the activation manually."
    fi
  done
  rm -f "${DOMAIN}"
fi
ln -s "${SELECTOR}.private" "${DOMAIN}"

Nobody Oracle

Now this is for real nerds only. And you need to know a bit of good music in order to understand the wit. Only less than 1% of people understand all the references in this song, but there’s a walkthrough for others at the end…

Music by Roger Waters (Pink Floyd)
Lyrics by me

I’ve got a linux black box with my programs in it
Got a bash, using twofish and a login
When I’m a good OP they sometimes throw me a like in.

I got elastic cloud keepin’ my sites on
Got those stolen source codes
I got thirteen panels that sit on the display to choose from
I’ve got unending nights
And I’ve got petabytes
I got amazing powers of the super cow
And that is how I know
When I try to get through
On the terminal to UEK
There’ll be no files in /home

I’ve got directory rwx perm.
And the inevitable enroll forms
All down the front of my favorite satin shirt.
I’ve got caffeine stains on my teeth.
I’ve got a mining pool and blockchain.
Got a grand keyboard to prop up Oracle remains.

I’ve got wild staring eyes
And I’ve got a strong urge to fry
’cause I got no heatsink on the CPU
Ooh…racle when I pick up the logs
there’s still no files in /home


Walkthrough (WARNING! Spoilers ahead!) :

I’ve got a linux black box with my programs in it
Got a bash, using twofish and a login
When I’m a good OP they sometimes throw me a like in.

I got elastic cloud keepin’ my sites on
Got those stolen source codes
I got thirteen panels that sit on the display to choose from
I’ve got unending nights
And I’ve got petabytes
I got amazing powers of the super cow
And that is how I know
When I try to get through
On the terminal to UEK
There’ll be no files in /home/…

I’ve got directory rwx perm.
And the inevitable enroll forms
All down the front of my favorite satin shirt.
I’ve got caffeine stains on my teeth.
I’ve got a mining pool and blockchain.
Got a grand keyboard to prop up Oracle remains.

I’ve got wild staring eyes
And I’ve got a strong urge to fry
’cause I got no heatsink on the CPU
Ooh…racle when I pick up the logs
there’s still no files in /home/…

Windows 10, le novità. Beh, quasi.

Il 29 luglio Microsoft rilascerà la nuova versione di Windows. Su questo blog la cosa è probabile che interessi poco, ma ci tenevo ad elencare le novità giusto per non perdere d’occhio in che direzione sta andando il mondo desktop.

Commento le novità seguendole una per una alla pagina ufficiale MS.

  • Windows 10 avrà il menù start, del tutto simile a quello che c’era fino a Windows 7. Cioè, in pratica, lo hanno tolto da Windows 8 per poi poterlo presentare come novità di Windows 10.
  • Windows 10 manderà (finalmente) in pensione Internet Explorer e con lui i peggiori spettri del medioevo informatico come ActiveX e BHO. Internet Explorer sarà sostituito da Microsoft Edge, un browser nuovo di zecca basato sugli standards (speriamo). Per i nostalgici comunque Internet Explorer 11 potrà ancora funzionare su Windows 10 (o forse per tutti quelli a cui MS Edge non funzionerà…). Detta in breve, Windows 10 ti permetterà di navigare su internet (forse).
  • Windows 10 permetterà di avere fino a QUATTRO finestre aperte contemporaneamente. Se non ricordo male su Windows 8 erano solo due. E, sempre se non ricordo male, fino a Windows 7 erano illimitate quindi la cosa non veniva neppure pubblicizzata… non è importante cosa vendi, l’importante è saperlo vendere.
  • Windows 10 ha uno store fornitissimo di app, musica, giochi e quant’altro. C’era già su Windows 8. E, a dirla tutta, non è che se ne sentisse la necessità.
  • Windows 10 include Cortana, ovvero un desktop semantico che si fa gli affari tuoi convinto di sapere meglio di te cosa in effetti stai cercando. De gustibus. Gli altri sistemi operativi ce l’hanno da circa una decade e da circa nove anni e 364 giorni gli utenti hanno imparato a disabilitarlo.
  • Windows 10 ha il supporto per periferiche di input diverse dal mouse, come schermi touch con dito o penna apposita. Wow.
  • Windows 10 funziona con schermi di varie dimensioni e risoluzioni, ovvero non ti obbliga ad avere un monitor che supporti la risoluzione 800×600 come Windows 3.1, ricordate?
  • XBox sta per arrivare su Windows. Beh questa onestamente non la capisco… non era Windows che era arrivato su XBox, fin dal giorno in cui hanno inventato la Xbox?

Comunque, anche se non capisco l’ultima, mi sembra che l’elenco di novità sia già talmente lungo ed interessante da voler assolutamente aggiornare alla nuova versione di Windows appena possibile… ma non prima che qualcuno mi dica che fine ha fatto Windows 9.

AutoCloseable EntityManager

Sembra che io non sia l’unico a chiederselo e sembra anche che le risposte date non siano necessariamente soddisfacenti. Non sarebbe bello poter usare il try-with-resources con gli EntityManager?
Pare che il motivo per cui EntityManager non implementa AutoCloseable sia che se lo facesse, potrebbe solo fare la close() di sè stesso, ma non la rollback() dell’eventuale transazione. Questo renderebbe l’intero sforzo inutile, dovendo comunque gestire la transazione attraverso la finally come al solito. Oppure illuderebbe l’utente che l’EntityManager, essendo AutoCloseable, faccia cose che in realtà non fa (la rollback() appunto). Onestamente mi sfugge il motivo per cui l’EntityManager non possa tenere traccia della EntityTransaction, così come fa il mio codice qui sotto, ma è probabile che sia un problema di specifiche e di ricerca della purezza a tutti costi, per venire incontro a qualche possibile implementazione di JPA proveniente da Marte o altri pianeti più lontani…

Ad ogni modo il mio codice funziona, o almeno funziona per me e per il mio stile di programmazione. Può darsi che in realtà non sia sufficientemente generico e che in altri casi non funzioni… se capita a voi fatemi sapere, grazie.

Notate che il fatto che io abbia aggiunto AutoCloseable è una conseguenza di un bug che avevo lasciato in un mio software per cui un EntityManager non veniva chiuso. Per scoprire quale fosse, ho dovuto aggiungere tutto il monitoraggio attraverso JConsole ed interfaccia MBean. Ora quindi avete una classe che vi permette di ottenere un EntityManager (in realtà lo dovete usare per quel che è, ovvero EMWrapper) che nel frattempo è anche monitorabile attraverso JConsole. A voi il codice.

/*
Copyright © 2014 Lucio Crusca <lucio@sulweb.org>

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 
*/

package com.virtual_bit.salix.web.persistence;

import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.metamodel.Metamodel;

public class PersistenceManager
{
  private static final PersistenceManager singleton = new PersistenceManager();

  private HashMap<String, EntityManagerFactory> emfs;
  
  private static final String mainUnitName = "my-unit";
  
  private static final String[] databases = {
    "some-unit",
    "other-unit"
  };
  
  public static interface EMapMBean extends Map<String, List<EMWrapper>>
  {
    
  }
  
  public static class EMap extends HashMap<String, List<EMWrapper>> implements EMapMBean
  {
    
  }
  
  public static interface EntityMapMXBean
  {
    EMap getMap();
    String[] getOverview();
    String[] getDetails();
  }

  private static EMap activeWrappers;
  
  public static class EntityMap implements EntityMapMXBean
  {
    @Override
    public EMap getMap()
    {
      return activeWrappers;
    }
    
    public String[] getOverview()
    {
      List<String> result = new ArrayList<>();
      synchronized(activeWrappers)
      {
        Set<String> keys = activeWrappers.keySet();
        for (String s: keys)
          result.add(s + " -> " + activeWrappers.get(s).size());
      }
      String[] ares = new String[result.size()];
      return result.toArray(ares);
    }

    @Override
    public String[] getDetails()
    {
      List<String> lresult = new LinkedList<>();
      synchronized(activeWrappers)
      {
        Set<String> keys = activeWrappers.keySet();
        int ki = 0;
        for (String s: keys)
        {
          List<EMWrapper> wrappers = activeWrappers.get(s);
          String[][] traces = new String[wrappers.size()][];
          int i = 0;
          for (EMWrapper emw: wrappers)
            traces[i++] = emw.getStackStrace();
          lresult.add(flatten(traces));
        }
      }
      String[] result = new String[lresult.size()];
      return lresult.toArray(result);
    }

    private String flatten(String[][] traces)
    {
      StringBuilder sb = new StringBuilder();
      for (String[] key: traces)
        for (String values: key)
          sb.append(values).append("\n");
      return sb.toString();
    }

  }

  private PersistenceManager()
  {
  }
  
  private static void initMBean()
  {
    synchronized(PersistenceManager.class)
    {
      if (activeWrappers != null)
        return;
      
      try
      {
        activeWrappers = new EMap();
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();  
        EntityMap map = new EntityMap();
        ObjectName name = new ObjectName(map.getClass().getPackage().getName() + ":type=" + map.getClass().getSimpleName());
        mbs.registerMBean(map, name);
      }
      catch (InstanceAlreadyExistsException | MBeanRegistrationException | MalformedObjectNameException | NotCompliantMBeanException ex)
      {
        Logger.getLogger(PersistenceManager.class.getName()).log(Level.SEVERE, null, ex);
      }    
    }
  }

  private static PersistenceManager getInstance()
  {
    return singleton;
  }

  private EMWrapper createEntityManager(String jndiName)
  {
    EntityManagerFactory emf = _getFactory(jndiName);
    EMWrapper result = new EMWrapper(emf.createEntityManager(), jndiName);
    return result;
  }
  
  private EntityManagerFactory _getFactory(String jndiName)
  {
    if (emfs == null)
      emfs = new HashMap<>();
    if (!emfs.containsKey(jndiName))
      emfs.put(jndiName, Persistence.createEntityManagerFactory(jndiName));
    return emfs.get(jndiName);
  }

  public void closeFactories()
  {
    if (emfs == null)
      return;
    for (EntityManagerFactory emf: emfs.values())
      emf.close();
    emfs.clear();
    emfs = null;
  }
  
  public static EMWrapper getEM()
  {
    return getInstance().createEntityManager(nameEM());
  }
  
  private static String suffix()
  {
    Thread current = Thread.currentThread();
    if (current.isDaemon())
      return "-bg";
    return "";
            
  }

  public static String nameEM()
  {
    return mainUnitName + suffix();
  }
  
  
  public static String nameSomeEM()
  {
    return databases[0] + suffix();
  }
  
  public static String nameOtherEM()
  {
    return databases[1] + suffix();
  }
  
  
  public static EMWrapper getSomeEM()
  {
    return getInstance().createEntityManager(nameSomeEM());
  }
  
  public static EMWrapper getOtherEM()
  {
    return getInstance().createEntityManager(nameOtherEM());
  }
  
  public static EMWrapper getEM(String unitname)
  {
    return getInstance().createEntityManager(unitname);
  }

  public static Iterable<String> nameDatabases()
  {
    ArrayList<String> dbs = new ArrayList<>();
    for (String d: databases)
      dbs.add(d + suffix());
    return dbs;
  }

  public static interface EMWrapperMBean extends EntityManager
  {
    
  };
  
  public static class EMWrapper implements EMWrapperMBean, AutoCloseable
  {
    private final EntityManager wrapped;
    private final String jndiName;
    private StackTraceElement[] ste;
    
    private EMWrapper(EntityManager wrapped, String jndiName)
    {
      this.wrapped = wrapped;
      this.jndiName = jndiName;
      this.ste = Thread.currentThread().getStackTrace();
      initMBean();
      synchronized(activeWrappers)
      {
        List<EMWrapper> active = activeWrappers.get(jndiName);
        if (active == null)
          active = new LinkedList<>();
        active.add(this);
        activeWrappers.put(jndiName, active);
      }
    }

    @Override
    public void persist(Object entity)
    {
      wrapped.persist(entity);
    }

    @Override
    public <T> T merge(T entity)
    {
      return wrapped.<T>merge(entity);
    }

    @Override
    public void remove(Object entity)
    {
      wrapped.remove(entity);
    }

    @Override
    public <T> T find(Class<T> entityClass, Object primaryKey)
    {
      return wrapped.<T>find(entityClass, primaryKey);
    }

    @Override
    public <T> T find(Class<T> entityClass, Object primaryKey, Map<String, Object> properties)
    {
      return wrapped.<T>find(entityClass, primaryKey, properties);
    }

    @Override
    public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode)
    {
      return wrapped.<T>find(entityClass, primaryKey, lockMode);
    }

    @Override
    public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode, Map<String, Object> properties)
    {
      return wrapped.<T>find(entityClass, primaryKey, lockMode, properties);
    }

    @Override
    public <T> T getReference(Class<T> entityClass, Object primaryKey)
    {
      return wrapped.<T>getReference(entityClass, primaryKey);
    }

    @Override
    public void flush()
    {
      wrapped.flush();
    }

    @Override
    public void setFlushMode(FlushModeType flushMode)
    {
      wrapped.setFlushMode(flushMode);
    }

    @Override
    public FlushModeType getFlushMode()
    {
      return wrapped.getFlushMode();
    }

    @Override
    public void lock(Object entity, LockModeType lockMode)
    {
      wrapped.lock(entity, lockMode);
    }

    @Override
    public void lock(Object entity, LockModeType lockMode, Map<String, Object> properties)
    {
      wrapped.lock(entity, lockMode, properties);
    }

    @Override
    public void refresh(Object entity)
    {
      wrapped.refresh(entity);
    }

    @Override
    public void refresh(Object entity, Map<String, Object> properties)
    {
      wrapped.refresh(entity, properties);
    }

    @Override
    public void refresh(Object entity, LockModeType lockMode)
    {
      wrapped.refresh(entity, lockMode);
    }

    @Override
    public void refresh(Object entity, LockModeType lockMode, Map<String, Object> properties)
    {
      wrapped.refresh(entity, lockMode, properties);
    }

    @Override
    public void clear()
    {
      wrapped.clear();
    }

    @Override
    public void detach(Object entity)
    {
      wrapped.detach(entity);
    }

    @Override
    public boolean contains(Object entity)
    {
      return wrapped.contains(entity);
    }

    @Override
    public LockModeType getLockMode(Object entity)
    {
      return wrapped.getLockMode(entity);
    }

    @Override
    public void setProperty(String propertyName, Object value)
    {
      wrapped.setProperty(propertyName, value);
    }

    @Override
    public Map<String, Object> getProperties()
    {
      return wrapped.getProperties();
    }

    @Override
    public Query createQuery(String qlString)
    {
      return wrapped.createQuery(qlString);
    }

    @Override
    public <T> TypedQuery<T> createQuery(CriteriaQuery<T> criteriaQuery)
    {
      return wrapped.<T>createQuery(criteriaQuery);
    }

    @Override
    public <T> TypedQuery<T> createQuery(String qlString, Class<T> resultClass)
    {
      return wrapped.<T>createNamedQuery(qlString, resultClass);
    }

    @Override
    public Query createNamedQuery(String name)
    {
      return wrapped.createNamedQuery(name);
    }

    @Override
    public <T> TypedQuery<T> createNamedQuery(String name, Class<T> resultClass)
    {
      return wrapped.<T>createNamedQuery(name, resultClass);
    }

    @Override
    public Query createNativeQuery(String sqlString)
    {
      return wrapped.createNativeQuery(sqlString);
    }

    @Override
    public Query createNativeQuery(String sqlString, Class resultClass)
    {
      return wrapped.createNativeQuery(sqlString, resultClass);
    }

    @Override
    public Query createNativeQuery(String sqlString, String resultSetMapping)
    {
      return wrapped.createNativeQuery(sqlString, resultSetMapping);
    }

    @Override
    public void joinTransaction()
    {
      wrapped.joinTransaction();         
    }

    @Override
    public <T> T unwrap(Class<T> cls)
    {
      return wrapped.<T>unwrap(cls);
    }

    @Override
    public Object getDelegate()
    {
      return wrapped.getDelegate();
    }
    
    private EntityTransaction transaction;
    
    @Override
    public void close()
    {
      if (transaction != null)
        if (transaction.isActive())
          transaction.rollback();
       
      wrapped.close();
      this.ste = null;
      synchronized(activeWrappers)
      {
        List<EMWrapper> active = activeWrappers.get(jndiName);
        active.remove(this);
        activeWrappers.put(jndiName, active);
      }
    }

    @Override
    public boolean isOpen()
    {
      return wrapped.isOpen();
    }

    @Override
    public EntityTransaction getTransaction()
    {
      transaction = wrapped.getTransaction();
      return transaction;
    }

    @Override
    public EntityManagerFactory getEntityManagerFactory()
    {
      return wrapped.getEntityManagerFactory();
    }

    @Override
    public CriteriaBuilder getCriteriaBuilder()
    {
      return wrapped.getCriteriaBuilder();
    }

    @Override
    public Metamodel getMetamodel()
    {
      return wrapped.getMetamodel();
    }
    
    public static HashMap<String, List<EMWrapper>> getWrappers()
    {
      return activeWrappers;
    }
    
    public String[] getStackStrace()
    {
      if (ste == null)
        return null;
      String[] result = new String[ste.length];
      int i = 0;
      for (StackTraceElement s: ste)
        result[i++] = s.getClassName() + "#" + s.getMethodName() + ":" + s.getLineNumber();
      return result;
    } 
  }
}

LibGDX: BestFitViewport

Today’s post is in english, because software development and code distribution know only one language: english.

While trying to solve this problem I wrote a new Viewport class (and solved said problem). You are encouraged to read that thread first.

Its name is BestFitViewport and it makes sense only on mobile devices in poorly designed apps that force the user to rotate the device between screens. I know, the right solution would be a good user interface design, but chances are you have no choice because someone else badly designed the app and they pay you to code that crap.

Assuming you end up in such a situation, this class may help you in that:
1. it offers cross platform screen orientation (Android and IOs only, desktop and web app developers DO NOT WANT TO USE THIS, unless they want to force users of a 23 inch. CRT display to physically rotate it now and then…). Please note that I tested this thing only on two Android devices (SGS GT I9000 and SGS GT I9505), however it does not make use of any esoteric features, only two common libgdx classes (Viewport and Camera), so I expect it to work more or less everywhere.
2. It’s way faster than setRequestedOrientation()
3. It works where setRequestedOrientation() was failing (SGS GT I9505), maybe my fault, but I couldn’t spot it

Enough bla bla bla, here is the code:

package org.sulweb.bestfitviewport;

/*
* Copyright © 2014 Lucio Crusca <lucio@sulweb.org>
* Based on LibGDX OrthographicCamera.java source code, nightly 2014-04-20.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/


import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;

public class BestFitCamera extends Camera
{
  public float zoom = 1, stretchFactorX = 1, stretchFactorY = 1;
  public boolean rotated;

  public BestFitCamera()
  {
    this.near = 0;
  }

  public BestFitCamera(float viewportWidth, float viewportHeight)
  {
    this.viewportWidth = viewportWidth;
    this.viewportHeight = viewportHeight;
    this.near = 0;
    update();
  }

  private final Vector3 tmp = new Vector3();

  @Override
  public void update()
  {
    update(true);
  }

  @Override
  public void update(boolean updateFrustum)
  {
    float left = zoom * stretchFactorX * -(viewportWidth / 2);
    float right = zoom * stretchFactorX * (viewportWidth / 2);
    float top = zoom * stretchFactorY * -(viewportHeight / 2);
    float bottom = zoom * stretchFactorY * viewportHeight / 2;
    if (rotated)
    {
      float tmp = left;
      left = top;
      top = tmp;
      tmp = right;
      right = bottom;
      bottom = tmp;
    }
    projection.setToOrtho(left, right, top, bottom, near, far);
    view.setToLookAt(position, tmp.set(position).add(direction), up);
    combined.set(projection);
    Matrix4.mul(combined.val, view.val);

    if (updateFrustum)
    {
      invProjectionView.set(combined);
      Matrix4.inv(invProjectionView.val);
      frustum.update(invProjectionView);
    }
  }

  /**
   * Sets this camera to an orthographic projection using a viewport fitting the screen resolution,
   * centered at (Gdx.graphics.getWidth()/2, Gdx.graphics.getHeight()/2), with the y-axis pointing
   * up or down.
   * 
   * @param yDown
   *          whether y should be pointing down
   */
  public void setToOrtho(boolean yDown)
  {
    setToOrtho(yDown, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
  }

  /**
   * Sets this camera to an orthographic projection, centered at (viewportWidth/2,
   * viewportHeight/2), with the y-axis pointing up or down.
   * 
   * @param yDown
   *          whether y should be pointing down.
   * @param viewportWidth
   * @param viewportHeight
   */
  public void setToOrtho(boolean yDown, float viewportWidth,
      float viewportHeight)
  {
    if (yDown)
    {
      up.set(0, -1, 0);
      direction.set(0, 0, 1);
    }
    else
    {
      up.set(0, 1, 0);
      direction.set(0, 0, -1);
    }
    position
        .set(zoom * viewportWidth / 2.0f, zoom * viewportHeight / 2.0f, 0);
    this.viewportWidth = viewportWidth;
    this.viewportHeight = viewportHeight;
    update();
  }

  /**
   * Rotates the camera by the given angle around the direction vector. The direction and up vector
   * will not be orthogonalized.
   * 
   * @param angle
   */
  public void rotate(float angle)
  {
    rotate(direction, angle);
  }

  /**
   * Moves the camera by the given amount on each axis.
   * 
   * @param x
   *          the displacement on the x-axis
   * @param y
   *          the displacement on the y-axis
   */
  public void translate(float x, float y)
  {
    translate(x, y, 0);
  }

  /**
   * Moves the camera by the given vector.
   * 
   * @param vec
   *          the displacement vector
   */
  public void translate(Vector2 vec)
  {
    translate(vec.x, vec.y, 0);
  }

}
package org.sulweb.bestfitviewport;

/*
* Copyright © 2014 Lucio Crusca <lucio@sulweb.org>
* Based on LibGDX FitViewport.java and ScalingViewport.java source code, nightly 2014-04-20.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Scaling;
import com.badlogic.gdx.utils.viewport.Viewport;

/**
 * This class behaves more or less like a FitViewport; it behaves different
 * when the world units orientation does not match the current display (or
 * window) orientation. This class may be useful only on mobile devices.
 * 
 * You may find this viewport useful only if all the following things hold
 * true for your mobile app:
 * 1. The app has at least one fixed portrait screen
 * 2. The app has at least one fixed landscape screen
 * 3. You agree that 1+2 above is bad user interface design, but your boss does not
 * want to change that
 * 4. You want portable code, i.e. you don't want to call setRequestedOrientation()
 *  because that's Android specific
 * 5. You want a fast orientation switch between screens
 * 6. You're fed up with Android Activity lifecycle and do not want to call setRequestedOrientation()
 * also to avoid a recreate()
 * 7. Your world units are "sane" enough, i.e. the long edge is actually longer, in numbers, 
 * than the short one. If this is not the case for you, it should be reasonably easy to patch
 * my code to support a 800x300 portrait world...
 * 8. Your Activity has a fixed orientation, e.g. the user cannot choose to rotate his device
 * and make the screen layout differently
 * 
 * How to use this class. You basically get the same as a FitViewport that fits the best 
 * fitting orientation, based on world coordinates you pass in and assuming world units
 * are squares.
 * 
 *   stage.setViewport(new BestFitViewport(1000,800));
 *   
 * such a viewport will force landscape orientation and fit the landscape display.
 * 
 *   stage.setViewport(new BestFitViewport(750,1030));
 *   
 * and this one will force portrait orientation and fit the portrait display.
 * 
 * @author Lucio Crusca
 *
 */
public class BestFitViewport extends Viewport
{
  private Scaling scaling;
  private boolean rotated;
  private int screen_width, screen_height;
  private float currentRotation;
  
  public BestFitViewport(float wwidth, float wheight)
  {
    super();
    this.scaling = Scaling.fit;
    this.worldWidth = wwidth;
    this.worldHeight = wheight;
    this.camera = new BestFitCamera();  
  }
  
  private void updateRotationState(int screenW, int screenH)
  {
    this.screen_width = screenW;
    this.screen_height = screenH;
    rotated = (worldWidth > worldHeight && screenW < screenH) || 
              (worldWidth < worldHeight && screenW > screenH);
  }

  @Override
  public void setWorldHeight(float worldHeight)
  {
    setWorldSize(getWorldWidth(), worldHeight);
  }

  @Override
  public void setWorldSize(float worldWidth, float worldHeight)
  {
    this.worldWidth = worldWidth;
    this.worldHeight = worldHeight;
    updateRotationState(screen_width, screen_height);
  }

  @Override
  public void setWorldWidth(float worldWidth)
  {
    setWorldSize(worldWidth, getWorldHeight());
  }

  @Override
  public void update(int screenWidth, int screenHeight, boolean centerCamera)
  {
    if (screenHeight != 0 && screenWidth != 0 &&
        (screenHeight != this.screen_height || screenWidth != this.screen_width)
       )
    {
      updateRotationState(screenWidth, screenHeight);
      Vector2 scaled;
      if (rotated)
      {
        scaled = scaling.apply(worldWidth, worldHeight, screenHeight, screenWidth);
        viewportWidth = Math.round(scaled.y);
        viewportHeight = Math.round(scaled.x);
      }
      else
      {
        scaled = scaling.apply(worldWidth, worldHeight, screenWidth, screenHeight);
        viewportWidth = Math.round(scaled.x);
        viewportHeight = Math.round(scaled.y);
      }
      
      // center the viewport in the middle of the screen
      viewportX = (screenWidth - viewportWidth) / 2;
      viewportY = (screenHeight - viewportHeight) / 2;

      Gdx.gl.glViewport(viewportY, viewportX, viewportWidth, viewportHeight);
      camera.viewportWidth = worldWidth;
      camera.viewportHeight = worldHeight;
      if (centerCamera) 
        camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0); 
  
      float previousRotation = currentRotation;
      currentRotation = rotated ? 270 : 0;
      camera.rotate(currentRotation - previousRotation, 0, 0, 1);
      if (rotated)
      {
        ((BestFitCamera)camera).stretchFactorX = (worldHeight / viewportWidth) / (worldWidth / viewportHeight);
        ((BestFitCamera)camera).stretchFactorY = 1 / ((BestFitCamera)camera).stretchFactorX; 
      }
      ((BestFitCamera)camera).rotated = rotated;      
    }
    camera.update(); 
  }
}

No jars provided, if you want to use it, just copy the code over into your project.

Sviluppo di applicazioni web in team

Le pagine di un sito dinamico che ha sia una forte componente di business logic, sia dei precisi requisiti estetici e comunicativi, devono necessariamente essere sviluppate da più persone, ognuna nel proprio ambito di competenza.

Lo sviluppo contemporaneo dei due aspetti, comunicativo e funzionale, è però problematico, indipendentemente da quale linguaggio usino i programmatori e da quale software di authoring usino i web designers.

I web designers devono poter scrivere (o far scrivere dal loro software preferito) il codice HTML ed il codice CSS e devono anche poterlo modificare all’occorrenza fino a raggiungere il risultato desiderato dal cliente. I programmatori devono, d’altro canto, modificare il codice HTML creato dai web designers, per aggiungere la caratteristica di generazione dinamica dei dati a tali pagine. Le modifiche apportate dai programmatori tipicamente non vengono interpretate nel modo corretto dal software utilizzato dai web designer, il quale smette di riconoscere e di renderizzare l’intera pagina modificata dal programmatore, oppure ignora le parti che non capisce, ma, in questo caso, non risalva le modifiche in modo corretto dopo che il designer ha cambiato qualcosa puramente estetico all’interno della pagina. Tutto ciò ovviamente supponendo che il web designer non usi solamente un semplice editor di testi, cosa piuttosto rara.

Molti framework e linguaggi di sviluppo software, inoltre, semplificano la vita al programmatore, permettendogli di spezzare la pagina HTML in files più piccoli e più facilmente gestibili, per poi ottenere la pagina finale attraverso una procedura automatica di ricomoposizione delle parti, che avviene al volo durante la generazione dinamica delle pagine stesse. Se da una parte questo semplifica la vita al programmatore, dall’altra la complica infinitamente al web designer, il quale ora si trova con tanti pezzetti di files HTML, apparentemente sparsi senza filo logico (il filo logico è scritto nel codice del linguaggio usato dal programmatore, ovvero in un modo incomprensibile al web designer).

A seconda dei casi specifici, dipendenti dai software e framework usati, dalla composizione del team e dalle competenze di ognuno, ci possono essere varie soluzioni più o meno efficaci a questi problemi, ma l’unica soluzione trasversale ed applicabile indipendentemente da tutti i fattori citati ci arriva dall’oriente: «Modo migliore di evitare pugno… è di non essere li». Tradotto, il modo migliore è evitare la sovrapposizione fra il lavoro dei web designers e quello degli sviluppatori software.
Per poterla evitare, è necessario che i web designers terminino il loro lavoro prima che gli sviluppatori inizino a lavorare sull’interfaccia utente. Per potersi permettere questo lusso, è necessario prevedere i due step di sviluppo, chiarire il problema al committente e chiedergli di approvare la grafica del sito prima che questo vada online, ovvero spiegargli che da quando lui approva la grafica a quando andrà online passerà un tempo maggiore di zero, durante il quale gli sviluppatori creeranno l’interfaccia utente e durante il quale nessuna modifica all’asspetto grafica sarà più possibile. Nella realtà dei fatti poi si cerca di non essere così rigidi, ma è importante far passare il messaggio a priori.

La metodologia di sviluppo del team deve tenere conto del fatto che lo sviluppo delle pagine HTML non deve sovrapporsi, in linea temporale, allo sviluppo dell’interfaccia utente dinamica, ma deve precederla.

I web designers sviluppano quindi prima la struttura HTML delle pagine, utilizzando dati statici e fittizi per le parti che saranno in futuro generate dinamicamente. Contemporaneamente, sul lato sviluppo software, gli sviluppatori partono dal backend (come fra l’altro è normale che sia) e solo in un secondo momento, quando l’aspetto estetico è ormai stabile e deciso, lo si congela e si procede allo sviluppo della parte software che si occuperà dell’interfaccia utente.

Dopo il congelamento dell’aspetto estetico, sarà ancora possibile, per i web designers, apportare qualsiasi modifica essi vogliano ai files CSS, ma non sarà più possibile per loro modificare autonomamente i files HTML. Qualsiasi modifica ai files HTML, dopo il congelamento, dovrà essere fatta dal team di sviluppo software su indicazione dei designers e, in caso di modifiche strutturali, potrebbe comportare il rifacimento di parte del lavoro di sviluppo software per l’interfaccia utente eventualmente già svolto.

Tutto questo deve essere tenuto in debita considerazione nel pianificare i lavori, nel prevedere le date di consegna, nell’anticipare al cliente gli step di produzione del sito ed in generale nel costruire un diagramma di Gantt il più possibile fedele al progetto che si sta facendo.

Va bene, direte voi, ma in che modo questa soluzione sarebbe migliore di altre? Beh, il meglio ed il peggio sono concetti soggettivi, ma a favore di questa soluzione c’è una minor dipendenza da variabili aleatorie che, per quanto aleatorie, giocano sempre a nostro sfavore, e fanno immancabilmente slittare in avanti la data di consegna. In pratica questa soluzione ha il grande vantaggio di permetterci di dire subito al cliente una data di consegna che più probabilmente riusciremo a rispettare. Cliente soddisfatto.

L’anno di Linux sul desktop sarà il 2096.

Microsoft cesserà il supporto di Windows XP a breve. Io nel 2006 scrivevo che quello era l’anno di Linux sul desktop (non su questo blog). In quell’anno qualcosa era cambiato, in effetti, probabilmente grazie alla crescente popolarità di Ubuntu. Siamo però ormai nel 2013 (2014 fra qualche ora) e ci chiediamo ancora quando sarà l’anno di Linux sul desktop. Ubuntu ha cambiato strada, pensa al mobile. Le altre distribuzioni non sono sostanzialmente diverse dal 2006 e non hanno guadagnato quell’appeal che attira le masse. Quale sarà dunque l’anno di Linux sul desktop? La risposta è mai. Oppure il 2096, che poi significa mai.

Gli utenti non passeranno in massa a Linux nell’arco di un anno per lo stesso motivo per cui non l’hanno fatto fino ad oggi: il prodotto migliore non è necessariamente il più usato. Fra qualche mese il 40% dei PC si troveranno con un Windows XP non più supportato. Gli utenti sommessamente, poco alla volta, acquisteranno un PC nuovo con Windows 8, non con Linux. Se ne lamenteranno, pagheranno per il PC nuovo, pagheranno per aggiornare tutti i software, pagheranno per cambiare la stampante che con XP funzionava ma che non ha un driver per Windows 8, pagheranno ancora per il tempo che ciò gli farà perdere, odieranno questa nuova versione di Windows perché completamente diversa dalla vecchia a loro nota e grideranno contro il cielo che il mondo è ingiusto. Tuttavia non installeranno Linux in massa.

Qualcuno, veramente stufo di essere maltrattato, cercherà di installare Linux dopo qualche mese dall’acquisto del PC nuovo, ma scoprirà di non poterlo fare a causa del SecureBoot di Microsoft. La colpa ovviamente sarà di Linux che non è compatibile.
Se Linux un giorno diventerà il sistema più usato sul desktop, sarà solo perché gli altri sistemi proprietari saranno lentamente passati di moda e non saranno più una fonte di guadagno interessante per chi li produce: a quel punto sarà Microsoft stessa a consigliare ai propri utenti di aggiornare da Windows 27 a Linux, fornendo tutte le istruzioni per farlo. Questo succederà quando i PC stessi saranno completamente passati di moda e di necessità. Di moda son già passati adesso, ma continuano ad essere una necessità, perché non mi si venga a raccontare che con un tablet si fa tutto quello che si fa con un PC, tanto non è vero. Un giorno, forse non lontano, potrebbe però diventare un po’ più vero: quel giorno i PC non interesseranno più a nessuno e Linux diventerà il sistema operativo più utilizzato sui PC.

Prima di allora ci sono troppi soldi in ballo e non succederà.

Eccezioni: prevedere l’imprevedibile.

Sale qb. Questa è la parte più antipatica di ogni ricetta di cucina per chi non ha esperienza di cuoco. Di solito arriva al fondo dell’elenco ingredienti. Cosa significa qb? Beh sì, significa “quanto basta”, ma a quanto corrisponde? Potremmo dire che il calcolo del valore della variabile qb procede per approssimazioni successive:

1. Assaggia l'impasto
2. È buono?
2A. Sì, allora non aggiungere sale, la ricetta è terminata
2B. No, allora aggiungi un pizzico di sale e torna al punto 1

Facile no? Magari è una procedura un po’ lenta e magari in alcuni casi si finisce per mangiarsi gran parte dell’impasto prima di raggiungere la quantità corretta di sale, ma nella maggior parte dei casi funziona. Non prevede però tutti i casi. Cosa succede se l’impasto è già troppo salato in partenza? Aggiungo zucchero? Butto via tutto e riparto da capo? Uso l’impasto per la cena del cane? Procediamo per passi. Prima di tutto traduciamo in codice la procedura descritta sopra. Faccio notare che ci sarebbero molti modi per tradurla in codice, questo è solo uno dei tanti:

public void adjustSalt()
{
  Recipe r = getRecipe();
  if (r.hasGoodTaste())
    return;
  else
  {
    r.addSalt(0.6);
    ajustSalt();
  }
}

Chiariamo subito che questo codice è incompleto, nulla si dice di cosa sia la classe Recipe o di come siano fatte le varie funzioni che vengono chiamate come se esistessero, a parte la adjustSalt() che è l’unica definita. Inoltre 0.6 è un numero arbitrario che ho deciso che significa “un pizzico di sale”. Quello che ho scritto è una via di mezzo fra codice Java e pseudocodice, serve solo a rendere l’idea ma così com’è non funzionerà mai da nessuna parte. È comunque utile ad illustrare il concetto.
Il problema di questa funzione è il fatto di dare per scontato che per rendere buono l’impasto basti aggiungere sale fino a quando la funzione hasGoodTaste() non ritorna true. Se la funziona hasGoodTaste() ritorna false, in realtà noi sappiamo che l’impasto non è buono, ma non sappiamo veramente il perché.
Un’alternativa potrebbe essere:

public void adjustSalt()
{
  Recipe r = getRecipe();
  try
  {
    r.taste();
    return;
  }
  catch(InsipidException ie)
  {
    r.addSalt(0.6);
  }
  catch(SaltyException se)
  {
    r.addPotato(0.4);
    r.addTomato(0.2);
    r.addCream(0.1);
    r.addWater(0.1);
  }
  adjustSalt();
}

Osserviamo che in questo codice non c’è più l’istruzione if. Al suo posto c’è una try, che significa: “prova a fare quel che ti dico, se succede qualcosa di strano, vediamo poi come arrangiarci”. Quello che si richiede di fare all’interno della try è assaggiare l’impasto, ovvero la chiamata alla funzione taste(). Se questa funzione termina il suo compito senza che accada nulla di strano, significa che l’impasto è buono: il programma prosegue normalmente con l’istruzione successiva che è una return e così la funzione adjustSalt() termina e il controllo ritorna al chiamante. Se invece l’impasto non è buono, la taste() lancia un’eccezione. La differenza, rispetto a prima, è che l’eccezione può essere ti qualsiasi tipo e portare con sè molte informazioni circa il problema che si è verificato, mentre prima il valore di ritorno della hasGoodTaste() poteva essere solo vero o falso, senza tanti perché.
L’istruzione try deve essere seguita da una o più istruzioni catch (non è del tutto vero, ma per ora prendiamolo per vero). Ogni istruzione catch specifica un tipo di problema che si può verificare all’interno del blocco try e la relativa soluzione. Per esempio se l’impasto è insipido (InsipidException) la soluzione è aggiungere sale, mentre se è già troppo salato la soluzione è stemperarlo con patate, pomodori, panna ed acqua.

Ci sono molti altri vantaggi nell’usare le eccezioni invece dei valori di ritorno contenenti codici di errore, ma per ora mi fermo qui. Il resto ve lo dirò in un prossimo post.

Tipizzazione forte, oltre lo scripting

Apro, con questo articolo, una serie di suggerimenti sulla programmazione. Non è un corso, è una collezione di informazioni sperabilmente utili. Userò del codice Java quando sarà necessario fare esempi, ma esporrò concetti validi in tutti i linguaggi. Partiamo.
Quale differenza passa fra Java e JavaScript? A parte i nomi somiglianti ed una sintassi simile, tutto il resto. Ed è circa la stessa differenza che passa fra una ricetta cucinata da un famoso chef e la stessa ricetta cucinata da me. Avete mai cucinato? No? Vi svelo un segreto: al posto delle acciughe, se restate senza, si possono usare i capperi. Incredibile no? I capperi, un vegetale, al posto delle acciughe, un pesce. Certo, un vero chef probabilmente non lo farebbe mai, ma io sì.
Allo stesso modo in Java, ovvero un qualsiasi linguaggio con tipizzazione forte, non potete scambiare gli ingredienti a piacimento, mentre in JavaScript, un linguaggio con tipizzazione debole, potete. Bene, ma quando si programma, quali sono gli ingredienti? Naturalmente i dati in input del programma, o della singola funzione, o ancora della singola istruzione. I dati elaborati, ovvero quelli in output, sono l’equivalente della ricetta cucinata, ed a cucinare i dati ci pensa il computer seguendo le nostre istruzioni. Il codice che scriviamo sono come le istruzioni di una ricetta, in cui insegnamo al computer come cucinare i dati. I tipi di dati sono equivalenti, più o meno, ai tipi di ingredienti. Le acciughe sono pesci, quello è il tipo di ingrediente. I capperi sono vegetali. Il mio nome è una stringa, ovvero “Lucio”. La mia età è un numero, ovvero 40, nel momento in cui sto scrivendo.
In un linguaggio con tipizzazione debole, come per esempio JavaScript, io posso calcolare la mia età usando stringhe al posto di numeri, così:
eta = "2013" - "1973";
In questo caso il linguaggio, trovando l’operatore di sottrazione, ovvero il segno meno, sa che ha bisogno di numeri per poterlo eseguire e tenta una conversione automatica in numeri delle stringhe che abbiamo messo attorno al segno meno. Questo è utile per piccoli programmi (script) dove eventuali errori in questa conversione automatica non siano catastrofici. Per esempio, supponiamo che le due stringhe “2013” e “1973” siano frutto di una digitazione da tastiera richiesta all’utente che usa il programma e supponiamo quindi che il programma serva esattamente a dire quanti anni hai. Prima ti chiede in che anno siamo, poi ti chiede in che anno sei nato ed infine ti dice quanti anni hai. Ora supponiamo anche che l’utente, mentre inserisce i dati, per sbaglio metta una lettera I maiuscola al posto della cifra 1 (uno). Ci troveremmo a fare questo calcolo:
eta = "20I3" - "I973";
Nel caso di JavaScript, tipizzazione debole, il calcolo viene eseguito comunque, ma il risultato non è quello sperato, in quanto la variabile eta a questo punto vale NaN, cioè “Not A Number”. Un valore inutile, perché eta è un “non numero” (caspita mi sembra di parlare dei “non morti” di Monkey Island…), ma che cosa sia veramente non lo sappiamo. Peggio che mai, visto che il calcolo viene eseguito comunque ed il programma non viene fermato, potremmo accorgerci di questo “non numero” molto più avanti, quando ormai è troppo tardi per scoprire la causa del problema e correggere l’errore. Supponiamo per esempio di usare l’età per scrivere il nome dell’utente in un file e di avere un file diverso per ogni età, perché stiamo organizzando le cene dei coscritti di tutte le età del nostro paese. Ad un certo punto scriveremo il nome in un file che invece di chiamarsi “coscritti40.txt” si chiamerà “coscrittiNaN.txt”. In quel file ci finiranno tutti quelli che sbagliano a digitare i numeri. E non parteciperanno quindi ad alcuna cena dei costritti. Vogliamo veramente far morire di fame le persone?
Lo stesso programma, scritto in Java o in altro linguaggio con tipizzazione forte, non eseguirebbe la sottrazione. In Java non posso proprio scrivere
int eta = "2013" - "1973";
perché è sintatticamente errato. La sintassi Java impedisce di sottrarre stringhe. Java non fa conversioni automatiche di tipo se non in casi particolari e non pericolosi. Sicuramente non converte mai automaticamente da stringa a numero. Una riga di codice come quella, in Java, non viene neppure compilata: Java non ci permette neppure di provare il programma fino a quando non correggiamo quella riga:
int anno = Integer.parseInt("2013");
int nascita = Integer.parseInt("1973");
int eta = anno - nascita;

Ecco, questo è ciò che Java ci costringe a fare, ovvero ci costringe a scrivere esplicitamente il codice per convertire da stringa a numero intero. Lo fa perché è brutto e cattivo? Lo fa perché è antiquato? No, lo fa perché è un linguaggio con tipizzazione forte. E così facendo, da una parte scarica sul programmatore la responsabilità di eventuali errori di conversione, dall’altra può bloccare il programma e darci tutte le informazioni del caso, se quello che stiamo cercando di convertire non è convertibile nel tipo di dato che ci serve. Il principio è quello del “fail fast”, ovvero ottenere informazioni sugli errori prima possibile, in modo da poterli correggere (richiedere all’utente di digitare correttamente l’anno) prima che sia troppo tardi.
È evidente che il codice Java è più lungo, tre righe al posto di una sola. È anche evidente che il codice Java è più preciso, spiega passo passo cosa stiamo facendo. E, infine, risulta evidente come il programma sia più robusto, ovvero come sia più difficile fregarlo e mandarlo in una condizione di errore non prevista: Java fa di tutto per costringerci a prevedere le condizioni di errore. Resta un dubbio: come fa Java a bloccare il programma nel caso in cui non si possa tradurre la stringa in un numero intero? Solleva un’eccezione, ma di questo vi parlerò la prossima volta.

Ovvio, quindi taciuto. Virtualizzazione con libvirt e KVM.

Ora se avete voglia provate a cercare una pagina che descriva, passo passo, con linguaggio tecnico capibile da un amministratore Linux che però non hai mai usato KVM, come fare a creare un firewall Linux in un sistema guest virtualizzato con libvirt e KVM.

Le informazioni sono tutte là fuori, da qualche parte, su questo non c’è dubbio. Io però non ho trovato un posto in cui fossero raccolte tutte e solo quelle necessarie a creare un firewall in un guest. Per esempio, è abbastanza intuitivo che per creare un firewall virtualizzato la scheda di rete virtuale dovrà essere posta in bridging con quella fisica e non sottoposta a NAT. Non pretendo che questo mi venga spiegato, se non lo capisco da me, ho sbagliato lavoro oppure devo prima studiare e poi mettermi a fare. Quello che però vorrei che mi fosse scritto a chiare lettere, è che il bridge, con la scheda di rete virbr0 (abbreviazione di “VIRtual BRidge 0”), non si può fare. E di nuovo, non è che non ci sia scritto, solo che è scritto in una noticina al fondo di una pagina all’interno del capitolo del networking sulla guida ufficiale RedHat che parla di libvirt. Il che è quasi come se non fosse scritto.

Ma dico io, una cosa con cui non si può fare un bridge, vai a chiamarla “Virtual Bridge 0”? E poi vai a nascondere in una nota a fondo pagina il fatto che si chiama Bridge, ma non è quello che la maggior parte degli amministratori Linux intende per Bridge? Potevano chiamarlo Bridge in riferimento al famoso gioco di carte e sarebbero stati più chiari! E tanto per ridere, si chiama bridge, ma serve a fare il NAT, cioè la configurazione diametralmente opposta ad un bridge!

Comunque sia, capito quello (e non ci è voluto esattamente poco), i giochi si fanno più semplici. Basta creare un bridge con le normali configurazioni di Linux (quel bridge che tutti i sysadmin conoscono), metterci dentro una sola scheda di rete fisica, ed assegnare quel bridge come scheda di rete al sistema guest, invece di virbr0. Ovviamente se il firewall, come probabile, dovrà gestire più schede di rete, si creano tanti bridge quante sono le schede di rete e si assegnano tutti al firewall guest. Ah sì, ma un bridge non dovrebbe servire a mettere in comunicazione due reti fisicamente separate e quindi non dovrebbe contenere due schede di rete invece di una sola? Certo, infatti è così: una delle due schede di rete nel bridge ce la mettete voi (quella fisica), l’altra ce la mette KVM (quella virtuale). Capito ora? Non dovete mettere in bridge la scheda di rete fisica con virbr0, ma dovete creare un bridge lasciando l’altro endpoint vuoto, mettendogli quindi una sola scheda di rete. Esempio di configurazione in /etc/network/interfaces, per sistemi host Debian e derivati:

iface br-eth0 inet static
  bridge_ports eth0
  address 192.168.1.254
  broadcast 192.168.1.255
  netmask 255.255.255.0
  gateway 192.168.1.1
  bridge_stp off
  bridge_waitport 0
  bridge_fd 0

Ovviamente l’indirizzo 192.168.1.254 è quello che si assegna alla scheda di rete per il sistema host. Il sistema guest (il firewall) assegnerà un indirizzo IP diverso, magari anche in una sottorete diversa. I due indirizzi alla fine coesisteranno (multihoming) sulla stessa scheda di rete fisica.

Bene, fin qui sembra abbastanza chiaro. Mancano dei pezzi però. Esempio: come assegno gli indirizzi IP all’interno del firewall? Come voglio (ovvero statici o dhcp, funzionano entrambi).

E alla fine, come faccio il firewall? Anche qui, come mi piace di più. Posso usare regole di iptables inserite a mano, oppure qualcosa come Shorewall o ancora una distro apposita.

Lo so, questo non è un how-to e non sopperisce alla mancanza di un how-to vero e proprio. Forse un giorno avrò il tempo di scrivere un vero how-to, ma per ora… accontentatevi!