Archivi autore: Kiraya

Informazioni su Kiraya

Me, myself and my crazy world..

Scala e Pattern Matching

Ultimamente ho iniziato a giocare con Scala, passato agli onori della cronaca per essere il linguaggio con cui è scritto Apache Spark, uno dei motori di data-processing più in voga nel mondo dei Big Data (e che ha soppiantato da tempo Pig e altre tecnologie basate su Map&Reduce).

Uno degli aspetti più interessanti, riguarda la possibilità di usare dei Pattern di Matching per tipo (sia scalare che su oggetti) per evitare le righe e righe di codice di instanceof , type ,if-else, o semplicemente realizzare la tanto agognata switch basata su stringhe. Altro utilizzo molto interessante del Pattern Matching potrebbe essere quello di eseguire diverse azioni sulla base del tipo di dato incontrato, durante il traversal di strutture composite, come ad esempio un DOM Xml.

Quello che in Java si scrive con diverse if, in Scala si riduce all’essenziale.
Il seguente pezzo di codice, definisce una funziona X che torna una stringa ed accetta qualsiasi tipo di dato in input (Any in scala è l’equivalente di Object) . Un Esempio classico è quello della decodifica dei parametri passati come argomento al nostro main.

def parseMainArgument(arg:String) = arg match{
  case "--help" | "-h" => displayHelpMessage
  case "--log"  | "-l" => showLog
  case "--verbose" | "-v" => setVerbose
  case _  => showArgumentNotFound
}

Il carattere underscore “_” sta a significare il valore di default, quando nessuno dei suddetti viene invocato, quindi nella switch sopra descritta, sta a rappresentare il caso “else”..

Un esempio di utilizzo di più valori nel match:

def myDecodeParam(funcName: String, value: Any) = (funcName,value) match{
   case ("test",val) => test(val)
   case ("log",msg) => log("messaggio:"+msg)
}

Altro aspetto interessante dei match, è la possibilità di analizzare i tipi di dati, ad esempio:

def myfunc(x:Any):String = x match{
   case i:Int => ""+ i + "è un Intero"
   case d:Double => "" + d + "+ un Double"  
   case s:String => s+ " è una Stringa"
}

Un utilizzo frequente è quello tramite le Case Classes.
Le Case Classes sono classi regolari che esportano in automatico i parametri del costruttore e che è possibile poi scomporre e analizzare attraverso il Pattern Matching. Le Case Classes non richiedono l’utilizzo della new, ed il compilatore genera in automatico il metodo “equals” ed il “toString”.

Un esempio classico, riportato anche dalla documentazione, è quello della definizione di classi case per la scomposizione e risoluzione di formule matematiche, ma che è facile poi utilizzare per creare un DSL applicativo.
In questo esempio definiamo una serie di case classes che descrivono operazioni matematiche che vengono svolte in quello che definiamo un ambiente di test, un insieme di valori che assumono le variabili del calcolo. Invece di usare una collection per la definizione delle variabili, lo facciamo utilizzando le funzioni. La notazione

{case "x" => 5}

ad esempio crea una funzione che quando riceve x in input ritorna il valore “5”.
la sua definizione è :

type NomeFunzione = String => Int

dove String è l’input, e Int l’output.

In questo esempio usiamo anche i Traits,cioè i tratti, le caratteristiche da aggiungere alle classi. Sono interfacce con la possibilità di avere implementazioni parziali.

trait Espressione {
    def decodifica(caso:AmbienteTest.Ambiente) : String = {
      this match {        
        case Somma (a,b) => "(" + a.decodifica(caso) +" + " + b.decodifica(caso) + ")"
        case Sottrai (a,b) => "(" + a.decodifica(caso) +" - " + b.decodifica(caso) + ")"        
        case Moltiplica (a,b) => "(" + a.decodifica(caso) +" * " + b.decodifica(caso) + ")"
        case Dividi (a,b) => "(" + a.decodifica(caso) +" / " + b.decodifica(caso) + ")"        
        case Variabile(x) => "" + caso(x)
        case Costante(n) => "" + n
      }
    }
   
    def risolvi(caso:AmbienteTest.Ambiente) : Int = {
      this match {  
        case Somma (a,b) =>a.risolvi(caso) + b.risolvi(caso)
        case Sottrai (a,b) =>a.risolvi(caso) - b.risolvi(caso)
        case Moltiplica (a,b) =>a.risolvi(caso) * b.risolvi(caso)
        case Dividi (a,b) =>a.risolvi(caso) / b.risolvi(caso)
        case Variabile(x) => caso(x)
        case Costante(n) =>  n
      }
    }
   
}

case class Somma (a:Espressione, b:Espressione) extends Espressione
case class Sottrai (a:Espressione, b:Espressione) extends Espressione
case class Moltiplica (a:Espressione, b:Espressione) extends Espressione
case class Dividi (a:Espressione, b:Espressione) extends Espressione
case class Variabile (x:String) extends Espressione
case class Costante (n:Int) extends Espressione
package object  AmbienteTest {type Ambiente =  String => Int}

object ExpressionDecoder {
  def main(args:Array[String]){
     val myTest:AmbienteTest.Ambiente = {case "x"=>1  case "y"=>2}
     val exp:Espressione =Sottrai(Moltiplica(Somma(Somma(Costante(5),Variabile("x")),Variabile("y")),Costante(2)),Variabile("x"))
     println("Espressione: "+exp.decodifica(myTest));
     println("Risultato: "+exp.risolvi(myTest));
  }
 
}

Il risultato in console dell’esecuzione del codice sopra esposto è:

Espressione: ((((5 + 1) + 2) * 2) - 1)
Risultato: 15

Come possiamo vedere, i pattern matching con Scala, aprono tutta una serie di alternative alla programmazione per “IF” , arrivando a rendere più snello e leggibile il nostro codice.

Tomcat 7, jndi datasource ed eclipse

Piccola guida per la risoluzione di un banale task ma che spesso crea problemi, e cioè la definizione di un datasource jdbc su Tomcat .
Nel caso specifico utilizzo un datasource con driver SqlServer di Microsoft.
La libreria Microsoft contenente il driver jdbc (nel mio caso la sqljdbc4.jar), deve essere messa nella cartella lib del server tomcat.
Va poi configurata la risorsa jndi nel file server.xml nella cartella conf di tomcat, aggiungendo ad esempio questo :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--?xml version="1.0" encoding="UTF-8"?-->
<!--contenuto vario del nostro file server,trovare l'elemento globalnamingresources e aggiungere la nostra resource-->
<GlobalNamingResources>
<Resource name="jdbc/testdb" auth="Container" type="javax.sql.DataSource"
        username="kiraya"
        password="kiraya"
        factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory"

        driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver"
        url="jdbc:sqlserver://xx.xx.xx.xx:1433;DatabaseName=MYDB"
        maxActive="50"
        maxIdle="10"
        maxWait="15000"
        removeAbandoned="true"
       removeAbandonedTimeout="30"
       logAbandoned="true"
        validationQuery="select 1" />

</GlobalNamingResources>

Fatto questo, per poter rendere visibile al contesto web della nostra webapplication il driver, senza essere costretti a registrarlo manualmente tramite il DriverManager, mettiamo un resource-link o copiamo direttamente l’xml della definizione resource sopra inserita, nel file context.xml della cartella conf di tomcat, od in un file context.xml nella cartella META-INF della webapplication.
Dovendo rilasciare l’applicazione su diversi application server, preferisco non portare specifici files di configurazione nell’applicazione(ove possibile) e quindi lascio il file context.xml nella cartella config del server tomcat.

Contenuto del context.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?xml version="1.0" encoding="UTF-8"?>
<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
--><!-- The contents of this file will be loaded for each web application --><Context>

    <!-- Default set of monitored resources -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <Resource name="jdbc/testdb" auth="Container" type="javax.sql.DataSource"
        username="kiraya"
        password="kiraya"
        factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory"

        driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver"
        url="jdbc:sqlserver://xx.xx.xx.xx:1433;DatabaseName=MYDB"
        maxActive="50"
        maxIdle="10"
        maxWait="15000"
        removeAbandoned="true"
        removeAbandonedTimeout="30"
        logAbandoned="true"
        validationQuery="select 1" />    -->

    <!-- Uncomment this to enable Comet connection tacking (provides events
         on session expiration as well as webapp lifecycle) -->
    <!--
    <Valve className="org.apache.catalina.valves.CometConnectionManagerValve" />
    -->

</Context>

Fatto questo, possiamo utilizzare una lookup jndi nella maniera classica per ottenere una connessione db.

1
2
3
javax.naming.InitialContext context = new javax.naming.InitialContext();
DataSource ds = (DataSource)context.lookup("java:/comp/env/jdbc/testdb");
Connection conn = ds.getConnection();

Se tomcat è configurato come server Runtime dentro eclipse, e quindi lo start e stop viene fatto da li, allora il file context da modificare non è quello della cartella conf del server ma quella della versione gestita da Eclipse, generalmente contenuta nel workspace, nella cartella Server..

Inno Setup & Browser Helper Objects & IE Toolbars

In un mio recente progetto, ho avuto la necessità di creare ,per Internet Explorer, un Browser Helper Object (un plugin che sfrutta le stesse interfacce e tecnologie delle Toolbar) che eseguisse delle operazioni custom su un particolare sito in ambiente Intranet.
La realizzazione del BHO, è relativamente semplice usando le interfacce IObjectWithSite e le direttive di interoperabilità del codice.
Per il setup invece è tutto particolarmente difficile. Le interfacce del BHO vanno registrate nel registry di Windows e rese accessibili al sistema di plugin di IE.
Per far questo ho usato un software free, INNO SETUP, che permette di generare degli installer a partire da uno script.
Inno Setup

Lo script, permette di definire il comportamento del setup, le informazioni da visualizzare nelle maschere, e tutte le operazioni da effettuare sul registry di Windows, compresa la problematica di differenze di posizionamento e nome di alcune voci tra i sistemi a 32 e a 64 bit. Il Bho di cui realizzo il setup è compilato in maniera da funzionare compatibilmente con tutti i sistemi e quindi richiede un setup compatibile sia con i sistemi a 32 che a 64 bit.

Lo script inizia con una serie di definizioni di costanti usate poi nel resto delle direttive, tra cui il nome della dll, il nome del namespace utilizzato, e il nome della classe interna che viene esposta al browser, contenente i metodi pubblici da utilizzare.

1
2
3
4
5
6
7
8
9
#define MyAppDescription "My Helper Objects"
#define MyAppName "MyBHO"
#define MyAppVersion "1.0.0.0"
#define MyAppPublisher "Metalide"
#define MyAppURL "http://www.metalide.com"
#define MyAppDllName "MyBHO.dll"
#define MyAppId "{A80C384E-4178-4AD0-85BC-3196241437C5}"
#define MyAppNamespace "MyBHO.MyHelperObject"
#define MyAppClass "MyBHOExtension"

Subito dopo si crea una sezione [Setup] per associare le costanti ai parametri dell’installer e altri attributi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Setup]
AppId={#MyAppName}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={pf}\MyTools\MyHelperObjects
DefaultGroupName=My_IETOOLS
DisableProgramGroupPage=yes
DisableDirPage=yes
CreateAppDir=yes
OutputBaseFilename=My_IETOOLSBHOSetup
Compression=lzma
SolidCompression=yes

Viene poi impostata la lingua del setup e il path di destinazione della dll (che in questo caso va registrata nella GAC di Windows), e l’eventuale icona da usare per le voci di unistall:

1
2
3
4
5
6
7
8
[Languages]
Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl"

[Files]
Source: "D:\Dev\MyBho\MyBHOBHOSETUP\MyBHO.dll"; DestDir: "{app}"; Flags: ignoreversion

[Icons]
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"

Fatto questo si passa alla sezione [Code] che mantiene delle funzioni (sono scritte in Pascal) generiche richiamate dai metodi “hook” gestiti da Inno stesso,come ad esempio il methodo InitializeSetup che viene eseguito a startup dell’installazione.
In questa sezione ho messo un metodo per la rilevazione dell’installazione su sistemi a 64 e 32 bit e sulla verifica del framework .net installato (il bho che ho realizzato in c# richiede, visto il codice managed, la presenza di .net 4.0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[Code]
function IsDotNetDetected(version: string; service: cardinal): boolean;
//    'v1.1.4322'     .NET Framework 1.1
//    'v2.0.50727'    .NET Framework 2.0
//    'v3.0'          .NET Framework 3.0
//    'v3.5'          .NET Framework 3.5
//    'v4\Client'     .NET Framework 4.0 Client Profile
//    'v4\Full'       .NET Framework 4.0 Full Installation
//service: per la presenza richiesta di service pack:
//    0               No service packs
//    1, 2, etc.      Service pack 1, 2, etc..
var
    key: string;
    install, serviceCount: cardinal;
    success: boolean;
begin
    key := 'SOFTWARE\Microsoft\NET Framework Setup\NDP\' + version;
    // .NET 3.0 usa InstallSuccess nella chiave Setup
    if Pos('v3.0', version) = 1 then begin
        success := RegQueryDWordValue(HKLM, key + '\Setup', 'InstallSuccess', install);
    end else begin
        success := RegQueryDWordValue(HKLM, key, 'Install', install);
    end;
    // .NET 4.0 usa Servicing al posto di SP
    if Pos('v4', version) = 1 then begin
        success := success and RegQueryDWordValue(HKLM, key, 'Servicing', serviceCount);
    end else begin
        success := success and RegQueryDWordValue(HKLM, key, 'SP', serviceCount);
    end;
    result := success and (install = 1) and (serviceCount >= service);
end;



function Install32BitOn64Bit(S: String):string;
begin
    if IsWin64 then
      Result := 'Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{#MyAppId}'
    else
      Result := 'Software\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{#MyAppId}'
end;

function InitializeSetup(): Boolean;
begin
  if  IsDotNetDetected('v4\Client', 0) or  IsDotNetDetected('v4\Full', 0) then
      Result :=true
  else begin
      MsgBox('Attenzione Framework .NET 4.0 non rilevato', mbError, mb_Ok);
      Result :=false;
     end
 
end;

Come ultimo step del nostro script andiamo ad indicare nella sezione [Registry] tutte le chiavi da impostare (e sono molte) per il funzionamento del nostro componente come plugin di IE. Il guid che si passa, è il guid associato serve a indicare i componenti .net.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[Registry]
Root: HKCR; Subkey: "CLSID\{{#MyAppId}"; Flags: uninsdeletekey
Root: HKCR; Subkey: "CLSID\{{#MyAppId}"; ValueType: string; ValueName: ; ValueData: {#MyAppNamespace}
Root: HKCR; Subkey: "CLSID\{{#MyAppId}"; ValueType: string; ValueName: MenuText; ValueData: {#MyAppName}
Root: HKCR; Subkey: "CLSID\{{#MyAppId}"; ValueType: string; ValueName: HelpText; ValueData: {#MyAppDescription}
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\Implemented Categories\{{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}"
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: ; ValueData: mscoree.dll
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: ThreadingModel; ValueData: Both
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: Class; ValueData: {#MyAppNamespace}
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: CodeBase; ValueData: file:///{pf}\MyTools\MyBrowserHelperObjects\{#MyAppDllName}
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: Assembly; ValueData: {#MyAppName}, Version={#MyAppVersion}, Culture=neutral, PublicKeyToken=dedfcbc464c1d691
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: RuntimeVersion; ValueData: v4.0.30319

Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}"; Flags: uninsdeletekey
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}"; ValueType: string; ValueName: ; ValueData: {#MyAppNamespace}
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}"; ValueType: string; ValueName: MenuText; ValueData: {#MyAppName}
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}"; ValueType: string; ValueName: HelpText; ValueData: {#MyAppDescription}
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\Implemented Categories\{{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}"
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: ; ValueData: mscoree.dll
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: ThreadingModel; ValueData: Both
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: Class; ValueData: {#MyAppNamespace}
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: CodeBase; ValueData: file:///{pf}\MyTools\MyBrowserHelperObjects\{#MyAppDllName}
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: Assembly; ValueData: {#MyAppName}, Version={#MyAppVersion}, Culture=neutral, PublicKeyToken=dedfcbc464c1d691
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: RuntimeVersion; ValueData: v4.0.30319

Root: HKLM; Subkey: {code:Install32BitOn64Bit};  Flags: uninsdeletekey
Root: HKLM; Subkey: {code:Install32BitOn64Bit}; ValueType: dword; ValueName: NoExplorer; ValueData: 1

Fatto questo, eseguendo lo script dal compilatore di Inno Setup, è possibile ottenere l’installer/uninstaller del nostro componente.

Ovviamente questo tutorial è il frutto di molte ricerche sulla rete, e di riferimenti presi da diversi blogger che hanno affrontato l’argomento, a cui vanno i miei ringraziamenti, e a quel pozzo infinito di spunti e riflessioni che è la comunità di StackOverflow, a cui va la mia gratitudine eterna 🙂 .