[JBoss&EJB] EJB remoti: come effettuare la lookup da client JBoss

In questo articolo, descrivo come effettuare una lookup da un client JBoss verso un servizio EJB (Enterprise Java Bean) remoto, esposto su una macchina differente dal client stesso.

Queste sono le tecnologie utilizzate in questo tutorial:

ejb_lookup

Il nostro EJB remoto di test è il seguente:

@Stateless
@Remote(TestService.class)
@TransactionManagement(TransactionManagementType.BEAN)
public class TestServiceBean implements TestService {

       public methodTest(){

           //code implementation here

       }

}

Il precedente EJB remoto verrà esposto su un application server installato su una macchina differente (che chiamerò destination server). Di seguito, divido le attività di configurazione in due: configurazione destination server e configurazione client server.

 

Configurazione destination server

Sull’application server su cui è deployato l’EJB remoto, occorre semplicemente creare un utente applicativo, per mettere in sicurezza la chiamata remota (security realm). Utilizziamo lo script add-user.sh (o .bat, se siete su Windows), presente in ${JBOSS_HOME}/bin, per creare l’utente ejb (con password test):

add-user JBoss

Dopo aver creato l’utente su JBoss, basta startare l’AS e deployare il servizio remoto.

 

Configurazione client server

Il grosso della configurazione va fatta sul client che deve richiamare l’EJB remoto.

Nel file di configurazione di JBoss, standalone.xml (o domain.xml), occorre eseguire quanto segue:

  • creare il security realm sul client server
   <management>
        <security-realms>
            ...
            <security-realm name="ejb-security-realm">
                <server-identities>
                    <secret value="dGVzdA=="/>
                </server-identities>
            </security-realm>
        </security-realms>

Dove in secret c’è la password (test) codifica in BASE-64.

  • creare l’outbound-socket-binding sul client server
 <socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
        ...
        <outbound-socket-binding name="remote-ejb">
            <remote-destination host="127.0.01" port="4447"/>
        </outbound-socket-binding>
     </socket-binding-group>

Dove al posto dell’indirizzo IP 127.0.0.1 potete inserire quello della macchina remota del destination server (la porta di default del remoting è la 4447, a meno che non l’abbiate cambiata).

  • creare un remote-outbound-connection che usa l’outbound-socket-binding
<subsystem xmlns="urn:jboss:domain:remoting:1.1">
....
            <outbound-connections>
                <remote-outbound-connection name="remote-ejb-connection" outbound-socket-binding-ref="remote-ejb" security-realm="ejb-security-realm" username="ejb">
                    <properties>
                        <property name="SASL_POLICY_NOANONYMOUS" value="false"/>
                        <property name="SSL_ENABLED" value="false"/>
                    </properties>
                </remote-outbound-connection>
            </outbound-connections>
        </subsystem>

Dove in corrispondenza dell’attributo username c’è l’utente di test che abbiamo creato sul destination server.

Lato configurazione del client server abbiamo terminato.

Di seguito, si riportano gli interventi da fare sull’applicazione client (client application), per poter richiamare il servizio remoto:

  • inserire nel classpath dell’applicazione client il file jboss-ejb-client.xml 
<jboss-ejb-client xmlns="urn:jboss:ejb-client:1.0">
    <client-context>
        <ejb-receivers>
            <remoting-ejb-receiver outbound-connection-ref="remote-ejb-connection"/>
        </ejb-receivers>
    </client-context>
</jboss-ejb-client>

Infine, per effettuare la lookup remota in modo programmatico, ecco un esempio di una classe client di test:

import javax.naming.Context;
import java.util.Hashtable;
import javax.naming.InitialContext;
 
...
public void invokeOnBean() {
        try {
            final Hashtable props = new Hashtable();
            // setup the ejb: namespace URL factory
            props.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
            // create the InitialContext
            final Context context = new javax.naming.InitialContext(props);
 
            // Lookup
            final TestService bean = (TestService) context.lookup("ejb:" + "myapp" + "/" + "myejb" + "/" + "" + "/" + "TestService" + "!" + it.francescoficetola.ejb.TestService.class.getName());
 
            // invoke on the bean
            final String greeting = bean.test();
 
 
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
}

Nota. Quando si deploya l’EJB remoto, il JNDI name viene automaticamente registrato dall’application server. Ad esempio, per gli EJB stateless, il JNDI name è come questo:

ejb:jboss-as-ejb-remote-app/TestServiceBean!it.francescoficetola.ejb.TestService

Come si riporta in questo articolo (EJB invocations from a remote client using JNDI), ecco la regola per la generazione del JNDI name:

ejb:<app-name>/<module-name>/<distinct-name>/<bean-name>!<fully-qualified-classname-of-the-remote-interface>

Poiché in app-namemodule-name mi venivano registrati anche i numeri di versione dei moduli applicativi (dopo aver buildato con Maven), ho effettuato un override dei loro valori in questo modo:

  • per settare l’app-name, ho inserito l’application-name nel file application.xml dell’EAR in cui è deployato l’EJB remoto:

<application-name>myapp</application-name>

  • per settare il module-name, occorre inserire nel classpath del modulo EJB (nella directory META-INF) il file ejb-jar.xml:
<ejb-jar xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/ejb-jar_3_2.xsd"
    version="3.2">
    <module-name>myejb</module-name>
</ejb-jar>

 

Riferimenti:

[PrimeFaces] Due approcci per customizzare i tag di PrimeFaces

Logo Prime FacesIn un recente post, ho “lodato” PrimeFaces come uno dei migliori framework Java Server Faces (JSF) attualmente in circolazione. In effetti, le componenti grafiche, rispetto ad altre librerie del genere (vedi Rich Faces), sono sicuramente più stabili, complete e graficamente più carine. Tuttavia, può capitare di dover fare delle customizzazioni, specie per renderne alcune davvero accessibili (nonostante da documentazione si dichiari l’aderenza di PrimeFaces alle specifiche Accessibile Rich Internet Applications – WAI-ARIA).

Illustro qui due metodi per customizzare i tag di Prime. Nell’esempio, verrà modificato la componente “PanelGrid” (p:panelGrid), ma la procedura può essere applicata a tutti i tag della libreria “core”.

Entrambi metodi che vi illustrerò, prendono come riferimento la documentazione dei tag di PrimeFaces. Ecco il link alla documentazione dell’attuale versione (4.0): Documentazione PrimeFaces 4.0

Ad esempio, per il tag PanelGrid ecco le informazioni che ci servono:

tagPanelGrid PrimeFaces

 

1° Metodo: Customizzazione della Renderer Class (RenderKit)

Se vi occorre customizzare soltanto il rendering della componente grafica, basta estendere la classe “Renderer” ad essa associata. Come già anticipato sopra, per capire qual è tale classe, occorre leggere la documentazione relativa al tag da customizzare. Nel caso del panelGrid, la classe da estendere è org.primefaces.component.panelgrid.PanelGridRenderer

NOTA. Gli attributi rendererType e rendererClass, che si leggono da documentazione, non coincidono. Quindi, state attenti ad annotarvi queste informazioni, perché occorre configurarle allo stesso modo per la classe “custom” che scriverete, come illustrato di seguito.

Ecco la classe CustomPanelGridRenderer che estende quella “core” PanelGridRenderer:

package it.francescoficetola.prime.web.component;

import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.primefaces.component.column.Column;
import org.primefaces.component.panelgrid.PanelGrid;
import org.primefaces.component.panelgrid.PanelGridRenderer;
import org.primefaces.component.row.Row;

public class CustomPanelGridRenderer extends PanelGridRenderer{

	 public CustomPanelGridRenderer() {
                super();
	        System.out.println("costruttore classe custom renderer");
	 }

         @Override
	 public void encodeRow(FacesContext context, Row row, String columnRole, String rowClass, String columnClass) throws IOException {
	        ResponseWriter writer = context.getResponseWriter();

	        writer.startElement("tr", null);
	        if(shouldWriteId(row)) {
	            writer.writeAttribute("id", row.getClientId(context), null);
	        }

	        writer.writeAttribute("class", rowClass, null);
	        writer.writeAttribute("role", "row", null);

	        for(UIComponent child : row.getChildren()) {
	            if(child instanceof Column && child.isRendered()) {
	                Column column = (Column) child;
	                String styleClass = null;
	                String userStyleClass = column.getStyleClass();

	                if(userStyleClass != null && columnClass != null) styleClass = columnClass + " " + userStyleClass;
	                else if(userStyleClass != null && columnClass == null) styleClass = userStyleClass;
	                else if(userStyleClass == null && columnClass != null) styleClass = columnClass;

	                writer.startElement("td", null);
	                if(shouldWriteId(column)) { 
	                    writer.writeAttribute("id", column.getClientId(context), null);
	                }
	                writer.writeAttribute("role", columnRole, null);

	                if(column.getStyle() != null) writer.writeAttribute("style", column.getStyle(), null);
	                if(styleClass != null) writer.writeAttribute("class", styleClass, null);
	                if(column.getColspan() > 1) writer.writeAttribute("colspan", column.getColspan(), null);
	                if(column.getRowspan() > 1) writer.writeAttribute("rowspan", column.getRowspan(), null);

	                column.encodeAll(context);

	                writer.endElement("td");
	            }
	        }

	        writer.endElement("tr");
	    }
}

Nell’esempio precedente, è stato effettuato l’override del metodo encodeRow. In base al comportamento che vorrete customizzare, occorre individuare nella classe “Renderer” della vostra componente, il metodo associato a tale comportamento ed effettuarne l’override, modificandone la logica.

Per poter registrare la vostra classe “custom” di rendering, occorre configurare il faces.config.xml:

<?xml version='1.0' encoding='UTF-8'?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
        version="2.0">

	<render-kit>
            <renderer>
                <component-family>org.primefaces.component</component-family>
                <renderer-type>org.primefaces.component.PanelGridRenderer</renderer-type>
                <renderer-class>it.francescoficetola.prime.web.component.CustomPanelGridRenderer</renderer-class>
            </renderer>
        </render-kit>

</faces-config>

I valori da associare agli attributi component-familyrenderer-type li prendete da documentazione del tag, come suddetto. In questo modo, ogni volta che utilizzate il “core tag” sulle pagine JSF della vostra applicazione (nell’esempio, p:panelGrid), in automatico verrà richiamata la vostra classe “renderer”.

NOTA. Se notate che nulla accade, provate ad inserire nel file web.xml della vostra applicazione le seguenti righe di codice (inserendo anche il file faces-config.xml nella cartella WEB-INF):

    <context-param>
        <param-name>javax.faces.CONFIG_FILES</param-name>
        <param-value>
            /WEB-INF/faces-config.xml, /faces-config.xml
        </param-value>
    </context-param>

 

2° Metodo: Customizzazione del tag (CustomTag)

Il secondo metodo che vi illustro è sicuramente il più completo, perché vi permette di modificare tutto il comportamento del tag associato alla componente grafica, e non solo il suo rendering.

In questo caso, vanno estesi sia il Component Class che il Renderer Class. Nel caso del p:panelGrid di questo esempio:

Renderer class: org.primefaces.component.panelgrid.PanelGridRenderer

Component class: org.primefaces.component.panelgrid.PanelGrid

package it.francescoficetola.prime.web.component;

import javax.faces.component.FacesComponent;
import org.primefaces.component.panelgrid.PanelGrid;

@FacesComponent("it.francescoficetola.prime.component.CustomPanelGrid")
public class CustomPanelGrid extends PanelGrid{

	public CustomPanelGrid() {
		super();
		System.out.println("Costruttore del component class");
	}

	@Override
	public void setColumns(int _columns) {
	    getStateHelper().put(PropertyKeys.columns, _columns);
	}
}

Nell’esempio del PanelGrid, è stato effettuato l’override del metodo setColumns, ma anche qui vale quanto detto nel 1° metodo di customizzazione, ossia che in base al comportamento che vorrete customizzare, occorre individuare nel Component Class il metodo associato per modificandone la logica, attraverso l’override.

Di seguito, vi ripeto la classe di “renderer” custom, che anche per questo metodo va prevista, ma che in più presenta anche delle annotazioni di classe:

package it.francescoficetola.prime.web.component;

import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.FacesRenderer;

import org.primefaces.component.column.Column;
import org.primefaces.component.panelgrid.PanelGrid;
import org.primefaces.component.panelgrid.PanelGridRenderer;
import org.primefaces.component.row.Row;

@FacesRenderer(
	    componentFamily=PanelGrid.COMPONENT_FAMILY,
	    rendererType="it.francescoficetola.prime.web.component.CustomPanelGridRenderer"
	)
public class CustomPanelGridRenderer extends PanelGridRenderer{

	public CustomPanelGridRenderer() {
	    System.out.println("costruttore della classe renderer");
	    }

         @Override
	 public void encodeRow(FacesContext context, Row row, String columnRole, String rowClass, String columnClass) throws IOException {
	        ResponseWriter writer = context.getResponseWriter();

	        writer.startElement("tr", null);
	        if(shouldWriteId(row)) {
	            writer.writeAttribute("id", row.getClientId(context), null);
	        }

	        writer.writeAttribute("class", rowClass, null);
	        writer.writeAttribute("role", "row", null);

	        for(UIComponent child : row.getChildren()) {
	            if(child instanceof Column && child.isRendered()) {
	                Column column = (Column) child;
	                String styleClass = null;
	                String userStyleClass = column.getStyleClass();

	                if(userStyleClass != null && columnClass != null) styleClass = columnClass + " " + userStyleClass;
	                else if(userStyleClass != null && columnClass == null) styleClass = userStyleClass;
	                else if(userStyleClass == null && columnClass != null) styleClass = columnClass;

	                writer.startElement("td", null);
	                if(shouldWriteId(column)) { 

	                    writer.writeAttribute("id", column.getClientId(context), null);
	                }
	                writer.writeAttribute("role", columnRole, null);

	                if(column.getStyle() != null) writer.writeAttribute("style", column.getStyle(), null);
	                if(styleClass != null) writer.writeAttribute("class", styleClass, null);
	                if(column.getColspan() > 1) writer.writeAttribute("colspan", column.getColspan(), null);
	                if(column.getRowspan() > 1) writer.writeAttribute("rowspan", column.getRowspan(), null);

	                column.encodeAll(context);

	                writer.endElement("td");
	            }
	        }

	        writer.endElement("tr");
	    }

}

A questo punto, occorre creare la taglib con i propri “custom tag”. Nel mio caso, ho creato un file customtag.taglib.xml, con il seguente contenuto:

<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
    version="2.0"
>
    <namespace>http://francescoficetola.it/ui</namespace>

    <tag>
        <tag-name>custompanelgrid</tag-name>
        <component>
            <component-type>it.francescoficetola.prime.web.component.CustomPanelGrid</component-type>
            <renderer-type>it.francescoficetola.prime.CustomPanelGridRenderer</renderer-type>
        </component>
    </tag>
</facelet-taglib>

Nel file della taglib, occorre registrare tutti i “custom tag” che prevedete (nel mio caso, ho per ora solo il custompanelgrid, che vi ricordo estende il tag “core” p:panelgrid di PrimeFaces). Salvate questo file nella directory WEB-INF del vostro progetto.

Infine, occorre registrare la taglib nel file web.xml, come segue:

	<!-- CONTEXT PARAM  -->
	<context-param>
	    <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
	    <param-value>/WEB-INF/customtag.taglib.xml</param-value>
	</context-param>

Ora siamo pronti ad utilizzare il nostro custom tag. Ricordatevi di definire il namespace nella pagina JSF in cui utilizzare la vostra componente custom:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<ui:composition 
	xmlns="http://www.w3.org/1999/xhtml"
	xmlns:c="http://java.sun.com/jsp/jstl/core"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:p="http://primefaces.org/ui"
	xmlns:customtag="http://francescoficetola.it/ui"
>

<h:html>

   <h:head>
   </h:head>

   <h:body>

	<h:form id="myForm">

            <p:messages id="messages" /> 

	     <h:panelGroup id="myPanelGroup">

		 <customtag:custompanelgrid>

		        <p:row>
				<p:column>Colonna 1</p:column>
                                <p:column>Colonna 1</p:column>
			</p:row>

	         </customtag:custompanelgrid>

	     </h:panelGroup>

	</h:form>

   </h:body>

</h:html>

</ui:composition>

 

Riferimenti utili:

[AppMobile] iParcheggiatori

iparcheggiatoriApplicazione Mobile: iParcheggiatori

Pagina Store: Apple Store

Sito web: http://www.iparcheggiatori.it

Descrizione: App per la segnalazione dei “parcheggiatori abusivi”. L’idea è quella di sfruttare la tecnologia per “stanare” questi estorsori in piena regola. Nasce così iParcheggiatori, un’applicazione gratuita che permette di inviare segnalazioni “Anonime” da qualsiasi punto d’Italia in cui ci si trovi.
Il sistema, attraverso la geolocalizzazione, permette di indicare il punto esatto della città in cui sono presenti i parcheggiatori abusivi. E’ possibile, in aggiunta, indicare se il pagamento è “a piacere” (ossia una somma da contrattare con il “lavoratore” di turno) e se qualcuno abbia subito minacce o danni alla propria vettura.

Requisiti: Richiede l’iOS 5.1.1 o successive. Compatibile con iPhone, iPad e iPod touch. Questa app è ottimizzata per iPhone 5.

Categoria: Utility

Tecnologie: XCode 5; iPhone 3GS/4/4S/5/5S; MacOx 10.9 (Maverick) – Linguaggio: Objective-C; Framework e Librerie: MapKit; Integrazione con servizi REST-JSON.

Creative Commons License
This work by Francesco Ficetola is licensed under a Creative Commons Attribution 4.0 International License.
Based on a work at www.francescoficetola.it.
Permissions beyond the scope of this license may be available at http://www.francescoficetola.it/2014/02/22/appmobile-iparcheggiatori/.

[PrimeFaces&jQuery] Letture consigliate su PrimeFaces e jQuery

Ho finito di leggere due libri ben fatti e molto pratici su PrimeFaces e jQuery, editi da Packt Publishing, e che consiglio a chi vuol imparare ad utilizzare queste due tecnologie web attualmente molto in voga.

Learning jQuery Learning jQuery

 

 

 

 

 

 

Sono corredati ovviamente anche da esempi scaricabili. Le versioni eBook sono economicamente accessibili, ma per Learning jQuery esiste anche una versione online free (disponibile QUI).

Il livello non è sicuramente avanzato, ma penso siano ottimi per chi sta approcciando ad un primo studio di questi framework. Sicuramente occorrono conoscenze di base di Javascript, CSS e, in generale, JSF (o tecnologie simili).

 

[iOs] Link Maker: generazione dinamica dei link di Apple Store

Per la generazione dei link di tutti gli articoli (ebook, musica, app, podcast, audiolibri, ecc.) disponibili su iTunes, iBook ed App Store, Apple mette a disposizione la pagina di iTunes Link Maker:  http://linkmaker.itunes.apple.com

Link Maker Apple

Esistono, in realtà, vari strumenti utili per la generazione dei link (creare link ad iTunes), utili però solo per la creazione di widget e link statici. Ad esempio, in questo Q&A si spiega come generare link a propri item (come, ad esempio, alla pagina delle app sviluppate da uno sviluppatore o da una società: Creating easy-to-read short links to the App Store for your apps and company).

Tuttavia, non esiste un vero e proprio framework che consenta di recuperare dinamicamente tali link. Occorre richiamare una URL di servizio, fornita proprio da Apple, così come si legge dalla Search API Documentation.

Ho così realizzato uno “scriptino” PHP in grado di restituirvi i risultati della pagina di iTunes Link Maker in formato JSON. Per fare ciò, mi sono aiutato con uno sniffer e ho recuperato tutte le URL richiamate dopo aver lanciato una ricerca sulla pagina di Link Maker. Le URL generate “dietro le quinte” dalla ricerca sono un bel po’ e cambiano in base alla combinazione tra “media” selezionato (categoria di item: audio, video, app, ebook, ecc.) e relative entity (sottocategorie di item non selezionabili, ma impostate da Apple come ulteriori keyword di ricerca, in base al media selezionato).

Potete provare la ricerca dalla form di esempio disponibile qui:

Nello script PHP che ho realizzato, non faccio altro che richiamare la stessa URL di Apple, passando una combinazioni di parametri (essenzialmente cambiano “media” ed “entity“):

http://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/wsSearch?term=%s&country=%s&media=%s&entity=%s&limit=%s&genreId=%s&version=2&output=json

Al posto di %s occorre inserire dei valori ammissibili (la lista completa la trovate sulla documentazione ufficiale – Search API Documentation):

  • term: contiene la keyword di ricerca (ad esempio, “Oasis what’s the story morning glory”)
  • country: identificativo del Paese (IT, US, UK, ecc.). La ricerca restituirà gli item disponibili sugli Apple Store del paese selezionato. La lista completa dei Paesi è disponibile qui: http://www.francescoficetola.it/link-maker/countries.json
  • media: deve contenere la categoria di item. I valori ammissibili sono: audiobook (per gli AudioLibri), shortFilm (per i Cortometraggi), movie (per i Film), software (per le iOs app), ebook (per i Libri), macSoftware (per le Mac Apps), music (per i brani audio), podcast (per i PodCast), musicVideo (per i Video).
  • limit: numero massimo di risultati di ricerca da restituire;
  • entity: contiene le sottocategorie dei prodotti. Questo valore dipende dal parametro media impostato (le varie combinazioni sono elencate di seguito);
  • genreId: (opzionale) contiene il codice relativo al genere di media desiderato. Esistono varie combinazioni, disponibili al seguente link:

Ecco le combinazioni di media e relative entity, così come mappate nel mio script PHP:

//AUDIOLIBRI
if($media=='audiobook'){
   $entity = array('audiobookAuthor','audiobook');
}

//CORTOMETRAGGI
if($media=='shortFilm'){
   $entity = array('shortFilmArtist','shortFilm');
}

//FILM
if($media=='movie'){
   $entity = array('movieArtist','movie');
}

//iOS Apps
if($media=='software'){
   $entity = array('softwareDeveloper','software','iPadSoftware');
}

//LIBRI
if($media=='ebook'){
   $entity = array('ebookAuthor','metaEbook,textbook,ebook');
}

//Mac Apps
if($media=='macSoftware'){
   $entity = array('softwareDeveloper','macSoftware');
}

//MUSICA
if($media=='music'){
   $entity = array('musicArtist','song','album','musicVideo');
}

//PODCAST
if($media=='podcast'){
   $entity = array('musicArtist','podcastAuthor','podcast');
}

//VIDEO
if($media=='musicVideo'){
   $entity = array('musicVideoArtist','musicVideo');
}

Se volete richiamare direttamente il mio script PHP, senza passare dalla form di esempio su linkata (magari per farvi restituire un JSON da utilizzare nelle vostre app), eccovi la URL diretta:

  • http://francescoficetola.it/link-maker/linkmaker.php?term=%s&country=%s&media=%s&limit=%s&genreId=%s

I valori ammissibili da passare sono quelli espressi precedentemente e di seguito riassunti:

  • media: audiobook, shortFilm, movie, software, ebook, macSoftware, music, podcast, musicVideo
  • country: IT, US, UK, ecc.
  • term: la vostra query di ricerca (ad esempio, Oasis what’s the story morning glory”)
  • limit: (opzionale, di default è impostato a 50) determina il numero massimo di risultati che occorre restituire per ognuna delle combinazioni di URL di ricerca richiamate da Apple, in base al media impostato.
  • genreId: (opzionale) codice del genere degli item, il cui valore ammissibile è disponibile nel dizionario lmMediaTypeAndGenreDictionaries fornito da Apple.

Le combinazioni di entity sono definite direttamente nello script e non occorre specificare il parametro in input.

Ecco un esempio di ricerca:

http://francescoficetola.it/link-maker/linkmaker.php?term=jovanotti+bella&country=IT&media=music&limit=50&genreId=14

[iOs] AudioStreamer e ShoutCast: come riprodurre la nostra web radio in un’app

internet-radioAvete una vostra web radio o semplicemente volete condividere la vostra playlist preferita? Sicuramente conoscete SHOUTcast, storico programma per la condivisione di streaming audio e la realizzazione di web-radio amatoriali e, direi, professionali. 

Potete scaricare il server di ShoutCast sul sito ufficiale gratuitamente per varie piattaforme:

http://www.shoutcast.com/broadcast-tools

Seguite le istruzioni di installazione e riuscirete in pochi passi a configurare un servizio online (su una pagina web pubblica) per la trasmissione in streaming della vostra radio, grazie a SHOUTcast D.N.A.S. Avrete una pagina web tipo questa, da dove andremo a recuperare lo streaming audio e le info sulla traccia corrente:

 

SHOUTCAST SERVER DNAS

 

Per ulteriori info su come creare la vostra webradio con ShoutCast e Winamp vi consiglio di leggere questo tutorial: http://www.gozzinet.net/2009/06/01/come-creare-una-webradio-con-winamp-e-shoutcast/

In questo articolo ci concentriamo più che altro sulla realizzazione di un’app che vi permetterà di riprodurre lo streaming di ShoutCast, accedendo alla pagina web creata dal server suddetto. Tutto è molto semplice, perché basta scaricare e customizzare l’ottimo progetto di Matt Gallagher su GitHub:

https://github.com/mattgallagher/AudioStreamer

Tale progetto è consigliato addirittura da Google (AudioStreamer Metadata Google Code). La classe AudioStreamer vi permette di:

  • a partire dalla URL della pagina web generata da SHOUTcast D.N.A.S., di riprodurre lo streaming audio
  • vi mette a disposizione i controlli PLAY e STOP per la riproduzione
  • permette il controllo del volume del vostro iPhone

Tuttavia, non vi permette di recuperare le informazioni sulla traccia correntemente in riproduzione. Teoricamente queste informazioni sono contenute nei metadati del pacchetto HTTP dello streaming (più precisamente nel pacchetto ICY), così come si legge in questo articolo: Shoutcast Metadata Protocol.

Onestamente non sono riuscito a recuperare in modo agevole le informazioni su titolo, artista e cover della copertura dell’album della traccia in riproduzione, dai metadati del pacchetto ICY. Occorrerebbe individuare il punto preciso in cui andare a “parsare” tali informazioni.

ShoutCast, però, genera delle pagine accessorie da cui recuperare le info che ci servono. Per esempio, per la web radio http://www.kissradio.info si riescono a recuperare le info su artista e brano in riproduzione, nonché la cover, dalle seguenti URL:

  • http://www.kissradio.info/OnAir.txt
  • http://www.kissradio.info/OnAir.jpg

Ma facendo varie ricerche ho scoperto che ShoutCast inserisce le info anche in una pagina “nascosta” del server di streaming, la 7.html. Per esempio, per il servizio di streaming http://kemoniastreaming2.com:8018, le info della traccia in riproduzione le troviamo qui: http://kemoniastreaming2.com:8018/7.html

Basterà, dunque, “parsare” queste info nella nostra app e visualizzarle mentre il brano viene riprodotto. Sarà ovviamente necessario un refresh delle info secondo un tempo prefissato, magari utilizzando un timer (NSTimer).

 

[iOs] Librerie per la scansione di codici a barre ed il riconoscimento OCR

Come acquisire un codice a barre in un’app iOs? Ce lo dice Marco Giraudo, giovane sviluppatore iOs e PHP, che mi ha mandato questo ottimo articolo, in cui si è trovato a valutare delle librerie per la scansione di codici a barre (BarCode Reader), ma anche per il riconoscimento OCR.

iPhone_barcode_scanner

Eccovi l’esperienza di Marco, a cui va il mio ringraziamento:

“Mi sono fatto questa domanda nelle ultime settimane per motivi di lavoro e vi voglio riportare 2 esempi che sono riuscito a trovare e testare. In particolare, mi serviva riconoscere un codice a barre e cosi ho utilizzato due librerie: ZBarSDK e Tesseract .

1° Esempio: ZBarSDK

ZBarSDKhttp://zbar.sourceforge.net/iphone/sdkdoc/

C’è stato subito un problema da superare, dopo l’installazione sul mio progetto xCode:

Warning: Only the iPhone 3GS and iPhone 4 are supported, as they have a camera with auto-focus. The ZBar library does not support the iPhone 3G and is unlikely to ever support it.

Su iPhone 5 con iOs 6 e successivi mi dava problemi, ma ho trovato la soluzione su http://stackoverflow.com:

http://stackoverflow.com/questions/12506671/zbar-sdk-is-not-working-in-ios6

Oltre a settare come “Valid Architecture” il processore armv7s nelle impostazioni del vostro progetto xCode, potete provare a scaricare ed installare la versione beta compatibile con iOs 6 (ZBarSDK-1.3.1.dmg):

http://sourceforge.net/projects/zbar/files/iPhoneSDK/beta/

Una  volta installato il .DMG, l’unica cosa da fare è leggere il file README riportato e seguire le istruzioni per inserire la libreria nel proprio progetto:

    • trascinare la cartella ZBarSDK nel proprio progetto
    • inserire nel proprio progetto i Frameworks: AVFoundation (weak), CoreMedia (weak), CoreVideo (weak), QuartzCore, libiconv.dylib

Per usare ZBar, basta importare nel vostro controller la libreria:

#import “ZBarSDK.h”

Nello stesso .dmg troverete una cartella con molti esempi da cui prendere spunto. L’ho anche provata con iPhone5 con su installato ios7 beta4 e funziona benissimo.

2° Esempio: Tesseract-OCR (Google)

Secondo esempio che vi riporto è tesseract-ocr, un OCR Engine che vi permette di riconoscere i testi riportati in un’immagine. Ecco il sito ufficiale su Google Code:

http://code.google.com/p/tesseract-ocr/

Per far funzionare l’OCR Engine in iOs, esiste un Wrapper: Tesseract for iOs (https://github.com/ldiqual/tesseract-ios)

Eccovi le istruzioni di installazione:

http://lois.di-qual.net/blog/install-and-use-tesseract-on-ios-with-tesseract-ios/

Spero che questi due esempi possano esservi utili. Alla prossima!”

[iOs] Quick Objective-C Tutorial

Consiglio un bel tutorial per chi volesse iniziare la programmazione su Objective-C:

This tutorial is the place to learn the Objective-C programming language. It’s designed to serve as both a concise quick-reference and a comprehensive introduction for newcomers to the language. A familiarity with basic programming concepts like variables, functions, and objects is recommended, but not strictly required.

Published  – Tested on Xcode 4.5 with Apple LLVM compiler 4.1ry's tutorial

Molto fighe sono le immagini esemplificative e la semplicità di scrittura. Grande Ry!

Creative Commons License
This work by Francesco Ficetola is licensed under a Creative Commons Attribution 4.0 International License.
Based on a work at www.francescoficetola.it.
Permissions beyond the scope of this license may be available at http://www.francescoficetola.it/2013/07/31/ios-quick-objective-c-tutorial/.

[iOs] Accesso alla fotocamera, alla Libreria e modifica di una foto con Aviary

Condivido qui un progettino xCode che esemplifica come accedere ad una foto salvata  nella Libreria Foto oppure scattata direttamente con la fotocamera del vostro device. Una volta prelevata la foto, è possibile modificarla, applicando su di essa vari effetti e filtri, grazie alla libreria Aviary (disponibile non solo per iOs, ma anche per Windows Phone, Android e HTML5).

Aviary-iOS-SDK_4

Ecco il link al mio progetto su GitHubhttps://github.com/fficetola/AviaryExample/

Quindi, ricapitolando, ecco le funzionalità che troverete nell’esempio:

  • visualizzazione di una foto, prelevata dalla Libreria Foto oppure dalla fotocamera del device, in una UIImageView
  • possibilità di modificarla grazie all’editor di Aviary
  • visualizzazione della foto in modalità fullscreen con gesture per lo zoom (pinch-on/pinch-off/double tap)
  • salvataggio dell’immagine modificata nella Libreria Foto

Procediamo per step, descrivendo le funzionalità su elencate (vi invito a visionare i file ViewController.m e BannerViewController.m  in cui vi è tutta la logica di questo tutorial).

 

1. Selezione di una immagine dalla Libreria Foto o dalla fotocamera

La selezione di una foto già salvata in Libreria o scattata dalla fotocamera avviene grazie al controller UIImagePickerController, il quale appare come una modale che a seconda del “source type” richiesto (camera o gallery), vi permetterà di importare appunto la foto da libreria o fotocamera.

- (void) selectPhoto:(UIImagePickerControllerSourceType) pickerType {

    picker = [[UIImagePickerController alloc] init];
    picker.delegate = self;

    picker.sourceType = pickerType;
    //picker.sourceType = UIImagePickerControllerSourceTypeCamera;
    //picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;

    [self presentModalViewController:picker animated:YES];

}

Il parametro in input al metodo selectPhoto è di tipo UIImagePickerControllerSourceType e può essere valorizzato con i seguenti valori:

  • UIImagePickerControllerSourceTypeCamera
  • UIImagePickerControllerSourceTypePhotoLibrary

I metodi di callback, che permettono di intercettare se l’utente ha selezionato o meno una foto, sono quelli dell’UIImagePickerControllerDelegate (metodi implementati nel ViewController):

#pragma mark - UIImagePicker Delegate

-(void)imagePickerControllerDidCancel:(UIImagePickerController *) photopicker {

    [photopicker dismissModalViewControllerAnimated:YES];

    [photopicker release];

}

- (void)imagePickerController:(UIImagePickerController *) photopicker didFinishPickingMediaWithInfo:(NSDictionary *)info {

    imageView.image = [info objectForKey:UIImagePickerControllerOriginalImage];
    [photopicker dismissModalViewControllerAnimated:YES];
    [photopicker release];

}

A cosa servono questi metodi del delegate?

  • imagePickerControllerDidCancel: permette di intercettare il click sul tasto Annulla del picker (o della libreria o della fotocamera), quindi senza che sia avvenuta la selezione della foto. In questo metodo viene chiuso il picker (con il dismissModalViewControllerAnimated)
  • didFinishPickingMediaWithInfo: permette di intercettare il click sul tasto Done con conseguente scelta della foto. Il recupero dell’immagine selezionata viene fatto accedendo alla proprietà UIImagePickerControllerOriginalImage del dizionario. Nell’esempio, tale foto viene inserita in una UIImageView per la visualizzazione.

 

2. Modifica dell’immagine con Aviary 

ios_aviary

Per poter modificare la foto, applicandovi  filtri ed effetti vari (cropping, resizing, rotating, ecc.), ho utilizzato la libreria Aviaryla cui versione “free” vi permette di eseguire tutte le features disponibili, ma non di trattare foto ad altissima risoluzione (vedi i dettagli nella sezione Pricing del sito www.aviary.com). Per maggiori dettagli sulle funzionalità previste, ecco la pagina della documentazione per iOshttp://www.aviary.com/ios/documentation

L’installazione della libreria Aviary nel vostro progetto è molto semplice:

  1. Innanzitutto scaricate il progetto di esempio direttamente dal sito ufficiale di Aviary: lo troverete nella sezione Documentazione, cliccando sul bottone GET CODE. Potete eseguirlo per vedere quali sono le features messe a disposizione dalla libreria o come esempio per implementare funzionalità più avanzate (visto che in questo tutorial vi spiego solo l’uso di base)
  2. Registratevi al sito (gratuitamente) per ottenere un’API key, che dovrete inserire all’interno del vostro progetto. Basta cliccare sul bottone GET API KEY della sezione Documentazione, registrarvi e poi aggiungere una nuova app. Vi verranno generate una API Key e una Secret Key ( a voi servirà solo la prima).
  3. Dal codice di esempio scaricato dal sito ufficiale, dovrete copiare la cartella AviarySDK e importarla nel vostro progetto. Questa contiene la libreria libAviarySDK.a, i file di Header e di Resources. Copiatela integralmente nel vostro progetto.
  4. inserite nel vostro progetto i seguenti framework, da cui Aviary dipende:
    1. Accelerate.framework
    2. AdSupport.framework (selezionare `Optional` invece di `Required`)
    3. CoreData.framework
    4. CoreText.framework
    5. libsqlite3.0.dylib
    6. libz.1.2.5.dylib
    7. Foundation.framework
    8. MessageUI.framework
    9. OpenGLES.framework
    10. QuartzCore.framework
    11. StoreKit.framework
    12. SystemConfiguration.framework
    13. UIKit.framework
  5. Infine, inserite nel vostro file MyProject-Info.plist la chiave Aviary-API-Key valorizzandola con l’API Key generata al punto 2.

Dopo la fase di configurazione della libreria, si passa all’utilizzo vero e proprio dell’editor. Innanzitutto il ViewController deve avere come delegate la classe AFPhotoEditorControllerDelegate, che ci permetterà di interagire con l’editor di Aviary, ossia con la classe AFPhotoEditorController.

Ecco come far apparire l’editor di Aviary in una modale del ViewController:

- (void)displayEditorForImage:(UIImage *)imageToEdit
{
    AFPhotoEditorController *editorController = [[AFPhotoEditorController alloc] initWithImage:imageToEdit];
    [editorController setDelegate:self];
    [self presentViewController:editorController animated:YES completion:nil];
}

Basta passare all’AFPhotoEditorController l’immagine da modificare (imageToEdit), in modo da farla apparire nell’editor. Qui Aviary vi permetterà di applicare gli effetti e i filtri disponibili. Per poter intercettare le modifiche o l’annullamento delle modifiche (chiusura con tasto Annulla dell’editor), basta implementare i seguenti metodi dell’ AFPhotoEditorControllerDelegate:

#pragma mark - Aviary Delegate

- (void)photoEditor:(AFPhotoEditorController *)editor finishedWithImage:(UIImage *)image
{

    //read modified image
    [imageView setImage:image];

    [self dismissViewControllerAnimated:YES completion:NULL];

}

- (void)photoEditorCanceled:(AFPhotoEditorController *)editor
{
    [self dismissViewControllerAnimated:YES completion:NULL];
}

Penso che le righe di codice precedenti siano abbastanza esplicative: photoEditorCanceled intercetta la chiusura di Aviary (tap su tasto Annulla) e permette di chiudere l’editor (dismissViewControllerAnimated), invece finishedWithImage permette di recuperare l’immagine modificata per riproporla nella UIImageView.

 

3. Zoom in/out dell’immagine (pinch-off/pinch-on o doppio tap per ingrandire/ridurre)

Per poter ingrandire/ridurre la foto visualizzata nell‘UIImageView, nel mio progettino di esempio trovate la classe BannerViewController.m. In essa vi è la logica per poter eseguire lo zoom-in e lo zoom-out della foto eseguendo le gesture pinch-on/pinch-off o il double tap sulla view. Inoltre, nel file BannerViewController.xib noterete una ScrollView contenente l’UIImageView, in cui visualizzare e zoomare l’immagine.

Ecco il codice:

-(void)handleDoubleTap:(UIGestureRecognizer *)gestureRecognizer {

    if(self.scrollView.zoomScale > scrollView.minimumZoomScale)
        [self.scrollView setZoomScale:scrollView.minimumZoomScale animated:YES];
    else
        [self.scrollView setZoomScale:scrollView.maximumZoomScale animated:YES];
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.title = NSLocalizedString(@"BANNER_TITLE", nil);

    self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(closeModal:)]autorelease];

    UITapGestureRecognizer *tapTwice = [[[UITapGestureRecognizer alloc] initWithTarget:self  action:@selector(handleDoubleTap:)]autorelease];

    tapTwice.numberOfTapsRequired = 2;

    [self.view addGestureRecognizer:tapTwice];

    UIImage *imageFullScreen = image;
    imageView.contentMode = UIViewContentModeScaleAspectFit;
    [self.imageView setImage:imageFullScreen];

    //self.scrollView.contentSize = image.size;
    self.scrollView.delegate = self;
    //self.scrollView.minimumZoomScale = 10.0;
    self.scrollView.maximumZoomScale = 50.0;   

}

Nelle precedenti righe notiamo l’allocazione di un oggetto di tipo UITapGestureRecognizer per la registrazione del doppio “tap” sulla vista: la action associata a tale gesture è handleDoubleTap, che permette di zoomare alla scala massima o minima impostata per la ScrollView. Infatti, il minimumZoomScale e il maximumZoomScale si impostano nel viewDidLoad.

Invece, per gestire le gesture di pinch-on e pinch-off sull’immagine, sono utili le seguenti righe di codice:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    CGPoint centerPoint = CGPointMake(CGRectGetMidX(self.scrollView.bounds),
                                      CGRectGetMidY(self.scrollView.bounds));
    [self view:self.imageView setCenter:centerPoint];
}

- (void)view:(UIView*)view setCenter:(CGPoint)centerPoint
{
    CGRect vf = view.frame;
    CGPoint co = self.scrollView.contentOffset;

    CGFloat x = centerPoint.x - vf.size.width / 2.0;
    CGFloat y = centerPoint.y - vf.size.height / 2.0;

    if(x < 0)
    {
        co.x = -x;
        vf.origin.x = 0.0;
    }
    else 
    {
        vf.origin.x = x;
    }
    if(y < 0)
    {
        co.y = -y;
        vf.origin.y = 0.0;
    }
    else 
    {
        vf.origin.y = y;
    }

    view.frame = vf;
    self.scrollView.contentOffset = co;
}

// MARK: - UIScrollViewDelegate
- (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    return  self.imageView;
}

- (void)scrollViewDidZoom:(UIScrollView *)sv
{
    UIView* zoomView = [sv.delegate viewForZoomingInScrollView:sv];
    CGRect zvf = zoomView.frame;
    if(zvf.size.width < sv.bounds.size.width)
    {
        zvf.origin.x = (sv.bounds.size.width - zvf.size.width) / 2.0;
    }
    else 
    {
        zvf.origin.x = 0.0;
    }
    if(zvf.size.height < sv.bounds.size.height)
    {
        zvf.origin.y = (sv.bounds.size.height - zvf.size.height) / 2.0;
    }
    else 
    {
        zvf.origin.y = 0.0;
    }
    zoomView.frame = zvf;
}

 

4. Salvataggio dell’immagine nella Libreria Foto

Infine, per salvare la foto nella Libreria Foto del device, basta utilizzare la classe nativa UIImageWriteToSavedPhotosAlbum:

//Per salvare la foto modificata nella Gallery

-(void)image:(UIImage *)image finishedSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {

    if (error) {

        UIAlertView *message = [[UIAlertView alloc] initWithTitle:@"Salvataggio Fallito"
                                                          message:@" *ATTENZIONE* non è stato possibile salvare l'immagine"
                                                         delegate:nil
                                                cancelButtonTitle:@"OK"
                                                otherButtonTitles:nil];
        [message show];
    }
    else{
        UIAlertView *message = [[UIAlertView alloc] initWithTitle:@"Salvataggio effettuato con successo"
                                                          message:@"La tua foto è stata salvata nella Gallery"
                                                         delegate:nil
                                                cancelButtonTitle:@"OK"
                                                otherButtonTitles:nil];
        [message show];
    }
}

-(IBAction)savePhotoInGallery:(id)sender{

    //salvo la foto nella Gallery
    UIImageWriteToSavedPhotosAlbum(imageView.image, self, @selector(image:finishedSavingWithError:contextInfo:), nil);
}

[iOs] Come configurare, inviare e ricevere le notifiche push in una app iOs

Eccovi un tutorial su come configurare, inviare e ricevere le push notification di Apple in una app iOs. Tali notifiche sono molto utili nel caso in cui vogliate segnalare aggiornamento ed eventi agli utenti che hanno installato la vostra app e che hanno, ovviamente, dato il consenso per la loro ricezione. La configurazione non è molto immediata, specie la parte di creazione dei certificati, per cui vi descriverò i singoli passi nel dettaglio.

Prima di essere operativi, vediamo il modello di programmazione per capire quali sono le componenti in gioco e il flusso funzionale di registrazione, di accreditamento e di invio/ricezione delle notifiche push:

  1. Push-NotificationL’app, registrata sul Provisioning Portal con un AppID, deve essere configurata in modo da avere la funzionalità di push notification abilitata. Una volta abilitata, occorre generare un Provisioning Profile (sia per l’ambiente di sandbox – o development –   che per quello di production – o distribution) da associare all’app stessa. Lato app, quando l’utente dà il consenso alla ricezione delle notifiche push, si fa richiesta ad iOs per la registrazione e l’autorizzazione alle notifiche.
  2. iOs contatterà il server APNS (Apple Push Notification Service), il quale ricevuta la richiesta, genererà un device token da inoltrare direttamente all’app che ha chiesto la registrazione. Il device token è un “indirizzo” univoco che serve all’APNS per rintracciare il device e per inoltrare le notifiche push tramite l’app.
  3. Una volta ricevuto il device token, dall’app occorrerà richiamare un nostro servizio remoto (registerDevice) in cui registrare tale token, oltre ad una serie di altre info utili del device. L’idea è quella di registrare su un nostro database online, la lista di tutti i device, e relativi device token generati, in modo da inoltrare tutte le nostre notifiche a chi ne ha fatto espressamente richiesta.
  4. Un altro servizio remoto presente sul nostro server (sendPush) sarà responsabile dell’invio delle notifiche push (un semplice messaggio di testo). Tale servizio dovrà essere configurato in modo da avere l’autorizzazione dall’APNS server: occorrerà, dunque, generare e configurare un certificato SSL (e una relativa chiave privata) per poter inoltrare una richiesta di notifica push a ciascun device accreditato.
  5. Quando l’APNS server riceve una richiesta di invio di una notifica push per un determinato device token (dopo aver attestato la veridicità del certificato SSL del servizio remoto sendPush), inoltra la notifica. Dall’altra parte, sul device a cui è associato il device token, verrà visualizzato un alert dell’app (o una notifica nel Centro Notifiche) con il messaggio inviato. Oltre l’alert, si può decidere se riprodorre anche un suono e/o visualizzare un badge (il numeretto con il totale delle notifiche sull’icona dell’app). Cliccando sulla notifica o facendo lo “swipe” sull’alert, verrà aperta la vostra app.

 

NOTA. Non è possibile testare le push notification sul simulatore, ma soltanto direttamente su un iPhone/iPad.

 

Push Notification Payload.  Una richiesta di invio di una push notification (dal nostro server all’APNS server) è fatta da un breve messaggio costituito da un device token a cui indirizzare la notifica, un payload ed altre parti. Il payload contiene il messaggio vero e proprio (in formato JSON), compresi alcuni parametri. In totale non deve superare i 256 bytes. Per maggiori informazioni sui parametri, vedi Local and Push Notification Programming Guide.

Un esempio di messaggio è il seguente:

{"aps":{"alert":"Hello, world!","sound":"default"}}

Vi riporto i tutorial da cui sono partito per poter scrivere questa mini-guida:

 

Partiamo ora con la parte operativa: